diff --git a/server/config/database.js b/server/config/database.js new file mode 100644 index 0000000..31aa566 --- /dev/null +++ b/server/config/database.js @@ -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; diff --git a/server/db/migrate.js b/server/db/migrate.js new file mode 100644 index 0000000..f0d8e6e --- /dev/null +++ b/server/db/migrate.js @@ -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(); diff --git a/server/db/migrations/001_initial.sql b/server/db/migrations/001_initial.sql new file mode 100644 index 0000000..2281aca --- /dev/null +++ b/server/db/migrations/001_initial.sql @@ -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 +); diff --git a/server/db/migrations/002_add_indexes.sql b/server/db/migrations/002_add_indexes.sql new file mode 100644 index 0000000..3d674b2 --- /dev/null +++ b/server/db/migrations/002_add_indexes.sql @@ -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);