This commit is contained in:
Baobhan Sith
2025-04-26 15:20:37 +08:00
parent 93b8863fdd
commit e269f40754
80 changed files with 868 additions and 1528 deletions
@@ -1,12 +1,7 @@
// packages/backend/src/repositories/terminal-theme.repository.ts
import { Database } from 'sqlite3'; // Import Database type if needed for type hints
import { getDbInstance, runDb, getDb, allDb } from '../database/connection'; // Import new async helpers, including getDb
// Remove the incorrect import of DbTerminalThemeRow
import { Database } from 'sqlite3';
import { getDbInstance, runDb, getDb, allDb } from '../database/connection';
import { TerminalTheme, CreateTerminalThemeDto, UpdateTerminalThemeDto } from '../types/terminal-theme.types';
import { defaultXtermTheme } from '../config/default-themes';
// Define the interface for the raw database row structure
// Interface matching the schema in schema.ts
interface DbTerminalThemeRow {
id: number;
name: string;
@@ -36,23 +31,18 @@ interface DbTerminalThemeRow {
updated_at: number;
}
// SQL_CREATE_TABLE and createTableIfNotExists removed as initialization is handled in database/connection.ts
// 辅助函数:将数据库行转换为 TerminalTheme 对象
// Add type annotation for the input row
const mapRowToTerminalTheme = (row: DbTerminalThemeRow): TerminalTheme => {
// Basic check if row exists and has id property
if (!row || typeof row.id === 'undefined') {
console.error("mapRowToTerminalTheme received invalid row:", row);
// Return a default or throw an error, depending on desired behavior
// For now, let's throw an error to make the issue visible
throw new Error("Invalid database row provided to mapRowToTerminalTheme");
}
try {
return {
_id: row.id.toString(),
name: row.name,
// Reconstruct themeData from individual columns
themeData: {
foreground: row.foreground ?? undefined,
background: row.background ?? undefined,
@@ -77,16 +67,12 @@ const mapRowToTerminalTheme = (row: DbTerminalThemeRow): TerminalTheme => {
brightWhite: row.bright_white ?? undefined,
},
isPreset: row.theme_type === 'preset',
// isSystemDefault needs to be handled differently, maybe based on name 'default'?
// For now, let's assume it's not directly mapped or needed here.
isSystemDefault: row.name === 'default', // Tentative mapping based on name
isSystemDefault: row.name === 'default',
createdAt: row.created_at,
updatedAt: row.updated_at,
};
} catch (e: any) {
// Log the entire row for debugging instead of the non-existent theme_data
console.error(`Error mapping theme data for theme ID ${row.id}:`, e.message, "Raw row:", row);
// Return a partially mapped object or throw error
throw new Error(`Failed to map theme data for theme ID ${row.id}`);
}
};
@@ -98,23 +84,20 @@ const mapRowToTerminalTheme = (row: DbTerminalThemeRow): TerminalTheme => {
export const findAllThemes = async (): Promise<TerminalTheme[]> => {
try {
const db = await getDbInstance();
// Specify the expected row type for allDb
// Correct the ORDER BY clause to use theme_type and sort presets first
const rows = await allDb<DbTerminalThemeRow>(db, 'SELECT * FROM terminal_themes ORDER BY CASE theme_type WHEN \'preset\' THEN 0 ELSE 1 END ASC, name ASC');
// Filter out potential errors during mapping
return rows.map(row => {
try {
return mapRowToTerminalTheme(row);
} catch (mapError: any) {
console.error(`Error mapping row ID ${row?.id}:`, mapError.message);
return null; // Or handle differently
return null;
}
}).filter((theme): theme is TerminalTheme => theme !== null);
} catch (err: any) { // Add type annotation for err
} catch (err: any) {
console.error('查询所有终端主题失败:', err.message);
// 添加详细错误日志
console.error('详细错误:', err);
throw new Error('查询终端主题失败'); // Re-throw or handle error appropriately
throw new Error('查询终端主题失败');
}
};
@@ -126,15 +109,13 @@ export const findAllThemes = async (): Promise<TerminalTheme[]> => {
export const findThemeById = async (id: number): Promise<TerminalTheme | null> => {
if (isNaN(id) || id <= 0) {
console.error("findThemeById called with invalid ID:", id);
return null; // Return null for invalid IDs
return null;
}
try {
const db = await getDbInstance();
// Specify the expected row type for getDbRow
// Use getDb instead of the non-existent getDbRow
const row = await getDb<DbTerminalThemeRow>(db, 'SELECT * FROM terminal_themes WHERE id = ?', [id]);
return row ? mapRowToTerminalTheme(row) : null;
} catch (err: any) { // Add type annotation for err
} catch (err: any) {
console.error(`查询 ID 为 ${id} 的终端主题失败:`, err.message);
throw new Error('查询终端主题失败');
}
@@ -146,10 +127,10 @@ export const findThemeById = async (id: number): Promise<TerminalTheme | null> =
* @returns Promise<TerminalTheme> 新创建的主题
*/
export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<TerminalTheme> => {
const nowSeconds = Math.floor(Date.now() / 1000); // Use seconds for DB consistency
const nowSeconds = Math.floor(Date.now() / 1000);
const theme = themeDto.themeData;
// Define columns based on the DbTerminalThemeRow interface (excluding id, created_at, updated_at)
const columns = [
'name', 'theme_type', 'foreground', 'background', 'cursor', 'cursor_accent',
'selection_background', 'black', 'red', 'green', 'yellow', 'blue',
@@ -157,14 +138,13 @@ export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<Ter
'bright_yellow', 'bright_blue', 'bright_magenta', 'bright_cyan', 'bright_white',
'created_at', 'updated_at'
];
// Map themeDto data to corresponding columns, using null for missing optional values
const values = [
themeDto.name, 'user', // theme_type is 'user' for created themes
themeDto.name, 'user',
theme?.foreground ?? null, theme?.background ?? null, theme?.cursor ?? null, theme?.cursorAccent ?? null,
theme?.selectionBackground ?? null, theme?.black ?? null, theme?.red ?? null, theme?.green ?? null, theme?.yellow ?? null, theme?.blue ?? null,
theme?.magenta ?? null, theme?.cyan ?? null, theme?.white ?? null, theme?.brightBlack ?? null, theme?.brightRed ?? null, theme?.brightGreen ?? null,
theme?.brightYellow ?? null, theme?.brightBlue ?? null, theme?.brightMagenta ?? null, theme?.brightCyan ?? null, theme?.brightWhite ?? null,
nowSeconds, nowSeconds // Use seconds for timestamps
nowSeconds, nowSeconds
];
const placeholders = columns.map(() => '?').join(', ');
@@ -175,8 +155,7 @@ export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<Ter
try {
const db = await getDbInstance();
const result = await runDb(db, sql, values); // Use the mapped values array
// Ensure lastID is valid before trying to find the theme
const result = await runDb(db, sql, values);
if (typeof result.lastID !== 'number' || result.lastID <= 0) {
throw new Error('创建主题后未能获取有效的 lastID');
}
@@ -184,10 +163,9 @@ export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<Ter
if (newTheme) {
return newTheme;
} else {
// This case might happen if findThemeById fails for some reason
throw new Error(`创建主题后未能检索到 ID 为 ${result.lastID} 的主题`);
}
} catch (err: any) { // Add type annotation for err
} catch (err: any) {
console.error('创建新终端主题失败:', err.message);
if (err.message.includes('UNIQUE constraint failed')) {
throw new Error(`主题名称 "${themeDto.name}" 已存在。`);
@@ -215,7 +193,7 @@ export const updateTheme = async (id: number, themeDto: UpdateTerminalThemeDto):
const db = await getDbInstance();
const result = await runDb(db, sql, [themeDto.name, themeDataJson, now, id]);
return result.changes > 0;
} catch (err: any) { // Add type annotation for err
} catch (err: any) {
console.error(`更新 ID 为 ${id} 的终端主题失败:`, err.message);
if (err.message.includes('UNIQUE constraint failed')) {
throw new Error(`主题名称 "${themeDto.name}" 已存在。`);
@@ -231,13 +209,12 @@ export const updateTheme = async (id: number, themeDto: UpdateTerminalThemeDto):
* @returns Promise<boolean> 是否成功删除
*/
export const deleteTheme = async (id: number): Promise<boolean> => {
// Correct the WHERE clause to use theme_type = 'user' instead of is_preset = 0
const sql = 'DELETE FROM terminal_themes WHERE id = ? AND theme_type = \'user\'';
try {
const db = await getDbInstance();
const result = await runDb(db, sql, [id]);
return result.changes > 0;
} catch (err: any) { // Add type annotation for err
} catch (err: any) {
console.error(`删除 ID 为 ${id} 的终端主题失败:`, err.message);
throw new Error('删除终端主题失败');
}
@@ -252,20 +229,15 @@ export const initializePresetThemes = async (db: Database, presets: Array<Omit<T
console.log('[DB Init] 开始检查并初始化预设主题...');
// 在这里添加日志,显示总共要处理多少个预设主题
console.log(`[DB Init] 发现 ${presets.length} 个预设主题定义。`);
const nowSeconds = Math.floor(Date.now() / 1000); // Use seconds for DB consistency
// const db = await getDbInstance(); // Use the passed db instance
const nowSeconds = Math.floor(Date.now() / 1000);
for (const preset of presets) {
// 在循环开始时添加日志,显示正在处理哪个主题
console.log(`[DB Init] 正在处理预设主题: "${preset.name}"`);
try {
// Check using name and theme_type
const existing = await getDb<{ id: number }>(db, `SELECT id FROM terminal_themes WHERE name = ? AND theme_type = 'preset'`, [preset.name]);
if (!existing) {
// 添加日志,表明正在插入新主题
console.log(`[DB Init] 主题 "${preset.name}" 不存在,正在插入...`);
// Map preset.themeData to individual columns
const theme = preset.themeData;
const columns = [
'name', 'theme_type', 'foreground', 'background', 'cursor', 'cursor_accent',
@@ -290,14 +262,10 @@ export const initializePresetThemes = async (db: Database, presets: Array<Omit<T
await runDb(db, insertSql, values);
console.log(`[DB Init] 预设主题 "${preset.name}" 已初始化到数据库。`);
} else {
// 取消注释并添加日志,表明主题已存在
console.log(`[DB Init] 预设主题 "${preset.name}" 已存在,跳过初始化。`);
}
} catch (err: any) { // Add type annotation for err
// Remove reference to non-existent preset_key
} catch (err: any) {
console.error(`[DB Init] 处理预设主题 "${preset.name}" 时出错:`, err.message);
// Decide if one error should stop the whole process or just log and continue
// throw err; // Uncomment to stop on first error
}
}
console.log('[DB Init] 预设主题检查和初始化完成。');