175 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Hono } from "hono";
 | 
						||
import { serve } from "@hono/node-server";
 | 
						||
import fs from "fs";
 | 
						||
import path from "path";
 | 
						||
import { Database } from "bun:sqlite";
 | 
						||
 | 
						||
const projectRoot = import.meta.dirname;
 | 
						||
 | 
						||
// データベースパスの設定
 | 
						||
const dbPath = path.join(projectRoot, "data/podcast.db");
 | 
						||
const dataDir = path.dirname(dbPath);
 | 
						||
if (!fs.existsSync(dataDir)) {
 | 
						||
  fs.mkdirSync(dataDir, { recursive: true });
 | 
						||
}
 | 
						||
const db = new Database(dbPath);
 | 
						||
if (!fs.existsSync(dbPath)) {
 | 
						||
  fs.closeSync(fs.openSync(dbPath, "w"));
 | 
						||
}
 | 
						||
db.exec(fs.readFileSync(path.join(projectRoot, "schema.sql"), "utf-8"));
 | 
						||
 | 
						||
// 静的ファイルパスの設定
 | 
						||
const frontendBuildDir = path.join(projectRoot, "frontend", "dist");
 | 
						||
const podcastAudioDir = path.join(projectRoot, "static", "podcast_audio");
 | 
						||
const generalPublicDir = path.join(projectRoot, "public");
 | 
						||
 | 
						||
const app = new Hono();
 | 
						||
 | 
						||
// APIルート(順序を最適化)
 | 
						||
app.get("/api/feeds", async (c) => {
 | 
						||
  const rows = db
 | 
						||
    .query("SELECT feed_url FROM processed_feed_items GROUP BY feed_url")
 | 
						||
    .all() as { feed_url: string }[];
 | 
						||
  return c.json(rows.map((r) => r.feed_url));
 | 
						||
});
 | 
						||
 | 
						||
app.post("/api/feeds", async (c) => {
 | 
						||
  try {
 | 
						||
    const { feedUrl } = await c.req.json<{ feedUrl: string }>();
 | 
						||
    console.log("Received feedUrl to add:", feedUrl);
 | 
						||
    // TODO: feedUrl をデータベースに保存する処理
 | 
						||
    return c.json({ result: "OK" });
 | 
						||
  } catch (e) {
 | 
						||
    return c.json({ error: "Invalid JSON body" }, 400);
 | 
						||
  }
 | 
						||
});
 | 
						||
 | 
						||
app.get("/api/episodes", (c) => {
 | 
						||
  const episodes = db
 | 
						||
    .query("SELECT * FROM episodes ORDER BY pubDate DESC")
 | 
						||
    .all();
 | 
						||
  return c.json(episodes);
 | 
						||
});
 | 
						||
 | 
						||
app.post("/api/episodes/:id/regenerate", (c) => {
 | 
						||
  const id = c.req.param("id");
 | 
						||
  console.log("Regeneration requested for episode ID:", id);
 | 
						||
  // TODO: 再生成ロジックを実装
 | 
						||
  return c.json({ result: `Regeneration requested for ${id}` });
 | 
						||
});
 | 
						||
 | 
						||
// 静的ファイルの処理
 | 
						||
 | 
						||
// Vite ビルドの静的ファイル(main.js, assets/ など)
 | 
						||
app.get("/assets/*", async (c) => {
 | 
						||
  const filePath = path.join(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();
 | 
						||
});
 | 
						||
 | 
						||
// podcast_audio
 | 
						||
app.get("/podcast_audio/*", async (c) => {
 | 
						||
  const audioFileName = c.req.path.substring("/podcast_audio/".length);
 | 
						||
  const audioFilePath = path.join(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();
 | 
						||
});
 | 
						||
 | 
						||
// podcast.xml
 | 
						||
app.get("/podcast.xml", async (c) => {
 | 
						||
  const filePath = path.join(generalPublicDir, "podcast.xml");
 | 
						||
  try {
 | 
						||
    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",
 | 
						||
      });
 | 
						||
    }
 | 
						||
  } catch (e) {
 | 
						||
    console.error(`Error serving podcast.xml ${filePath}:`, e);
 | 
						||
  }
 | 
						||
  return c.notFound();
 | 
						||
});
 | 
						||
 | 
						||
// フィードURL追加API
 | 
						||
app.post("/api/add-feed", async (c) => {
 | 
						||
  const { feedUrl } = await c.req.json();
 | 
						||
  if (!feedUrl || typeof feedUrl !== "string") {
 | 
						||
    return c.json({ error: "フィードURLが無効です" }, 400);
 | 
						||
  }
 | 
						||
 | 
						||
  try {
 | 
						||
    // フィードURLを追加するロジック(例: scripts/fetch_and_generate.ts で実装)
 | 
						||
    const { addNewFeedUrl } = require("./scripts/fetch_and_generate");
 | 
						||
    await addNewFeedUrl(feedUrl);
 | 
						||
    return c.json({ message: "フィードが追加されました" });
 | 
						||
  } catch (err) {
 | 
						||
    console.error("フィード追加エラー:", err);
 | 
						||
    return c.json({ error: "フィードの追加に失敗しました" }, 500);
 | 
						||
  }
 | 
						||
});
 | 
						||
 | 
						||
// フォールバックとして index.html(ルートパス)
 | 
						||
app.get("/", async (c) => {
 | 
						||
  const indexPath = path.join(frontendBuildDir, "index.html");
 | 
						||
  const file = Bun.file(indexPath);
 | 
						||
  if (await file.exists()) {
 | 
						||
    console.log(`Serving index.html from ${indexPath}`);
 | 
						||
    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.notFound();
 | 
						||
});
 | 
						||
 | 
						||
// フォールバックとして index.html(明示的なパス)
 | 
						||
app.get("/index.html", async (c) => {
 | 
						||
  const indexPath = path.join(frontendBuildDir, "index.html");
 | 
						||
  const file = Bun.file(indexPath);
 | 
						||
  if (await file.exists()) {
 | 
						||
    console.log(`Serving index.html from ${indexPath}`);
 | 
						||
    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.notFound();
 | 
						||
});
 | 
						||
 | 
						||
// その他のパスも index.html へフォールバック
 | 
						||
app.get("*", async (c) => {
 | 
						||
  const indexPath = path.join(frontendBuildDir, "index.html");
 | 
						||
  const file = Bun.file(indexPath);
 | 
						||
  if (await file.exists()) {
 | 
						||
    console.log(`Serving index.html from ${indexPath}`);
 | 
						||
    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.notFound();
 | 
						||
});
 | 
						||
 | 
						||
// サーバー起動
 | 
						||
serve(
 | 
						||
  {
 | 
						||
    fetch: app.fetch,
 | 
						||
    port: 3000,
 | 
						||
  },
 | 
						||
  (info) => {
 | 
						||
    console.log(`Server is running on http://localhost:${info.port}`);
 | 
						||
  },
 | 
						||
);
 |