mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-14 08:25:03 -05:00
feat(a2a): added a2a protocol (#2784)
* feat(a2a): a2a added * feat(a2a): added a2a protocol * remove migrations * readd migrations * consolidated permissions utils * consolidated tag-input, output select -> combobox, added tags for A2A * cleanup up utils, share same deployed state as other tabs * ack PR comments * more * updated code examples * solely rely on tanstack query to vend data and invalidate query key's, remove custom caching --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
This commit is contained in:
@@ -40,11 +40,15 @@ vi.mock('drizzle-orm', () => drizzleOrmMock)
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import {
|
||||
checkWorkspaceAccess,
|
||||
getManageableWorkspaces,
|
||||
getUserEntityPermissions,
|
||||
getUsersWithPermissions,
|
||||
getWorkspaceById,
|
||||
getWorkspaceWithOwner,
|
||||
hasAdminPermission,
|
||||
hasWorkspaceAdminAccess,
|
||||
workspaceExists,
|
||||
} from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
const mockDb = db as any
|
||||
@@ -610,4 +614,209 @@ describe('Permission Utils', () => {
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWorkspaceById', () => {
|
||||
it.concurrent('should return workspace when it exists', async () => {
|
||||
const chain = createMockChain([{ id: 'workspace123' }])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceById('workspace123')
|
||||
|
||||
expect(result).toEqual({ id: 'workspace123' })
|
||||
})
|
||||
|
||||
it.concurrent('should return null when workspace does not exist', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceById('non-existent')
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it.concurrent('should handle empty workspace ID', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceById('')
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWorkspaceWithOwner', () => {
|
||||
it.concurrent('should return workspace with owner when it exists', async () => {
|
||||
const chain = createMockChain([{ id: 'workspace123', ownerId: 'owner456' }])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceWithOwner('workspace123')
|
||||
|
||||
expect(result).toEqual({ id: 'workspace123', ownerId: 'owner456' })
|
||||
})
|
||||
|
||||
it.concurrent('should return null when workspace does not exist', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceWithOwner('non-existent')
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it.concurrent('should handle workspace with null owner ID', async () => {
|
||||
const chain = createMockChain([{ id: 'workspace123', ownerId: null }])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await getWorkspaceWithOwner('workspace123')
|
||||
|
||||
expect(result).toEqual({ id: 'workspace123', ownerId: null })
|
||||
})
|
||||
})
|
||||
|
||||
describe('workspaceExists', () => {
|
||||
it.concurrent('should return true when workspace exists', async () => {
|
||||
const chain = createMockChain([{ id: 'workspace123' }])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await workspaceExists('workspace123')
|
||||
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it.concurrent('should return false when workspace does not exist', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await workspaceExists('non-existent')
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it.concurrent('should handle empty workspace ID', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await workspaceExists('')
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkWorkspaceAccess', () => {
|
||||
it('should return exists=false when workspace does not exist', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await checkWorkspaceAccess('non-existent', 'user123')
|
||||
|
||||
expect(result).toEqual({
|
||||
exists: false,
|
||||
hasAccess: false,
|
||||
canWrite: false,
|
||||
workspace: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return full access when user is workspace owner', async () => {
|
||||
const chain = createMockChain([{ id: 'workspace123', ownerId: 'user123' }])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', 'user123')
|
||||
|
||||
expect(result).toEqual({
|
||||
exists: true,
|
||||
hasAccess: true,
|
||||
canWrite: true,
|
||||
workspace: { id: 'workspace123', ownerId: 'user123' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return hasAccess=false when user has no permissions', async () => {
|
||||
let callCount = 0
|
||||
mockDb.select.mockImplementation(() => {
|
||||
callCount++
|
||||
if (callCount === 1) {
|
||||
return createMockChain([{ id: 'workspace123', ownerId: 'other-user' }])
|
||||
}
|
||||
return createMockChain([]) // No permissions
|
||||
})
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', 'user123')
|
||||
|
||||
expect(result.exists).toBe(true)
|
||||
expect(result.hasAccess).toBe(false)
|
||||
expect(result.canWrite).toBe(false)
|
||||
})
|
||||
|
||||
it('should return canWrite=true when user has admin permission', async () => {
|
||||
let callCount = 0
|
||||
mockDb.select.mockImplementation(() => {
|
||||
callCount++
|
||||
if (callCount === 1) {
|
||||
return createMockChain([{ id: 'workspace123', ownerId: 'other-user' }])
|
||||
}
|
||||
return createMockChain([{ permissionType: 'admin' }])
|
||||
})
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', 'user123')
|
||||
|
||||
expect(result.exists).toBe(true)
|
||||
expect(result.hasAccess).toBe(true)
|
||||
expect(result.canWrite).toBe(true)
|
||||
})
|
||||
|
||||
it('should return canWrite=true when user has write permission', async () => {
|
||||
let callCount = 0
|
||||
mockDb.select.mockImplementation(() => {
|
||||
callCount++
|
||||
if (callCount === 1) {
|
||||
return createMockChain([{ id: 'workspace123', ownerId: 'other-user' }])
|
||||
}
|
||||
return createMockChain([{ permissionType: 'write' }])
|
||||
})
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', 'user123')
|
||||
|
||||
expect(result.exists).toBe(true)
|
||||
expect(result.hasAccess).toBe(true)
|
||||
expect(result.canWrite).toBe(true)
|
||||
})
|
||||
|
||||
it('should return canWrite=false when user has read permission', async () => {
|
||||
let callCount = 0
|
||||
mockDb.select.mockImplementation(() => {
|
||||
callCount++
|
||||
if (callCount === 1) {
|
||||
return createMockChain([{ id: 'workspace123', ownerId: 'other-user' }])
|
||||
}
|
||||
return createMockChain([{ permissionType: 'read' }])
|
||||
})
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', 'user123')
|
||||
|
||||
expect(result.exists).toBe(true)
|
||||
expect(result.hasAccess).toBe(true)
|
||||
expect(result.canWrite).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle empty user ID', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await checkWorkspaceAccess('workspace123', '')
|
||||
|
||||
expect(result.exists).toBe(false)
|
||||
expect(result.hasAccess).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle empty workspace ID', async () => {
|
||||
const chain = createMockChain([])
|
||||
mockDb.select.mockReturnValue(chain)
|
||||
|
||||
const result = await checkWorkspaceAccess('', 'user123')
|
||||
|
||||
expect(result.exists).toBe(false)
|
||||
expect(result.hasAccess).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,112 @@ import { permissions, type permissionTypeEnum, user, workspace } from '@sim/db/s
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
|
||||
export type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
|
||||
export interface WorkspaceBasic {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface WorkspaceWithOwner {
|
||||
id: string
|
||||
ownerId: string
|
||||
}
|
||||
|
||||
export interface WorkspaceAccess {
|
||||
exists: boolean
|
||||
hasAccess: boolean
|
||||
canWrite: boolean
|
||||
workspace: WorkspaceWithOwner | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a workspace exists
|
||||
*
|
||||
* @param workspaceId - The workspace ID to check
|
||||
* @returns True if the workspace exists, false otherwise
|
||||
*/
|
||||
export async function workspaceExists(workspaceId: string): Promise<boolean> {
|
||||
const [ws] = await db
|
||||
.select({ id: workspace.id })
|
||||
.from(workspace)
|
||||
.where(eq(workspace.id, workspaceId))
|
||||
.limit(1)
|
||||
|
||||
return !!ws
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a workspace by ID for existence check
|
||||
*
|
||||
* @param workspaceId - The workspace ID to look up
|
||||
* @returns The workspace if found, null otherwise
|
||||
*/
|
||||
export async function getWorkspaceById(workspaceId: string): Promise<WorkspaceBasic | null> {
|
||||
const exists = await workspaceExists(workspaceId)
|
||||
return exists ? { id: workspaceId } : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a workspace with owner info by ID
|
||||
*
|
||||
* @param workspaceId - The workspace ID to look up
|
||||
* @returns The workspace with owner info if found, null otherwise
|
||||
*/
|
||||
export async function getWorkspaceWithOwner(
|
||||
workspaceId: string
|
||||
): Promise<WorkspaceWithOwner | null> {
|
||||
const [ws] = await db
|
||||
.select({ id: workspace.id, ownerId: workspace.ownerId })
|
||||
.from(workspace)
|
||||
.where(eq(workspace.id, workspaceId))
|
||||
.limit(1)
|
||||
|
||||
return ws || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check workspace access for a user
|
||||
*
|
||||
* Verifies the workspace exists and the user has access to it.
|
||||
* Returns access level (read/write) based on ownership and permissions.
|
||||
*
|
||||
* @param workspaceId - The workspace ID to check
|
||||
* @param userId - The user ID to check access for
|
||||
* @returns WorkspaceAccess object with exists, hasAccess, canWrite, and workspace data
|
||||
*/
|
||||
export async function checkWorkspaceAccess(
|
||||
workspaceId: string,
|
||||
userId: string
|
||||
): Promise<WorkspaceAccess> {
|
||||
const ws = await getWorkspaceWithOwner(workspaceId)
|
||||
|
||||
if (!ws) {
|
||||
return { exists: false, hasAccess: false, canWrite: false, workspace: null }
|
||||
}
|
||||
|
||||
if (ws.ownerId === userId) {
|
||||
return { exists: true, hasAccess: true, canWrite: true, workspace: ws }
|
||||
}
|
||||
|
||||
const [permissionRow] = await db
|
||||
.select({ permissionType: permissions.permissionType })
|
||||
.from(permissions)
|
||||
.where(
|
||||
and(
|
||||
eq(permissions.userId, userId),
|
||||
eq(permissions.entityType, 'workspace'),
|
||||
eq(permissions.entityId, workspaceId)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (!permissionRow) {
|
||||
return { exists: true, hasAccess: false, canWrite: false, workspace: ws }
|
||||
}
|
||||
|
||||
const canWrite =
|
||||
permissionRow.permissionType === 'write' || permissionRow.permissionType === 'admin'
|
||||
|
||||
return { exists: true, hasAccess: true, canWrite, workspace: ws }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest permission level a user has for a specific entity
|
||||
@@ -111,17 +217,13 @@ export async function hasWorkspaceAdminAccess(
|
||||
userId: string,
|
||||
workspaceId: string
|
||||
): Promise<boolean> {
|
||||
const workspaceResult = await db
|
||||
.select({ ownerId: workspace.ownerId })
|
||||
.from(workspace)
|
||||
.where(eq(workspace.id, workspaceId))
|
||||
.limit(1)
|
||||
const ws = await getWorkspaceWithOwner(workspaceId)
|
||||
|
||||
if (workspaceResult.length === 0) {
|
||||
if (!ws) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (workspaceResult[0].ownerId === userId) {
|
||||
if (ws.ownerId === userId) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user