mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 01:14:40 +08:00
feat: soft deleted exchange add
This commit is contained in:
@@ -78,6 +78,7 @@ type ExchangeConfig struct {
|
|||||||
AsterUser string `json:"asterUser"`
|
AsterUser string `json:"asterUser"`
|
||||||
AsterSigner string `json:"asterSigner"`
|
AsterSigner string `json:"asterSigner"`
|
||||||
AsterPrivateKey string `json:"asterPrivateKey"`
|
AsterPrivateKey string `json:"asterPrivateKey"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,13 +250,16 @@ func (d *PostgreSQLDatabase) UpdateAIModel(userID, id string, enabled bool, apiK
|
|||||||
// GetExchanges 获取用户的交易所配置
|
// GetExchanges 获取用户的交易所配置
|
||||||
func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||||
rows, err := d.db.Query(`
|
rows, err := d.db.Query(`
|
||||||
SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
||||||
COALESCE(hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
|
COALESCE(hyperliquid_wallet_addr, '') AS hyperliquid_wallet_addr,
|
||||||
COALESCE(aster_user, '') as aster_user,
|
COALESCE(aster_user, '') AS aster_user,
|
||||||
COALESCE(aster_signer, '') as aster_signer,
|
COALESCE(aster_signer, '') AS aster_signer,
|
||||||
COALESCE(aster_private_key, '') as aster_private_key,
|
COALESCE(aster_private_key, '') AS aster_private_key,
|
||||||
created_at, updated_at
|
COALESCE(deleted, FALSE) AS deleted,
|
||||||
FROM exchanges WHERE user_id = $1 ORDER BY id
|
created_at, updated_at
|
||||||
|
FROM exchanges
|
||||||
|
WHERE user_id = $1 AND COALESCE(deleted, FALSE) = FALSE
|
||||||
|
ORDER BY id
|
||||||
`, userID)
|
`, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -272,6 +275,7 @@ func (d *PostgreSQLDatabase) GetExchanges(userID string) ([]*ExchangeConfig, err
|
|||||||
&exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
&exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||||
&exchange.HyperliquidWalletAddr, &exchange.AsterUser,
|
&exchange.HyperliquidWalletAddr, &exchange.AsterUser,
|
||||||
&exchange.AsterSigner, &exchange.AsterPrivateKey,
|
&exchange.AsterSigner, &exchange.AsterPrivateKey,
|
||||||
|
&exchange.Deleted,
|
||||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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 {
|
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)
|
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(`
|
result, err := d.db.Exec(`
|
||||||
UPDATE exchanges SET enabled = $1, api_key = $2, secret_key = $3, testnet = $4,
|
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
|
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
|
WHERE id = $9 AND user_id = $10
|
||||||
`, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID)
|
`, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,10 +360,11 @@ func (d *PostgreSQLDatabase) UpdateExchange(userID, id string, enabled bool, api
|
|||||||
|
|
||||||
// 创建用户特定的配置,使用原始的交易所ID
|
// 创建用户特定的配置,使用原始的交易所ID
|
||||||
_, err = d.db.Exec(`
|
_, err = d.db.Exec(`
|
||||||
INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
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)
|
hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
deleted, created_at, updated_at)
|
||||||
`, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
|
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 {
|
if err != nil {
|
||||||
log.Printf("❌ UpdateExchange: 创建记录失败: %v", err)
|
log.Printf("❌ UpdateExchange: 创建记录失败: %v", err)
|
||||||
@@ -550,12 +580,12 @@ func (d *PostgreSQLDatabase) UpdateUserSignalSource(userID, coinPoolURL, oiTopUR
|
|||||||
func (d *PostgreSQLDatabase) GetCustomCoins() []string {
|
func (d *PostgreSQLDatabase) GetCustomCoins() []string {
|
||||||
var symbol string
|
var symbol string
|
||||||
var symbols []string
|
var symbols []string
|
||||||
|
|
||||||
err := d.db.QueryRow(`
|
err := d.db.QueryRow(`
|
||||||
SELECT STRING_AGG(custom_coins, ',') as symbol
|
SELECT STRING_AGG(custom_coins, ',') as symbol
|
||||||
FROM traders WHERE custom_coins != ''
|
FROM traders WHERE custom_coins != ''
|
||||||
`).Scan(&symbol)
|
`).Scan(&symbol)
|
||||||
|
|
||||||
// 检测用户是否未配置币种 - 兼容性
|
// 检测用户是否未配置币种 - 兼容性
|
||||||
if err != nil || symbol == "" {
|
if err != nil || symbol == "" {
|
||||||
symbolJSON, _ := d.GetSystemConfig("default_coins")
|
symbolJSON, _ := d.GetSystemConfig("default_coins")
|
||||||
@@ -564,7 +594,7 @@ func (d *PostgreSQLDatabase) GetCustomCoins() []string {
|
|||||||
symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"}
|
symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter Symbol
|
// filter Symbol
|
||||||
for _, s := range strings.Split(symbol, ",") {
|
for _, s := range strings.Split(symbol, ",") {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
@@ -616,7 +646,7 @@ func (d *PostgreSQLDatabase) LoadBetaCodesFromFile(filePath string) error {
|
|||||||
log.Printf("插入内测码 %s 失败: %v", code, err)
|
log.Printf("插入内测码 %s 失败: %v", code, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 {
|
if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 {
|
||||||
insertedCount++
|
insertedCount++
|
||||||
}
|
}
|
||||||
@@ -687,6 +717,11 @@ func (d *PostgreSQLDatabase) initDefaultData() error {
|
|||||||
return fmt.Errorf("添加custom_coins列失败: %w", err)
|
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用户(如果不存在)
|
// 首先创建default用户(如果不存在)
|
||||||
_, err := d.db.Exec(`
|
_, err := d.db.Exec(`
|
||||||
INSERT INTO users (id, email, password_hash, otp_secret, otp_verified)
|
INSERT INTO users (id, email, password_hash, otp_secret, otp_verified)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS exchanges (
|
|||||||
aster_user TEXT DEFAULT '',
|
aster_user TEXT DEFAULT '',
|
||||||
aster_signer TEXT DEFAULT '',
|
aster_signer TEXT DEFAULT '',
|
||||||
aster_private_key TEXT DEFAULT '',
|
aster_private_key TEXT DEFAULT '',
|
||||||
|
deleted BOOLEAN DEFAULT FALSE,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id, user_id),
|
PRIMARY KEY (id, user_id),
|
||||||
|
|||||||
@@ -430,44 +430,25 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
if (!confirm(t('confirmDeleteExchange', language))) return
|
if (!confirm(t('confirmDeleteExchange', language))) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedExchanges =
|
|
||||||
allExchanges?.map((e) =>
|
|
||||||
e.id === exchangeId
|
|
||||||
? {
|
|
||||||
...e,
|
|
||||||
apiKey: '',
|
|
||||||
secretKey: '',
|
|
||||||
hyperliquidWalletAddr: '',
|
|
||||||
asterUser: '',
|
|
||||||
asterSigner: '',
|
|
||||||
asterPrivateKey: '',
|
|
||||||
testnet: false,
|
|
||||||
enabled: false,
|
|
||||||
}
|
|
||||||
: e
|
|
||||||
) || []
|
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
exchanges: Object.fromEntries(
|
exchanges: {
|
||||||
updatedExchanges.map((exchange) => [
|
[exchangeId]: {
|
||||||
exchange.id,
|
enabled: false,
|
||||||
{
|
api_key: '',
|
||||||
enabled: exchange.enabled,
|
secret_key: '',
|
||||||
api_key: exchange.apiKey || '',
|
testnet: false,
|
||||||
secret_key: exchange.secretKey || '',
|
hyperliquid_wallet_addr: '',
|
||||||
testnet: exchange.testnet || false,
|
aster_user: '',
|
||||||
hyperliquid_wallet_addr:
|
aster_signer: '',
|
||||||
exchange.hyperliquidWalletAddr || '',
|
aster_private_key: '',
|
||||||
aster_user: exchange.asterUser || '',
|
},
|
||||||
aster_signer: exchange.asterSigner || '',
|
},
|
||||||
aster_private_key: exchange.asterPrivateKey || '',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.updateExchangeConfigs(request)
|
await api.updateExchangeConfigs(request)
|
||||||
setAllExchanges(updatedExchanges)
|
|
||||||
|
const refreshed = await api.getExchangeConfigs()
|
||||||
|
setAllExchanges(refreshed)
|
||||||
setShowExchangeModal(false)
|
setShowExchangeModal(false)
|
||||||
setEditingExchange(null)
|
setEditingExchange(null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -496,65 +477,23 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
return
|
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 = {
|
const request = {
|
||||||
exchanges: Object.fromEntries(
|
exchanges: {
|
||||||
updatedExchanges.map((exchange) => [
|
[exchangeId]: {
|
||||||
exchange.id,
|
enabled: true,
|
||||||
{
|
api_key: apiKey || '',
|
||||||
enabled: exchange.enabled,
|
secret_key: secretKey || '',
|
||||||
api_key: exchange.apiKey || '',
|
testnet: !!testnet,
|
||||||
secret_key: exchange.secretKey || '',
|
hyperliquid_wallet_addr: hyperliquidWalletAddr || '',
|
||||||
testnet: exchange.testnet || false,
|
aster_user: asterUser || '',
|
||||||
hyperliquid_wallet_addr: exchange.hyperliquidWalletAddr || '',
|
aster_signer: asterSigner || '',
|
||||||
aster_user: exchange.asterUser || '',
|
aster_private_key: asterPrivateKey || '',
|
||||||
aster_signer: exchange.asterSigner || '',
|
},
|
||||||
aster_private_key: exchange.asterPrivateKey || '',
|
},
|
||||||
},
|
|
||||||
])
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.updateExchangeConfigs(request)
|
await api.updateExchangeConfigs(request)
|
||||||
|
|
||||||
// 重新获取用户配置以确保数据同步
|
|
||||||
const refreshedExchanges = await api.getExchangeConfigs()
|
const refreshedExchanges = await api.getExchangeConfigs()
|
||||||
setAllExchanges(refreshedExchanges)
|
setAllExchanges(refreshedExchanges)
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export interface Exchange {
|
|||||||
asterUser?: string
|
asterUser?: string
|
||||||
asterSigner?: string
|
asterSigner?: string
|
||||||
asterPrivateKey?: string
|
asterPrivateKey?: string
|
||||||
|
deleted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateTraderRequest {
|
export interface CreateTraderRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user