Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 56 additions & 10 deletions apps/docs/openapi-v2-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -15997,6 +15997,16 @@
"disableKnowledgeBaseExport": {
"type": "boolean",
"description": "Prevent downloading a whole knowledge base as an archive."
},
"deniedPartialAccessProjectIssues": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "Issues in the listed Projects are unavailable to teammates without access to every active environment."
}
},
"required": [
Expand Down Expand Up @@ -16041,7 +16051,8 @@
"disableToolAutoApproval",
"hideSandboxesTab",
"disableOAuthAppAccess",
"disableKnowledgeBaseExport"
"disableKnowledgeBaseExport",
"deniedPartialAccessProjectIssues"
],
"additionalProperties": false,
"description": "Resolved restrictions. True disables a boolean capability; null allowlists permit every value and empty allowlists permit none."
Expand Down Expand Up @@ -16171,7 +16182,8 @@
"disableToolAutoApproval": false,
"hideSandboxesTab": false,
"disableOAuthAppAccess": false,
"disableKnowledgeBaseExport": false
"disableKnowledgeBaseExport": false,
"deniedPartialAccessProjectIssues": []
},
"isDefault": false,
"membershipMode": "inherit",
Expand Down Expand Up @@ -16246,7 +16258,8 @@
"disableToolAutoApproval": false,
"hideSandboxesTab": false,
"disableOAuthAppAccess": false,
"disableKnowledgeBaseExport": false
"disableKnowledgeBaseExport": false,
"deniedPartialAccessProjectIssues": []
},
"isDefault": false,
"membershipMode": "inherit",
Expand Down Expand Up @@ -16500,6 +16513,15 @@
"disableKnowledgeBaseExport": {
"type": "boolean",
"description": "Prevent downloading a whole knowledge base as an archive."
},
"deniedPartialAccessProjectIssues": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "Issues in the listed Projects are unavailable to teammates without access to every active environment."
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -16591,7 +16613,8 @@
"disableToolAutoApproval": false,
"hideSandboxesTab": false,
"disableOAuthAppAccess": false,
"disableKnowledgeBaseExport": false
"disableKnowledgeBaseExport": false,
"deniedPartialAccessProjectIssues": []
},
"isDefault": false,
"membershipMode": "inherit",
Expand Down Expand Up @@ -16664,7 +16687,8 @@
"disableToolAutoApproval": false,
"hideSandboxesTab": false,
"disableOAuthAppAccess": false,
"disableKnowledgeBaseExport": false
"disableKnowledgeBaseExport": false,
"deniedPartialAccessProjectIssues": []
},
"isDefault": false,
"membershipMode": "inherit",
Expand Down Expand Up @@ -16925,6 +16949,15 @@
"disableKnowledgeBaseExport": {
"type": "boolean",
"description": "Prevent downloading a whole knowledge base as an archive."
},
"deniedPartialAccessProjectIssues": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "Issues in the listed Projects are unavailable to teammates without access to every active environment."
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -18201,6 +18234,16 @@
"disableKnowledgeBaseExport": {
"type": "boolean",
"description": "Prevent downloading a whole knowledge base as an archive."
},
"deniedPartialAccessProjectIssues": {
"default": [],
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "Issues in the listed Projects are unavailable to teammates without access to every active environment."
}
},
"required": [
Expand Down Expand Up @@ -18245,7 +18288,8 @@
"disableToolAutoApproval",
"hideSandboxesTab",
"disableOAuthAppAccess",
"disableKnowledgeBaseExport"
"disableKnowledgeBaseExport",
"deniedPartialAccessProjectIssues"
],
"additionalProperties": false
},
Expand Down Expand Up @@ -20730,7 +20774,7 @@
"description": "Access request being reviewed."
},
"changes": {
"maxItems": 42,
"maxItems": 43,
"type": "array",
"items": {
"type": "object",
Expand Down Expand Up @@ -20779,7 +20823,8 @@
"disableToolAutoApproval",
"hideSandboxesTab",
"disableOAuthAppAccess",
"disableKnowledgeBaseExport"
"disableKnowledgeBaseExport",
"deniedPartialAccessProjectIssues"
],
"description": "Permission restriction changed by approval."
},
Expand Down Expand Up @@ -21291,7 +21336,7 @@
"description": "Access request being reviewed."
},
"changes": {
"maxItems": 42,
"maxItems": 43,
"type": "array",
"items": {
"type": "object",
Expand Down Expand Up @@ -21340,7 +21385,8 @@
"disableToolAutoApproval",
"hideSandboxesTab",
"disableOAuthAppAccess",
"disableKnowledgeBaseExport"
"disableKnowledgeBaseExport",
"deniedPartialAccessProjectIssues"
],
"description": "Permission restriction changed by approval."
},
Expand Down
45 changes: 45 additions & 0 deletions apps/sim/app/api/projects/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
archiveProjectContract,
getProjectContract,
renameProjectContract,
} from '@/lib/api/contracts/projects'
import {
defineInternalJsonRoute,
internalOrchestrationErrorPolicy,
internalRateLimits,
internalSessionAuth,
} from '@/lib/api/server/routes'
import {
archiveProject,
getProject,
projectOperations,
renameProject,
} from '@/lib/projects/application'

