import Database from 'better-sqlite3';
import path from 'path';
import os from 'os';
// Database file path - using the same path as the main application
const DB_DIR = process.env.DB_DIR || path.join(os.homedir(), '.mcp-rss-crawler');
const DB_FILE = process.env.DB_FILE || path.join(DB_DIR, 'feeds.db');
// Create singleton database connections
let readOnlyDb: Database.Database | null = null;
let writeDb: Database.Database | null = null;
export function getDb(): Database.Database {
if (!readOnlyDb) {
try {
readOnlyDb = new Database(DB_FILE, { readonly: true });
console.log(`Connected to database at ${DB_FILE}`);
} catch (error) {
console.error('Error connecting to database:', error);
throw new Error('Failed to connect to the database');
}
}
return readOnlyDb;
}
export function getWriteDb(): Database.Database {
if (!writeDb) {
try {
writeDb = new Database(DB_FILE);
console.log(`Connected to database for writing at ${DB_FILE}`);
} catch (error) {
console.error('Error connecting to database for writing:', error);
throw new Error('Failed to connect to the database for writing');
}
}
return writeDb;
}
// Feed types
export interface Feed {
id: string;
url: string;
name: string;
category: string | null;
last_updated: number;
}
// Item types
export interface FeedItem {
id: string;
feed_id: string;
title: string;
link: string;
summary: string | null;
content: string | null;
published: number;
author: string | null;
feed_title?: string;
feed_url?: string;
categories?: string[];
}
// Get all feeds
export function getAllFeeds(): Feed[] {
const db = getDb();
return db.prepare('SELECT * FROM feeds ORDER BY category, name').all() as Feed[];
}
// Get feeds by category
export function getFeedsByCategory(category: string): Feed[] {
const db = getDb();
return db.prepare('SELECT * FROM feeds WHERE category = ? ORDER BY name').all(category) as Feed[];
}
// Get all categories
export function getAllCategories(): string[] {
const db = getDb();
const categories = db.prepare('SELECT DISTINCT category FROM feeds WHERE category IS NOT NULL').all() as { category: string }[];
return categories.map(c => c.category);
}
// Get items from a specific feed
export function getItemsByFeed(feedId: string, limit: number = 50): FeedItem[] {
const db = getDb();
return db.prepare(`
SELECT i.*, f.name as feed_title, f.url as feed_url
FROM items i
JOIN feeds f ON i.feed_id = f.id
WHERE i.feed_id = ?
ORDER BY i.published DESC
LIMIT ?
`).all(feedId, limit) as FeedItem[];
}
// Get recent items
export function getRecentItems(limit: number = 50): FeedItem[] {
const db = getDb();
return db.prepare(`
SELECT i.*, f.name as feed_title, f.url as feed_url
FROM items i
JOIN feeds f ON i.feed_id = f.id
ORDER BY i.published DESC
LIMIT ?
`).all(limit) as FeedItem[];
}
// Get items by category
export function getItemsByCategory(category: string, limit: number = 50): FeedItem[] {
const db = getDb();
return db.prepare(`
SELECT i.*, f.name as feed_title, f.url as feed_url
FROM items i
JOIN feeds f ON i.feed_id = f.id
WHERE f.category = ?
ORDER BY i.published DESC
LIMIT ?
`).all(category, limit) as FeedItem[];
}
// Search items
export function searchItems(query: string, limit: number = 50): FeedItem[] {
const db = getDb();
const searchPattern = `%${query}%`;
return db.prepare(`
SELECT i.*, f.name as feed_title, f.url as feed_url
FROM items i
JOIN feeds f ON i.feed_id = f.id
WHERE i.title LIKE ? OR i.summary LIKE ? OR i.content LIKE ?
ORDER BY i.published DESC
LIMIT ?
`).all(searchPattern, searchPattern, searchPattern, limit) as FeedItem[];
}
// Get categories for an item
export function getItemCategories(itemId: string): string[] {
const db = getDb();
const rows = db.prepare('SELECT category FROM categories WHERE item_id = ?').all(itemId) as { category: string }[];
return rows.map(row => row.category);
}
// Get a single item by ID
export function getItemById(itemId: string): FeedItem | null {
const db = getDb();
const item = db.prepare(`
SELECT i.*, f.name as feed_title, f.url as feed_url
FROM items i
JOIN feeds f ON i.feed_id = f.id
WHERE i.id = ?
`).get(itemId) as FeedItem | null;
if (item) {
item.categories = getItemCategories(itemId);
}
return item;
}
// Close the database connections
export function closeDb(): void {
if (readOnlyDb) {
readOnlyDb.close();
readOnlyDb = null;
}
if (writeDb) {
writeDb.close();
writeDb = null;
}
}