This commit is contained in:
2025-06-08 18:07:34 +09:00
parent 023a7ab926
commit 2a7ebfe639

View File

@ -107,7 +107,7 @@ function extractDomain(url: string): string | null {
} }
// Initialize database with proper error handling // Initialize database with proper error handling
function initializeDatabase(): Database { async function initializeDatabase(): Promise<Database> {
// Ensure data directory exists // Ensure data directory exists
if (!fs.existsSync(config.paths.dataDir)) { if (!fs.existsSync(config.paths.dataDir)) {
fs.mkdirSync(config.paths.dataDir, { recursive: true }); fs.mkdirSync(config.paths.dataDir, { recursive: true });
@ -205,10 +205,12 @@ function initializeDatabase(): Database {
// Perform database integrity checks and fixes // Perform database integrity checks and fixes
performDatabaseIntegrityFixes(db); performDatabaseIntegrityFixes(db);
await migrateFeedsWithCategories();
return db; return db;
} }
const db = initializeDatabase(); const db = await initializeDatabase();
export interface Feed { export interface Feed {
id: string; id: string;
@ -581,11 +583,15 @@ export async function getFeedsByCategory(category?: string): Promise<Feed[]> {
let rows; let rows;
if (category) { if (category) {
stmt = db.prepare("SELECT * FROM feeds WHERE category = ? AND active = 1 ORDER BY created_at DESC"); stmt = db.prepare(
"SELECT * FROM feeds WHERE category = ? AND active = 1 ORDER BY created_at DESC",
);
rows = stmt.all(category) as any[]; rows = stmt.all(category) as any[];
} else { } else {
// If no category specified, return all active feeds // If no category specified, return all active feeds
stmt = db.prepare("SELECT * FROM feeds WHERE active = 1 ORDER BY created_at DESC"); stmt = db.prepare(
"SELECT * FROM feeds WHERE active = 1 ORDER BY created_at DESC",
);
rows = stmt.all() as any[]; rows = stmt.all() as any[];
} }
@ -607,7 +613,9 @@ export async function getFeedsByCategory(category?: string): Promise<Feed[]> {
export async function getAllCategories(): Promise<string[]> { export async function getAllCategories(): Promise<string[]> {
try { try {
const stmt = db.prepare("SELECT DISTINCT category FROM feeds WHERE category IS NOT NULL AND active = 1 ORDER BY category"); 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[]; const rows = stmt.all() as any[];
return rows.map((row) => row.category).filter(Boolean); return rows.map((row) => row.category).filter(Boolean);
@ -617,13 +625,15 @@ export async function getAllCategories(): Promise<string[]> {
} }
} }
export async function getFeedsGroupedByCategory(): Promise<{ [category: string]: Feed[] }> { export async function getFeedsGroupedByCategory(): Promise<{
[category: string]: Feed[];
}> {
try { try {
const feeds = await getAllFeeds(); const feeds = await getAllFeeds();
const grouped: { [category: string]: Feed[] } = {}; const grouped: { [category: string]: Feed[] } = {};
for (const feed of feeds) { for (const feed of feeds) {
const category = feed.category || 'Uncategorized'; const category = feed.category || "Uncategorized";
if (!grouped[category]) { if (!grouped[category]) {
grouped[category] = []; grouped[category] = [];
} }
@ -1121,7 +1131,9 @@ export async function migrateFeedsWithCategories(): Promise<void> {
console.log("🔄 Starting feed category migration..."); console.log("🔄 Starting feed category migration...");
// Get all feeds without categories // Get all feeds without categories
const stmt = db.prepare("SELECT * FROM feeds WHERE category IS NULL OR category = ''"); const stmt = db.prepare(
"SELECT * FROM feeds WHERE category IS NULL OR category = ''",
);
const feedsWithoutCategories = stmt.all() as any[]; const feedsWithoutCategories = stmt.all() as any[];
if (feedsWithoutCategories.length === 0) { if (feedsWithoutCategories.length === 0) {
@ -1129,7 +1141,9 @@ export async function migrateFeedsWithCategories(): Promise<void> {
return; return;
} }
console.log(`📋 Found ${feedsWithoutCategories.length} feeds without categories`); console.log(
`📋 Found ${feedsWithoutCategories.length} feeds without categories`,
);
// Import LLM service // Import LLM service
const { openAI_ClassifyFeed } = await import("./llm.js"); const { openAI_ClassifyFeed } = await import("./llm.js");
@ -1148,30 +1162,41 @@ export async function migrateFeedsWithCategories(): Promise<void> {
const category = await openAI_ClassifyFeed(titleForClassification); const category = await openAI_ClassifyFeed(titleForClassification);
// Update the feed with the category // Update the feed with the category
const updateStmt = db.prepare("UPDATE feeds SET category = ? WHERE id = ?"); const updateStmt = db.prepare(
"UPDATE feeds SET category = ? WHERE id = ?",
);
updateStmt.run(category, feed.id); updateStmt.run(category, feed.id);
console.log(`✅ Assigned category "${category}" to feed: ${titleForClassification}`); console.log(
`✅ Assigned category "${category}" to feed: ${titleForClassification}`,
);
processedCount++; processedCount++;
// Add a small delay to avoid rate limiting // Add a small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) { } catch (error) {
console.error(`❌ Failed to classify feed ${feed.title || feed.url}:`, error); console.error(
`❌ Failed to classify feed ${feed.title || feed.url}:`,
error,
);
errorCount++; errorCount++;
// Set a default category for failed classifications // Set a default category for failed classifications
const defaultCategory = "その他"; const defaultCategory = "その他";
const updateStmt = db.prepare("UPDATE feeds SET category = ? WHERE id = ?"); const updateStmt = db.prepare(
"UPDATE feeds SET category = ? WHERE id = ?",
);
updateStmt.run(defaultCategory, feed.id); updateStmt.run(defaultCategory, feed.id);
console.log(`⚠️ Assigned default category "${defaultCategory}" to feed: ${feed.title || feed.url}`); console.log(
`! Assigned default category "${defaultCategory}" to feed: ${feed.title || feed.url}`,
);
} }
} }
console.log(`✅ Feed category migration completed`); console.log(`✅ Feed category migration completed`);
console.log(`📊 Processed: ${processedCount}, Errors: ${errorCount}, Total: ${feedsWithoutCategories.length}`); console.log(
`📊 Processed: ${processedCount}, Errors: ${errorCount}, Total: ${feedsWithoutCategories.length}`,
);
} catch (error) { } catch (error) {
console.error("❌ Error during feed category migration:", error); console.error("❌ Error during feed category migration:", error);
throw error; throw error;
@ -1186,11 +1211,15 @@ export async function getFeedCategoryMigrationStatus(): Promise<{
migrationComplete: boolean; migrationComplete: boolean;
}> { }> {
try { try {
const totalStmt = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE active = 1"); const totalStmt = db.prepare(
"SELECT COUNT(*) as count FROM feeds WHERE active = 1",
);
const totalResult = totalStmt.get() as any; const totalResult = totalStmt.get() as any;
const totalFeeds = totalResult.count; 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 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 withCategoriesResult = withCategoriesStmt.get() as any;
const feedsWithCategories = withCategoriesResult.count; const feedsWithCategories = withCategoriesResult.count;
@ -1212,7 +1241,9 @@ export async function getFeedCategoryMigrationStatus(): Promise<{
export async function deleteEpisode(episodeId: string): Promise<boolean> { export async function deleteEpisode(episodeId: string): Promise<boolean> {
try { try {
// Get episode info first to find the audio file path // Get episode info first to find the audio file path
const episodeStmt = db.prepare("SELECT audio_path FROM episodes WHERE id = ?"); const episodeStmt = db.prepare(
"SELECT audio_path FROM episodes WHERE id = ?",
);
const episode = episodeStmt.get(episodeId) as any; const episode = episodeStmt.get(episodeId) as any;
if (!episode) { if (!episode) {
@ -1226,13 +1257,19 @@ export async function deleteEpisode(episodeId: string): Promise<boolean> {
// If database deletion successful, try to delete the audio file // If database deletion successful, try to delete the audio file
if (result.changes > 0 && episode.audio_path) { if (result.changes > 0 && episode.audio_path) {
try { try {
const fullAudioPath = path.join(config.paths.projectRoot, episode.audio_path); const fullAudioPath = path.join(
config.paths.projectRoot,
episode.audio_path,
);
if (fs.existsSync(fullAudioPath)) { if (fs.existsSync(fullAudioPath)) {
fs.unlinkSync(fullAudioPath); fs.unlinkSync(fullAudioPath);
console.log(`🗑 Deleted audio file: ${fullAudioPath}`); console.log(`🗑 Deleted audio file: ${fullAudioPath}`);
} }
} catch (fileError) { } catch (fileError) {
console.warn(`⚠️ Failed to delete audio file ${episode.audio_path}:`, fileError); console.warn(
`! Failed to delete audio file ${episode.audio_path}:`,
fileError,
);
// Don't fail the operation if file deletion fails // Don't fail the operation if file deletion fails
} }
} }