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