219 lines
6.2 KiB
JavaScript
219 lines
6.2 KiB
JavaScript
const Database = require('better-sqlite3')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
const dataDir = path.join(process.cwd(), 'data')
|
|
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
|
|
const dbPath = path.join(dataDir, 'toolbox.db')
|
|
const db = new Database(dbPath)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS posts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid TEXT NOT NULL,
|
|
mid TEXT NOT NULL UNIQUE,
|
|
screen_name TEXT,
|
|
created_at TEXT,
|
|
text TEXT,
|
|
text_plain TEXT,
|
|
url TEXT,
|
|
reposts_count INTEGER,
|
|
comments_count INTEGER,
|
|
attitudes_count INTEGER,
|
|
pics_json TEXT,
|
|
fetched_at TEXT
|
|
);`
|
|
)
|
|
|
|
// migrations: add retweeted_* columns if missing
|
|
const cols = db.prepare(`PRAGMA table_info(posts)`).all().map(r => r.name)
|
|
const addCol = name => db.exec(`ALTER TABLE posts ADD COLUMN ${name} TEXT`)
|
|
if (!cols.includes('retweeted_mid')) addCol('retweeted_mid')
|
|
if (!cols.includes('retweeted_screen_name')) addCol('retweeted_screen_name')
|
|
if (!cols.includes('retweeted_text')) addCol('retweeted_text')
|
|
if (!cols.includes('retweeted_text_plain')) addCol('retweeted_text_plain')
|
|
if (!cols.includes('retweeted_url')) addCol('retweeted_url')
|
|
if (!cols.includes('retweeted_pics_json')) addCol('retweeted_pics_json')
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS keywords (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
keyword TEXT NOT NULL UNIQUE,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS tracked_users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid TEXT NOT NULL UNIQUE,
|
|
screen_name TEXT,
|
|
added_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS settings_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
tool TEXT NOT NULL,
|
|
payload TEXT NOT NULL,
|
|
saved_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS archives (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid TEXT NOT NULL,
|
|
week_start TEXT NOT NULL,
|
|
week_end TEXT NOT NULL,
|
|
html TEXT NOT NULL,
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
UNIQUE(uid, week_start)
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS market_archives (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
day TEXT NOT NULL UNIQUE,
|
|
html TEXT NOT NULL,
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS alert_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
skill TEXT NOT NULL,
|
|
alert_id TEXT NOT NULL,
|
|
slot_key TEXT NOT NULL,
|
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
UNIQUE(skill, alert_id)
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS market_state (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT,
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
db.exec(
|
|
`CREATE TABLE IF NOT EXISTS alert_email_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
skill TEXT NOT NULL,
|
|
alert_id TEXT NOT NULL,
|
|
subject TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
error_message TEXT,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);`
|
|
)
|
|
|
|
// 唯一索引:同一个告警事件同一种状态不重复记录
|
|
try { db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_alert_email_log_uniq ON alert_email_log(skill, alert_id, status)`) } catch {}
|
|
|
|
const insertPost = db.prepare(
|
|
`INSERT OR IGNORE INTO posts (
|
|
uid, mid, screen_name, created_at, text, text_plain, url,
|
|
reposts_count, comments_count, attitudes_count, pics_json, fetched_at
|
|
) VALUES (
|
|
@uid, @mid, @screen_name, @created_at, @text, @text_plain, @url,
|
|
@reposts_count, @comments_count, @attitudes_count, @pics_json, @fetched_at
|
|
)`
|
|
)
|
|
|
|
const updateRetweeted = db.prepare(
|
|
`UPDATE posts SET
|
|
retweeted_mid = @retweeted_mid,
|
|
retweeted_screen_name = @retweeted_screen_name,
|
|
retweeted_text = @retweeted_text,
|
|
retweeted_text_plain = @retweeted_text_plain,
|
|
retweeted_url = @retweeted_url,
|
|
retweeted_pics_json = @retweeted_pics_json,
|
|
text_plain = @text_plain
|
|
WHERE mid = @mid`
|
|
)
|
|
|
|
const queryPostsAny = (uid, terms, limit) => {
|
|
const likeClauses = terms.map(() => 'text_plain LIKE ?').join(' OR ')
|
|
const args = terms.map(t => `%${t}%`)
|
|
const stmt = db.prepare(
|
|
`SELECT * FROM posts WHERE uid = ? AND (${likeClauses}) ORDER BY fetched_at DESC, id ASC LIMIT ?`
|
|
)
|
|
return stmt.all(uid, ...args, limit)
|
|
}
|
|
|
|
const queryPostsAll = (uid, terms, limit) => {
|
|
const likeClauses = terms.map(() => 'text_plain LIKE ?').join(' AND ')
|
|
const args = terms.map(t => `%${t}%`)
|
|
const stmt = db.prepare(
|
|
`SELECT * FROM posts WHERE uid = ? AND (${likeClauses}) ORDER BY fetched_at DESC, id ASC LIMIT ?`
|
|
)
|
|
return stmt.all(uid, ...args, limit)
|
|
}
|
|
|
|
const insertKeyword = db.prepare(
|
|
`INSERT OR IGNORE INTO keywords (keyword) VALUES (?)`
|
|
)
|
|
|
|
const insertTrackedUser = db.prepare(
|
|
`INSERT OR IGNORE INTO tracked_users (uid, screen_name) VALUES (?, ?)`
|
|
)
|
|
|
|
const getSettings = () => {
|
|
const keywords = db.prepare(`SELECT keyword FROM keywords ORDER BY id DESC`).all().map(r => r.keyword)
|
|
const users = db.prepare(`SELECT uid, screen_name FROM tracked_users ORDER BY id DESC`).all()
|
|
return { keywords, users }
|
|
}
|
|
|
|
const insertSettingHistory = db.prepare(
|
|
`INSERT INTO settings_history (tool, payload) VALUES (@tool, @payload)`
|
|
)
|
|
|
|
const upsertArchive = db.prepare(
|
|
`INSERT INTO archives (uid, week_start, week_end, html, created_at, updated_at)
|
|
VALUES (@uid, @week_start, @week_end, @html, datetime('now'), datetime('now'))
|
|
ON CONFLICT(uid, week_start) DO UPDATE SET
|
|
week_end = excluded.week_end,
|
|
html = excluded.html,
|
|
updated_at = datetime('now')`
|
|
)
|
|
|
|
const getArchive = db.prepare(
|
|
`SELECT * FROM archives WHERE uid = ? AND week_start = ?`
|
|
)
|
|
|
|
const upsertMarketArchive = db.prepare(
|
|
`INSERT INTO market_archives (day, html, created_at, updated_at)
|
|
VALUES (@day, @html, datetime('now'), datetime('now'))
|
|
ON CONFLICT(day) DO UPDATE SET
|
|
html = excluded.html,
|
|
updated_at = datetime('now')`
|
|
)
|
|
|
|
const getMarketArchive = db.prepare(
|
|
`SELECT * FROM market_archives WHERE day = ?`
|
|
)
|
|
|
|
module.exports = {
|
|
db,
|
|
insertPost,
|
|
updateRetweeted,
|
|
queryPostsAny,
|
|
queryPostsAll,
|
|
insertKeyword,
|
|
insertTrackedUser,
|
|
getSettings
|
|
, upsertArchive
|
|
, getArchive
|
|
, insertSettingHistory
|
|
, upsertMarketArchive
|
|
, getMarketArchive
|
|
}
|