Update
This commit is contained in:
		
							
								
								
									
										109
									
								
								admin-server.ts
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								admin-server.ts
									
									
									
									
									
								
							@@ -14,6 +14,7 @@ import {
 | 
			
		||||
  getFeedRequests,
 | 
			
		||||
  updateFeedRequestStatus,
 | 
			
		||||
} from "./services/database.js";
 | 
			
		||||
import { Database } from "bun:sqlite";
 | 
			
		||||
import { batchProcess, addNewFeedUrl } from "./scripts/fetch_and_generate.js";
 | 
			
		||||
import { batchScheduler } from "./services/batch-scheduler.js";
 | 
			
		||||
 | 
			
		||||
@@ -204,6 +205,114 @@ app.get("/api/admin/episodes/simple", async (c) => {
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Database diagnostic endpoint
 | 
			
		||||
app.get("/api/admin/db-diagnostic", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const db = new Database(config.paths.dbPath);
 | 
			
		||||
    
 | 
			
		||||
    // 1. Check episodes table
 | 
			
		||||
    const episodeCount = db.prepare("SELECT COUNT(*) as count FROM episodes").get() as any;
 | 
			
		||||
    
 | 
			
		||||
    // 2. Check articles table
 | 
			
		||||
    const articleCount = db.prepare("SELECT COUNT(*) as count FROM articles").get() as any;
 | 
			
		||||
    
 | 
			
		||||
    // 3. Check feeds table
 | 
			
		||||
    const feedCount = db.prepare("SELECT COUNT(*) as count FROM feeds").get() as any;
 | 
			
		||||
    const activeFeedCount = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE active = 1").get() as any;
 | 
			
		||||
    const inactiveFeedCount = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE active = 0 OR active IS NULL").get() as any;
 | 
			
		||||
    
 | 
			
		||||
    // 4. Check orphaned episodes
 | 
			
		||||
    const orphanedEpisodes = db.prepare(`
 | 
			
		||||
      SELECT e.id, e.title, e.article_id 
 | 
			
		||||
      FROM episodes e 
 | 
			
		||||
      LEFT JOIN articles a ON e.article_id = a.id 
 | 
			
		||||
      WHERE a.id IS NULL
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    // 5. Check orphaned articles
 | 
			
		||||
    const orphanedArticles = db.prepare(`
 | 
			
		||||
      SELECT a.id, a.title, a.feed_id 
 | 
			
		||||
      FROM articles a 
 | 
			
		||||
      LEFT JOIN feeds f ON a.feed_id = f.id 
 | 
			
		||||
      WHERE f.id IS NULL
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    // 6. Check episodes with articles but feeds are inactive
 | 
			
		||||
    const episodesInactiveFeeds = db.prepare(`
 | 
			
		||||
      SELECT e.id, e.title, f.active, f.title as feed_title
 | 
			
		||||
      FROM episodes e
 | 
			
		||||
      JOIN articles a ON e.article_id = a.id
 | 
			
		||||
      JOIN feeds f ON a.feed_id = f.id
 | 
			
		||||
      WHERE f.active = 0 OR f.active IS NULL
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    // 7. Test the JOIN query
 | 
			
		||||
    const joinResult = db.prepare(`
 | 
			
		||||
      SELECT COUNT(*) as count
 | 
			
		||||
      FROM episodes e
 | 
			
		||||
      JOIN articles a ON e.article_id = a.id
 | 
			
		||||
      JOIN feeds f ON a.feed_id = f.id
 | 
			
		||||
      WHERE f.active = 1
 | 
			
		||||
    `).get() as any;
 | 
			
		||||
    
 | 
			
		||||
    // 8. Sample feed details
 | 
			
		||||
    const sampleFeeds = db.prepare(`
 | 
			
		||||
      SELECT id, title, url, active, created_at
 | 
			
		||||
      FROM feeds
 | 
			
		||||
      ORDER BY created_at DESC
 | 
			
		||||
      LIMIT 5
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    // 9. Sample episode-article-feed chain
 | 
			
		||||
    const sampleChain = db.prepare(`
 | 
			
		||||
      SELECT 
 | 
			
		||||
        e.id as episode_id, e.title as episode_title,
 | 
			
		||||
        a.id as article_id, a.title as article_title,
 | 
			
		||||
        f.id as feed_id, f.title as feed_title, f.active
 | 
			
		||||
      FROM episodes e
 | 
			
		||||
      LEFT JOIN articles a ON e.article_id = a.id
 | 
			
		||||
      LEFT JOIN feeds f ON a.feed_id = f.id
 | 
			
		||||
      ORDER BY e.created_at DESC
 | 
			
		||||
      LIMIT 5
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    db.close();
 | 
			
		||||
    
 | 
			
		||||
    const diagnosticResult = {
 | 
			
		||||
      counts: {
 | 
			
		||||
        episodes: episodeCount.count,
 | 
			
		||||
        articles: articleCount.count,
 | 
			
		||||
        feeds: feedCount.count,
 | 
			
		||||
        activeFeeds: activeFeedCount.count,
 | 
			
		||||
        inactiveFeeds: inactiveFeedCount.count,
 | 
			
		||||
      },
 | 
			
		||||
      orphaned: {
 | 
			
		||||
        episodes: orphanedEpisodes.length,
 | 
			
		||||
        episodeDetails: orphanedEpisodes.slice(0, 3),
 | 
			
		||||
        articles: orphanedArticles.length,
 | 
			
		||||
        articleDetails: orphanedArticles.slice(0, 3),
 | 
			
		||||
      },
 | 
			
		||||
      episodesFromInactiveFeeds: {
 | 
			
		||||
        count: episodesInactiveFeeds.length,
 | 
			
		||||
        details: episodesInactiveFeeds.slice(0, 3),
 | 
			
		||||
      },
 | 
			
		||||
      joinQuery: {
 | 
			
		||||
        episodesWithActiveFeeds: joinResult.count,
 | 
			
		||||
      },
 | 
			
		||||
      samples: {
 | 
			
		||||
        feeds: sampleFeeds,
 | 
			
		||||
        episodeChain: sampleChain,
 | 
			
		||||
      },
 | 
			
		||||
      timestamp: new Date().toISOString(),
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    return c.json(diagnosticResult);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error running database diagnostic:", error);
 | 
			
		||||
    return c.json({ error: "Failed to run database diagnostic", details: error.message }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Feed requests management
 | 
			
		||||
app.get("/api/admin/feed-requests", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,59 +4,88 @@ import crypto from "crypto";
 | 
			
		||||
import { config } from "./config.js";
 | 
			
		||||
 | 
			
		||||
// Database integrity fixes function
 | 
			
		||||
function performDatabaseIntegrityFixes(db: Database): void {
 | 
			
		||||
export function performDatabaseIntegrityFixes(db: Database): void {
 | 
			
		||||
  console.log("🔧 Performing database integrity checks...");
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    // Fix 1: Set active flag to 1 for feeds where it's NULL
 | 
			
		||||
    const nullActiveFeeds = db.prepare("UPDATE feeds SET active = 1 WHERE active IS NULL").run();
 | 
			
		||||
    const nullActiveFeeds = db
 | 
			
		||||
      .prepare("UPDATE feeds SET active = 1 WHERE active IS NULL")
 | 
			
		||||
      .run();
 | 
			
		||||
    if (nullActiveFeeds.changes > 0) {
 | 
			
		||||
      console.log(`✅ Fixed ${nullActiveFeeds.changes} feeds with NULL active flag`);
 | 
			
		||||
      console.log(
 | 
			
		||||
        `✅ Fixed ${nullActiveFeeds.changes} feeds with NULL active flag`,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fix 2: Fix orphaned articles (articles referencing non-existent feeds)
 | 
			
		||||
    const orphanedArticles = db.prepare(`
 | 
			
		||||
    const orphanedArticles = db
 | 
			
		||||
      .prepare(
 | 
			
		||||
        `
 | 
			
		||||
      SELECT a.id, a.link, a.title 
 | 
			
		||||
      FROM articles a 
 | 
			
		||||
      LEFT JOIN feeds f ON a.feed_id = f.id 
 | 
			
		||||
      WHERE f.id IS NULL
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    `,
 | 
			
		||||
      )
 | 
			
		||||
      .all() as any[];
 | 
			
		||||
 | 
			
		||||
    if (orphanedArticles.length > 0) {
 | 
			
		||||
      console.log(`🔍 Found ${orphanedArticles.length} orphaned articles, attempting to fix...`);
 | 
			
		||||
      
 | 
			
		||||
      console.log(
 | 
			
		||||
        `🔍 Found ${orphanedArticles.length} orphaned articles, attempting to fix...`,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      for (const article of orphanedArticles) {
 | 
			
		||||
        // Try to match article to feed based on URL domain
 | 
			
		||||
        const articleDomain = extractDomain(article.link);
 | 
			
		||||
        if (articleDomain) {
 | 
			
		||||
          const matchingFeed = db.prepare(`
 | 
			
		||||
          const matchingFeed = db
 | 
			
		||||
            .prepare(
 | 
			
		||||
              `
 | 
			
		||||
            SELECT id FROM feeds 
 | 
			
		||||
            WHERE url LIKE ? OR url LIKE ? 
 | 
			
		||||
            ORDER BY created_at DESC 
 | 
			
		||||
            LIMIT 1
 | 
			
		||||
          `).get(`%${articleDomain}%`, `%${articleDomain.replace('www.', '')}%`) as any;
 | 
			
		||||
          `,
 | 
			
		||||
            )
 | 
			
		||||
            .get(
 | 
			
		||||
              `%${articleDomain}%`,
 | 
			
		||||
              `%${articleDomain.replace("www.", "")}%`,
 | 
			
		||||
            ) as any;
 | 
			
		||||
 | 
			
		||||
          if (matchingFeed) {
 | 
			
		||||
            db.prepare("UPDATE articles SET feed_id = ? WHERE id = ?")
 | 
			
		||||
              .run(matchingFeed.id, article.id);
 | 
			
		||||
            console.log(`✅ Fixed article "${article.title}" -> feed ${matchingFeed.id}`);
 | 
			
		||||
            db.prepare("UPDATE articles SET feed_id = ? WHERE id = ?").run(
 | 
			
		||||
              matchingFeed.id,
 | 
			
		||||
              article.id,
 | 
			
		||||
            );
 | 
			
		||||
            console.log(
 | 
			
		||||
              `✅ Fixed article "${article.title}" -> feed ${matchingFeed.id}`,
 | 
			
		||||
            );
 | 
			
		||||
          } else {
 | 
			
		||||
            console.log(`⚠️  Could not find matching feed for article: ${article.title} (${articleDomain})`);
 | 
			
		||||
            console.log(
 | 
			
		||||
              `⚠️  Could not find matching feed for article: ${article.title} (${articleDomain})`,
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fix 3: Ensure all episodes have valid article references
 | 
			
		||||
    const orphanedEpisodes = db.prepare(`
 | 
			
		||||
    const orphanedEpisodes = db
 | 
			
		||||
      .prepare(
 | 
			
		||||
        `
 | 
			
		||||
      SELECT e.id, e.title, e.article_id 
 | 
			
		||||
      FROM episodes e 
 | 
			
		||||
      LEFT JOIN articles a ON e.article_id = a.id 
 | 
			
		||||
      WHERE a.id IS NULL
 | 
			
		||||
    `).all() as any[];
 | 
			
		||||
    `,
 | 
			
		||||
      )
 | 
			
		||||
      .all() as any[];
 | 
			
		||||
 | 
			
		||||
    if (orphanedEpisodes.length > 0) {
 | 
			
		||||
      console.log(`⚠️  Found ${orphanedEpisodes.length} episodes with invalid article references`);
 | 
			
		||||
      console.log(
 | 
			
		||||
        `⚠️  Found ${orphanedEpisodes.length} episodes with invalid article references`,
 | 
			
		||||
      );
 | 
			
		||||
      // We could delete these or try to fix them, but for now just log
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +118,7 @@ function initializeDatabase(): Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const db = new Database(config.paths.dbPath);
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  // Enable WAL mode for better concurrent access
 | 
			
		||||
  db.exec("PRAGMA journal_mode = WAL;");
 | 
			
		||||
  db.exec("PRAGMA synchronous = NORMAL;");
 | 
			
		||||
@@ -259,6 +288,17 @@ export async function saveFeed(
 | 
			
		||||
      createdAt,
 | 
			
		||||
      feed.active !== undefined ? (feed.active ? 1 : 0) : 1, // Default to active=1 if not specified
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      performDatabaseIntegrityFixes(db);
 | 
			
		||||
      console.log(`Feed saved: ${feed.url}`);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(
 | 
			
		||||
        "Error performing integrity fixes after saving feed:",
 | 
			
		||||
        error,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return id;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error saving feed:", error);
 | 
			
		||||
@@ -336,7 +376,9 @@ export async function fetchActiveFeeds(): Promise<Feed[]> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get episodes with feed information for enhanced display
 | 
			
		||||
export async function fetchEpisodesWithFeedInfo(): Promise<EpisodeWithFeedInfo[]> {
 | 
			
		||||
export async function fetchEpisodesWithFeedInfo(): Promise<
 | 
			
		||||
  EpisodeWithFeedInfo[]
 | 
			
		||||
> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(`
 | 
			
		||||
      SELECT 
 | 
			
		||||
@@ -386,7 +428,9 @@ export async function fetchEpisodesWithFeedInfo(): Promise<EpisodeWithFeedInfo[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get episodes by feed ID
 | 
			
		||||
export async function fetchEpisodesByFeedId(feedId: string): Promise<EpisodeWithFeedInfo[]> {
 | 
			
		||||
export async function fetchEpisodesByFeedId(
 | 
			
		||||
  feedId: string,
 | 
			
		||||
): Promise<EpisodeWithFeedInfo[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(`
 | 
			
		||||
      SELECT 
 | 
			
		||||
@@ -436,7 +480,9 @@ export async function fetchEpisodesByFeedId(feedId: string): Promise<EpisodeWith
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get single episode with source information
 | 
			
		||||
export async function fetchEpisodeWithSourceInfo(episodeId: string): Promise<EpisodeWithFeedInfo | null> {
 | 
			
		||||
export async function fetchEpisodeWithSourceInfo(
 | 
			
		||||
  episodeId: string,
 | 
			
		||||
): Promise<EpisodeWithFeedInfo | null> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(`
 | 
			
		||||
      SELECT 
 | 
			
		||||
@@ -487,9 +533,7 @@ export async function fetchEpisodeWithSourceInfo(episodeId: string): Promise<Epi
 | 
			
		||||
 | 
			
		||||
export async function getAllFeedsIncludingInactive(): Promise<Feed[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(
 | 
			
		||||
      "SELECT * FROM feeds ORDER BY created_at DESC",
 | 
			
		||||
    );
 | 
			
		||||
    const stmt = db.prepare("SELECT * FROM feeds ORDER BY created_at DESC");
 | 
			
		||||
    const rows = stmt.all() as any[];
 | 
			
		||||
 | 
			
		||||
    return rows.map((row) => ({
 | 
			
		||||
@@ -511,7 +555,7 @@ export async function deleteFeed(feedId: string): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    // Start transaction
 | 
			
		||||
    db.exec("BEGIN TRANSACTION");
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Delete all episodes for articles belonging to this feed
 | 
			
		||||
    const deleteEpisodesStmt = db.prepare(`
 | 
			
		||||
      DELETE FROM episodes 
 | 
			
		||||
@@ -520,17 +564,19 @@ export async function deleteFeed(feedId: string): Promise<boolean> {
 | 
			
		||||
      )
 | 
			
		||||
    `);
 | 
			
		||||
    deleteEpisodesStmt.run(feedId);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Delete all articles for this feed
 | 
			
		||||
    const deleteArticlesStmt = db.prepare("DELETE FROM articles WHERE feed_id = ?");
 | 
			
		||||
    const deleteArticlesStmt = db.prepare(
 | 
			
		||||
      "DELETE FROM articles WHERE feed_id = ?",
 | 
			
		||||
    );
 | 
			
		||||
    deleteArticlesStmt.run(feedId);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Delete the feed itself
 | 
			
		||||
    const deleteFeedStmt = db.prepare("DELETE FROM feeds WHERE id = ?");
 | 
			
		||||
    const result = deleteFeedStmt.run(feedId);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    db.exec("COMMIT");
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    return result.changes > 0;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    db.exec("ROLLBACK");
 | 
			
		||||
@@ -539,7 +585,10 @@ export async function deleteFeed(feedId: string): Promise<boolean> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function toggleFeedActive(feedId: string, active: boolean): Promise<boolean> {
 | 
			
		||||
export async function toggleFeedActive(
 | 
			
		||||
  feedId: string,
 | 
			
		||||
  active: boolean,
 | 
			
		||||
): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare("UPDATE feeds SET active = ? WHERE id = ?");
 | 
			
		||||
    const result = stmt.run(active ? 1 : 0, feedId);
 | 
			
		||||
@@ -816,7 +865,7 @@ export interface TTSQueueItem {
 | 
			
		||||
  retryCount: number;
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
  lastAttemptedAt?: string;
 | 
			
		||||
  status: 'pending' | 'processing' | 'failed';
 | 
			
		||||
  status: "pending" | "processing" | "failed";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function addToQueue(
 | 
			
		||||
@@ -840,7 +889,9 @@ export async function addToQueue(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getQueueItems(limit: number = 10): Promise<TTSQueueItem[]> {
 | 
			
		||||
export async function getQueueItems(
 | 
			
		||||
  limit: number = 10,
 | 
			
		||||
): Promise<TTSQueueItem[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(`
 | 
			
		||||
      SELECT * FROM tts_queue
 | 
			
		||||
@@ -867,7 +918,7 @@ export async function getQueueItems(limit: number = 10): Promise<TTSQueueItem[]>
 | 
			
		||||
 | 
			
		||||
export async function updateQueueItemStatus(
 | 
			
		||||
  queueId: string,
 | 
			
		||||
  status: 'pending' | 'processing' | 'failed',
 | 
			
		||||
  status: "pending" | "processing" | "failed",
 | 
			
		||||
  lastAttemptedAt?: string,
 | 
			
		||||
): Promise<void> {
 | 
			
		||||
  try {
 | 
			
		||||
@@ -897,7 +948,7 @@ export interface FeedRequest {
 | 
			
		||||
  url: string;
 | 
			
		||||
  requestedBy?: string;
 | 
			
		||||
  requestMessage?: string;
 | 
			
		||||
  status: 'pending' | 'approved' | 'rejected';
 | 
			
		||||
  status: "pending" | "approved" | "rejected";
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
  reviewedAt?: string;
 | 
			
		||||
  reviewedBy?: string;
 | 
			
		||||
@@ -905,7 +956,7 @@ export interface FeedRequest {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function submitFeedRequest(
 | 
			
		||||
  request: Omit<FeedRequest, "id" | "createdAt" | "status">
 | 
			
		||||
  request: Omit<FeedRequest, "id" | "createdAt" | "status">,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  const id = crypto.randomUUID();
 | 
			
		||||
  const createdAt = new Date().toISOString();
 | 
			
		||||
@@ -914,7 +965,13 @@ export async function submitFeedRequest(
 | 
			
		||||
    const stmt = db.prepare(
 | 
			
		||||
      "INSERT INTO feed_requests (id, url, requested_by, request_message, status, created_at) VALUES (?, ?, ?, ?, 'pending', ?)",
 | 
			
		||||
    );
 | 
			
		||||
    stmt.run(id, request.url, request.requestedBy || null, request.requestMessage || null, createdAt);
 | 
			
		||||
    stmt.run(
 | 
			
		||||
      id,
 | 
			
		||||
      request.url,
 | 
			
		||||
      request.requestedBy || null,
 | 
			
		||||
      request.requestMessage || null,
 | 
			
		||||
      createdAt,
 | 
			
		||||
    );
 | 
			
		||||
    console.log(`Feed request submitted: ${request.url}`);
 | 
			
		||||
    return id;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
@@ -928,7 +985,7 @@ export async function getFeedRequests(status?: string): Promise<FeedRequest[]> {
 | 
			
		||||
    const sql = status
 | 
			
		||||
      ? "SELECT * FROM feed_requests WHERE status = ? ORDER BY created_at DESC"
 | 
			
		||||
      : "SELECT * FROM feed_requests ORDER BY created_at DESC";
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    const stmt = db.prepare(sql);
 | 
			
		||||
    const rows = status ? stmt.all(status) : stmt.all();
 | 
			
		||||
 | 
			
		||||
@@ -951,7 +1008,7 @@ export async function getFeedRequests(status?: string): Promise<FeedRequest[]> {
 | 
			
		||||
 | 
			
		||||
export async function updateFeedRequestStatus(
 | 
			
		||||
  requestId: string,
 | 
			
		||||
  status: 'approved' | 'rejected',
 | 
			
		||||
  status: "approved" | "rejected",
 | 
			
		||||
  reviewedBy?: string,
 | 
			
		||||
  adminNotes?: string,
 | 
			
		||||
): Promise<boolean> {
 | 
			
		||||
@@ -960,7 +1017,13 @@ export async function updateFeedRequestStatus(
 | 
			
		||||
    const stmt = db.prepare(
 | 
			
		||||
      "UPDATE feed_requests SET status = ?, reviewed_at = ?, reviewed_by = ?, admin_notes = ? WHERE id = ?",
 | 
			
		||||
    );
 | 
			
		||||
    const result = stmt.run(status, reviewedAt, reviewedBy || null, adminNotes || null, requestId);
 | 
			
		||||
    const result = stmt.run(
 | 
			
		||||
      status,
 | 
			
		||||
      reviewedAt,
 | 
			
		||||
      reviewedBy || null,
 | 
			
		||||
      adminNotes || null,
 | 
			
		||||
      requestId,
 | 
			
		||||
    );
 | 
			
		||||
    return result.changes > 0;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error updating feed request status:", error);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
import { promises as fs } from "fs";
 | 
			
		||||
import { dirname } from "path";
 | 
			
		||||
import { Episode, fetchAllEpisodes } from "./database.js";
 | 
			
		||||
import {
 | 
			
		||||
  Episode,
 | 
			
		||||
  fetchAllEpisodes,
 | 
			
		||||
  performDatabaseIntegrityFixes,
 | 
			
		||||
} from "./database.js";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import fsSync from "node:fs";
 | 
			
		||||
import { config } from "./config.js";
 | 
			
		||||
@@ -17,17 +21,20 @@ function escapeXml(text: string): string {
 | 
			
		||||
function createItemXml(episode: Episode): string {
 | 
			
		||||
  const fileUrl = `${config.podcast.baseUrl}/podcast_audio/${path.basename(episode.audioPath)}`;
 | 
			
		||||
  const pubDate = new Date(episode.createdAt).toUTCString();
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  let fileSize = 0;
 | 
			
		||||
  try {
 | 
			
		||||
    const audioPath = path.join(config.paths.podcastAudioDir, episode.audioPath);
 | 
			
		||||
    const audioPath = path.join(
 | 
			
		||||
      config.paths.podcastAudioDir,
 | 
			
		||||
      episode.audioPath,
 | 
			
		||||
    );
 | 
			
		||||
    if (fsSync.existsSync(audioPath)) {
 | 
			
		||||
      fileSize = fsSync.statSync(audioPath).size;
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.warn(`Could not get file size for ${episode.audioPath}:`, error);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  return `
 | 
			
		||||
    <item>
 | 
			
		||||
      <title><![CDATA[${escapeXml(episode.title)}]]></title>
 | 
			
		||||
@@ -45,11 +52,14 @@ function createItemXml(episode: Episode): string {
 | 
			
		||||
export async function updatePodcastRSS(): Promise<void> {
 | 
			
		||||
  try {
 | 
			
		||||
    const episodes: Episode[] = await fetchAllEpisodes();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Filter episodes to only include those with valid audio files
 | 
			
		||||
    const validEpisodes = episodes.filter(episode => {
 | 
			
		||||
    const validEpisodes = episodes.filter((episode) => {
 | 
			
		||||
      try {
 | 
			
		||||
        const audioPath = path.join(config.paths.podcastAudioDir, episode.audioPath);
 | 
			
		||||
        const audioPath = path.join(
 | 
			
		||||
          config.paths.podcastAudioDir,
 | 
			
		||||
          episode.audioPath,
 | 
			
		||||
        );
 | 
			
		||||
        return fsSync.existsSync(audioPath);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.warn(`Audio file not found for episode: ${episode.title}`);
 | 
			
		||||
@@ -57,7 +67,9 @@ export async function updatePodcastRSS(): Promise<void> {
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    console.log(`Found ${episodes.length} episodes, ${validEpisodes.length} with valid audio files`);
 | 
			
		||||
    console.log(
 | 
			
		||||
      `Found ${episodes.length} episodes, ${validEpisodes.length} with valid audio files`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const lastBuildDate = new Date().toUTCString();
 | 
			
		||||
    const itemsXml = validEpisodes.map(createItemXml).join("\n");
 | 
			
		||||
@@ -81,8 +93,10 @@ export async function updatePodcastRSS(): Promise<void> {
 | 
			
		||||
    // Ensure directory exists
 | 
			
		||||
    await fs.mkdir(dirname(outputPath), { recursive: true });
 | 
			
		||||
    await fs.writeFile(outputPath, rssXml);
 | 
			
		||||
    
 | 
			
		||||
    console.log(`RSS feed updated with ${validEpisodes.length} episodes (audio files verified)`);
 | 
			
		||||
 | 
			
		||||
    console.log(
 | 
			
		||||
      `RSS feed updated with ${validEpisodes.length} episodes (audio files verified)`,
 | 
			
		||||
    );
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error updating podcast RSS:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user