diff --git a/admin-server.ts b/admin-server.ts index 508dd44..2e49d47 100644 --- a/admin-server.ts +++ b/admin-server.ts @@ -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 { - try { - await batchProcess(); - } catch (error) { - console.error("Admin batch process failed:", error); - throw error; - } -} - // Start admin server serve( { diff --git a/server.ts b/server.ts index 7f1b6d5..ca1b6f7 100644 --- a/server.ts +++ b/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"); diff --git a/services/database.ts b/services/database.ts index ec90977..d1b457e 100644 --- a/services/database.ts +++ b/services/database.ts @@ -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 { 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 { 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 { 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 { 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 { } } +export async function getFeedsByCategory(category?: string): Promise { + 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 { + 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 { try { // Start transaction