From 08f57fe5c98a58870ce3984f550a9eace95c9b0a Mon Sep 17 00:00:00 2001 From: icy Date: Thu, 6 Nov 2025 20:39:46 +0800 Subject: [PATCH] feat: soft deleted exchange add --- config/database.go | 1 + config/database_pg.go | 69 ++++++++++++---- db/init.sql | 1 + web/src/components/AITradersPage.tsx | 115 +++++++-------------------- web/src/types.ts | 1 + 5 files changed, 82 insertions(+), 105 deletions(-) diff --git a/config/database.go b/config/database.go index a1f8da45..51876587 100644 --- a/config/database.go +++ b/config/database.go @@ -78,6 +78,7 @@ type ExchangeConfig struct { AsterUser string `json:"asterUser"` AsterSigner string `json:"asterSigner"` AsterPrivateKey string `json:"asterPrivateKey"` + Deleted bool `json:"deleted"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/config/database_pg.go b/config/database_pg.go index ef41bf07..a029948c 100644 --- a/config/database_pg.go +++ b/config/database_pg.go @@ -250,13 +250,16 @@ func (d *PostgreSQLDatabase) UpdateAIModel(userID, id string, enabled bool, apiK // GetExchanges 获取用户的交易所配置 func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, error) { rows, err := d.db.Query(` - SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet, - COALESCE(hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr, - COALESCE(aster_user, '') as aster_user, - COALESCE(aster_signer, '') as aster_signer, - COALESCE(aster_private_key, '') as aster_private_key, - created_at, updated_at - FROM exchanges WHERE user_id = $1 ORDER BY id + SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet, + COALESCE(hyperliquid_wallet_addr, '') AS hyperliquid_wallet_addr, + COALESCE(aster_user, '') AS aster_user, + COALESCE(aster_signer, '') AS aster_signer, + COALESCE(aster_private_key, '') AS aster_private_key, + COALESCE(deleted, FALSE) AS deleted, + created_at, updated_at + FROM exchanges + WHERE user_id = $1 AND COALESCE(deleted, FALSE) = FALSE + ORDER BY id `, userID) if err != nil { return nil, err @@ -272,6 +275,7 @@ func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, err &exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet, &exchange.HyperliquidWalletAddr, &exchange.AsterUser, &exchange.AsterSigner, &exchange.AsterPrivateKey, + &exchange.Deleted, &exchange.CreatedAt, &exchange.UpdatedAt, ) if err != nil { @@ -287,10 +291,35 @@ func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, err func (d *PostgreSQLDatabase) UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error { log.Printf("🔧 UpdateExchange: userID=%s, id=%s, enabled=%v", userID, id, enabled) + // 如果请求禁用该交易所,标记为已删除 + if !enabled { + _, err := d.db.Exec(` + UPDATE exchanges + SET enabled = FALSE, + deleted = TRUE, + api_key = '', + secret_key = '', + testnet = FALSE, + hyperliquid_wallet_addr = '', + aster_user = '', + aster_signer = '', + aster_private_key = '', + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 AND user_id = $2 + `, id, userID) + if err != nil { + log.Printf("❌ UpdateExchange: 标记删除失败: %v", err) + return err + } + log.Printf("🗑️ UpdateExchange: 已标记删除用户 %s 的交易所配置 %s", userID, id) + return nil + } + // 首先尝试更新现有的用户配置 result, err := d.db.Exec(` - UPDATE exchanges SET enabled = $1, api_key = $2, secret_key = $3, testnet = $4, - hyperliquid_wallet_addr = $5, aster_user = $6, aster_signer = $7, aster_private_key = $8, updated_at = CURRENT_TIMESTAMP + UPDATE exchanges SET enabled = $1, api_key = $2, secret_key = $3, testnet = $4, + hyperliquid_wallet_addr = $5, aster_user = $6, aster_signer = $7, aster_private_key = $8, + deleted = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = $9 AND user_id = $10 `, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID) if err != nil { @@ -331,10 +360,11 @@ func (d *PostgreSQLDatabase) UpdateExchange(userID, id string, enabled bool, api // 创建用户特定的配置,使用原始的交易所ID _, err = d.db.Exec(` - INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, - hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - `, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey) + INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, + hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, + deleted, created_at, updated_at) + VALUES ($1, $2, $3, $4, TRUE, $5, $6, $7, $8, $9, $10, $11, FALSE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + `, id, userID, name, typ, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey) if err != nil { log.Printf("❌ UpdateExchange: 创建记录失败: %v", err) @@ -550,12 +580,12 @@ func (d *PostgreSQLDatabase) UpdateUserSignalSource(userID, coinPoolURL, oiTopUR func (d *PostgreSQLDatabase) GetCustomCoins() []string { var symbol string var symbols []string - + err := d.db.QueryRow(` SELECT STRING_AGG(custom_coins, ',') as symbol FROM traders WHERE custom_coins != '' `).Scan(&symbol) - + // 检测用户是否未配置币种 - 兼容性 if err != nil || symbol == "" { symbolJSON, _ := d.GetSystemConfig("default_coins") @@ -564,7 +594,7 @@ func (d *PostgreSQLDatabase) GetCustomCoins() []string { symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"} } } - + // filter Symbol for _, s := range strings.Split(symbol, ",") { if s == "" { @@ -616,7 +646,7 @@ func (d *PostgreSQLDatabase) LoadBetaCodesFromFile(filePath string) error { log.Printf("插入内测码 %s 失败: %v", code, err) continue } - + if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 { insertedCount++ } @@ -687,6 +717,11 @@ func (d *PostgreSQLDatabase) initDefaultData() error { return fmt.Errorf("添加custom_coins列失败: %w", err) } + // 确保exchanges表存在deleted列 + if _, err := d.db.Exec(`ALTER TABLE exchanges ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE`); err != nil { + return fmt.Errorf("添加deleted列失败: %w", err) + } + // 首先创建default用户(如果不存在) _, err := d.db.Exec(` INSERT INTO users (id, email, password_hash, otp_secret, otp_verified) diff --git a/db/init.sql b/db/init.sql index 4def4446..efec4646 100644 --- a/db/init.sql +++ b/db/init.sql @@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS exchanges ( aster_user TEXT DEFAULT '', aster_signer TEXT DEFAULT '', aster_private_key TEXT DEFAULT '', + deleted BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id, user_id), diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index 065012d9..8b85d6c8 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -430,44 +430,25 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { if (!confirm(t('confirmDeleteExchange', language))) return try { - const updatedExchanges = - allExchanges?.map((e) => - e.id === exchangeId - ? { - ...e, - apiKey: '', - secretKey: '', - hyperliquidWalletAddr: '', - asterUser: '', - asterSigner: '', - asterPrivateKey: '', - testnet: false, - enabled: false, - } - : e - ) || [] - const request = { - exchanges: Object.fromEntries( - updatedExchanges.map((exchange) => [ - exchange.id, - { - enabled: exchange.enabled, - api_key: exchange.apiKey || '', - secret_key: exchange.secretKey || '', - testnet: exchange.testnet || false, - hyperliquid_wallet_addr: - exchange.hyperliquidWalletAddr || '', - aster_user: exchange.asterUser || '', - aster_signer: exchange.asterSigner || '', - aster_private_key: exchange.asterPrivateKey || '', - }, - ]) - ), + exchanges: { + [exchangeId]: { + enabled: false, + api_key: '', + secret_key: '', + testnet: false, + hyperliquid_wallet_addr: '', + aster_user: '', + aster_signer: '', + aster_private_key: '', + }, + }, } await api.updateExchangeConfigs(request) - setAllExchanges(updatedExchanges) + + const refreshed = await api.getExchangeConfigs() + setAllExchanges(refreshed) setShowExchangeModal(false) setEditingExchange(null) } catch (error) { @@ -496,65 +477,23 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { return } - // 创建或更新用户的交易所配置 - const existingExchange = allExchanges?.find((e) => e.id === exchangeId) - let updatedExchanges - - if (existingExchange) { - // 更新现有配置 - updatedExchanges = - allExchanges?.map((e) => - e.id === exchangeId - ? { - ...e, - apiKey, - secretKey, - testnet, - hyperliquidWalletAddr, - asterUser, - asterSigner, - asterPrivateKey, - enabled: true, - } - : e - ) || [] - } else { - // 添加新配置 - const newExchange = { - ...exchangeToUpdate, - apiKey, - secretKey, - testnet, - hyperliquidWalletAddr, - asterUser, - asterSigner, - asterPrivateKey, - enabled: true, - } - updatedExchanges = [...(allExchanges || []), newExchange] - } - const request = { - exchanges: Object.fromEntries( - updatedExchanges.map((exchange) => [ - exchange.id, - { - enabled: exchange.enabled, - api_key: exchange.apiKey || '', - secret_key: exchange.secretKey || '', - testnet: exchange.testnet || false, - hyperliquid_wallet_addr: exchange.hyperliquidWalletAddr || '', - aster_user: exchange.asterUser || '', - aster_signer: exchange.asterSigner || '', - aster_private_key: exchange.asterPrivateKey || '', - }, - ]) - ), + exchanges: { + [exchangeId]: { + enabled: true, + api_key: apiKey || '', + secret_key: secretKey || '', + testnet: !!testnet, + hyperliquid_wallet_addr: hyperliquidWalletAddr || '', + aster_user: asterUser || '', + aster_signer: asterSigner || '', + aster_private_key: asterPrivateKey || '', + }, + }, } await api.updateExchangeConfigs(request) - // 重新获取用户配置以确保数据同步 const refreshedExchanges = await api.getExchangeConfigs() setAllExchanges(refreshedExchanges) diff --git a/web/src/types.ts b/web/src/types.ts index d1368c01..efe0aa73 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -120,6 +120,7 @@ export interface Exchange { asterUser?: string asterSigner?: string asterPrivateKey?: string + deleted?: boolean } export interface CreateTraderRequest {