import * as ProxyRepository from '../repositories/proxy.repository'; import { encrypt, decrypt } from '../utils/crypto'; // Assuming crypto utils are needed // Re-export or define types (ideally from a shared types file) export interface ProxyData extends ProxyRepository.ProxyData {} // Input type for creating a proxy export interface CreateProxyInput { name: string; type: 'SOCKS5' | 'HTTP'; host: string; port: number; username?: string | null; auth_method?: 'none' | 'password' | 'key'; // Optional, defaults to 'none' password?: string | null; // Plain text password private_key?: string | null; // Plain text private key passphrase?: string | null; // Plain text passphrase } // Input type for updating a proxy export interface UpdateProxyInput { name?: string; type?: 'SOCKS5' | 'HTTP'; host?: string; port?: number; username?: string | null; auth_method?: 'none' | 'password' | 'key'; password?: string | null; // Use undefined for no change, null/empty to clear private_key?: string | null; // Use undefined for no change, null/empty to clear passphrase?: string | null; // Use undefined for no change, null/empty to clear } /** * 获取所有代理 */ export const getAllProxies = async (): Promise => { // Repository returns data with encrypted fields, which is fine for listing generally // If decryption is needed for display, it should happen closer to the presentation layer or selectively return ProxyRepository.findAllProxies(); }; /** * 根据 ID 获取单个代理 */ export const getProxyById = async (id: number): Promise => { // Repository returns data with encrypted fields return ProxyRepository.findProxyById(id); }; /** * 创建新代理 */ export const createProxy = async (input: CreateProxyInput): Promise => { // 1. Validate input if (!input.name || !input.type || !input.host || !input.port) { throw new Error('缺少必要的代理信息 (name, type, host, port)。'); } if (input.auth_method === 'password' && !input.password) { throw new Error('代理密码认证方式需要提供 password。'); } if (input.auth_method === 'key' && !input.private_key) { throw new Error('代理密钥认证方式需要提供 private_key。'); } // Add more validation (port range, type check etc.) // 2. Encrypt credentials if provided const encryptedPassword = input.password ? encrypt(input.password) : null; const encryptedPrivateKey = input.private_key ? encrypt(input.private_key) : null; const encryptedPassphrase = input.passphrase ? encrypt(input.passphrase) : null; // 3. Prepare data for repository const proxyData: Omit = { name: input.name, type: input.type, host: input.host, port: input.port, username: input.username || null, auth_method: input.auth_method || 'none', encrypted_password: encryptedPassword, encrypted_private_key: encryptedPrivateKey, encrypted_passphrase: encryptedPassphrase, }; // 4. Create proxy record const newProxyId = await ProxyRepository.createProxy(proxyData); // 5. Fetch and return the newly created proxy const newProxy = await getProxyById(newProxyId); if (!newProxy) { throw new Error('创建代理后无法检索到该代理。'); } return newProxy; }; /** * 更新代理信息 */ export const updateProxy = async (id: number, input: UpdateProxyInput): Promise => { // 1. Fetch current proxy data to compare if needed (e.g., for auth method change logic) const currentProxy = await ProxyRepository.findProxyById(id); if (!currentProxy) { return null; // Proxy not found } // 2. Prepare data for update const dataToUpdate: Partial> = {}; let needsCredentialUpdate = false; const newAuthMethod = input.auth_method || currentProxy.auth_method; // Update standard fields if (input.name !== undefined) dataToUpdate.name = input.name; if (input.type !== undefined) dataToUpdate.type = input.type; if (input.host !== undefined) dataToUpdate.host = input.host; if (input.port !== undefined) dataToUpdate.port = input.port; if (input.username !== undefined) dataToUpdate.username = input.username; // Allows clearing // Handle auth method change or credential update if (input.auth_method && input.auth_method !== currentProxy.auth_method) { dataToUpdate.auth_method = input.auth_method; needsCredentialUpdate = true; // Encrypt new credentials based on the *new* auth_method if (input.auth_method === 'password') { if (input.password === undefined) throw new Error('切换到密码认证时需要提供 password。'); dataToUpdate.encrypted_password = input.password ? encrypt(input.password) : null; dataToUpdate.encrypted_private_key = null; // Clear old key info dataToUpdate.encrypted_passphrase = null; } else if (input.auth_method === 'key') { if (input.private_key === undefined) throw new Error('切换到密钥认证时需要提供 private_key。'); dataToUpdate.encrypted_private_key = input.private_key ? encrypt(input.private_key) : null; dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; dataToUpdate.encrypted_password = null; // Clear old password info } else { // 'none' dataToUpdate.encrypted_password = null; dataToUpdate.encrypted_private_key = null; dataToUpdate.encrypted_passphrase = null; } } else { // Auth method unchanged, update credentials if provided for the current method if (newAuthMethod === 'password' && input.password !== undefined) { dataToUpdate.encrypted_password = input.password ? encrypt(input.password) : null; needsCredentialUpdate = true; } else if (newAuthMethod === 'key') { if (input.private_key !== undefined) { dataToUpdate.encrypted_private_key = input.private_key ? encrypt(input.private_key) : null; dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; // Update passphrase together needsCredentialUpdate = true; } else if (input.passphrase !== undefined) { // Only passphrase updated dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; needsCredentialUpdate = true; } } } // 3. Update proxy record if there are changes const hasChanges = Object.keys(dataToUpdate).length > 0; if (hasChanges) { const updated = await ProxyRepository.updateProxy(id, dataToUpdate); if (!updated) { throw new Error('更新代理记录失败。'); } } // 4. Fetch and return the updated proxy return getProxyById(id); }; /** * 删除代理 */ export const deleteProxy = async (id: number): Promise => { // Repository handles setting foreign keys to NULL in connections table return ProxyRepository.deleteProxy(id); };