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,13 +1,10 @@
// packages/backend/src/services/import-export.service.ts
import * as ConnectionRepository from '../repositories/connection.repository';
import * as ProxyRepository from '../repositories/proxy.repository';
// Import the instance getter and helpers
import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection';
import { Database } from 'sqlite3'; // Import Database type
// Remove top-level db instance
// --- Interface definitions remain the same ---
interface ImportedConnectionData {
name: string;
host: string;
@@ -36,7 +33,6 @@ export interface ImportResult {
failureCount: number;
errors: { connectionName?: string; message: string }[];
}
// --- End Interface definitions ---
/**
@@ -44,9 +40,8 @@ export interface ImportResult {
*/
export const exportConnections = async (): Promise<ExportedConnectionData[]> => {
try {
const db = await getDbInstance(); // Get DB instance
const db = await getDbInstance();
// Define a more specific type for the row structure
type ExportRow = ConnectionRepository.FullConnectionData & {
proxy_db_id: number | null;
proxy_name: string | null;
@@ -60,7 +55,7 @@ export const exportConnections = async (): Promise<ExportedConnectionData[]> =>
proxy_encrypted_passphrase?: string | null;
};
// Fetch connections joined with proxies using await allDb
const connectionsWithProxies = await allDb<ExportRow>(db,
`SELECT
c.*,
@@ -75,22 +70,22 @@ export const exportConnections = async (): Promise<ExportedConnectionData[]> =>
ORDER BY c.name ASC`
);
// Fetch all tag associations using await allDb
const tagRows = await allDb<{ connection_id: number, tag_id: number }>(db,
'SELECT connection_id, tag_id FROM connection_tags'
);
// Create a map for easy tag lookup
const tagsMap: { [connId: number]: number[] } = {};
tagRows.forEach(row => {
if (!tagsMap[row.connection_id]) tagsMap[row.connection_id] = [];
tagsMap[row.connection_id].push(row.tag_id);
});
// Format data for export
const formattedData: ExportedConnectionData[] = connectionsWithProxies.map(row => {
const connection: ExportedConnectionData = {
name: row.name ?? 'Unnamed', // Provide default if name is null
name: row.name ?? 'Unnamed',
host: row.host,
port: row.port,
username: row.username,
@@ -104,12 +99,12 @@ export const exportConnections = async (): Promise<ExportedConnectionData[]> =>
if (row.proxy_db_id) {
connection.proxy = {
name: row.proxy_name ?? 'Unnamed Proxy', // Provide default
type: row.proxy_type ?? 'SOCKS5', // Provide default or handle error
host: row.proxy_host ?? '', // Provide default or handle error
port: row.proxy_port ?? 0, // Provide default or handle error
name: row.proxy_name ?? 'Unnamed Proxy',
type: row.proxy_type ?? 'SOCKS5',
host: row.proxy_host ?? '',
port: row.proxy_port ?? 0,
username: row.proxy_username,
auth_method: row.proxy_auth_method ?? 'none', // Provide default
auth_method: row.proxy_auth_method ?? 'none',
encrypted_password: row.proxy_encrypted_password,
encrypted_private_key: row.proxy_encrypted_private_key,
encrypted_passphrase: row.proxy_encrypted_passphrase,
@@ -122,7 +117,7 @@ export const exportConnections = async (): Promise<ExportedConnectionData[]> =>
} catch (err: any) {
console.error('Service: 导出连接时出错:', err.message);
throw new Error(`导出连接失败: ${err.message}`); // Re-throw for controller
throw new Error(`导出连接失败: ${err.message}`);
}
};
@@ -147,26 +142,25 @@ export const importConnections = async (fileBuffer: Buffer): Promise<ImportResul
let successCount = 0;
let failureCount = 0;
const errors: { connectionName?: string; message: string }[] = [];
const db = await getDbInstance(); // Get DB instance once for the transaction
const db = await getDbInstance();
try {
await runDb(db, 'BEGIN TRANSACTION'); // Start transaction using await runDb
await runDb(db, 'BEGIN TRANSACTION');
const connectionsToInsert: Array<Omit<ConnectionRepository.FullConnectionData, 'id' | 'created_at' | 'updated_at' | 'last_connected_at'> & { tag_ids?: number[] }> = [];
const proxyCache: { [key: string]: number } = {}; // Cache for created/found proxy IDs
const proxyCache: { [key: string]: number } = {};
// --- Pass 1: Validate data and prepare for insertion ---
for (const connData of importedData) {
try {
// Basic validation
if (!connData.name || !connData.host || !connData.port || !connData.username || !connData.auth_method) {
throw new Error('缺少必要的连接字段 (name, host, port, username, auth_method)。');
}
// ... (add other validation as before) ...
let proxyIdToUse: number | null = null;
// Handle proxy (find or create) - uses async repository functions
if (connData.proxy) {
const proxyData = connData.proxy;
if (!proxyData.name || !proxyData.type || !proxyData.host || !proxyData.port) {
@@ -194,11 +188,10 @@ export const importConnections = async (fileBuffer: Buffer): Promise<ImportResul
proxyIdToUse = await ProxyRepository.createProxy(newProxyData);
console.log(`Service: 导入连接 ${connData.name}: 新代理 ${proxyData.name} 创建成功 (ID: ${proxyIdToUse})`);
}
if (proxyIdToUse) proxyCache[cacheKey] = proxyIdToUse; // Cache the ID
if (proxyIdToUse) proxyCache[cacheKey] = proxyIdToUse;
}
}
// Prepare connection data for bulk insert (add tag_ids here)
connectionsToInsert.push({
name: connData.name,
host: connData.host,
@@ -209,7 +202,7 @@ export const importConnections = async (fileBuffer: Buffer): Promise<ImportResul
encrypted_private_key: connData.encrypted_private_key || null,
encrypted_passphrase: connData.encrypted_passphrase || null,
proxy_id: proxyIdToUse,
tag_ids: connData.tag_ids || [] // Include tag_ids
tag_ids: connData.tag_ids || []
});
} catch (connError: any) {
@@ -217,25 +210,22 @@ export const importConnections = async (fileBuffer: Buffer): Promise<ImportResul
errors.push({ connectionName: connData.name || '未知连接', message: connError.message });
console.warn(`Service: 处理导入连接 "${connData.name || '未知'}" 时出错: ${connError.message}`);
}
} // End for loop
// --- Pass 2: Bulk insert connections ---
}
let insertedResults: { connectionId: number, originalData: any }[] = [];
if (connectionsToInsert.length > 0) {
// Pass the transaction-aware db instance
insertedResults = await ConnectionRepository.bulkInsertConnections(db, connectionsToInsert);
successCount = insertedResults.length;
}
// --- Pass 3: Associate tags ---
const insertTagSql = `INSERT OR IGNORE INTO connection_tags (connection_id, tag_id) VALUES (?, ?)`; // Use INSERT OR IGNORE
const insertTagSql = `INSERT OR IGNORE INTO connection_tags (connection_id, tag_id) VALUES (?, ?)`;
for (const result of insertedResults) {
const originalTagIds = result.originalData?.tag_ids;
if (Array.isArray(originalTagIds) && originalTagIds.length > 0) {
const validTagIds = originalTagIds.filter((id: any) => typeof id === 'number' && id > 0);
if (validTagIds.length > 0) {
const tagPromises = validTagIds.map(tagId =>
runDb(db, insertTagSql, [result.connectionId, tagId]).catch(tagError => { // Use await runDb
runDb(db, insertTagSql, [result.connectionId, tagId]).catch(tagError => {
console.warn(`Service: 导入连接 ${result.originalData.name}: 关联标签 ID ${tagId} 失败: ${tagError.message}`);
})
);
@@ -245,20 +235,19 @@ export const importConnections = async (fileBuffer: Buffer): Promise<ImportResul
}
// Commit transaction using await runDb
await runDb(db, 'COMMIT');
console.log(`Service: 导入事务提交。成功: ${successCount}, 失败: ${failureCount}`);
return { successCount, failureCount, errors };
} catch (error: any) {
// Rollback transaction on any error during the process
console.error('Service: 导入事务处理出错,正在回滚:', error);
try {
await runDb(db, 'ROLLBACK'); // Use await runDb
await runDb(db, 'ROLLBACK');
} catch (rollbackErr: any) {
console.error("Service: 回滚事务失败:", rollbackErr);
}
// Adjust failure count and return error summary
failureCount = importedData.length;
successCount = 0;
errors.push({ message: `事务处理失败: ${error.message}` });