From 244c086ef2bcf817e9ac10c303e997845f9a0f84 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Fri, 27 Jun 2025 19:07:23 -0700 Subject: [PATCH] fix sidebar issue --- apps/sim/app/api/workflows/sync/route.ts | 78 ++----------------- .../w/components/sidebar/sidebar.tsx | 10 ++- .../socket-server/middleware/permissions.ts | 14 ++-- apps/sim/stores/index.ts | 26 ------- apps/sim/stores/workflows/registry/store.ts | 7 +- 5 files changed, 23 insertions(+), 112 deletions(-) diff --git a/apps/sim/app/api/workflows/sync/route.ts b/apps/sim/app/api/workflows/sync/route.ts index 3c7a25af0f..47ba94bec2 100644 --- a/apps/sim/app/api/workflows/sync/route.ts +++ b/apps/sim/app/api/workflows/sync/route.ts @@ -4,93 +4,29 @@ import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' import { db } from '@/db' -import { workflow, workspace, workspaceMember } from '@/db/schema' +import { workflow, workspace } from '@/db/schema' +import { getUserEntityPermissions } from '@/lib/permissions/utils' const logger = createLogger('WorkflowAPI') -// Cache for workspace membership to reduce DB queries -const workspaceMembershipCache = new Map() -const CACHE_TTL = 60000 // 1 minute cache expiration -const MAX_CACHE_SIZE = 1000 // Maximum number of entries to prevent unbounded growth - /** - * Cleans up expired entries from the workspace membership cache - */ -function cleanupExpiredCacheEntries(): void { - const now = Date.now() - let expiredCount = 0 - - // Remove expired entries - for (const [key, value] of workspaceMembershipCache.entries()) { - if (value.expires <= now) { - workspaceMembershipCache.delete(key) - expiredCount++ - } - } - - // If we're still over the limit after removing expired entries, - // remove the oldest entries (those that will expire soonest) - if (workspaceMembershipCache.size > MAX_CACHE_SIZE) { - const entries = Array.from(workspaceMembershipCache.entries()).sort( - (a, b) => a[1].expires - b[1].expires - ) - - const toRemove = entries.slice(0, workspaceMembershipCache.size - MAX_CACHE_SIZE) - toRemove.forEach(([key]) => workspaceMembershipCache.delete(key)) - - logger.debug( - `Cache cleanup: removed ${expiredCount} expired entries and ${toRemove.length} additional entries due to size limit` - ) - } else if (expiredCount > 0) { - logger.debug(`Cache cleanup: removed ${expiredCount} expired entries`) - } -} - -/** - * Efficiently verifies user's membership and role in a workspace with caching + * Verifies user's workspace permissions using the permissions table * @param userId User ID to check * @param workspaceId Workspace ID to check - * @returns Role if user is a member, null otherwise + * @returns Permission type if user has access, null otherwise */ async function verifyWorkspaceMembership( userId: string, workspaceId: string ): Promise { - // Opportunistic cleanup of expired cache entries - if (workspaceMembershipCache.size > MAX_CACHE_SIZE / 2) { - cleanupExpiredCacheEntries() - } - - // Create cache key from userId and workspaceId - const cacheKey = `${userId}:${workspaceId}` - - // Check cache first - const cached = workspaceMembershipCache.get(cacheKey) - if (cached && cached.expires > Date.now()) { - return cached.role - } - - // If not in cache or expired, query the database try { - const membership = await db - .select({ role: workspaceMember.role }) - .from(workspaceMember) - .where(and(eq(workspaceMember.workspaceId, workspaceId), eq(workspaceMember.userId, userId))) - .then((rows) => rows[0]) + const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) - if (!membership) { - return null - } - // Cache the result - workspaceMembershipCache.set(cacheKey, { - role: membership.role, - expires: Date.now() + CACHE_TTL, - }) - return membership.role + return permission } catch (error) { - logger.error(`Error verifying workspace membership for ${userId} in ${workspaceId}:`, error) + logger.error(`Error verifying workspace permissions for ${userId} in ${workspaceId}:`, error) return null } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx index ace5b0b0c3..ab994a9753 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx @@ -32,7 +32,7 @@ const IS_DEV = process.env.NODE_ENV === 'development' export function Sidebar() { useGlobalShortcuts() - const { workflows, createWorkflow, isLoading: workflowsLoading } = useWorkflowRegistry() + const { workflows, createWorkflow, isLoading: workflowsLoading, loadWorkflows } = useWorkflowRegistry() const { isPending: sessionLoading } = useSession() const userPermissions = useUserPermissionsContext() const isLoading = workflowsLoading || sessionLoading @@ -41,6 +41,14 @@ export function Sidebar() { const workspaceId = params.workspaceId as string const pathname = usePathname() + // Load workflows for the current workspace when workspaceId changes + // This is the single source of truth for workflow loading + useEffect(() => { + if (workspaceId) { + loadWorkflows(workspaceId) + } + }, [workspaceId, loadWorkflows]) + const [showSettings, setShowSettings] = useState(false) const [showHelp, setShowHelp] = useState(false) const [showInviteMembers, setShowInviteMembers] = useState(false) diff --git a/apps/sim/socket-server/middleware/permissions.ts b/apps/sim/socket-server/middleware/permissions.ts index ca5aa4af82..7a21d2484b 100644 --- a/apps/sim/socket-server/middleware/permissions.ts +++ b/apps/sim/socket-server/middleware/permissions.ts @@ -1,7 +1,8 @@ import { and, eq } from 'drizzle-orm' import { db } from '../../db' -import { workflow, workspaceMember } from '../../db/schema' +import { workflow } from '../../db/schema' import { createLogger } from '../../lib/logs/console-logger' +import { getUserEntityPermissions } from '../../lib/permissions/utils' const logger = createLogger('SocketPermissions') @@ -10,15 +11,10 @@ export async function verifyWorkspaceMembership( workspaceId: string ): Promise { try { - const membership = await db - .select({ role: workspaceMember.role }) - .from(workspaceMember) - .where(and(eq(workspaceMember.workspaceId, workspaceId), eq(workspaceMember.userId, userId))) - .limit(1) - - return membership.length > 0 ? membership[0].role : null + const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + return permission } catch (error) { - logger.error(`Error verifying workspace membership for ${userId} in ${workspaceId}:`, error) + logger.error(`Error verifying workspace permissions for ${userId} in ${workspaceId}:`, error) return null } } diff --git a/apps/sim/stores/index.ts b/apps/sim/stores/index.ts index 8c265e0345..64e0caa296 100644 --- a/apps/sim/stores/index.ts +++ b/apps/sim/stores/index.ts @@ -40,9 +40,6 @@ async function initializeApplication(): Promise { // Load custom tools from server await useCustomToolsStore.getState().loadCustomTools() - // Load workflows from database (replaced sync system) - await useWorkflowRegistry.getState().loadWorkflows() - // Mark data as initialized only after sync managers have loaded data from DB dataInitialized = true @@ -66,30 +63,7 @@ async function initializeApplication(): Promise { } } -/** - * Extract workflow ID from current URL - * @returns workflow ID if found in URL, null otherwise - */ -function extractWorkflowIdFromUrl(): string | null { - if (typeof window === 'undefined') return null - try { - const pathSegments = window.location.pathname.split('/') - // Check if URL matches pattern /w/{workflowId} - if (pathSegments.length >= 3 && pathSegments[1] === 'w') { - const workflowId = pathSegments[2] - // Basic UUID validation (36 characters, contains hyphens) - if (workflowId && workflowId.length === 36 && workflowId.includes('-')) { - logger.info(`Extracted workflow ID from URL: ${workflowId}`) - return workflowId - } - } - return null - } catch (error) { - logger.warn('Failed to extract workflow ID from URL:', error) - return null - } -} /** * Checks if application is fully initialized diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index 27ffabe4f0..9237eee58d 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -267,7 +267,7 @@ export const useWorkflowRegistry = create()( await fetchWorkflowsFromDB(workspaceId) }, - // Switch to workspace with comprehensive error handling and loading states + // Switch to workspace - just clear state, let sidebar handle workflow loading switchToWorkspace: async (workspaceId: string) => { // Prevent multiple simultaneous transitions if (isWorkspaceTransitioning) { @@ -286,7 +286,7 @@ export const useWorkflowRegistry = create()( // Clear current workspace state resetWorkflowStores() - // Update state + // Update state - sidebar will load workflows when URL changes set({ activeWorkflowId: null, workflows: {}, @@ -294,9 +294,6 @@ export const useWorkflowRegistry = create()( error: null, }) - // Fetch workflows for the new workspace - await fetchWorkflowsFromDB(workspaceId) - logger.info(`Successfully switched to workspace: ${workspaceId}`) } catch (error) { logger.error(`Error switching to workspace ${workspaceId}:`, { error })