171 lines
4.7 KiB
TypeScript
171 lines
4.7 KiB
TypeScript
import { Hono } from "hono";
|
|
import { serve } from "@hono/node-server";
|
|
import path from "path";
|
|
import { config, validateConfig } from "./services/config.js";
|
|
import { batchProcess } from "./scripts/fetch_and_generate.js";
|
|
|
|
// Validate configuration on startup
|
|
try {
|
|
validateConfig();
|
|
console.log("Configuration validated successfully");
|
|
} catch (error) {
|
|
console.error("Configuration validation failed:", error);
|
|
process.exit(1);
|
|
}
|
|
|
|
const app = new Hono();
|
|
|
|
// 静的ファイルの処理
|
|
|
|
// Static file handlers
|
|
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"
|
|
: filePath.endsWith(".css")
|
|
? "text/css"
|
|
: "application/octet-stream";
|
|
const blob = await file.arrayBuffer();
|
|
return c.body(blob, 200, { "Content-Type": contentType });
|
|
}
|
|
return c.notFound();
|
|
} catch (error) {
|
|
console.error("Error serving asset:", error);
|
|
return c.notFound();
|
|
}
|
|
});
|
|
|
|
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 file = Bun.file(audioFilePath);
|
|
|
|
if (await file.exists()) {
|
|
const blob = await file.arrayBuffer();
|
|
return c.body(blob, 200, { "Content-Type": "audio/mpeg" });
|
|
}
|
|
return c.notFound();
|
|
} catch (error) {
|
|
console.error("Error serving audio file:", error);
|
|
return c.notFound();
|
|
}
|
|
});
|
|
|
|
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, {
|
|
"Content-Type": "application/xml; charset=utf-8",
|
|
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
|
|
});
|
|
}
|
|
|
|
console.warn("podcast.xml not found");
|
|
return c.notFound();
|
|
} catch (error) {
|
|
console.error("Error serving podcast.xml:", error);
|
|
return c.notFound();
|
|
}
|
|
});
|
|
|
|
|
|
// 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) {
|
|
console.error("Error serving index.html:", error);
|
|
return c.text("Internal server error", 500);
|
|
}
|
|
}
|
|
|
|
app.get("/", serveIndex);
|
|
|
|
app.get("/index.html", serveIndex);
|
|
|
|
// Catch-all for SPA routing
|
|
app.get("*", serveIndex);
|
|
|
|
// Batch processing functions
|
|
function scheduleFirstBatchProcess() {
|
|
setTimeout(async () => {
|
|
try {
|
|
console.log("🚀 Running initial batch process...");
|
|
await runBatchProcess();
|
|
console.log("✅ Initial batch process completed");
|
|
} catch (error) {
|
|
console.error("❌ Error during initial batch process:", error);
|
|
}
|
|
}, 10000); // Wait 10 seconds after startup
|
|
}
|
|
|
|
function scheduleSixHourlyBatchProcess() {
|
|
const SIX_HOURS_MS = 6 * 60 * 60 * 1000; // 6 hours in milliseconds
|
|
|
|
console.log(
|
|
`🕕 Next batch process scheduled in 6 hours (${new Date(Date.now() + SIX_HOURS_MS).toLocaleString()})`
|
|
);
|
|
|
|
setTimeout(async () => {
|
|
try {
|
|
console.log("🔄 Running scheduled 6-hourly batch process...");
|
|
await runBatchProcess();
|
|
console.log("✅ Scheduled batch process completed");
|
|
} catch (error) {
|
|
console.error("❌ Error during scheduled batch process:", error);
|
|
}
|
|
// Schedule next run
|
|
scheduleSixHourlyBatchProcess();
|
|
}, SIX_HOURS_MS);
|
|
}
|
|
|
|
async function runBatchProcess(): Promise<void> {
|
|
try {
|
|
await batchProcess();
|
|
} catch (error) {
|
|
console.error("Batch process failed:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// サーバー起動
|
|
serve(
|
|
{
|
|
fetch: app.fetch,
|
|
port: 3000,
|
|
},
|
|
(info) => {
|
|
console.log(`🌟 Server is running on http://localhost:${info.port}`);
|
|
console.log(`📡 Using configuration from: ${config.paths.projectRoot}`);
|
|
console.log(`🗄️ Database: ${config.paths.dbPath}`);
|
|
|
|
// Schedule batch processes
|
|
scheduleFirstBatchProcess();
|
|
scheduleSixHourlyBatchProcess();
|
|
},
|
|
);
|