Skip to content

Commit 4fbe871

Browse files
authored
Merge branch 'brokermr810:main' into main
2 parents eae534a + f332cc6 commit 4fbe871

35 files changed

Lines changed: 1232 additions & 24324 deletions

File tree

backend_api_python/app/routes/dashboard.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,16 @@ def _truthy(v: Any) -> bool:
354354
""",
355355
(user_id,)
356356
)
357-
recent_trades = cur.fetchall() or []
357+
recent_trades_raw = cur.fetchall() or []
358358
cur.close()
359+
360+
# Convert datetime to timestamp for frontend compatibility
361+
recent_trades = []
362+
for t in recent_trades_raw:
363+
trade = dict(t)
364+
if trade.get('created_at') and hasattr(trade['created_at'], 'timestamp'):
365+
trade['created_at'] = int(trade['created_at'].timestamp())
366+
recent_trades.append(trade)
359367

360368
# Compute performance statistics
361369
perf_stats = _compute_performance_stats(recent_trades)

backend_api_python/app/routes/settings.py

Lines changed: 15 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -228,22 +228,6 @@
228228
'default': '10',
229229
'description': 'Time-to-live for cached price data in seconds'
230230
},
231-
{
232-
'key': 'MARKET_TYPES_JSON',
233-
'label': 'Market Types (JSON)',
234-
'type': 'text',
235-
'default': '[]',
236-
'required': False,
237-
'description': 'Custom market type definitions in JSON format'
238-
},
239-
{
240-
'key': 'TRADING_SUPPORTED_SYMBOLS_JSON',
241-
'label': 'Supported Symbols (JSON)',
242-
'type': 'text',
243-
'default': '[]',
244-
'required': False,
245-
'description': 'List of supported trading symbols in JSON format'
246-
},
247231
]
248232
},
249233

@@ -353,50 +337,11 @@
353337
]
354338
},
355339

356-
# ==================== 7. 通知推送 ====================
357-
'notification': {
358-
'title': 'Notifications',
359-
'icon': 'notification',
360-
'order': 7,
361-
'items': [
362-
{
363-
'key': 'SIGNAL_WEBHOOK_URL',
364-
'label': 'Webhook URL',
365-
'type': 'text',
366-
'required': False,
367-
'description': 'Custom webhook URL for signal notifications (POST JSON)'
368-
},
369-
{
370-
'key': 'SIGNAL_WEBHOOK_TOKEN',
371-
'label': 'Webhook Token',
372-
'type': 'password',
373-
'required': False,
374-
'description': 'Authentication token sent in webhook header'
375-
},
376-
{
377-
'key': 'SIGNAL_NOTIFY_TIMEOUT_SEC',
378-
'label': 'Notify Timeout (sec)',
379-
'type': 'number',
380-
'default': '6',
381-
'description': 'Notification request timeout'
382-
},
383-
{
384-
'key': 'TELEGRAM_BOT_TOKEN',
385-
'label': 'Telegram Bot Token',
386-
'type': 'password',
387-
'required': False,
388-
'link': 'https://t.me/BotFather',
389-
'link_text': 'settings.link.createBot',
390-
'description': 'Telegram bot token from @BotFather for signal notifications'
391-
},
392-
]
393-
},
394-
395-
# ==================== 8. 邮件配置 ====================
340+
# ==================== 7. 邮件配置 (公共 SMTP) ====================
396341
'email': {
397342
'title': 'Email (SMTP)',
398343
'icon': 'mail',
399-
'order': 8,
344+
'order': 7,
400345
'items': [
401346
{
402347
'key': 'SMTP_HOST',
@@ -454,7 +399,7 @@
454399
'sms': {
455400
'title': 'SMS (Twilio)',
456401
'icon': 'phone',
457-
'order': 9,
402+
'order': 8,
458403
'items': [
459404
{
460405
'key': 'TWILIO_ACCOUNT_SID',
@@ -482,11 +427,11 @@
482427
]
483428
},
484429

485-
# ==================== 10. AI Agent 配置 ====================
430+
# ==================== 9. AI Agent 配置 ====================
486431
'agent': {
487432
'title': 'AI Agent',
488433
'icon': 'experiment',
489-
'order': 10,
434+
'order': 9,
490435
'items': [
491436
{
492437
'key': 'ENABLE_AGENT_MEMORY',
@@ -568,11 +513,11 @@
568513
]
569514
},
570515

571-
# ==================== 11. 网络代理 ====================
516+
# ==================== 10. 网络代理 ====================
572517
'network': {
573518
'title': 'Network & Proxy',
574519
'icon': 'global',
575-
'order': 11,
520+
'order': 10,
576521
'items': [
577522
{
578523
'key': 'PROXY_HOST',
@@ -606,11 +551,11 @@
606551
]
607552
},
608553

609-
# ==================== 12. 搜索配置 ====================
554+
# ==================== 11. 搜索配置 ====================
610555
'search': {
611556
'title': 'Web Search',
612557
'icon': 'search',
613-
'order': 12,
558+
'order': 11,
614559
'items': [
615560
{
616561
'key': 'SEARCH_PROVIDER',
@@ -664,11 +609,11 @@
664609
]
665610
},
666611

667-
# ==================== 13. 注册与安全 ====================
612+
# ==================== 12. 注册与安全 ====================
668613
'security': {
669614
'title': 'Registration & Security',
670615
'icon': 'safety',
671-
'order': 13,
616+
'order': 12,
672617
'items': [
673618
{
674619
'key': 'ENABLE_REGISTRATION',
@@ -826,11 +771,11 @@
826771
]
827772
},
828773

829-
# ==================== 14. 计费配置 ====================
774+
# ==================== 13. 计费配置 ====================
830775
'billing': {
831776
'title': 'Billing & Credits',
832777
'icon': 'dollar',
833-
'order': 14,
778+
'order': 13,
834779
'items': [
835780
{
836781
'key': 'BILLING_ENABLED',
@@ -898,11 +843,11 @@
898843
]
899844
},
900845

901-
# ==================== 15. 应用配置 ====================
846+
# ==================== 14. 应用配置 ====================
902847
'app': {
903848
'title': 'Application',
904849
'icon': 'appstore',
905-
'order': 15,
850+
'order': 14,
906851
'items': [
907852
{
908853
'key': 'CORS_ORIGINS',

backend_api_python/app/routes/strategy.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,13 @@ def get_equity_curve():
471471
equity += float(r.get('profit') or 0)
472472
except Exception:
473473
pass
474-
ts = int(r.get('created_at').timestamp() or time.time())
474+
created_at = r.get('created_at')
475+
if created_at and hasattr(created_at, 'timestamp'):
476+
ts = int(created_at.timestamp())
477+
elif created_at:
478+
ts = int(created_at)
479+
else:
480+
ts = int(time.time())
475481
curve.append({'time': ts, 'equity': equity})
476482

477483
return jsonify({'code': 1, 'msg': 'success', 'data': curve})

backend_api_python/app/routes/user.py

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,9 +348,11 @@ def get_user_credits_log():
348348
@user_bp.route('/profile', methods=['GET'])
349349
@login_required
350350
def get_profile():
351-
"""Get current user's profile with billing info"""
351+
"""Get current user's profile with billing info and notification settings"""
352352
try:
353+
import json
353354
from app.services.billing_service import get_billing_service
355+
from app.utils.db import get_db_connection
354356

