feat: add database config and migration system
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
const Database = require('better-sqlite3');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '../../accountbook.db');
|
||||||
|
|
||||||
|
const db = new Database(DB_PATH);
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
|
||||||
|
module.exports = db;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const db = require('../config/database');
|
||||||
|
|
||||||
|
const migrationsDir = path.join(__dirname, 'migrations');
|
||||||
|
|
||||||
|
function getExecutedMigrations() {
|
||||||
|
try {
|
||||||
|
return db.prepare('SELECT name FROM migrations').all().map(m => m.name);
|
||||||
|
} catch (err) {
|
||||||
|
// migrations 表可能还不存在(第一次运行)
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runMigration(filename) {
|
||||||
|
const filepath = path.join(migrationsDir, filename);
|
||||||
|
const sql = fs.readFileSync(filepath, 'utf-8');
|
||||||
|
|
||||||
|
console.log(`Running migration: ${filename}`);
|
||||||
|
db.exec(sql);
|
||||||
|
db.prepare('INSERT INTO migrations (name) VALUES (?)').run(filename);
|
||||||
|
console.log(`Completed: ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrate() {
|
||||||
|
const executed = getExecutedMigrations();
|
||||||
|
const files = fs.readdirSync(migrationsDir)
|
||||||
|
.filter(f => f.endsWith('.sql'))
|
||||||
|
.filter(f => !executed.includes(f))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
runMigration(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log('No pending migrations.');
|
||||||
|
} else {
|
||||||
|
console.log(`Completed ${files.length} migration(s).`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate();
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS records (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL CHECK(type IN ('income', 'expense')),
|
||||||
|
amount REAL NOT NULL,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
date DATETIME NOT NULL,
|
||||||
|
note TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS recurring_bills (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL CHECK(type IN ('income', 'expense')),
|
||||||
|
amount REAL NOT NULL,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
day INTEGER NOT NULL,
|
||||||
|
note TEXT,
|
||||||
|
is_active INTEGER DEFAULT 1,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL CHECK(type IN ('income', 'expense')),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
icon TEXT NOT NULL,
|
||||||
|
is_default INTEGER DEFAULT 0,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- 优化查询性能
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_records_user_date ON records(user_id, date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_records_user_category ON records(user_id, category);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_records_user_type ON records(user_id, type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recurring_user ON recurring_bills(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_categories_user_type ON categories(user_id, type);
|
||||||
Reference in New Issue
Block a user