Add grouping categories feature
This commit is contained in:
		@@ -10,10 +10,12 @@ import {
 | 
			
		||||
  deleteFeed,
 | 
			
		||||
  fetchAllEpisodes,
 | 
			
		||||
  fetchEpisodesWithArticles,
 | 
			
		||||
  getAllCategories,
 | 
			
		||||
  getAllFeedsIncludingInactive,
 | 
			
		||||
  getFeedById,
 | 
			
		||||
  getFeedByUrl,
 | 
			
		||||
  getFeedRequests,
 | 
			
		||||
  getFeedsByCategory,
 | 
			
		||||
  getFeedsGroupedByCategory,
 | 
			
		||||
  toggleFeedActive,
 | 
			
		||||
  updateFeedRequestStatus,
 | 
			
		||||
} from "./services/database.js";
 | 
			
		||||
@@ -199,6 +201,38 @@ app.patch("/api/admin/feeds/:id/toggle", async (c) => {
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Categories management
 | 
			
		||||
app.get("/api/admin/categories", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const categories = await getAllCategories();
 | 
			
		||||
    return c.json(categories);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching categories:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch categories" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/admin/feeds/by-category", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const category = c.req.query("category");
 | 
			
		||||
    const feeds = await getFeedsByCategory(category);
 | 
			
		||||
    return c.json(feeds);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching feeds by category:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch feeds by category" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/admin/feeds/grouped-by-category", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const groupedFeeds = await getFeedsGroupedByCategory();
 | 
			
		||||
    return c.json(groupedFeeds);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching feeds grouped by category:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch feeds grouped by category" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Episodes management
 | 
			
		||||
app.get("/api/admin/episodes", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
@@ -349,7 +383,10 @@ app.get("/api/admin/db-diagnostic", async (c) => {
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error running database diagnostic:", error);
 | 
			
		||||
    return c.json(
 | 
			
		||||
      { error: "Failed to run database diagnostic", details: error.message },
 | 
			
		||||
      { 
 | 
			
		||||
        error: "Failed to run database diagnostic", 
 | 
			
		||||
        details: error instanceof Error ? error.message : String(error) 
 | 
			
		||||
      },
 | 
			
		||||
      500,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@@ -635,16 +672,6 @@ app.get("/", serveAdminIndex);
 | 
			
		||||
app.get("/index.html", serveAdminIndex);
 | 
			
		||||
app.get("*", serveAdminIndex);
 | 
			
		||||
 | 
			
		||||
// Utility functions
 | 
			
		||||
async function runBatchProcess(): Promise<void> {
 | 
			
		||||
  try {
 | 
			
		||||
    await batchProcess();
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Admin batch process failed:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start admin server
 | 
			
		||||
serve(
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								server.ts
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								server.ts
									
									
									
									
									
								
							@@ -449,6 +449,40 @@ app.get("/api/feeds/:feedId/episodes", async (c) => {
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/categories", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const { getAllCategories } = await import("./services/database.js");
 | 
			
		||||
    const categories = await getAllCategories();
 | 
			
		||||
    return c.json({ categories });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching categories:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch categories" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/feeds/by-category", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const category = c.req.query("category");
 | 
			
		||||
    const { getFeedsByCategory } = await import("./services/database.js");
 | 
			
		||||
    const feeds = await getFeedsByCategory(category);
 | 
			
		||||
    return c.json({ feeds });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching feeds by category:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch feeds by category" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/feeds/grouped-by-category", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const { getFeedsGroupedByCategory } = await import("./services/database.js");
 | 
			
		||||
    const groupedFeeds = await getFeedsGroupedByCategory();
 | 
			
		||||
    return c.json({ groupedFeeds });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching feeds grouped by category:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch feeds grouped by category" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/episode-with-source/:episodeId", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const episodeId = c.req.param("episodeId");
 | 
			
		||||
 
 | 
			
		||||
@@ -213,6 +213,7 @@ export interface Feed {
 | 
			
		||||
  url: string;
 | 
			
		||||
  title?: string;
 | 
			
		||||
  description?: string;
 | 
			
		||||
  category?: string;
 | 
			
		||||
  lastUpdated?: string;
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
  active: boolean;
 | 
			
		||||
@@ -325,6 +326,7 @@ export async function getFeedByUrl(url: string): Promise<Feed | null> {
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      title: row.title,
 | 
			
		||||
      description: row.description,
 | 
			
		||||
      category: row.category,
 | 
			
		||||
      lastUpdated: row.last_updated,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      active: Boolean(row.active),
 | 
			
		||||
@@ -346,6 +348,7 @@ export async function getFeedById(id: string): Promise<Feed | null> {
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      title: row.title,
 | 
			
		||||
      description: row.description,
 | 
			
		||||
      category: row.category,
 | 
			
		||||
      lastUpdated: row.last_updated,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      active: Boolean(row.active),
 | 
			
		||||
@@ -368,6 +371,7 @@ export async function getAllFeeds(): Promise<Feed[]> {
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      title: row.title,
 | 
			
		||||
      description: row.description,
 | 
			
		||||
      category: row.category,
 | 
			
		||||
      lastUpdated: row.last_updated,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      active: Boolean(row.active),
 | 
			
		||||
@@ -549,6 +553,7 @@ export async function getAllFeedsIncludingInactive(): Promise<Feed[]> {
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      title: row.title,
 | 
			
		||||
      description: row.description,
 | 
			
		||||
      category: row.category,
 | 
			
		||||
      lastUpdated: row.last_updated,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      active: Boolean(row.active),
 | 
			
		||||
@@ -559,6 +564,68 @@ export async function getAllFeedsIncludingInactive(): Promise<Feed[]> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getFeedsByCategory(category?: string): Promise<Feed[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    let stmt;
 | 
			
		||||
    let rows;
 | 
			
		||||
    
 | 
			
		||||
    if (category) {
 | 
			
		||||
      stmt = db.prepare("SELECT * FROM feeds WHERE category = ? AND active = 1 ORDER BY created_at DESC");
 | 
			
		||||
      rows = stmt.all(category) as any[];
 | 
			
		||||
    } else {
 | 
			
		||||
      // If no category specified, return all active feeds
 | 
			
		||||
      stmt = db.prepare("SELECT * FROM feeds WHERE active = 1 ORDER BY created_at DESC");
 | 
			
		||||
      rows = stmt.all() as any[];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rows.map((row) => ({
 | 
			
		||||
      id: row.id,
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      title: row.title,
 | 
			
		||||
      description: row.description,
 | 
			
		||||
      category: row.category,
 | 
			
		||||
      lastUpdated: row.last_updated,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      active: Boolean(row.active),
 | 
			
		||||
    }));
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error getting feeds by category:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getAllCategories(): Promise<string[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare("SELECT DISTINCT category FROM feeds WHERE category IS NOT NULL AND active = 1 ORDER BY category");
 | 
			
		||||
    const rows = stmt.all() as any[];
 | 
			
		||||
    
 | 
			
		||||
    return rows.map((row) => row.category).filter(Boolean);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error getting all categories:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getFeedsGroupedByCategory(): Promise<{ [category: string]: Feed[] }> {
 | 
			
		||||
  try {
 | 
			
		||||
    const feeds = await getAllFeeds();
 | 
			
		||||
    const grouped: { [category: string]: Feed[] } = {};
 | 
			
		||||
    
 | 
			
		||||
    for (const feed of feeds) {
 | 
			
		||||
      const category = feed.category || 'Uncategorized';
 | 
			
		||||
      if (!grouped[category]) {
 | 
			
		||||
        grouped[category] = [];
 | 
			
		||||
      }
 | 
			
		||||
      grouped[category].push(feed);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return grouped;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error getting feeds grouped by category:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function deleteFeed(feedId: string): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    // Start transaction
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user