Fix caching and loop query on the get current user

This commit is contained in:
2025-10-10 00:57:28 +07:00
parent e4c2f8b7a5
commit e45e140b41
9 changed files with 150 additions and 54 deletions

View File

@@ -334,3 +334,4 @@ This comprehensive SQL monitoring system provides the tools needed to identify a
- **Configurable settings** for different environments
The system is designed to be non-intrusive while providing maximum visibility into database operations, helping you quickly identify and resolve performance issues and potential infinite loops.

View File

@@ -64,7 +64,6 @@ public class UserController : BaseController
public async Task<ActionResult<User>> GetCurrentUser()
{
var user = await base.GetUser();
user = await _userService.GetUserByName(user.Name);
return Ok(user);
}

View File

@@ -149,10 +149,10 @@ public class UserService : IUserService
// Use proper async version to avoid DbContext concurrency issues
user.Accounts = (await _accountService.GetAccountsByUserAsync(user)).ToList();
// Save to cache for 10 minutes if caching is enabled (JWT middleware calls this on every request)
// Save to cache for 5 minutes if caching is enabled (JWT middleware calls this on every request)
if (useCache)
{
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(10));
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(5));
}
return user;

View File

@@ -33,7 +33,8 @@ public class SqlLoopDetectionService
/// <param name="queryPattern">Pattern or hash of the query being executed</param>
/// <param name="executionTime">Time taken to execute the query</param>
/// <returns>True if a potential loop is detected</returns>
public bool TrackQueryExecution(string repositoryName, string methodName, string queryPattern, TimeSpan executionTime)
public bool TrackQueryExecution(string repositoryName, string methodName, string queryPattern,
TimeSpan executionTime)
{
var key = $"{repositoryName}.{methodName}.{queryPattern}";
var now = DateTime.UtcNow;
@@ -56,8 +57,12 @@ public class SqlLoopDetectionService
existing.LastExecution = now;
existing.ExecutionCount++;
existing.TotalExecutionTime += executionTime;
existing.MaxExecutionTime = existing.MaxExecutionTime > executionTime ? existing.MaxExecutionTime : executionTime;
existing.MinExecutionTime = existing.MinExecutionTime < executionTime ? existing.MinExecutionTime : executionTime;
existing.MaxExecutionTime = existing.MaxExecutionTime > executionTime
? existing.MaxExecutionTime
: executionTime;
existing.MinExecutionTime = existing.MinExecutionTime < executionTime
? existing.MinExecutionTime
: executionTime;
return existing;
});
@@ -86,7 +91,8 @@ public class SqlLoopDetectionService
if (tracker.ExecutionCount > 5 && timeSinceFirst.TotalSeconds < 10)
{
isLoopDetected = true;
reasons.Add($"Rapid execution: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalSeconds:F1} seconds");
reasons.Add(
$"Rapid execution: {tracker.ExecutionCount} executions in {timeSinceFirst.TotalSeconds:F1} seconds");
}
// Check for consistently slow queries

View File