export const GET = defineInternalJsonRoute({
contract: getProjectContract,
auth: internalSessionAuth,
operation: projectOperations.get,
rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ params, query }) => ({ projectId: params.id, ...query }),
useCase: getProject,
})
export const PATCH = defineInternalJsonRoute({
contract: renameProjectContract,
auth: internalSessionAuth,
operation: projectOperations.rename,
rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ params, body }) => ({ projectId: params.id, name: body.name }),
useCase: renameProject,
})
export const DELETE = defineInternalJsonRoute({
contract: archiveProjectContract,
auth: internalSessionAuth,
operation: projectOperations.archive,
rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ params }) => ({ projectId: params.id }),
useCase: archiveProject,
})
18 changes: 18 additions & 0 deletions apps/sim/app/api/projects/by-workspace/[workspaceId]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getWorkspaceProjectContract } from '@/lib/api/contracts/projects'
import {
defineInternalJsonRoute,
internalOrchestrationErrorPolicy,
internalRateLimits,
internalSessionAuth,
} from '@/lib/api/server/routes'
import { getWorkspaceProject, projectOperations } from '@/lib/projects/application'

export const GET = defineInternalJsonRoute({
contract: getWorkspaceProjectContract,
auth: internalSessionAuth,
operation: projectOperations.get,
rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ params }) => params,
useCase: getWorkspaceProject,
})
28 changes: 28 additions & 0 deletions apps/sim/app/api/projects/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createProjectContract, listProjectsContract } from '@/lib/api/contracts/projects'
import {
defineInternalJsonRoute,
internalOrchestrationErrorPolicy,
internalRateLimits,
internalSessionAuth,
} from '@/lib/api/server/routes'
import { createProject, listProjects, projectOperations } from '@/lib/projects/application'

export const GET = defineInternalJsonRoute({
contract: listProjectsContract,
auth: internalSessionAuth,
operation: projectOperations.list,
rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ query }) => query,
useCase: listProjects,
})

export const POST = defineInternalJsonRoute({
contract: createProjectContract,
auth: internalSessionAuth,
operation: projectOperations.create,
rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }),
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ body }) => body,
useCase: createProject,
})
3 changes: 3 additions & 0 deletions apps/sim/app/api/workflows/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ describe('Workflows API Route - POST ordering', () => {

beforeEach(() => {
resetDbChainMock()
queueTableRows(schemaMock.workspace, [
{ archivedAt: null, forkSyncNewWorkflowsExcluded: false },
])

vi.stubGlobal('crypto', {
randomUUID: vi.fn().mockReturnValue('workflow-new-id'),
Expand Down
11 changes: 11 additions & 0 deletions apps/sim/ee/access-control/components/group-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { getAllBlocks } from '@/blocks'
import { useCustomBlockOverlayVersion } from '@/blocks/custom/client-overlay'
import type { BlockConfig } from '@/blocks/types'
import { CONNECTOR_META_REGISTRY } from '@/connectors/registry'
import { ProjectIssueRestrictions } from '@/ee/access-control/components/project-issue-restrictions'
import { WorkspaceSelect } from '@/ee/access-control/components/workspace-select'
import {
type PermissionGroup,
Expand Down Expand Up @@ -1783,6 +1784,16 @@ export function GroupDetail({

{configTab === 'platform' && (
<div className='flex flex-col gap-7'>
<ProjectIssueRestrictions
organizationId={organizationId}
value={editingConfig.deniedPartialAccessProjectIssues}
onChange={(value) =>
setEditingConfig((previous) => ({
...previous,
deniedPartialAccessProjectIssues: value,
}))
}
/>
<div className='flex items-center gap-2'>
<ChipInput
icon={Search}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import { Checkbox, Chip } from '@sim/emcn'
import { useProjects } from '@/hooks/queries/projects'

interface ProjectIssueRestrictionsProps {
organizationId: string
value: string[]
onChange: (value: string[]) => void
}

/** Project choices use the authorized inventory; policy remains enforced at the application boundary. */
export function ProjectIssueRestrictions({
organizationId,
value,
onChange,
}: ProjectIssueRestrictionsProps) {
const projects = useProjects(organizationId)
const selected = new Set(value)
return (
<div className='flex flex-col gap-2'>
<p className='text-small'>Restrict Issues for partial-access teammates</p>
<p className='text-[var(--text-muted)] text-small'>
For selected Projects, teammates governed by this group need access to every active
environment to use Issues.
</p>
{projects.isPending && <p className='text-small'>Loading Projects…</p>}
{projects.error && (
<p className='text-[var(--text-error)] text-small'>{projects.error.message}</p>
)}
{projects.data?.pages
.flatMap((page) => page.projects)
.map((project) => (
<label
htmlFor={`project-issues-${project.id}`}
key={project.id}
className='flex items-center gap-2'
>
<Checkbox
id={`project-issues-${project.id}`}
checked={selected.has(project.id)}
onCheckedChange={(checked) =>
onChange(
checked === true
? [...new Set([...value, project.id])]
: value.filter((id) => id !== project.id)
)
}
/>
<span className='text-small'>{project.name}</span>
</label>
))}
{projects.hasNextPage && (
<Chip disabled={projects.isFetchingNextPage} onClick={() => void projects.fetchNextPage()}>
Load more
</Chip>
)}
</div>
)
}
Loading
Loading