chore: 初始提交 - 移除所有硬编码凭据,统一从环境变量读取
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user