update
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
// packages/backend/src/database/connection.ts
|
||||
import sqlite3, { OPEN_READWRITE, OPEN_CREATE } from 'sqlite3'; // Import flags
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import * as schema from './schema';
|
||||
// Import the table definitions registry instead of individual repositories here
|
||||
import { tableDefinitions } from './schema.registry';
|
||||
// presetTerminalThemes might still be needed if passed directly, but likely handled in registry now
|
||||
// import { presetTerminalThemes } from '../config/preset-themes-definition';
|
||||
|
||||
// --- Revert to original path and filename ---
|
||||
// 使用 process.cwd() 获取项目根目录,然后拼接路径,确保路径一致性
|
||||
console.log('[Connection CWD]', process.cwd()); // 添加 CWD 日志
|
||||
const dbDir = path.join(process.cwd(), 'data'); // Correct path relative to CWD (packages/backend)
|
||||
const dbFilename = 'nexus-terminal.db'; // Revert to original filename
|
||||
const dbPath = path.join(dbDir, dbFilename);
|
||||
console.log(`[DB Path] Determined database directory: ${dbDir}`);
|
||||
console.log(`[DB Path] Determined database file path: ${dbPath}`);
|
||||
|
||||
// Add logging before checking/creating directory
|
||||
console.log(`[DB FS] Checking existence of directory: ${dbDir}`);
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
console.log(`[DB FS] Directory does not exist. Attempting to create: ${dbDir}`);
|
||||
try {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
console.log(`[DB FS] Directory successfully created: ${dbDir}`);
|
||||
} catch (mkdirErr: any) {
|
||||
console.error(`[DB FS] Failed to create directory ${dbDir}:`, mkdirErr.message);
|
||||
// Consider throwing error here to prevent proceeding if directory creation fails
|
||||
throw new Error(`Failed to create database directory: ${mkdirErr.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[DB FS] Directory already exists: ${dbDir}`);
|
||||
}
|
||||
|
||||
const verboseSqlite3 = sqlite3.verbose();
|
||||
let dbInstancePromise: Promise<sqlite3.Database> | null = null;
|
||||
|
||||
// --- Promisified Database Operations ---
|
||||
|
||||
interface RunResult {
|
||||
lastID: number;
|
||||
changes: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promisified version of db.run(). Resolves with { lastID, changes }.
|
||||
*/
|
||||
export const runDb = (db: sqlite3.Database, sql: string, params: any[] = []): Promise<RunResult> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err: Error | null) { // Use function() to access this
|
||||
if (err) {
|
||||
console.error(`[DB Error] SQL: ${sql.substring(0, 100)}... Params: ${JSON.stringify(params)} Error: ${err.message}`);
|
||||
reject(err);
|
||||
} else {
|
||||
// 'this' context provides lastID and changes for INSERT/UPDATE/DELETE
|
||||
resolve({ lastID: this.lastID, changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Promisified version of db.get(). Resolves with the row found, or undefined.
|
||||
*/
|
||||
export const getDb = <T = any>(db: sqlite3.Database, sql: string, params: any[] = []): Promise<T | undefined> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, params, (err: Error | null, row: T) => { // Add type annotation for row
|
||||
if (err) {
|
||||
console.error(`[DB Error] SQL: ${sql.substring(0, 100)}... Params: ${JSON.stringify(params)} Error: ${err.message}`);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(row); // row will be undefined if not found
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Promisified version of db.all(). Resolves with an array of rows found.
|
||||
*/
|
||||
export const allDb = <T = any>(db: sqlite3.Database, sql: string, params: any[] = []): Promise<T[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, params, (err: Error | null, rows: T[]) => { // Add type annotation for rows
|
||||
if (err) {
|
||||
console.error(`[DB Error] SQL: ${sql.substring(0, 100)}... Params: ${JSON.stringify(params)} Error: ${err.message}`);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows); // rows will be an empty array if no matches
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Executes the database initialization sequence: creates all tables, inserts preset/default data.
|
||||
* Now returns a Promise that resolves when all initializations are complete.
|
||||
* @param db The database instance
|
||||
*/
|
||||
const runDatabaseInitializations = async (db: sqlite3.Database): Promise<void> => {
|
||||
console.log('[DB Init] 开始数据库初始化序列...');
|
||||
|
||||
try {
|
||||
// 1. Enable foreign key constraints
|
||||
await runDb(db, 'PRAGMA foreign_keys = ON;'); // Use promisified runDb
|
||||
console.log('[DB Init] 外键约束已启用。');
|
||||
|
||||
// 2. Create tables and run initializations based on the registry
|
||||
for (const tableDef of tableDefinitions) {
|
||||
await runDb(db, tableDef.sql); // Create table (IF NOT EXISTS)
|
||||
console.log(`[DB Init] ${tableDef.name} 表已存在或已创建。`);
|
||||
if (tableDef.init) {
|
||||
// Pass the db instance to the init function
|
||||
await tableDef.init(db);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrations (if any) would run after initial schema setup
|
||||
// import { runMigrations } from './migrations';
|
||||
// await runMigrations(db);
|
||||
// console.log('[DB Init] 迁移检查完成。');
|
||||
|
||||
console.log('[DB Init] 数据库初始化序列成功完成。');
|
||||
|
||||
} catch (error) {
|
||||
console.error('[DB Init] 数据库初始化序列失败:', error);
|
||||
// Propagate the error to stop the application startup in index.ts
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the database instance. Initializes the connection and runs initializations if not already done.
|
||||
* Returns a Promise that resolves with the database instance once ready.
|
||||
*/
|
||||
// Renamed original getDb to getDbInstance to avoid confusion with the promisified getDb helper
|
||||
export const getDbInstance = (): Promise<sqlite3.Database> => {
|
||||
if (!dbInstancePromise) {
|
||||
dbInstancePromise = new Promise((resolve, reject) => {
|
||||
// Remove connectionFailed flag and double check logic
|
||||
|
||||
// Add logging before attempting connection
|
||||
console.log(`[DB Connection] Attempting to connect/open database file with explicit create flag: ${dbPath}`);
|
||||
// Explicitly add OPEN_READWRITE and OPEN_CREATE flags
|
||||
const db = new verboseSqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, async (err) => { // Mark callback as async
|
||||
// --- Strict Error Check FIRST ---
|
||||
if (err) {
|
||||
console.error(`[DB Connection] Error opening database file ${dbPath}:`, err.message);
|
||||
// connectionFailed = true; // Remove flag setting
|
||||
dbInstancePromise = null; // Reset promise on error
|
||||
reject(err); // Reject the main promise
|
||||
return; // Explicitly return
|
||||
}
|
||||
// --- End Strict Error Check ---
|
||||
|
||||
// Remove Double Check Flag logic
|
||||
|
||||
// If no error, proceed with success logging and initialization
|
||||
console.log(`[DB Connection] Successfully connected to SQLite database: ${dbPath}`);
|
||||
try {
|
||||
// Wait for initializations to complete
|
||||
await runDatabaseInitializations(db);
|
||||
console.log('[DB] Database initialization complete. Ready.');
|
||||
resolve(db); // Resolve the main promise with the db instance
|
||||
} catch (initError) {
|
||||
console.error('[DB] Initialization failed after connection, closing connection...');
|
||||
// connectionFailed = true; // Remove flag setting
|
||||
dbInstancePromise = null; // Reset promise on error
|
||||
db.close((closeErr) => {
|
||||
if (closeErr) console.error('[DB] Error closing connection after init failure:', closeErr.message);
|
||||
reject(initError); // Reject with the initialization error
|
||||
});
|
||||
// process.exit(1); // Consider exiting on init failure
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return dbInstancePromise;
|
||||
};
|
||||
|
||||
// Graceful shutdown remains the same, but it might need access to the resolved instance
|
||||
// Consider a way to get the instance if needed during shutdown, e.g., a global variable set after promise resolution.
|
||||
// For now, it checks the promise state indirectly.
|
||||
process.on('SIGINT', async () => { // Mark as async if needed
|
||||
if (dbInstancePromise) {
|
||||
console.log('[DB] 收到 SIGINT,尝试关闭数据库连接...');
|
||||
try {
|
||||
// We need the actual instance, not the promise, to close
|
||||
// Let's assume if the promise exists, we try to resolve it to get the instance
|
||||
const db = await dbInstancePromise;
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error('[DB] 关闭数据库时出错:', err.message);
|
||||
} else {
|
||||
console.log('[DB] 数据库连接已关闭。');
|
||||
}
|
||||
process.exit(err ? 1 : 0);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[DB] 获取数据库实例以关闭时出错 (可能初始化失败):', error);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log('[DB] 收到 SIGINT,但数据库连接从未初始化或已失败。');
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
// Note: We now export getDbInstance (the promise for the connection)
|
||||
// and the helper functions runDb, getDb, allDb.
|
||||
// Files needing the db instance will call `const db = await getDbInstance();`
|
||||
// and then use `await runDb(db, ...)` etc.
|
||||
@@ -0,0 +1,24 @@
|
||||
// packages/backend/src/migrations.ts
|
||||
import { Database } from 'sqlite3';
|
||||
// import { getDb } from './database'; // 可能不再需要直接从这里获取 db
|
||||
|
||||
/**
|
||||
* 运行数据库迁移。
|
||||
* 注意:此函数目前为空,仅作为未来迁移的占位符。
|
||||
* 数据库的初始模式创建在 database.ts 的初始化逻辑中处理。
|
||||
* @param db 数据库实例
|
||||
*/
|
||||
export const runMigrations = (db: Database): Promise<void> => {
|
||||
return new Promise<void>((resolve) => {
|
||||
console.log('[Migrations] 检查数据库迁移(当前无操作)。');
|
||||
// 在这里添加未来的迁移逻辑,例如:
|
||||
// db.serialize(() => {
|
||||
// db.run("ALTER TABLE users ADD COLUMN last_login INTEGER;", (err) => { ... });
|
||||
// // 更多迁移步骤...
|
||||
// });
|
||||
resolve(); // 立即解决,因为没有迁移要运行
|
||||
});
|
||||
};
|
||||
|
||||
// 可以保留一个默认导出或根据需要移除
|
||||
// export default runMigrations;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Database } from 'sqlite3';
|
||||
import * as schemaSql from './schema';
|
||||
import * as appearanceRepository from '../repositories/appearance.repository';
|
||||
import * as terminalThemeRepository from '../repositories/terminal-theme.repository';
|
||||
import { presetTerminalThemes } from '../config/preset-themes-definition';
|
||||
import { runDb } from './connection'; // Import runDb for init functions
|
||||
|
||||
/**
|
||||
* Interface describing a database table definition for initialization.
|
||||
*/
|
||||
export interface TableDefinition {
|
||||
name: string;
|
||||
sql: string;
|
||||
init?: (db: Database) => Promise<void>; // Optional initialization function
|
||||
}
|
||||
|
||||
// --- Initialization Functions ---
|
||||
|
||||
/**
|
||||
* Initializes default settings in the settings table.
|
||||
*/
|
||||
const initSettingsTable = async (db: Database): Promise<void> => {
|
||||
const defaultSettings = [
|
||||
{ key: 'ipWhitelistEnabled', value: 'false' },
|
||||
{ key: 'ipWhitelist', value: '' }
|
||||
];
|
||||
for (const setting of defaultSettings) {
|
||||
// Use INSERT OR IGNORE to avoid errors if settings already exist
|
||||
await runDb(db, "INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", [setting.key, setting.value]);
|
||||
}
|
||||
console.log('[DB Init] 默认 settings 初始化检查完成。');
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes preset terminal themes.
|
||||
* Assumes terminalThemeRepository.initializePresetThemes might need the db instance.
|
||||
*/
|
||||
const initTerminalThemesTable = async (db: Database): Promise<void> => {
|
||||
// Pass the db instance to the repository function
|
||||
// Note: This might require modifying initializePresetThemes if it doesn't accept db
|
||||
await terminalThemeRepository.initializePresetThemes(db, presetTerminalThemes);
|
||||
console.log('[DB Init] 预设主题初始化检查完成。');
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures default appearance settings exist.
|
||||
* Assumes appearanceRepository.ensureDefaultSettingsExist might need the db instance.
|
||||
*/
|
||||
const initAppearanceSettingsTable = async (db: Database): Promise<void> => {
|
||||
// Pass the db instance to the repository function
|
||||
// Note: This might require modifying ensureDefaultSettingsExist if it doesn't accept db
|
||||
await appearanceRepository.ensureDefaultSettingsExist(db);
|
||||
console.log('[DB Init] 外观设置初始化检查完成。');
|
||||
};
|
||||
|
||||
|
||||
// --- Table Definitions Registry ---
|
||||
|
||||
/**
|
||||
* Array containing definitions for all tables to be created and initialized.
|
||||
* The order might matter if there are strict foreign key dependencies without ON DELETE/UPDATE clauses,
|
||||
* but CREATE IF NOT EXISTS makes it generally safe. Initialization order might also matter.
|
||||
*/
|
||||
export const tableDefinitions: TableDefinition[] = [
|
||||
// Core settings and logs first
|
||||
{
|
||||
name: 'settings',
|
||||
sql: schemaSql.createSettingsTableSQL,
|
||||
init: initSettingsTable
|
||||
},
|
||||
{ name: 'audit_logs', sql: schemaSql.createAuditLogsTableSQL },
|
||||
{ name: 'api_keys', sql: schemaSql.createApiKeysTableSQL },
|
||||
{ name: 'passkeys', sql: schemaSql.createPasskeysTableSQL },
|
||||
{ name: 'notification_settings', sql: schemaSql.createNotificationSettingsTableSQL },
|
||||
{ name: 'users', sql: schemaSql.createUsersTableSQL },
|
||||
|
||||
// Features like proxies, connections, tags
|
||||
{ name: 'proxies', sql: schemaSql.createProxiesTableSQL },
|
||||
{ name: 'connections', sql: schemaSql.createConnectionsTableSQL }, // Depends on proxies
|
||||
{ name: 'tags', sql: schemaSql.createTagsTableSQL },
|
||||
{ name: 'connection_tags', sql: schemaSql.createConnectionTagsTableSQL }, // Depends on connections, tags
|
||||
|
||||
// Other utilities
|
||||
{ name: 'ip_blacklist', sql: schemaSql.createIpBlacklistTableSQL },
|
||||
{ name: 'command_history', sql: schemaSql.createCommandHistoryTableSQL },
|
||||
{ name: 'quick_commands', sql: schemaSql.createQuickCommandsTableSQL },
|
||||
|
||||
// Appearance related tables (often depend on others or have init logic)
|
||||
{
|
||||
name: 'terminal_themes',
|
||||
sql: schemaSql.createTerminalThemesTableSQL,
|
||||
init: initTerminalThemesTable
|
||||
},
|
||||
{
|
||||
name: 'appearance_settings',
|
||||
sql: schemaSql.createAppearanceSettingsTableSQL,
|
||||
init: initAppearanceSettingsTable
|
||||
}, // Depends on terminal_themes
|
||||
];
|
||||
@@ -0,0 +1,190 @@
|
||||
// packages/backend/src/schema.ts
|
||||
|
||||
export const createSettingsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createAuditLogsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
action_type TEXT NOT NULL,
|
||||
details TEXT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const createApiKeysTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
hashed_key TEXT UNIQUE NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const createPasskeysTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS passkeys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
credential_id TEXT UNIQUE NOT NULL, -- Base64URL encoded
|
||||
public_key TEXT NOT NULL, -- Base64URL encoded
|
||||
counter INTEGER NOT NULL,
|
||||
transports TEXT, -- JSON array as string, e.g., '["internal", "usb"]'
|
||||
name TEXT, -- User-provided name for the key
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createNotificationSettingsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS notification_settings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel_type TEXT NOT NULL CHECK(channel_type IN ('webhook', 'email', 'telegram')),
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
config TEXT NOT NULL DEFAULT '{}', -- JSON string for channel-specific config
|
||||
enabled_events TEXT NOT NULL DEFAULT '[]', -- JSON array of event names
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createUsersTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
hashed_password TEXT NOT NULL,
|
||||
two_factor_secret TEXT NULL, -- 添加 2FA 密钥列,允许为空
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createProxiesTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS proxies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('SOCKS5', 'HTTP')),
|
||||
host TEXT NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
username TEXT NULL,
|
||||
auth_method TEXT NOT NULL DEFAULT 'none' CHECK(auth_method IN ('none', 'password', 'key')),
|
||||
encrypted_password TEXT NULL,
|
||||
encrypted_private_key TEXT NULL,
|
||||
encrypted_passphrase TEXT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
UNIQUE(name, type, host, port)
|
||||
);
|
||||
`;
|
||||
|
||||
export const createConnectionsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS connections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NULL, -- 允许 name 为空
|
||||
host TEXT NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
auth_method TEXT NOT NULL CHECK(auth_method IN ('password', 'key')),
|
||||
encrypted_password TEXT NULL,
|
||||
encrypted_private_key TEXT NULL,
|
||||
encrypted_passphrase TEXT NULL,
|
||||
proxy_id INTEGER NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
last_connected_at INTEGER NULL,
|
||||
FOREIGN KEY (proxy_id) REFERENCES proxies(id) ON DELETE SET NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const createTagsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createConnectionTagsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS connection_tags (
|
||||
connection_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (connection_id, tag_id),
|
||||
FOREIGN KEY (connection_id) REFERENCES connections(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const createIpBlacklistTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS ip_blacklist (
|
||||
ip TEXT PRIMARY KEY NOT NULL,
|
||||
attempts INTEGER NOT NULL DEFAULT 1,
|
||||
last_attempt_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
blocked_until INTEGER NULL -- 封禁截止时间戳 (秒),NULL 表示未封禁或永久封禁 (根据逻辑决定)
|
||||
);
|
||||
`;
|
||||
|
||||
export const createCommandHistoryTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS command_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createQuickCommandsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS quick_commands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NULL, -- 名称可选
|
||||
command TEXT NOT NULL, -- 指令必选
|
||||
usage_count INTEGER NOT NULL DEFAULT 0, -- 使用频率
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
// 从 database.ts 移动过来的,保持一致性
|
||||
export const createTerminalThemesTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS terminal_themes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
theme_type TEXT NOT NULL CHECK(theme_type IN ('preset', 'user')),
|
||||
foreground TEXT,
|
||||
background TEXT,
|
||||
cursor TEXT,
|
||||
cursor_accent TEXT,
|
||||
selection_background TEXT,
|
||||
black TEXT,
|
||||
red TEXT,
|
||||
green TEXT,
|
||||
yellow TEXT,
|
||||
blue TEXT,
|
||||
magenta TEXT,
|
||||
cyan TEXT,
|
||||
white TEXT,
|
||||
bright_black TEXT,
|
||||
bright_red TEXT,
|
||||
bright_green TEXT,
|
||||
bright_yellow TEXT,
|
||||
bright_blue TEXT,
|
||||
bright_magenta TEXT,
|
||||
bright_cyan TEXT,
|
||||
bright_white TEXT,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createAppearanceSettingsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS appearance_settings (
|
||||
key TEXT PRIMARY KEY NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
Reference in New Issue
Block a user