Apply formatting
This commit is contained in:
133
server.ts
133
server.ts
@ -22,7 +22,7 @@ app.get("/assets/*", async (c) => {
|
||||
try {
|
||||
const filePath = path.join(config.paths.frontendBuildDir, c.req.path);
|
||||
const file = Bun.file(filePath);
|
||||
|
||||
|
||||
if (await file.exists()) {
|
||||
const contentType = filePath.endsWith(".js")
|
||||
? "application/javascript"
|
||||
@ -42,15 +42,18 @@ app.get("/assets/*", async (c) => {
|
||||
app.get("/podcast_audio/*", async (c) => {
|
||||
try {
|
||||
const audioFileName = c.req.path.substring("/podcast_audio/".length);
|
||||
|
||||
|
||||
// Basic security check
|
||||
if (audioFileName.includes("..") || audioFileName.includes("/")) {
|
||||
return c.notFound();
|
||||
}
|
||||
|
||||
const audioFilePath = path.join(config.paths.podcastAudioDir, audioFileName);
|
||||
|
||||
const audioFilePath = path.join(
|
||||
config.paths.podcastAudioDir,
|
||||
audioFileName,
|
||||
);
|
||||
const file = Bun.file(audioFilePath);
|
||||
|
||||
|
||||
if (await file.exists()) {
|
||||
const blob = await file.arrayBuffer();
|
||||
return c.body(blob, 200, { "Content-Type": "audio/mpeg" });
|
||||
@ -66,7 +69,7 @@ app.get("/podcast.xml", async (c) => {
|
||||
try {
|
||||
const filePath = path.join(config.paths.publicDir, "podcast.xml");
|
||||
const file = Bun.file(filePath);
|
||||
|
||||
|
||||
if (await file.exists()) {
|
||||
const blob = await file.arrayBuffer();
|
||||
return c.body(blob, 200, {
|
||||
@ -74,7 +77,7 @@ app.get("/podcast.xml", async (c) => {
|
||||
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.warn("podcast.xml not found");
|
||||
return c.notFound();
|
||||
} catch (error) {
|
||||
@ -83,18 +86,17 @@ app.get("/podcast.xml", async (c) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Frontend fallback routes
|
||||
async function serveIndex(c: any) {
|
||||
try {
|
||||
const indexPath = path.join(config.paths.frontendBuildDir, "index.html");
|
||||
const file = Bun.file(indexPath);
|
||||
|
||||
|
||||
if (await file.exists()) {
|
||||
const blob = await file.arrayBuffer();
|
||||
return c.body(blob, 200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
}
|
||||
|
||||
|
||||
console.error(`index.html not found at ${indexPath}`);
|
||||
return c.text("Frontend not built. Run 'bun run build:frontend'", 404);
|
||||
} catch (error) {
|
||||
@ -110,7 +112,9 @@ app.get("/index.html", serveIndex);
|
||||
// API endpoints for frontend
|
||||
app.get("/api/episodes", async (c) => {
|
||||
try {
|
||||
const { fetchEpisodesWithFeedInfo } = await import("./services/database.js");
|
||||
const { fetchEpisodesWithFeedInfo } = await import(
|
||||
"./services/database.js"
|
||||
);
|
||||
const episodes = await fetchEpisodesWithFeedInfo();
|
||||
return c.json({ episodes });
|
||||
} catch (error) {
|
||||
@ -124,34 +128,34 @@ app.get("/api/episodes-from-xml", async (c) => {
|
||||
const xml2js = await import("xml2js");
|
||||
const fs = await import("fs");
|
||||
const podcastXmlPath = path.join(config.paths.publicDir, "podcast.xml");
|
||||
|
||||
|
||||
// Check if podcast.xml exists
|
||||
if (!fs.existsSync(podcastXmlPath)) {
|
||||
return c.json({ episodes: [], message: "podcast.xml not found" });
|
||||
}
|
||||
|
||||
// Read and parse XML
|
||||
const xmlContent = fs.readFileSync(podcastXmlPath, 'utf-8');
|
||||
const xmlContent = fs.readFileSync(podcastXmlPath, "utf-8");
|
||||
const parser = new xml2js.Parser();
|
||||
const result = await parser.parseStringPromise(xmlContent);
|
||||
|
||||
|
||||
const episodes = [];
|
||||
const items = result?.rss?.channel?.[0]?.item || [];
|
||||
|
||||
|
||||
for (const item of items) {
|
||||
const episode = {
|
||||
id: generateEpisodeId(item),
|
||||
title: item.title?.[0] || 'Untitled',
|
||||
description: item.description?.[0] || '',
|
||||
pubDate: item.pubDate?.[0] || '',
|
||||
audioUrl: item.enclosure?.[0]?.$?.url || '',
|
||||
audioLength: item.enclosure?.[0]?.$?.length || '0',
|
||||
guid: item.guid?.[0] || '',
|
||||
link: item.link?.[0] || ''
|
||||
title: item.title?.[0] || "Untitled",
|
||||
description: item.description?.[0] || "",
|
||||
pubDate: item.pubDate?.[0] || "",
|
||||
audioUrl: item.enclosure?.[0]?.$?.url || "",
|
||||
audioLength: item.enclosure?.[0]?.$?.length || "0",
|
||||
guid: item.guid?.[0] || "",
|
||||
link: item.link?.[0] || "",
|
||||
};
|
||||
episodes.push(episode);
|
||||
}
|
||||
|
||||
|
||||
return c.json({ episodes });
|
||||
} catch (error) {
|
||||
console.error("Error parsing podcast XML:", error);
|
||||
@ -163,19 +167,20 @@ app.get("/api/episodes-from-xml", async (c) => {
|
||||
function generateEpisodeId(item: any): string {
|
||||
// Use GUID if available, otherwise generate from title and audio URL
|
||||
if (item.guid?.[0]) {
|
||||
return encodeURIComponent(item.guid[0].replace(/[^a-zA-Z0-9-_]/g, '-'));
|
||||
return encodeURIComponent(item.guid[0].replace(/[^a-zA-Z0-9-_]/g, "-"));
|
||||
}
|
||||
|
||||
const title = item.title?.[0] || '';
|
||||
const audioUrl = item.enclosure?.[0]?.$?.url || '';
|
||||
const titleSlug = title.toLowerCase()
|
||||
.replace(/[^a-zA-Z0-9\s]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
|
||||
const title = item.title?.[0] || "";
|
||||
const audioUrl = item.enclosure?.[0]?.$?.url || "";
|
||||
const titleSlug = title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-zA-Z0-9\s]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.substring(0, 50);
|
||||
|
||||
|
||||
// Extract filename from audio URL as fallback
|
||||
const audioFilename = audioUrl.split('/').pop()?.split('.')[0] || 'episode';
|
||||
|
||||
const audioFilename = audioUrl.split("/").pop()?.split(".")[0] || "episode";
|
||||
|
||||
return titleSlug || audioFilename;
|
||||
}
|
||||
|
||||
@ -185,33 +190,35 @@ app.get("/api/episode/:episodeId", async (c) => {
|
||||
const xml2js = await import("xml2js");
|
||||
const fs = await import("fs");
|
||||
const podcastXmlPath = path.join(config.paths.publicDir, "podcast.xml");
|
||||
|
||||
|
||||
if (!fs.existsSync(podcastXmlPath)) {
|
||||
return c.json({ error: "podcast.xml not found" }, 404);
|
||||
}
|
||||
|
||||
const xmlContent = fs.readFileSync(podcastXmlPath, 'utf-8');
|
||||
const xmlContent = fs.readFileSync(podcastXmlPath, "utf-8");
|
||||
const parser = new xml2js.Parser();
|
||||
const result = await parser.parseStringPromise(xmlContent);
|
||||
|
||||
|
||||
const items = result?.rss?.channel?.[0]?.item || [];
|
||||
const targetItem = items.find((item: any) => generateEpisodeId(item) === episodeId);
|
||||
|
||||
const targetItem = items.find(
|
||||
(item: any) => generateEpisodeId(item) === episodeId,
|
||||
);
|
||||
|
||||
if (!targetItem) {
|
||||
return c.json({ error: "Episode not found" }, 404);
|
||||
}
|
||||
|
||||
|
||||
const episode = {
|
||||
id: episodeId,
|
||||
title: targetItem.title?.[0] || 'Untitled',
|
||||
description: targetItem.description?.[0] || '',
|
||||
pubDate: targetItem.pubDate?.[0] || '',
|
||||
audioUrl: targetItem.enclosure?.[0]?.$?.url || '',
|
||||
audioLength: targetItem.enclosure?.[0]?.$?.length || '0',
|
||||
guid: targetItem.guid?.[0] || '',
|
||||
link: targetItem.link?.[0] || ''
|
||||
title: targetItem.title?.[0] || "Untitled",
|
||||
description: targetItem.description?.[0] || "",
|
||||
pubDate: targetItem.pubDate?.[0] || "",
|
||||
audioUrl: targetItem.enclosure?.[0]?.$?.url || "",
|
||||
audioLength: targetItem.enclosure?.[0]?.$?.length || "0",
|
||||
guid: targetItem.guid?.[0] || "",
|
||||
link: targetItem.link?.[0] || "",
|
||||
};
|
||||
|
||||
|
||||
return c.json({ episode });
|
||||
} catch (error) {
|
||||
console.error("Error fetching episode:", error);
|
||||
@ -235,11 +242,11 @@ app.get("/api/feeds/:feedId", async (c) => {
|
||||
const feedId = c.req.param("feedId");
|
||||
const { getFeedById } = await import("./services/database.js");
|
||||
const feed = await getFeedById(feedId);
|
||||
|
||||
|
||||
if (!feed) {
|
||||
return c.json({ error: "Feed not found" }, 404);
|
||||
}
|
||||
|
||||
|
||||
return c.json({ feed });
|
||||
} catch (error) {
|
||||
console.error("Error fetching feed:", error);
|
||||
@ -261,7 +268,9 @@ app.get("/api/feeds/:feedId/episodes", async (c) => {
|
||||
|
||||
app.get("/api/episodes-with-feed-info", async (c) => {
|
||||
try {
|
||||
const { fetchEpisodesWithFeedInfo } = await import("./services/database.js");
|
||||
const { fetchEpisodesWithFeedInfo } = await import(
|
||||
"./services/database.js"
|
||||
);
|
||||
const episodes = await fetchEpisodesWithFeedInfo();
|
||||
return c.json({ episodes });
|
||||
} catch (error) {
|
||||
@ -273,13 +282,15 @@ app.get("/api/episodes-with-feed-info", async (c) => {
|
||||
app.get("/api/episode-with-source/:episodeId", async (c) => {
|
||||
try {
|
||||
const episodeId = c.req.param("episodeId");
|
||||
const { fetchEpisodeWithSourceInfo } = await import("./services/database.js");
|
||||
const { fetchEpisodeWithSourceInfo } = await import(
|
||||
"./services/database.js"
|
||||
);
|
||||
const episode = await fetchEpisodeWithSourceInfo(episodeId);
|
||||
|
||||
|
||||
if (!episode) {
|
||||
return c.json({ error: "Episode not found" }, 404);
|
||||
}
|
||||
|
||||
|
||||
return c.json({ episode });
|
||||
} catch (error) {
|
||||
console.error("Error fetching episode with source info:", error);
|
||||
@ -291,8 +302,8 @@ app.post("/api/feed-requests", async (c) => {
|
||||
try {
|
||||
const body = await c.req.json();
|
||||
const { url, requestMessage } = body;
|
||||
|
||||
if (!url || typeof url !== 'string') {
|
||||
|
||||
if (!url || typeof url !== "string") {
|
||||
return c.json({ error: "URL is required" }, 400);
|
||||
}
|
||||
|
||||
@ -301,11 +312,11 @@ app.post("/api/feed-requests", async (c) => {
|
||||
url,
|
||||
requestMessage,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: "Feed request submitted successfully",
|
||||
requestId
|
||||
requestId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error submitting feed request:", error);
|
||||
@ -329,6 +340,8 @@ serve(
|
||||
console.log(`🌟 Server is running on http://localhost:${info.port}`);
|
||||
console.log(`📡 Using configuration from: ${config.paths.projectRoot}`);
|
||||
console.log(`🗄️ Database: ${config.paths.dbPath}`);
|
||||
console.log(`🔄 Batch scheduler is active and will manage automatic processing`);
|
||||
console.log(
|
||||
`🔄 Batch scheduler is active and will manage automatic processing`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
Reference in New Issue
Block a user