Signed-off-by: Dinger <quantdinger@gmail.com>
This commit is contained in:
Dinger
2026-03-25 13:27:48 +08:00
parent 2d969d6661
commit c0cb332432
55 changed files with 827 additions and 255 deletions
+5
View File
@@ -207,6 +207,7 @@ def login():
'username': user.get('username', username),
'nickname': user.get('nickname', 'User'),
'avatar': user.get('avatar', '/avatar2.jpg'),
'timezone': str(user.get('timezone') or '').strip(),
'role': {
'id': user.get('role', 'admin'),
'permissions': _get_permissions(user.get('role', 'admin'))
@@ -419,6 +420,7 @@ def login_with_code():
'nickname': user.get('nickname', user['username']),
'email': user.get('email'),
'avatar': user.get('avatar', '/avatar2.jpg'),
'timezone': str(user.get('timezone') or '').strip(),
'role': {
'id': user.get('role', 'user'),
'permissions': _get_permissions(user.get('role', 'user'))
@@ -685,6 +687,7 @@ def register():
'nickname': username,
'email': email,
'avatar': '/avatar2.jpg',
'timezone': '',
'role': {
'id': 'user',
'permissions': _get_permissions('user')
@@ -1049,6 +1052,7 @@ def get_user_info():
'nickname': user_data.get('nickname', 'User'),
'email': user_data.get('email'),
'avatar': user_data.get('avatar', '/avatar2.jpg'),
'timezone': str(user_data.get('timezone') or '').strip(),
'role': {
'id': user_data.get('role', 'user'),
'permissions': _get_permissions(user_data.get('role', 'user'))
@@ -1065,6 +1069,7 @@ def get_user_info():
'username': username,
'nickname': 'Admin',
'avatar': '/avatar2.jpg',
'timezone': '',
'role': {
'id': role,
'permissions': _get_permissions(role)
+26 -4
View File
@@ -3,6 +3,7 @@ Portfolio API routes (local-only).
Manages manual positions (user's existing holdings) and AI monitoring tasks.
"""
from flask import Blueprint, request, jsonify, g
from datetime import date, datetime, timezone
import json
import traceback
import time
@@ -39,6 +40,27 @@ def _now_ts() -> int:
return int(time.time())
def _serialize_monitor_ts(value):
"""
JSON 序列化监控时间字段。PostgreSQL TIMESTAMP 无 tz 时按 UTC 解释(与 Docker 默认一致),
输出带 Z 的 ISO,避免前端把无时区字符串当本地时间而偏差 8 小时。
"""
if value is None:
return None
if isinstance(value, datetime):
if value.tzinfo is None:
dt = value.replace(tzinfo=timezone.utc)
else:
dt = value.astimezone(timezone.utc)
s = dt.isoformat()
if s.endswith("+00:00"):
return s[:-6] + "Z"
return s
if isinstance(value, date):
return value.isoformat()
return value
def _normalize_symbol(symbol: str) -> str:
return (symbol or '').strip().upper()
@@ -519,12 +541,12 @@ def get_monitors():
'config': _safe_json_loads(row.get('config'), {}),
'notification_config': _safe_json_loads(row.get('notification_config'), {}),
'is_active': bool(row.get('is_active')),
'last_run_at': row.get('last_run_at'),
'next_run_at': row.get('next_run_at'),
'last_run_at': _serialize_monitor_ts(row.get('last_run_at')),
'next_run_at': _serialize_monitor_ts(row.get('next_run_at')),
'last_result': _safe_json_loads(row.get('last_result'), {}),
'run_count': row.get('run_count') or 0,
'created_at': row.get('created_at'),
'updated_at': row.get('updated_at')
'created_at': _serialize_monitor_ts(row.get('created_at')),
'updated_at': _serialize_monitor_ts(row.get('updated_at'))
})
return jsonify({'code': 1, 'msg': 'success', 'data': monitors})
@@ -872,12 +872,16 @@ def get_strategy_notifications():
cur.close()
# Convert created_at to UTC timestamp (seconds) for frontend
from datetime import timezone as _dt_tz
processed_rows = []
for row in rows:
item = dict(row)
created_at = item.get('created_at')
if created_at:
if hasattr(created_at, 'timestamp'):
# 无时区 datetime:连接已 SET TIME ZONE UTC,按 UTC 解释再转 Unix,避免服务端本地 TZ 误判
if getattr(created_at, 'tzinfo', None) is None:
created_at = created_at.replace(tzinfo=_dt_tz.utc)
item['created_at'] = int(created_at.timestamp())
elif isinstance(created_at, str):
try:
+14
View File
@@ -5,6 +5,7 @@ Provides endpoints for user CRUD operations, role management, etc.
Only accessible by admin users.
"""
import json
import re
from flask import Blueprint, request, jsonify, g
from app.services.user_service import get_user_service
from app.utils.auth import login_required, admin_required
@@ -13,6 +14,8 @@ from app.utils.logger import get_logger
logger = get_logger(__name__)
_PROFILE_TIMEZONE_RE = re.compile(r'^[A-Za-z0-9_/+\-.]+$')
user_bp = Blueprint('user_manage', __name__)
@@ -411,6 +414,7 @@ def update_profile():
Request body:
nickname: str (optional)
avatar: str (optional)
timezone: str (optional, IANA id; empty = follow client)
Note: Email cannot be changed after registration (for security).
Only admin can change user email via User Management.
@@ -429,6 +433,16 @@ def update_profile():
if field in data:
allowed[field] = data[field]
if 'timezone' in data:
tz = (data.get('timezone') or '').strip()
if tz and (len(tz) > 64 or not _PROFILE_TIMEZONE_RE.match(tz)):
return jsonify({
'code': 0,
'msg': 'Invalid timezone identifier',
'data': None
}), 400
allowed['timezone'] = tz
if not allowed:
return jsonify({'code': 0, 'msg': 'No valid fields to update', 'data': None}), 400