143 lines
4.0 KiB
JavaScript
143 lines
4.0 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const dbPath = path.join(__dirname, 'data.db');
|
|
|
|
export const db = new Database(dbPath);
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS articles (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
category TEXT,
|
|
type TEXT,
|
|
author TEXT,
|
|
author_role TEXT,
|
|
author_initial TEXT,
|
|
publish_date TEXT,
|
|
pages INTEGER DEFAULT 0,
|
|
price INTEGER DEFAULT 0,
|
|
is_free INTEGER DEFAULT 1,
|
|
summary TEXT,
|
|
body TEXT,
|
|
cover_image TEXT,
|
|
tags TEXT,
|
|
featured INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_articles_created ON articles(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_articles_featured ON articles(featured);
|
|
|
|
CREATE TABLE IF NOT EXISTS risk_signals (
|
|
id TEXT PRIMARY KEY,
|
|
quote TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
date TEXT,
|
|
level TEXT NOT NULL CHECK (level IN ('critical','high','medium','low','opportunity')),
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_risks_sort ON risk_signals(sort_order, created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS prices (
|
|
symbol TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
value REAL,
|
|
unit TEXT,
|
|
change_value REAL,
|
|
change_pct REAL,
|
|
source_url TEXT,
|
|
fetched_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
`);
|
|
|
|
export function rowToPrice(row) {
|
|
if (!row) return null;
|
|
return {
|
|
symbol: row.symbol,
|
|
name: row.name,
|
|
value: row.value,
|
|
unit: row.unit,
|
|
changeValue: row.change_value,
|
|
changePct: row.change_pct,
|
|
sourceUrl: row.source_url,
|
|
fetchedAt: row.fetched_at,
|
|
};
|
|
}
|
|
|
|
export function upsertPrice(p) {
|
|
db.prepare(`
|
|
INSERT INTO prices (symbol, name, value, unit, change_value, change_pct, source_url, fetched_at)
|
|
VALUES (@symbol, @name, @value, @unit, @change_value, @change_pct, @source_url, datetime('now'))
|
|
ON CONFLICT(symbol) DO UPDATE SET
|
|
name = excluded.name,
|
|
value = excluded.value,
|
|
unit = excluded.unit,
|
|
change_value = excluded.change_value,
|
|
change_pct = excluded.change_pct,
|
|
source_url = excluded.source_url,
|
|
fetched_at = excluded.fetched_at
|
|
`).run({
|
|
symbol: p.symbol,
|
|
name: p.name,
|
|
value: p.value ?? null,
|
|
unit: p.unit ?? null,
|
|
change_value: p.changeValue ?? null,
|
|
change_pct: p.changePct ?? null,
|
|
source_url: p.sourceUrl ?? null,
|
|
});
|
|
}
|
|
|
|
export function rowToRiskSignal(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
quote: row.quote,
|
|
name: row.name,
|
|
date: row.date,
|
|
level: row.level,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToArticle(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
category: row.category,
|
|
type: row.type,
|
|
author: row.author,
|
|
authorRole: row.author_role,
|
|
authorInitial: row.author_initial,
|
|
publishDate: row.publish_date,
|
|
pages: row.pages,
|
|
price: row.price,
|
|
isFree: !!row.is_free,
|
|
summary: row.summary,
|
|
body: row.body,
|
|
coverImage: row.cover_image,
|
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
featured: !!row.featured,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|