This commit is contained in:
2025-06-08 17:48:07 +09:00
parent 41a433893b
commit f34c601ec0
7 changed files with 705 additions and 13 deletions

View File

@ -131,6 +131,7 @@ function initializeDatabase(): Database {
url TEXT NOT NULL UNIQUE,
title TEXT,
description TEXT,
category TEXT,
last_updated TEXT,
created_at TEXT NOT NULL,
active BOOLEAN DEFAULT 1
@ -267,6 +268,7 @@ export interface EpisodeWithFeedInfo {
feedId: string;
feedTitle?: string;
feedUrl: string;
feedCategory?: string;
}
// Feed management functions
@ -280,11 +282,12 @@ export async function saveFeed(
if (existingFeed) {
// Update existing feed
const updateStmt = db.prepare(
"UPDATE feeds SET title = ?, description = ?, last_updated = ?, active = ? WHERE url = ?",
"UPDATE feeds SET title = ?, description = ?, category = ?, last_updated = ?, active = ? WHERE url = ?",
);
updateStmt.run(
feed.title || null,
feed.description || null,
feed.category || null,
feed.lastUpdated || null,
feed.active !== undefined ? (feed.active ? 1 : 0) : 1,
feed.url,
@ -296,13 +299,14 @@ export async function saveFeed(
const createdAt = new Date().toISOString();
const insertStmt = db.prepare(
"INSERT INTO feeds (id, url, title, description, last_updated, created_at, active) VALUES (?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO feeds (id, url, title, description, category, last_updated, created_at, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
);
insertStmt.run(
id,
feed.url,
feed.title || null,
feed.description || null,
feed.category || null,
feed.lastUpdated || null,
createdAt,
feed.active !== undefined ? (feed.active ? 1 : 0) : 1,
@ -407,7 +411,8 @@ export async function fetchEpisodesWithFeedInfo(): Promise<
a.pub_date as articlePubDate,
f.id as feedId,
f.title as feedTitle,
f.url as feedUrl
f.url as feedUrl,
f.category as feedCategory
FROM episodes e
JOIN articles a ON e.article_id = a.id
JOIN feeds f ON a.feed_id = f.id
@ -432,6 +437,7 @@ export async function fetchEpisodesWithFeedInfo(): Promise<
feedId: row.feedId,
feedTitle: row.feedTitle,
feedUrl: row.feedUrl,
feedCategory: row.feedCategory,
}));
} catch (error) {
console.error("Error fetching episodes with feed info:", error);
@ -459,7 +465,8 @@ export async function fetchEpisodesByFeedId(
a.pub_date as articlePubDate,
f.id as feedId,
f.title as feedTitle,
f.url as feedUrl
f.url as feedUrl,
f.category as feedCategory
FROM episodes e
JOIN articles a ON e.article_id = a.id
JOIN feeds f ON a.feed_id = f.id
@ -484,6 +491,7 @@ export async function fetchEpisodesByFeedId(
feedId: row.feedId,
feedTitle: row.feedTitle,
feedUrl: row.feedUrl,
feedCategory: row.feedCategory,
}));
} catch (error) {
console.error("Error fetching episodes by feed ID:", error);
@ -511,7 +519,8 @@ export async function fetchEpisodeWithSourceInfo(
a.pub_date as articlePubDate,
f.id as feedId,
f.title as feedTitle,
f.url as feedUrl
f.url as feedUrl,
f.category as feedCategory
FROM episodes e
JOIN articles a ON e.article_id = a.id
JOIN feeds f ON a.feed_id = f.id
@ -536,6 +545,7 @@ export async function fetchEpisodeWithSourceInfo(
feedId: row.feedId,
feedTitle: row.feedTitle,
feedUrl: row.feedUrl,
feedCategory: row.feedCategory,
};
} catch (error) {
console.error("Error fetching episode with source info:", error);
@ -1104,6 +1114,100 @@ export async function updateFeedRequestStatus(
}
}
// Migration function to classify existing feeds without categories
export async function migrateFeedsWithCategories(): Promise<void> {
try {
console.log("🔄 Starting feed category migration...");
// Get all feeds without categories
const stmt = db.prepare("SELECT * FROM feeds WHERE category IS NULL OR category = ''");
const feedsWithoutCategories = stmt.all() as any[];
if (feedsWithoutCategories.length === 0) {
console.log("✅ All feeds already have categories assigned");
return;
}
console.log(`📋 Found ${feedsWithoutCategories.length} feeds without categories`);
// Import LLM service
const { openAI_ClassifyFeed } = await import("./llm.js");
let processedCount = 0;
let errorCount = 0;
for (const feed of feedsWithoutCategories) {
try {
// Use title for classification, fallback to URL if no title
const titleForClassification = feed.title || feed.url;
console.log(`🔍 Classifying feed: ${titleForClassification}`);
// Classify the feed
const category = await openAI_ClassifyFeed(titleForClassification);
// Update the feed with the category
const updateStmt = db.prepare("UPDATE feeds SET category = ? WHERE id = ?");
updateStmt.run(category, feed.id);
console.log(`✅ Assigned category "${category}" to feed: ${titleForClassification}`);
processedCount++;
// Add a small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`❌ Failed to classify feed ${feed.title || feed.url}:`, error);
errorCount++;
// Set a default category for failed classifications
const defaultCategory = "その他";
const updateStmt = db.prepare("UPDATE feeds SET category = ? WHERE id = ?");
updateStmt.run(defaultCategory, feed.id);
console.log(`⚠️ Assigned default category "${defaultCategory}" to feed: ${feed.title || feed.url}`);
}
}
console.log(`✅ Feed category migration completed`);
console.log(`📊 Processed: ${processedCount}, Errors: ${errorCount}, Total: ${feedsWithoutCategories.length}`);
} catch (error) {
console.error("❌ Error during feed category migration:", error);
throw error;
}
}
// Function to get migration status
export async function getFeedCategoryMigrationStatus(): Promise<{
totalFeeds: number;
feedsWithCategories: number;
feedsWithoutCategories: number;
migrationComplete: boolean;
}> {
try {
const totalStmt = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE active = 1");
const totalResult = totalStmt.get() as any;
const totalFeeds = totalResult.count;
const withCategoriesStmt = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE active = 1 AND category IS NOT NULL AND category != ''");
const withCategoriesResult = withCategoriesStmt.get() as any;
const feedsWithCategories = withCategoriesResult.count;
const feedsWithoutCategories = totalFeeds - feedsWithCategories;
const migrationComplete = feedsWithoutCategories === 0;
return {
totalFeeds,
feedsWithCategories,
feedsWithoutCategories,
migrationComplete,
};
} catch (error) {
console.error("Error getting migration status:", error);
throw error;
}
}
export function closeDatabase(): void {
db.close();
}