@@ -45,7 +45,8 @@ namespace Managing.Infrastructure.Storage
{
var options = new DistributedCacheEntryOptions()
{
SlidingExpiration = slidingExpiration
SlidingExpiration = slidingExpiration,
AbsoluteExpiration = DateTime.UtcNow.AddHours(1)
};
distributedCache.SetString(name, JsonConvert.SerializeObject(value), options);
@@ -62,7 +63,8 @@ namespace Managing.Infrastructure.Storage
var options = new DistributedCacheEntryOptions()
{
SlidingExpiration = slidingExpiration
SlidingExpiration = slidingExpiration,
AbsoluteExpiration = DateTime.UtcNow.AddHours(1)
};
var result = action();

View File

@@ -0,0 +1,105 @@
import {create} from 'zustand'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {User, UserClient} from '../../generated/ManagingApi'
import useApiUrlStore from './apiStore'
import useCookie from '../../hooks/useCookie'
// Import React for useEffect
import React from 'react'
interface UserStore {
currentUser: User | null
isLoading: boolean
error: Error | null
setCurrentUser: (user: User | null) => void
setLoading: (loading: boolean) => void
setError: (error: Error | null) => void
clearUser: () => void
}
export const useUserStore = create<UserStore>((set, get) => ({
currentUser: null,
isLoading: false,
error: null,
setCurrentUser: (user: User | null) => set({ currentUser: user }),
setLoading: (loading: boolean) => set({ isLoading: loading }),
setError: (error: Error | null) => set({ error }),
clearUser: () => set({ currentUser: null, error: null }),
}))
// Custom hook that integrates TanStack Query with Zustand store
export const useCurrentUser = () => {
const { apiUrl } = useApiUrlStore()
const { getCookie } = useCookie()
const queryClient = useQueryClient()
// Get JWT token from cookies
const jwtToken = getCookie('token')
// TanStack Query for fetching user data
const query = useQuery({
queryKey: ['currentUser'],
queryFn: async (): Promise<User> => {
const userClient = new UserClient({}, apiUrl)
return await userClient.user_GetCurrentUser()
},
enabled: !!jwtToken, // Only fetch when JWT token exists
staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes
retry: 2,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
// Zustand store state
const store = useUserStore()
// Sync TanStack Query state with Zustand store
React.useEffect(() => {
if (query.data) {
store.setCurrentUser(query.data)
}
store.setLoading(query.isLoading)
store.setError(query.error as Error | null)
}, [query.data, query.isLoading, query.error, store])
// Return both TanStack Query data and store state for flexibility
return {
// TanStack Query data (preferred for most use cases)
user: query.data,
isLoading: query.isLoading,
error: query.error,
refetch: query.refetch,
// Store state (for components that need it)
storeUser: store.currentUser,
storeLoading: store.isLoading,
storeError: store.error,
// Utility functions
clearCache: () => {
queryClient.removeQueries({ queryKey: ['currentUser'] })
store.clearUser()
},
invalidateCache: () => {
queryClient.invalidateQueries({ queryKey: ['currentUser'] })
},
}
}
// Hook for components that only need the user data without loading states
export const useCurrentUserData = () => {
const { user } = useCurrentUser()
return user
}
// Hook for components that need to check if current user owns something
export const useIsBotOwner = (botAgentName: string) => {
const { user } = useCurrentUser()
return user?.agentName === botAgentName
}
// Hook for getting just the agent name
export const useCurrentAgentName = () => {
const { user } = useCurrentUser()
return user?.agentName || null
}

View File

@@ -2,6 +2,7 @@ import {ChartBarIcon, CogIcon, EyeIcon, PlayIcon, PlusCircleIcon, StopIcon, Tras
import React, {useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore'
import {useIsBotOwner} from '../../app/store/userStore'
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
@@ -12,13 +13,10 @@ import {
MoneyManagement,
Position,
TradingBotConfig,
TradingBotResponse,
UserClient
TradingBotResponse
} from '../../generated/ManagingApi'
import type {IBotList} from '../../global/type.tsx'
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
import {useQuery} from '@tanstack/react-query'
import useCookie from '../../hooks/useCookie'
function baseBadgeClass(isOutlined = false) {
let classes = 'text-xs badge badge-sm transition-all duration-200 hover:scale-105 '
@@ -53,17 +51,9 @@ function cardClasses(botStatus: BotStatus) {
const BotList: React.FC<IBotList> = ({ list }) => {
const { apiUrl } = useApiUrlStore()
const client = new BotClient({}, apiUrl)
const userClient = new UserClient({}, apiUrl)
const { getCookie } = useCookie()
// Get JWT token from cookies
const jwtToken = getCookie('token')
const { data: currentUser } = useQuery({
queryFn: () => userClient.user_GetCurrentUser(),
queryKey: ['currentUser'],
enabled: !!jwtToken, // Only fetch when JWT token exists
})
// Use the new user store hook for bot ownership checking
const checkIsBotOwner = useIsBotOwner
const [showMoneyManagementModal, setShowMoneyManagementModal] =
useState(false)
@@ -79,11 +69,6 @@ const BotList: React.FC<IBotList> = ({ list }) => {
config: TradingBotConfig
} | null>(null)
// Helper function to check if current user owns the bot
const isBotOwner = (botAgentName: string): boolean => {
return currentUser?.agentName === botAgentName
}
function getDeleteBadge(identifier: string) {
const classes = baseBadgeClass() + 'bg-error'
return (
@@ -259,7 +244,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
<div className="flex flex-wrap gap-1 sm:gap-2">
{/* Action Badges - Only show for bot owners */}
{isBotOwner(bot.agentName) && (
{checkIsBotOwner(bot.agentName) && (
<div className="flex flex-wrap gap-1">
{getToggleBotStatusBadge(bot.status as BotStatus, bot.identifier)}
{getUpdateBotBadge(bot)}

View File

@@ -2,11 +2,12 @@ import {ViewGridAddIcon} from '@heroicons/react/solid'
import React, {useState} from 'react'
import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import {useCurrentAgentName} from '../../app/store/userStore'
import {UnifiedTradingModal} from '../../components/organism'
import {BotClient, BotSortableColumn, BotStatus, UserClient} from '../../generated/ManagingApi'
import {BotClient, BotSortableColumn, BotStatus} from '../../generated/ManagingApi'
import {useQuery} from '@tanstack/react-query'
import BotList from './botList'
import {useQuery} from '@tanstack/react-query'
const Bots: React.FC = () => {
const [activeTab, setActiveTab] = useState(0)
@@ -21,12 +22,9 @@ const Bots: React.FC = () => {
}
const { apiUrl } = useApiUrlStore()
const botClient = new BotClient({}, apiUrl)
const userClient = new UserClient({}, apiUrl)
const { data: currentUser } = useQuery({
queryFn: () => userClient.user_GetCurrentUser(),
queryKey: ['currentUser'],
})
// Use the new user store hook to get current agent name
const currentAgentName = useCurrentAgentName()
// Query for paginated bots using the new endpoint
const { data: paginatedBots } = useQuery({
@@ -35,17 +33,17 @@ const Bots: React.FC = () => {
case 0: // All Active Bots
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Running, undefined, undefined, undefined, BotSortableColumn.Roi, 'Desc')
case 1: // My Active Bots
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Running, undefined, undefined, currentUser?.agentName, BotSortableColumn.Roi, 'Desc')
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Running, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
case 2: // My Down Bots
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Stopped, undefined, undefined, currentUser?.agentName, BotSortableColumn.Roi, 'Desc')
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Stopped, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
case 3: // Saved Bots
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Saved, undefined, undefined, currentUser?.agentName, BotSortableColumn.Roi, 'Desc')
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Saved, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
default:
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, undefined, undefined, undefined, undefined, BotSortableColumn.Roi, 'Desc')
}
},
queryKey: ['paginatedBots', activeTab, pageNumber, pageSize, currentUser?.agentName],
enabled: !!currentUser,
queryKey: ['paginatedBots', activeTab, pageNumber, pageSize, currentAgentName],
enabled: !!currentAgentName,
})
const filteredBots = paginatedBots?.items || []

View File

@@ -32,7 +32,7 @@ function UserInfoSettings() {
const jwtToken = getCookie('token')
const { data: user } = useQuery({
queryKey: ['user'],
queryKey: ['currentUser'],
queryFn: () => api.user_GetCurrentUser(),
enabled: !!jwtToken, // Only fetch when JWT token exists
})
@@ -59,7 +59,7 @@ function UserInfoSettings() {
const toast = new Toast('Updating agent name')
try {
await api.user_UpdateAgentName(data.agentName)
queryClient.invalidateQueries({ queryKey: ['user'] })
queryClient.invalidateQueries({ queryKey: ['currentUser'] })
setShowUpdateModal(false)
toast.update('success', 'Agent name updated successfully')
} catch (error) {
@@ -72,7 +72,7 @@ function UserInfoSettings() {
const toast = new Toast('Updating avatar')
try {
await api.user_UpdateAvatarUrl(data.avatarUrl)
queryClient.invalidateQueries({ queryKey: ['user'] })
queryClient.invalidateQueries({ queryKey: ['currentUser'] })
setShowAvatarModal(false)
toast.update('success', 'Avatar updated successfully')
} catch (error) {
@@ -85,7 +85,7 @@ function UserInfoSettings() {
const toast = new Toast('Updating telegram channel')
try {
await api.user_UpdateTelegramChannel(data.telegramChannel)
queryClient.invalidateQueries({ queryKey: ['user'] })
queryClient.invalidateQueries({ queryKey: ['currentUser'] })
setShowTelegramModal(false)
toast.update('success', 'Telegram channel updated successfully')
} catch (error) {