v2.2.1: frontend closed-source + Docker one-click deploy
- Remove frontend source code (now in private repo) - Add pre-built frontend/dist/ with Nginx serving - Simplify docker-compose.yml (no Node.js build needed) - Update README with docs index and Docker deploy guide - Add admin order list and AI analysis stats tabs - Add quick trade API routes - Clean up redundant files (package-lock.json, yarn.lock, .iml) - Add GitHub Actions workflow for frontend update automation
This commit is contained in:
@@ -6,7 +6,6 @@ Local deployment notes:
|
||||
- Credentials are stored as plaintext JSON in DB (encrypted_config column kept for compatibility).
|
||||
"""
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import json
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
@@ -42,7 +41,7 @@ def list_credentials():
|
||||
"""
|
||||
SELECT id, user_id, name, exchange_id, api_key_hint, created_at, updated_at
|
||||
FROM qd_exchange_credentials
|
||||
WHERE user_id = ?
|
||||
WHERE user_id = %s
|
||||
ORDER BY id DESC
|
||||
""",
|
||||
(user_id,)
|
||||
@@ -57,41 +56,84 @@ def list_credentials():
|
||||
return jsonify({'code': 0, 'msg': str(e), 'data': {'items': []}}), 500
|
||||
|
||||
|
||||
CRYPTO_EXCHANGES = [
|
||||
'binance', 'okx', 'bitget', 'bybit', 'coinbaseexchange',
|
||||
'kraken', 'kucoin', 'gate', 'bitfinex', 'deepcoin'
|
||||
]
|
||||
|
||||
|
||||
@credentials_bp.route('/create', methods=['POST'])
|
||||
@login_required
|
||||
def create_credential():
|
||||
"""Create a new credential for the current user."""
|
||||
"""Create a new credential for the current user.
|
||||
|
||||
Supports crypto exchanges, IBKR (US stocks) and MT5 (Forex).
|
||||
"""
|
||||
try:
|
||||
user_id = g.user_id
|
||||
data = request.get_json() or {}
|
||||
name = (data.get('name') or '').strip()
|
||||
exchange_id = (data.get('exchange_id') or '').strip()
|
||||
api_key = (data.get('api_key') or '').strip()
|
||||
secret_key = (data.get('secret_key') or '').strip()
|
||||
passphrase = (data.get('passphrase') or '').strip()
|
||||
exchange_id = (data.get('exchange_id') or '').strip().lower()
|
||||
|
||||
if not exchange_id:
|
||||
return jsonify({'code': 0, 'msg': 'Missing exchange_id', 'data': None}), 400
|
||||
if not api_key or not secret_key:
|
||||
return jsonify({'code': 0, 'msg': 'Missing api_key/secret_key', 'data': None}), 400
|
||||
|
||||
plaintext_config = json.dumps({
|
||||
'exchange_id': exchange_id,
|
||||
'api_key': api_key,
|
||||
'secret_key': secret_key,
|
||||
'passphrase': passphrase
|
||||
}, ensure_ascii=False)
|
||||
config = {'exchange_id': exchange_id}
|
||||
hint = ''
|
||||
|
||||
if exchange_id == 'ibkr':
|
||||
# Interactive Brokers (US stocks)
|
||||
config.update({
|
||||
'ibkr_host': (data.get('ibkr_host') or '127.0.0.1').strip(),
|
||||
'ibkr_port': int(data.get('ibkr_port') or 7497),
|
||||
'ibkr_client_id': int(data.get('ibkr_client_id') or 1),
|
||||
'ibkr_account': (data.get('ibkr_account') or '').strip()
|
||||
})
|
||||
hint = f"{config['ibkr_host']}:{config['ibkr_port']}"
|
||||
elif exchange_id == 'mt5':
|
||||
# MetaTrader 5 (Forex)
|
||||
mt5_server = (data.get('mt5_server') or '').strip()
|
||||
mt5_login = str(data.get('mt5_login') or '').strip()
|
||||
mt5_password = (data.get('mt5_password') or '').strip()
|
||||
if not mt5_server or not mt5_login or not mt5_password:
|
||||
return jsonify({'code': 0, 'msg': 'Missing mt5_server/mt5_login/mt5_password', 'data': None}), 400
|
||||
config.update({
|
||||
'mt5_server': mt5_server,
|
||||
'mt5_login': mt5_login,
|
||||
'mt5_password': mt5_password,
|
||||
'mt5_terminal_path': (data.get('mt5_terminal_path') or '').strip()
|
||||
})
|
||||
hint = f"{mt5_server}/{mt5_login}"
|
||||
elif exchange_id in CRYPTO_EXCHANGES:
|
||||
# Crypto exchanges
|
||||
api_key = (data.get('api_key') or '').strip()
|
||||
secret_key = (data.get('secret_key') or '').strip()
|
||||
if not api_key or not secret_key:
|
||||
return jsonify({'code': 0, 'msg': 'Missing api_key/secret_key', 'data': None}), 400
|
||||
config.update({
|
||||
'api_key': api_key,
|
||||
'secret_key': secret_key,
|
||||
'passphrase': (data.get('passphrase') or '').strip(),
|
||||
'enable_demo_trading': bool(data.get('enable_demo_trading', False))
|
||||
})
|
||||
hint = _api_key_hint(api_key)
|
||||
else:
|
||||
return jsonify({'code': 0, 'msg': f'Unsupported exchange: {exchange_id}', 'data': None}), 400
|
||||
|
||||
plaintext_config = json.dumps(config, ensure_ascii=False)
|
||||
|
||||
with get_db_connection() as db:
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO qd_exchange_credentials (user_id, name, exchange_id, api_key_hint, encrypted_config, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW(), NOW())
|
||||
VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
|
||||
RETURNING id
|
||||
""",
|
||||
(user_id, name, exchange_id, _api_key_hint(api_key), plaintext_config)
|
||||
(user_id, name, exchange_id, hint, plaintext_config)
|
||||
)
|
||||
new_id = cur.lastrowid
|
||||
row = cur.fetchone()
|
||||
new_id = (row or {}).get('id')
|
||||
db.commit()
|
||||
cur.close()
|
||||
|
||||
@@ -115,7 +157,7 @@ def delete_credential():
|
||||
with get_db_connection() as db:
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"DELETE FROM qd_exchange_credentials WHERE id = ? AND user_id = ?",
|
||||
"DELETE FROM qd_exchange_credentials WHERE id = %s AND user_id = %s",
|
||||
(cred_id, user_id)
|
||||
)
|
||||
db.commit()
|
||||
@@ -146,7 +188,7 @@ def get_credential():
|
||||
"""
|
||||
SELECT id, user_id, name, exchange_id, encrypted_config, api_key_hint, created_at, updated_at
|
||||
FROM qd_exchange_credentials
|
||||
WHERE id = ? AND user_id = ?
|
||||
WHERE id = %s AND user_id = %s
|
||||
""",
|
||||
(cred_id, user_id)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user