@@ -73,8 +73,41 @@ const definedMigrations: Migration[] = [
|
||||
-- UPDATE connections SET encrypted_passphrase = NULL WHERE encrypted_passphrase = ''; -- 示例
|
||||
`
|
||||
},
|
||||
// --- Quick Command Tags Migrations ---
|
||||
{
|
||||
id: 2,
|
||||
name: 'Create quick_command_tags table',
|
||||
check: async (db: Database): Promise<boolean> => {
|
||||
const tableAlreadyExists = await tableExists(db, 'quick_command_tags');
|
||||
return !tableAlreadyExists; // Only run if the table does NOT exist
|
||||
},
|
||||
sql: `
|
||||
CREATE TABLE IF NOT EXISTS quick_command_tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Create quick_command_tag_associations table',
|
||||
check: async (db: Database): Promise<boolean> => {
|
||||
const tableAlreadyExists = await tableExists(db, 'quick_command_tag_associations');
|
||||
return !tableAlreadyExists; // Only run if the table does NOT exist
|
||||
},
|
||||
sql: `
|
||||
CREATE TABLE IF NOT EXISTS quick_command_tag_associations (
|
||||
quick_command_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (quick_command_id, tag_id),
|
||||
FOREIGN KEY (quick_command_id) REFERENCES quick_commands(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tag_id) REFERENCES quick_command_tags(id) ON DELETE CASCADE
|
||||
);
|
||||
`
|
||||
}
|
||||
// --- 未来可以添加更多迁移 ---
|
||||
// { id: 2, name: '...', sql: '...' },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -152,6 +152,30 @@ CREATE TABLE IF NOT EXISTS quick_commands (
|
||||
);
|
||||
`;
|
||||
|
||||
// --- Quick Command Tags ---
|
||||
|
||||
export const createQuickCommandTagsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS quick_command_tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`;
|
||||
|
||||
export const createQuickCommandTagAssociationsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS quick_command_tag_associations (
|
||||
quick_command_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (quick_command_id, tag_id),
|
||||
FOREIGN KEY (quick_command_id) REFERENCES quick_commands(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tag_id) REFERENCES quick_command_tags(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
// --- End Quick Command Tags ---
|
||||
|
||||
|
||||
// 从 database.ts 移动过来的,保持一致性
|
||||
export const createTerminalThemesTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS terminal_themes (
|
||||
|
||||
@@ -21,6 +21,7 @@ import quickCommandsRoutes from './quick-commands/quick-commands.routes';
|
||||
import terminalThemeRoutes from './terminal-themes/terminal-theme.routes';
|
||||
import appearanceRoutes from './appearance/appearance.routes';
|
||||
import sshKeysRouter from './ssh_keys/ssh_keys.routes'; // +++ Import SSH Key routes +++
|
||||
import quickCommandTagRoutes from './quick-command-tags/quick-command-tag.routes'; // +++ Import Quick Command Tag routes +++
|
||||
import { initializeWebSocket } from './websocket';
|
||||
import { ipWhitelistMiddleware } from './auth/ipWhitelist.middleware';
|
||||
|
||||
@@ -261,7 +262,8 @@ const startServer = () => {
|
||||
app.use('/api/v1/terminal-themes', terminalThemeRoutes);
|
||||
app.use('/api/v1/appearance', appearanceRoutes);
|
||||
app.use('/api/v1/ssh-keys', sshKeysRouter); // +++ Register SSH Key routes +++
|
||||
|
||||
app.use('/api/v1/quick-command-tags', quickCommandTagRoutes); // +++ Register Quick Command Tag routes +++
|
||||
|
||||
// 状态检查接口
|
||||
app.get('/api/v1/status', (req: Request, res: Response) => {
|
||||
res.json({ status: '后端服务运行中!' });
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as QuickCommandTagService from '../services/quick-command-tag.service';
|
||||
|
||||
/**
|
||||
* 处理获取所有快捷指令标签的请求
|
||||
*/
|
||||
export const getAllQuickCommandTags = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const tags = await QuickCommandTagService.getAllQuickCommandTags();
|
||||
res.status(200).json(tags);
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 获取快捷指令标签列表失败:', error.message);
|
||||
res.status(500).json({ message: error.message || '无法获取快捷指令标签列表' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理添加新快捷指令标签的请求
|
||||
*/
|
||||
export const addQuickCommandTag = async (req: Request, res: Response): Promise<void> => {
|
||||
const { name } = req.body;
|
||||
|
||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||
res.status(400).json({ message: '标签名称不能为空且必须是字符串' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newId = await QuickCommandTagService.addQuickCommandTag(name);
|
||||
// 成功添加后,获取新创建的标签信息返回给前端
|
||||
const newTag = await QuickCommandTagService.getQuickCommandTagById(newId);
|
||||
if (newTag) {
|
||||
res.status(201).json({ message: '快捷指令标签已添加', tag: newTag });
|
||||
} else {
|
||||
// 理论上不应该发生,但作为健壮性检查
|
||||
console.error(`[Controller] 添加快捷指令标签后未能找到 ID: ${newId}`);
|
||||
res.status(201).json({ message: '快捷指令标签已添加,但无法检索新记录', id: newId });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 添加快捷指令标签失败:', error.message);
|
||||
// 检查是否是名称重复错误
|
||||
if (error.message && error.message.includes('已存在')) {
|
||||
res.status(409).json({ message: error.message }); // 409 Conflict
|
||||
} else {
|
||||
res.status(500).json({ message: error.message || '无法添加快捷指令标签' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理更新快捷指令标签的请求
|
||||
*/
|
||||
export const updateQuickCommandTag = async (req: Request, res: Response): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const { name } = req.body;
|
||||
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ message: '无效的标签 ID' });
|
||||
return;
|
||||
}
|
||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||
res.status(400).json({ message: '标签名称不能为空且必须是字符串' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await QuickCommandTagService.updateQuickCommandTag(id, name);
|
||||
if (success) {
|
||||
// 成功更新后,获取更新后的标签信息返回给前端
|
||||
const updatedTag = await QuickCommandTagService.getQuickCommandTagById(id);
|
||||
if (updatedTag) {
|
||||
res.status(200).json({ message: '快捷指令标签已更新', tag: updatedTag });
|
||||
} else {
|
||||
console.error(`[Controller] 更新快捷指令标签后未能找到 ID: ${id}`);
|
||||
res.status(200).json({ message: '快捷指令标签已更新,但无法检索更新后的记录' });
|
||||
}
|
||||
} else {
|
||||
// 检查标签是否真的不存在
|
||||
const tagExists = await QuickCommandTagService.getQuickCommandTagById(id);
|
||||
if (!tagExists) {
|
||||
res.status(404).json({ message: '未找到要更新的快捷指令标签' });
|
||||
} else {
|
||||
// 如果标签存在但更新失败(理论上不太可能,除非并发问题),返回服务器错误
|
||||
console.error(`[Controller] 更新快捷指令标签 ${id} 失败,但标签存在。`);
|
||||
res.status(500).json({ message: '更新快捷指令标签时发生未知错误' });
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 更新快捷指令标签失败:', error.message);
|
||||
// 检查是否是名称重复错误
|
||||
if (error.message && error.message.includes('已存在')) {
|
||||
res.status(409).json({ message: error.message }); // 409 Conflict
|
||||
} else {
|
||||
res.status(500).json({ message: error.message || '无法更新快捷指令标签' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理删除快捷指令标签的请求
|
||||
*/
|
||||
export const deleteQuickCommandTag = async (req: Request, res: Response): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ message: '无效的标签 ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 先检查标签是否存在,以便返回 404
|
||||
const tagExists = await QuickCommandTagService.getQuickCommandTagById(id);
|
||||
if (!tagExists) {
|
||||
res.status(404).json({ message: '未找到要删除的快捷指令标签' });
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await QuickCommandTagService.deleteQuickCommandTag(id);
|
||||
if (success) {
|
||||
res.status(200).json({ message: '快捷指令标签已删除' });
|
||||
} else {
|
||||
// 如果上面检查存在但删除失败,说明有内部错误
|
||||
console.error(`[Controller] 删除快捷指令标签 ${id} 失败,但标签存在。`);
|
||||
res.status(500).json({ message: '删除快捷指令标签时发生未知错误' });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 删除快捷指令标签失败:', error.message);
|
||||
res.status(500).json({ message: error.message || '无法删除快捷指令标签' });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import express from 'express';
|
||||
import * as QuickCommandTagController from './quick-command-tag.controller';
|
||||
import { isAuthenticated } from '../auth/auth.middleware'; // 假设需要认证
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 获取所有快捷指令标签
|
||||
router.get('/', isAuthenticated, QuickCommandTagController.getAllQuickCommandTags);
|
||||
|
||||
// 添加新的快捷指令标签
|
||||
router.post('/', isAuthenticated, QuickCommandTagController.addQuickCommandTag);
|
||||
|
||||
// 更新快捷指令标签
|
||||
router.put('/:id', isAuthenticated, QuickCommandTagController.updateQuickCommandTag);
|
||||
|
||||
// 删除快捷指令标签
|
||||
router.delete('/:id', isAuthenticated, QuickCommandTagController.deleteQuickCommandTag);
|
||||
|
||||
export default router;
|
||||
@@ -6,8 +6,10 @@ import { QuickCommandSortBy } from '../services/quick-commands.service';
|
||||
* 处理添加新快捷指令的请求
|
||||
*/
|
||||
export const addQuickCommand = async (req: Request, res: Response): Promise<void> => {
|
||||
const { name, command } = req.body;
|
||||
// 从请求体中解构出 name, command, 以及可选的 tagIds
|
||||
const { name, command, tagIds } = req.body;
|
||||
|
||||
// --- 基本验证 ---
|
||||
if (!command || typeof command !== 'string' || command.trim().length === 0) {
|
||||
res.status(400).json({ message: '指令内容不能为空' });
|
||||
return;
|
||||
@@ -17,12 +19,26 @@ export const addQuickCommand = async (req: Request, res: Response): Promise<void
|
||||
res.status(400).json({ message: '名称必须是字符串或 null' });
|
||||
return;
|
||||
}
|
||||
// 验证 tagIds (如果提供的话)
|
||||
if (tagIds !== undefined && (!Array.isArray(tagIds) || !tagIds.every(id => typeof id === 'number'))) {
|
||||
res.status(400).json({ message: 'tagIds 必须是一个数字数组' });
|
||||
return;
|
||||
}
|
||||
// --- 结束验证 ---
|
||||
|
||||
try {
|
||||
const newId = await QuickCommandsService.addQuickCommand(name, command);
|
||||
res.status(201).json({ id: newId, message: '快捷指令已添加' });
|
||||
// 将 tagIds 传递给 Service 层
|
||||
const newId = await QuickCommandsService.addQuickCommand(name, command, tagIds);
|
||||
// 尝试获取新创建的带标签的指令信息返回
|
||||
const newCommand = await QuickCommandsService.getQuickCommandById(newId);
|
||||
if (newCommand) {
|
||||
res.status(201).json({ message: '快捷指令已添加', command: newCommand });
|
||||
} else {
|
||||
console.error(`[Controller] 添加快捷指令后未能找到 ID: ${newId}`);
|
||||
res.status(201).json({ message: '快捷指令已添加,但无法检索新记录', id: newId });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('添加快捷指令控制器出错:', error);
|
||||
console.error('[Controller] 添加快捷指令失败:', error.message);
|
||||
res.status(500).json({ message: error.message || '无法添加快捷指令' });
|
||||
}
|
||||
};
|
||||
@@ -49,8 +65,10 @@ export const getAllQuickCommands = async (req: Request, res: Response): Promise<
|
||||
*/
|
||||
export const updateQuickCommand = async (req: Request, res: Response): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const { name, command } = req.body;
|
||||
// 从请求体中解构出 name, command, 以及可选的 tagIds
|
||||
const { name, command, tagIds } = req.body;
|
||||
|
||||
// --- 基本验证 ---
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ message: '无效的 ID' });
|
||||
return;
|
||||
@@ -63,13 +81,35 @@ export const updateQuickCommand = async (req: Request, res: Response): Promise<v
|
||||
res.status(400).json({ message: '名称必须是字符串或 null' });
|
||||
return;
|
||||
}
|
||||
// 验证 tagIds (如果提供的话)
|
||||
// 注意: tagIds 为 undefined 表示不更新标签,空数组 [] 表示清除所有标签
|
||||
if (tagIds !== undefined && (!Array.isArray(tagIds) || !tagIds.every(id => typeof id === 'number'))) {
|
||||
res.status(400).json({ message: 'tagIds 必须是一个数字数组' });
|
||||
return;
|
||||
}
|
||||
// --- 结束验证 ---
|
||||
|
||||
try {
|
||||
const success = await QuickCommandsService.updateQuickCommand(id, name, command);
|
||||
// 将 tagIds 传递给 Service 层
|
||||
const success = await QuickCommandsService.updateQuickCommand(id, name, command, tagIds);
|
||||
if (success) {
|
||||
res.status(200).json({ message: '快捷指令已更新' });
|
||||
// 尝试获取更新后的带标签的指令信息返回
|
||||
const updatedCommand = await QuickCommandsService.getQuickCommandById(id);
|
||||
if (updatedCommand) {
|
||||
res.status(200).json({ message: '快捷指令已更新', command: updatedCommand });
|
||||
} else {
|
||||
console.error(`[Controller] 更新快捷指令后未能找到 ID: ${id}`);
|
||||
res.status(200).json({ message: '快捷指令已更新,但无法检索更新后的记录' });
|
||||
}
|
||||
} else {
|
||||
res.status(404).json({ message: '未找到要更新的快捷指令' });
|
||||
// 检查指令是否真的不存在
|
||||
const commandExists = await QuickCommandsService.getQuickCommandById(id);
|
||||
if (!commandExists) {
|
||||
res.status(404).json({ message: '未找到要更新的快捷指令' });
|
||||
} else {
|
||||
console.error(`[Controller] 更新快捷指令 ${id} 失败,但指令存在。`);
|
||||
res.status(500).json({ message: '更新快捷指令时发生未知错误' });
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('更新快捷指令控制器出错:', error);
|
||||
@@ -126,3 +166,28 @@ export const incrementUsage = async (req: Request, res: Response): Promise<void>
|
||||
res.status(500).json({ message: error.message || '无法增加使用次数' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量将标签分配给多个快捷指令
|
||||
*/
|
||||
export const assignTagToCommands = async (req: Request, res: Response): Promise<void> => { // Add : Promise<void>
|
||||
const { commandIds, tagId } = req.body;
|
||||
|
||||
// 基本验证
|
||||
if (!Array.isArray(commandIds) || commandIds.length === 0 || typeof tagId !== 'number') {
|
||||
res.status(400).json({ success: false, message: '请求体必须包含 commandIds (非空数组) 和 tagId (数字)。' });
|
||||
return; // Use return without value to exit early
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用 Service 函数处理批量分配
|
||||
console.log(`[Controller] assignTagToCommands: Received commandIds: ${JSON.stringify(commandIds)}, tagId: ${tagId}`); // +++ 添加日志 +++
|
||||
await QuickCommandsService.assignTagToCommands(commandIds, tagId);
|
||||
res.status(200).json({ success: true, message: `标签 ${tagId} 已成功尝试关联到 ${commandIds.length} 个指令。` });
|
||||
} catch (error: any) {
|
||||
console.error('[Controller] 批量分配标签时出错:', error.message);
|
||||
// 根据错误类型返回不同的状态码可能更好,但这里简化处理
|
||||
res.status(500).json({ success: false, message: error.message || '批量分配标签时发生内部服务器错误。' });
|
||||
// No return needed here, error handling completes the response
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,5 +13,6 @@ router.get('/', QuickCommandsController.getAllQuickCommands); // GET /api/v1/qui
|
||||
router.put('/:id', QuickCommandsController.updateQuickCommand); // PUT /api/v1/quick-commands/:id
|
||||
router.delete('/:id', QuickCommandsController.deleteQuickCommand); // DELETE /api/v1/quick-commands/:id
|
||||
router.post('/:id/increment-usage', QuickCommandsController.incrementUsage); // POST /api/v1/quick-commands/:id/increment-usage
|
||||
router.post('/bulk-assign-tag', QuickCommandsController.assignTagToCommands); // POST /api/v1/quick-commands/bulk-assign-tag
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { Database } from 'sqlite3';
|
||||
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection';
|
||||
|
||||
// 定义 Quick Command Tag 类型
|
||||
export interface QuickCommandTag {
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有快捷指令标签
|
||||
*/
|
||||
export const findAllQuickCommandTags = async (): Promise<QuickCommandTag[]> => {
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const rows = await allDb<QuickCommandTag>(db, `SELECT * FROM quick_command_tags ORDER BY name ASC`);
|
||||
return rows;
|
||||
} catch (err: any) {
|
||||
console.error('[仓库] 查询快捷指令标签列表时出错:', err.message);
|
||||
throw new Error('获取快捷指令标签列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 获取单个快捷指令标签
|
||||
*/
|
||||
export const findQuickCommandTagById = async (id: number): Promise<QuickCommandTag | null> => {
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const row = await getDbRow<QuickCommandTag>(db, `SELECT * FROM quick_command_tags WHERE id = ?`, [id]);
|
||||
return row || null;
|
||||
} catch (err: any) {
|
||||
console.error(`[仓库] 查询快捷指令标签 ${id} 时出错:`, err.message);
|
||||
throw new Error('获取快捷指令标签信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建新快捷指令标签
|
||||
*/
|
||||
export const createQuickCommandTag = async (name: string): Promise<number> => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const sql = `INSERT INTO quick_command_tags (name, created_at, updated_at) VALUES (?, ?, ?)`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const result = await runDb(db, sql, [name, now, now]);
|
||||
if (typeof result.lastID !== 'number' || result.lastID <= 0) {
|
||||
throw new Error('创建快捷指令标签后未能获取有效的 lastID');
|
||||
}
|
||||
return result.lastID;
|
||||
} catch (err: any) {
|
||||
console.error('[仓库] 创建快捷指令标签时出错:', err.message);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
throw new Error(`快捷指令标签名称 "${name}" 已存在。`);
|
||||
}
|
||||
throw new Error(`创建快捷指令标签失败: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新快捷指令标签名称
|
||||
*/
|
||||
export const updateQuickCommandTag = async (id: number, name: string): Promise<boolean> => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const sql = `UPDATE quick_command_tags SET name = ?, updated_at = ? WHERE id = ?`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const result = await runDb(db, sql, [name, now, id]);
|
||||
return result.changes > 0;
|
||||
} catch (err: any) {
|
||||
console.error(`[仓库] 更新快捷指令标签 ${id} 时出错:`, err.message);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
throw new Error(`快捷指令标签名称 "${name}" 已存在。`);
|
||||
}
|
||||
throw new Error(`更新快捷指令标签失败: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除快捷指令标签 (同时会通过外键 CASCADE 删除关联)
|
||||
*/
|
||||
export const deleteQuickCommandTag = async (id: number): Promise<boolean> => {
|
||||
const sql = `DELETE FROM quick_command_tags WHERE id = ?`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
// 由于 quick_command_tag_associations 设置了 ON DELETE CASCADE,
|
||||
// 删除 quick_command_tags 中的记录会自动删除关联表中的相关记录。
|
||||
const result = await runDb(db, sql, [id]);
|
||||
return result.changes > 0;
|
||||
} catch (err: any) {
|
||||
console.error(`[仓库] 删除快捷指令标签 ${id} 时出错:`, err.message);
|
||||
throw new Error('删除快捷指令标签失败');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置单个快捷指令的标签关联 (先删除旧关联,再插入新关联)
|
||||
* @param commandId - 快捷指令 ID
|
||||
* @param tagIds - 新的标签 ID 数组 (空数组表示清除所有关联)
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export const setCommandTagAssociations = async (commandId: number, tagIds: number[]): Promise<void> => {
|
||||
const db = await getDbInstance();
|
||||
const deleteSql = `DELETE FROM quick_command_tag_associations WHERE quick_command_id = ?`;
|
||||
const insertSql = `INSERT INTO quick_command_tag_associations (quick_command_id, tag_id) VALUES (?, ?)`;
|
||||
|
||||
try {
|
||||
await runDb(db, 'BEGIN TRANSACTION');
|
||||
// 1. 删除该指令的所有旧关联
|
||||
await runDb(db, deleteSql, [commandId]);
|
||||
|
||||
// 2. 插入新关联 (如果 tagIds 不为空)
|
||||
if (tagIds && tagIds.length > 0) {
|
||||
const stmt = await db.prepare(insertSql);
|
||||
for (const tagId of tagIds) {
|
||||
// 验证 tagId 是否为有效数字
|
||||
if (typeof tagId !== 'number' || isNaN(tagId)) {
|
||||
console.warn(`[Repo] setCommandTagAssociations: 无效的 tagId (${tagId}),跳过关联到指令 ${commandId}。`);
|
||||
continue;
|
||||
}
|
||||
await stmt.run(commandId, tagId);
|
||||
}
|
||||
await stmt.finalize();
|
||||
}
|
||||
await runDb(db, 'COMMIT');
|
||||
} catch (err: any) {
|
||||
console.error('设置快捷指令标签关联时出错:', err.message);
|
||||
await runDb(db, 'ROLLBACK'); // 出错时回滚
|
||||
throw new Error('无法设置快捷指令标签关联');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将单个标签批量添加到多个快捷指令
|
||||
* @param commandIds - 需要添加标签的快捷指令 ID 数组
|
||||
* @param tagId - 要添加的标签 ID
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export const addTagToCommands = async (commandIds: number[], tagId: number): Promise<void> => {
|
||||
if (!commandIds || commandIds.length === 0) {
|
||||
return; // 没有指令需要关联
|
||||
}
|
||||
const db = await getDbInstance();
|
||||
const insertSql = `INSERT OR IGNORE INTO quick_command_tag_associations (quick_command_id, tag_id) VALUES (?, ?)`;
|
||||
|
||||
try {
|
||||
await runDb(db, 'BEGIN TRANSACTION');
|
||||
// 准备批量插入语句
|
||||
const stmt = await db.prepare(insertSql);
|
||||
for (const commandId of commandIds) {
|
||||
// 验证 commandId 和 tagId 是否为有效数字(可选,但推荐)
|
||||
if (typeof commandId !== 'number' || isNaN(commandId) || typeof tagId !== 'number' || isNaN(tagId)) {
|
||||
console.warn(`[Repo] addTagToCommands: 无效的 commandId (${commandId}) 或 tagId (${tagId}),跳过关联。`);
|
||||
continue;
|
||||
}
|
||||
await stmt.run(commandId, tagId);
|
||||
}
|
||||
await stmt.finalize(); // 完成批量插入
|
||||
await runDb(db, 'COMMIT');
|
||||
console.log(`[Repo] addTagToCommands: 成功将标签 ${tagId} 关联到 ${commandIds.length} 个指令。`);
|
||||
} catch (err: any) {
|
||||
console.error(`[Repo] addTagToCommands: 批量关联标签 ${tagId} 到指令时出错:`, err.message);
|
||||
await runDb(db, 'ROLLBACK');
|
||||
throw new Error('无法批量关联标签到快捷指令');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新指定快捷指令的标签关联 (使用事务)
|
||||
* @param commandId 快捷指令 ID
|
||||
* @param tagIds 新的快捷指令标签 ID 数组 (空数组表示清除所有标签)
|
||||
*/
|
||||
// Removed the duplicate function declaration that returned Promise<boolean>
|
||||
|
||||
/**
|
||||
* 查找指定快捷指令的所有标签
|
||||
* @param commandId 快捷指令 ID
|
||||
* @returns 标签对象数组 { id: number, name: string }[]
|
||||
*/
|
||||
export const findTagsByCommandId = async (commandId: number): Promise<QuickCommandTag[]> => {
|
||||
const sql = `
|
||||
SELECT t.id, t.name, t.created_at, t.updated_at
|
||||
FROM quick_command_tags t
|
||||
JOIN quick_command_tag_associations ta ON t.id = ta.tag_id
|
||||
WHERE ta.quick_command_id = ?
|
||||
ORDER BY t.name ASC`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const rows = await allDb<QuickCommandTag>(db, sql, [commandId]);
|
||||
return rows;
|
||||
} catch (err: any) {
|
||||
console.error(`Repository: 查询快捷指令 ${commandId} 的标签时出错:`, err.message);
|
||||
throw new Error('获取快捷指令标签失败');
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection';
|
||||
|
||||
// 定义快捷指令的接口
|
||||
// 定义基础快捷指令接口
|
||||
export interface QuickCommand {
|
||||
id: number;
|
||||
name: string | null; // 名称可选
|
||||
@@ -10,7 +10,16 @@ export interface QuickCommand {
|
||||
updated_at: number; // Unix 时间戳 (秒)
|
||||
}
|
||||
|
||||
type DbQuickCommandRow = QuickCommand;
|
||||
// 定义包含标签 ID 的接口
|
||||
export interface QuickCommandWithTags extends QuickCommand {
|
||||
tagIds: number[];
|
||||
}
|
||||
|
||||
// 用于从数据库获取带 tag_ids_str 的行
|
||||
interface DbQuickCommandWithTagsRow extends QuickCommand {
|
||||
tag_ids_str: string | null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加一条新的快捷指令
|
||||
@@ -70,22 +79,34 @@ export const deleteQuickCommand = async (id: number): Promise<boolean> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有快捷指令
|
||||
* 获取所有快捷指令及其关联的标签 ID
|
||||
* @param sortBy - 排序字段 ('name' 或 'usage_count')
|
||||
* @returns 返回包含所有快捷指令条目的数组
|
||||
* @returns 返回包含所有快捷指令条目及标签 ID 的数组
|
||||
*/
|
||||
export const getAllQuickCommands = async (sortBy: 'name' | 'usage_count' = 'name'): Promise<QuickCommand[]> => {
|
||||
let orderByClause = 'ORDER BY name ASC'; // 默认按名称升序
|
||||
export const getAllQuickCommands = async (sortBy: 'name' | 'usage_count' = 'name'): Promise<QuickCommandWithTags[]> => {
|
||||
let orderByClause = 'ORDER BY qc.name ASC'; // 默认按名称升序
|
||||
if (sortBy === 'usage_count') {
|
||||
orderByClause = 'ORDER BY usage_count DESC, name ASC'; // 按使用频率降序,同频率按名称升序
|
||||
orderByClause = 'ORDER BY qc.usage_count DESC, qc.name ASC'; // 按使用频率降序,同频率按名称升序
|
||||
}
|
||||
const sql = `SELECT id, name, command, usage_count, created_at, updated_at FROM quick_commands ${orderByClause}`;
|
||||
// 使用 LEFT JOIN 连接关联表,并使用 GROUP_CONCAT 获取标签 ID 字符串
|
||||
const sql = `
|
||||
SELECT
|
||||
qc.id, qc.name, qc.command, qc.usage_count, qc.created_at, qc.updated_at,
|
||||
GROUP_CONCAT(qta.tag_id) as tag_ids_str
|
||||
FROM quick_commands qc
|
||||
LEFT JOIN quick_command_tag_associations qta ON qc.id = qta.quick_command_id
|
||||
GROUP BY qc.id
|
||||
${orderByClause}`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const rows = await allDb<DbQuickCommandRow>(db, sql);
|
||||
return rows;
|
||||
const rows = await allDb<DbQuickCommandWithTagsRow>(db, sql);
|
||||
// 将 tag_ids_str 解析为数字数组
|
||||
return rows.map(row => ({
|
||||
...row,
|
||||
tagIds: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number).filter(id => !isNaN(id)) : []
|
||||
}));
|
||||
} catch (err: any) {
|
||||
console.error('获取快捷指令时出错:', err.message);
|
||||
console.error('获取快捷指令(带标签)时出错:', err.message);
|
||||
throw new Error('无法获取快捷指令');
|
||||
}
|
||||
};
|
||||
@@ -108,18 +129,34 @@ export const incrementUsageCount = async (id: number): Promise<boolean> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 查找快捷指令 (用于编辑前获取数据)
|
||||
* 根据 ID 查找快捷指令及其关联的标签 ID
|
||||
* @param id - 要查找的记录 ID
|
||||
* @returns 返回找到的快捷指令条目,如果未找到则返回 undefined
|
||||
* @returns 返回找到的快捷指令条目及标签 ID,如果未找到则返回 undefined
|
||||
*/
|
||||
export const findQuickCommandById = async (id: number): Promise<QuickCommand | undefined> => {
|
||||
const sql = `SELECT id, name, command, usage_count, created_at, updated_at FROM quick_commands WHERE id = ?`;
|
||||
export const findQuickCommandById = async (id: number): Promise<QuickCommandWithTags | undefined> => {
|
||||
// 使用 LEFT JOIN 连接关联表,并使用 GROUP_CONCAT 获取标签 ID 字符串
|
||||
const sql = `
|
||||
SELECT
|
||||
qc.id, qc.name, qc.command, qc.usage_count, qc.created_at, qc.updated_at,
|
||||
GROUP_CONCAT(qta.tag_id) as tag_ids_str
|
||||
FROM quick_commands qc
|
||||
LEFT JOIN quick_command_tag_associations qta ON qc.id = qta.quick_command_id
|
||||
WHERE qc.id = ?
|
||||
GROUP BY qc.id`;
|
||||
try {
|
||||
const db = await getDbInstance();
|
||||
const row = await getDbRow<DbQuickCommandRow>(db, sql, [id]);
|
||||
return row;
|
||||
const row = await getDbRow<DbQuickCommandWithTagsRow>(db, sql, [id]);
|
||||
if (row && typeof row.id !== 'undefined') {
|
||||
// 将 tag_ids_str 解析为数字数组
|
||||
return {
|
||||
...row,
|
||||
tagIds: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number).filter(id => !isNaN(id)) : []
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('查找快捷指令时出错:', err.message);
|
||||
console.error('查找快捷指令(带标签)时出错:', err.message);
|
||||
throw new Error('无法查找快捷指令');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import * as QuickCommandTagRepository from '../repositories/quick-command-tag.repository';
|
||||
import { QuickCommandTag } from '../repositories/quick-command-tag.repository';
|
||||
|
||||
/**
|
||||
* 获取所有快捷指令标签
|
||||
*/
|
||||
export const getAllQuickCommandTags = async (): Promise<QuickCommandTag[]> => {
|
||||
return QuickCommandTagRepository.findAllQuickCommandTags();
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 获取单个快捷指令标签
|
||||
*/
|
||||
export const getQuickCommandTagById = async (id: number): Promise<QuickCommandTag | null> => {
|
||||
return QuickCommandTagRepository.findQuickCommandTagById(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加新的快捷指令标签
|
||||
* @param name 标签名称
|
||||
* @returns 返回新标签的 ID
|
||||
*/
|
||||
export const addQuickCommandTag = async (name: string): Promise<number> => {
|
||||
if (!name || name.trim().length === 0) {
|
||||
throw new Error('标签名称不能为空');
|
||||
}
|
||||
const trimmedName = name.trim();
|
||||
// 可以在这里添加更多验证逻辑,例如检查名称格式等
|
||||
try {
|
||||
const newId = await QuickCommandTagRepository.createQuickCommandTag(trimmedName);
|
||||
return newId;
|
||||
} catch (error: any) {
|
||||
// Service 层可以重新抛出或处理 Repository 抛出的错误
|
||||
console.error(`[Service] 添加快捷指令标签 "${trimmedName}" 失败:`, error.message);
|
||||
throw error; // 重新抛出,让 Controller 处理 HTTP 响应
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新快捷指令标签
|
||||
* @param id 标签 ID
|
||||
* @param name 新的标签名称
|
||||
* @returns 返回是否成功更新
|
||||
*/
|
||||
export const updateQuickCommandTag = async (id: number, name: string): Promise<boolean> => {
|
||||
if (!name || name.trim().length === 0) {
|
||||
throw new Error('标签名称不能为空');
|
||||
}
|
||||
const trimmedName = name.trim();
|
||||
// 可以在这里添加更多验证逻辑
|
||||
try {
|
||||
const success = await QuickCommandTagRepository.updateQuickCommandTag(id, trimmedName);
|
||||
if (!success) {
|
||||
// 可能需要检查标签是否存在,或者让 Repository 处理
|
||||
console.warn(`[Service] 尝试更新不存在的快捷指令标签 ID: ${id}`);
|
||||
}
|
||||
return success;
|
||||
} catch (error: any) {
|
||||
console.error(`[Service] 更新快捷指令标签 ${id} 失败:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除快捷指令标签
|
||||
* @param id 标签 ID
|
||||
* @returns 返回是否成功删除
|
||||
*/
|
||||
export const deleteQuickCommandTag = async (id: number): Promise<boolean> => {
|
||||
try {
|
||||
const success = await QuickCommandTagRepository.deleteQuickCommandTag(id);
|
||||
if (!success) {
|
||||
console.warn(`[Service] 尝试删除不存在的快捷指令标签 ID: ${id}`);
|
||||
}
|
||||
return success;
|
||||
} catch (error: any) {
|
||||
console.error(`[Service] 删除快捷指令标签 ${id} 失败:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置指定快捷指令的标签关联
|
||||
* @param commandId 快捷指令 ID
|
||||
* @param tagIds 新的快捷指令标签 ID 数组
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export const setCommandTags = async (commandId: number, tagIds: number[]): Promise<void> => {
|
||||
// 验证 tagIds 是否为数字数组 (基本验证)
|
||||
if (!Array.isArray(tagIds) || !tagIds.every(id => typeof id === 'number')) {
|
||||
throw new Error('标签 ID 列表必须是一个数字数组');
|
||||
}
|
||||
// 可以在这里添加更复杂的验证,例如检查 tagIds 是否都存在于 quick_command_tags 表中
|
||||
// 但 Repository 中的 setCommandTagAssociations 已包含基本的检查和错误处理
|
||||
|
||||
try {
|
||||
// 直接调用 Repository 处理关联更新 (Repository 函数现在返回 void)
|
||||
await QuickCommandTagRepository.setCommandTagAssociations(commandId, tagIds);
|
||||
// Service 函数也返回 void,所以不需要 return
|
||||
} catch (error: any) {
|
||||
console.error(`[Service] 设置快捷指令 ${commandId} 的标签失败:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定快捷指令的所有标签
|
||||
* @param commandId 快捷指令 ID
|
||||
* @returns 标签对象数组
|
||||
*/
|
||||
export const getTagsForCommand = async (commandId: number): Promise<QuickCommandTag[]> => {
|
||||
try {
|
||||
return await QuickCommandTagRepository.findTagsByCommandId(commandId);
|
||||
} catch (error: any) {
|
||||
console.error(`[Service] 获取快捷指令 ${commandId} 的标签失败:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as QuickCommandsRepository from '../repositories/quick-commands.repository';
|
||||
import { QuickCommand } from '../repositories/quick-commands.repository';
|
||||
import { QuickCommandWithTags } from '../repositories/quick-commands.repository'; // Import the type with tags
|
||||
import * as QuickCommandTagRepository from '../repositories/quick-command-tag.repository'; // Import the new tag repository
|
||||
|
||||
// 定义排序类型
|
||||
export type QuickCommandSortBy = 'name' | 'usage_count';
|
||||
@@ -8,15 +9,28 @@ export type QuickCommandSortBy = 'name' | 'usage_count';
|
||||
* 添加快捷指令
|
||||
* @param name - 指令名称 (可选)
|
||||
* @param command - 指令内容
|
||||
* @param tagIds - 关联的快捷指令标签 ID 数组 (可选)
|
||||
* @returns 返回添加记录的 ID
|
||||
*/
|
||||
export const addQuickCommand = async (name: string | null, command: string): Promise<number> => {
|
||||
export const addQuickCommand = async (name: string | null, command: string, tagIds?: number[]): Promise<number> => {
|
||||
if (!command || command.trim().length === 0) {
|
||||
throw new Error('指令内容不能为空');
|
||||
}
|
||||
// 如果 name 是空字符串,则视为 null
|
||||
const finalName = name && name.trim().length > 0 ? name.trim() : null;
|
||||
return QuickCommandsRepository.addQuickCommand(finalName, command.trim());
|
||||
const commandId = await QuickCommandsRepository.addQuickCommand(finalName, command.trim());
|
||||
|
||||
// 添加成功后,设置标签关联
|
||||
if (commandId > 0 && tagIds && Array.isArray(tagIds)) {
|
||||
try {
|
||||
await QuickCommandTagRepository.setCommandTagAssociations(commandId, tagIds);
|
||||
} catch (tagError: any) {
|
||||
// 如果标签关联失败,可以选择记录警告或回滚(但通常不回滚主记录)
|
||||
console.warn(`[Service] 添加快捷指令 ${commandId} 成功,但设置标签关联失败:`, tagError.message);
|
||||
// 可以考虑是否需要通知用户部分操作失败
|
||||
}
|
||||
}
|
||||
return commandId;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,15 +38,27 @@ export const addQuickCommand = async (name: string | null, command: string): Pro
|
||||
* @param id - 要更新的记录 ID
|
||||
* @param name - 新的指令名称 (可选)
|
||||
* @param command - 新的指令内容
|
||||
* @param tagIds - 新的关联标签 ID 数组 (可选, undefined 表示不更新标签)
|
||||
* @returns 返回是否成功更新 (更新行数 > 0)
|
||||
*/
|
||||
export const updateQuickCommand = async (id: number, name: string | null, command: string): Promise<boolean> => {
|
||||
export const updateQuickCommand = async (id: number, name: string | null, command: string, tagIds?: number[]): Promise<boolean> => {
|
||||
if (!command || command.trim().length === 0) {
|
||||
throw new Error('指令内容不能为空');
|
||||
}
|
||||
const finalName = name && name.trim().length > 0 ? name.trim() : null;
|
||||
const changes = await QuickCommandsRepository.updateQuickCommand(id, finalName, command.trim());
|
||||
return changes;
|
||||
const commandUpdated = await QuickCommandsRepository.updateQuickCommand(id, finalName, command.trim());
|
||||
|
||||
// 如果指令更新成功,并且提供了 tagIds (即使是空数组也表示要更新),则更新标签关联
|
||||
if (commandUpdated && typeof tagIds !== 'undefined') {
|
||||
try {
|
||||
await QuickCommandTagRepository.setCommandTagAssociations(id, tagIds);
|
||||
} catch (tagError: any) {
|
||||
console.warn(`[Service] 更新快捷指令 ${id} 成功,但更新标签关联失败:`, tagError.message);
|
||||
// 即使标签更新失败,主记录已更新,通常返回 true
|
||||
}
|
||||
}
|
||||
// 返回主记录是否更新成功
|
||||
return commandUpdated;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -48,9 +74,10 @@ export const deleteQuickCommand = async (id: number): Promise<boolean> => {
|
||||
/**
|
||||
* 获取所有快捷指令,并按指定方式排序
|
||||
* @param sortBy - 排序字段 ('name' 或 'usage_count')
|
||||
* @returns 返回排序后的快捷指令数组
|
||||
* @returns 返回排序后的快捷指令数组 (包含 tagIds)
|
||||
*/
|
||||
export const getAllQuickCommands = async (sortBy: QuickCommandSortBy = 'name'): Promise<QuickCommand[]> => {
|
||||
export const getAllQuickCommands = async (sortBy: QuickCommandSortBy = 'name'): Promise<QuickCommandWithTags[]> => {
|
||||
// Repository 已返回带 tagIds 的数据
|
||||
return QuickCommandsRepository.getAllQuickCommands(sortBy);
|
||||
};
|
||||
|
||||
@@ -67,8 +94,38 @@ export const incrementUsageCount = async (id: number): Promise<boolean> => {
|
||||
/**
|
||||
* 根据 ID 获取单个快捷指令 (可能用于编辑)
|
||||
* @param id - 记录 ID
|
||||
* @returns 返回找到的快捷指令,或 undefined
|
||||
* @returns 返回找到的快捷指令 (包含 tagIds),或 undefined
|
||||
*/
|
||||
export const getQuickCommandById = async (id: number): Promise<QuickCommand | undefined> => {
|
||||
export const getQuickCommandById = async (id: number): Promise<QuickCommandWithTags | undefined> => {
|
||||
// Repository 已返回带 tagIds 的数据
|
||||
return QuickCommandsRepository.findQuickCommandById(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 将单个标签批量关联到多个快捷指令
|
||||
* @param commandIds - 需要添加标签的快捷指令 ID 数组
|
||||
* @param tagId - 要添加的标签 ID
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export const assignTagToCommands = async (commandIds: number[], tagId: number): Promise<void> => {
|
||||
try {
|
||||
// 基本验证
|
||||
if (!Array.isArray(commandIds) || commandIds.some(id => typeof id !== 'number' || isNaN(id))) {
|
||||
throw new Error('无效的指令 ID 列表');
|
||||
}
|
||||
if (typeof tagId !== 'number' || isNaN(tagId)) {
|
||||
throw new Error('无效的标签 ID');
|
||||
}
|
||||
|
||||
// 调用 Repository 函数执行批量关联
|
||||
// 注意:这里需要导入 QuickCommandTagRepository
|
||||
console.log(`[Service] assignTagToCommands: Calling repo with commandIds: ${JSON.stringify(commandIds)}, tagId: ${tagId}`); // +++ 添加日志 +++
|
||||
await QuickCommandTagRepository.addTagToCommands(commandIds, tagId);
|
||||
console.log(`[Service] assignTagToCommands: Repo call finished for tag ${tagId}.`); // +++ 修改日志 +++
|
||||
// 可以在这里添加额外的业务逻辑,例如发送事件通知等
|
||||
} catch (error: any) {
|
||||
console.error(`[Service] assignTagToCommands: 批量关联标签 ${tagId} 到指令时出错:`, error.message);
|
||||
// 向上抛出错误,让 Controller 处理 HTTP 响应
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user