355357
user_id = getattr(g, 'user_id', None)
356358
if not user_id:
@@ -367,6 +369,27 @@ def get_profile():
367369
billing_info = get_billing_service().get_user_billing_info(user_id)
368370
user['billing'] = billing_info
369371

372+
# Add notification settings
373+
with get_db_connection() as db:
374+
cur = db.cursor()
375+
cur.execute("SELECT notification_settings FROM qd_users WHERE id = ?", (user_id,))
376+
row = cur.fetchone()
377+
cur.close()
378+
379+
settings_str = (row.get('notification_settings') if row else '') or ''
380+
notification_settings = {}
381+
if settings_str:
382+
try:
383+
notification_settings = json.loads(settings_str)
384+
except Exception:
385+
notification_settings = {}
386+
387+
# Default values
388+
if 'default_channels' not in notification_settings:
389+
notification_settings['default_channels'] = ['browser']
390+
391+
user['notification_settings'] = notification_settings
392+
370393
return jsonify({
371394
'code': 1,
372395
'msg': 'success',
@@ -529,6 +552,129 @@ def get_my_referrals():
529552
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
530553

531554

555+
@user_bp.route('/notification-settings', methods=['GET'])
556+
@login_required
557+
def get_notification_settings():
558+
"""
559+
Get current user's notification settings.
560+
561+
Returns:
562+
notification_settings: {
563+
default_channels: ['browser', 'telegram', ...],
564+
telegram_chat_id: str,
565+
email: str (optional, override for notifications),
566+
discord_webhook: str (optional)
567+
}
568+
"""
569+
try:
570+
import json
571+
from app.utils.db import get_db_connection
572+
573+
user_id = getattr(g, 'user_id', None)
574+
if not user_id:
575+
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
576+
577+
with get_db_connection() as db:
578+
cur = db.cursor()
579+
cur.execute("SELECT notification_settings, email FROM qd_users WHERE id = ?", (user_id,))
580+
row = cur.fetchone()
581+
cur.close()
582+
583+
if not row:
584+
return jsonify({'code': 0, 'msg': 'User not found', 'data': None}), 404
585+
586+
# Parse notification_settings JSON
587+
settings_str = row.get('notification_settings') or ''
588+
settings = {}
589+
if settings_str:
590+
try:
591+
settings = json.loads(settings_str)
592+
except Exception:
593+
settings = {}
594+
595+
# Default values
596+
if 'default_channels' not in settings:
597+
settings['default_channels'] = ['browser']
598+
if 'email' not in settings:
599+
settings['email'] = row.get('email') or ''
600+
601+
return jsonify({
602+
'code': 1,
603+
'msg': 'success',
604+
'data': settings
605+
})
606+
except Exception as e:
607+
logger.error(f"get_notification_settings failed: {e}")
608+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
609+
610+
611+
@user_bp.route('/notification-settings', methods=['PUT'])
612+
@login_required
613+
def update_notification_settings():
614+
"""
615+
Update current user's notification settings.
616+
617+
Request body:
618+
default_channels: list of str (optional, e.g. ['browser', 'telegram'])
619+
telegram_bot_token: str (optional, user's own Telegram bot token)
620+
telegram_chat_id: str (optional)
621+
email: str (optional, for notification override)
622+
discord_webhook: str (optional)
623+
"""
624+
try:
625+
import json
626+
from app.utils.db import get_db_connection
627+
628+
user_id = getattr(g, 'user_id', None)
629+
if not user_id:
630+
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
631+
632+
data = request.get_json() or {}
633+
634+
# Validate channels
635+
valid_channels = ['browser', 'email', 'telegram', 'discord', 'webhook', 'phone']
636+
default_channels = data.get('default_channels', [])
637+
if not isinstance(default_channels, list):
638+
default_channels = ['browser']
639+
default_channels = [c for c in default_channels if c in valid_channels]
640+
if not default_channels:
641+
default_channels = ['browser']
642+
643+
# Build settings object
644+
settings = {
645+
'default_channels': default_channels,
646+
'telegram_bot_token': str(data.get('telegram_bot_token') or '').strip(),
647+
'telegram_chat_id': str(data.get('telegram_chat_id') or '').strip(),
648+
'email': str(data.get('email') or '').strip(),
649+
'discord_webhook': str(data.get('discord_webhook') or '').strip(),
650+
'webhook_url': str(data.get('webhook_url') or '').strip(),
651+
'phone': str(data.get('phone') or '').strip(),
652+
}
653+
654+
# Remove empty values (but keep default_channels and telegram_bot_token even if partially filled)
655+
settings = {k: v for k, v in settings.items() if v or k == 'default_channels'}
656+
657+
settings_json = json.dumps(settings, ensure_ascii=False)
658+
659+
with get_db_connection() as db:
660+
cur = db.cursor()
661+
cur.execute(
662+
"UPDATE qd_users SET notification_settings = ?, updated_at = NOW() WHERE id = ?",
663+
(settings_json, user_id)
664+
)
665+
db.commit()
666+
cur.close()
667+
668+
return jsonify({
669+
'code': 1,
670+
'msg': 'Notification settings updated',
671+
'data': settings
672+
})
673+
except Exception as e:
674+
logger.error(f"update_notification_settings failed: {e}")
675+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
676+
677+
532678
@user_bp.route('/change-password', methods=['POST'])
533679
@login_required
534680
def change_password():

0 commit comments

Comments
 (0)