2026-02-08 03:03:53 -05:00
|
|
|
import sqlite3 from 'sqlite3';
|
|
|
|
|
import { open, Database } from 'sqlite';
|
|
|
|
|
|
|
|
|
|
let db: Database | null = null;
|
|
|
|
|
|
|
|
|
|
export async function getDb() {
|
2026-02-08 03:20:36 -05:00
|
|
|
if (db) return db;
|
2026-02-08 03:03:53 -05:00
|
|
|
|
2026-02-08 03:20:36 -05:00
|
|
|
// Enable verbose mode for debugging
|
|
|
|
|
sqlite3.verbose();
|
2026-02-08 03:03:53 -05:00
|
|
|
|
2026-02-08 03:20:36 -05:00
|
|
|
const dbPath = process.env.DB_PATH || './dashboard.db';
|
2026-02-08 03:03:53 -05:00
|
|
|
|
2026-02-08 03:20:36 -05:00
|
|
|
db = await open({
|
|
|
|
|
filename: dbPath,
|
|
|
|
|
driver: sqlite3.Database
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await db.exec(`
|
2026-02-08 03:03:53 -05:00
|
|
|
CREATE TABLE IF NOT EXISTS uptime_logs (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
service_name TEXT NOT NULL,
|
|
|
|
|
url TEXT NOT NULL,
|
|
|
|
|
status TEXT NOT NULL,
|
|
|
|
|
latency INTEGER,
|
|
|
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS visitors (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
ip_hash TEXT,
|
|
|
|
|
city TEXT,
|
|
|
|
|
country TEXT,
|
|
|
|
|
lat REAL,
|
|
|
|
|
lon REAL,
|
|
|
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
2026-02-09 02:05:22 -05:00
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS monitored_services (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
name TEXT NOT NULL UNIQUE,
|
|
|
|
|
url TEXT NOT NULL,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
2026-02-09 03:28:24 -05:00
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_logs_service_timestamp
|
|
|
|
|
ON uptime_logs(service_name, timestamp);
|
Overhaul RSS feed widget: persistence, multi-feed management, search, bookmarks
- Add rss_feeds + rss_items tables with indexes and HN default seed
- Add 5-min background RSS sync loop in monitor.js with 90-day prune
- New /api/rss/feeds route for feed CRUD with immediate fetch on add
- Rewrite /api/rss route with search, feed filter, pagination, read/bookmark PATCH
- Full NewsFeed component rewrite: feed manager, search bar, filter pills,
read/unread tracking, bookmarks, favicons, auto-refresh with new items badge
- Remove placeholder widget, NewsFeed now spans 4 cols / 3 rows
- Add rss-parser deps to Dockerfile for standalone monitor
2026-02-09 04:50:06 -05:00
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS rss_feeds (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
url TEXT NOT NULL UNIQUE,
|
|
|
|
|
last_fetched DATETIME,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS rss_items (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
feed_id INTEGER NOT NULL,
|
|
|
|
|
title TEXT NOT NULL,
|
|
|
|
|
link TEXT NOT NULL UNIQUE,
|
|
|
|
|
pub_date DATETIME,
|
|
|
|
|
creator TEXT,
|
|
|
|
|
snippet TEXT,
|
|
|
|
|
read INTEGER DEFAULT 0,
|
|
|
|
|
bookmarked INTEGER DEFAULT 0,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_rss_items_feed ON rss_items(feed_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_rss_items_pubdate ON rss_items(pub_date DESC);
|
2026-02-08 03:03:53 -05:00
|
|
|
`);
|
|
|
|
|
|
Overhaul RSS feed widget: persistence, multi-feed management, search, bookmarks
- Add rss_feeds + rss_items tables with indexes and HN default seed
- Add 5-min background RSS sync loop in monitor.js with 90-day prune
- New /api/rss/feeds route for feed CRUD with immediate fetch on add
- Rewrite /api/rss route with search, feed filter, pagination, read/bookmark PATCH
- Full NewsFeed component rewrite: feed manager, search bar, filter pills,
read/unread tracking, bookmarks, favicons, auto-refresh with new items badge
- Remove placeholder widget, NewsFeed now spans 4 cols / 3 rows
- Add rss-parser deps to Dockerfile for standalone monitor
2026-02-09 04:50:06 -05:00
|
|
|
// Seed default HN feed if rss_feeds is empty
|
|
|
|
|
const feedCount = await db.get('SELECT COUNT(*) as cnt FROM rss_feeds');
|
|
|
|
|
if (feedCount?.cnt === 0) {
|
|
|
|
|
await db.run(
|
|
|
|
|
'INSERT INTO rss_feeds (name, url) VALUES (?, ?)',
|
|
|
|
|
'Hacker News', 'https://news.ycombinator.com/rss'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 17:51:06 -05:00
|
|
|
// Migrate existing non-ISO pub_date values to ISO 8601
|
|
|
|
|
try {
|
|
|
|
|
const rows = await db.all(
|
|
|
|
|
"SELECT id, pub_date FROM rss_items WHERE pub_date IS NOT NULL AND pub_date NOT LIKE '____-__-__T%'"
|
|
|
|
|
);
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const d = new Date(row.pub_date);
|
|
|
|
|
if (!isNaN(d.getTime())) {
|
|
|
|
|
await db.run('UPDATE rss_items SET pub_date = ? WHERE id = ?', d.toISOString(), row.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (rows.length > 0) {
|
|
|
|
|
console.log(`Migrated ${rows.length} rss_items pub_date values to ISO 8601`);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Table may not have data yet, ignore
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 03:20:36 -05:00
|
|
|
return db;
|
2026-02-08 03:03:53 -05:00
|
|
|
}
|