Merge branch 'main' into andrew/update-members-table-remove-from-project
This commit is contained in:
+2
-1
@@ -38,7 +38,8 @@
|
||||
"postinstall": "husky install",
|
||||
"cm": "cz",
|
||||
"eslint:inspect": "eslint-config-inspector",
|
||||
"eslint:projectwide": "node ./utils/eslint-projectwide.mjs"
|
||||
"eslint:projectwide": "node ./utils/eslint-projectwide.mjs",
|
||||
"npkill": "npkill"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/config-inspector": "^0.4.10",
|
||||
|
||||
@@ -4012,8 +4012,11 @@ export type UserProjectCollection = {
|
||||
export type UserProjectsFilter = {
|
||||
/** Only include projects where user has the specified roles */
|
||||
onlyWithRoles?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
/** Only include personal projects (not in any workspace) */
|
||||
personalOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only include projects in the specified workspace */
|
||||
workspaceId?: InputMaybe<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
@@ -4698,6 +4701,7 @@ export type WorkspacePlan = {
|
||||
name: WorkspacePlans;
|
||||
paymentMethod: WorkspacePaymentMethod;
|
||||
status: WorkspacePlanStatuses;
|
||||
usage: WorkspacePlanUsage;
|
||||
};
|
||||
|
||||
export type WorkspacePlanPrice = {
|
||||
@@ -4716,6 +4720,12 @@ export enum WorkspacePlanStatuses {
|
||||
Valid = 'valid'
|
||||
}
|
||||
|
||||
export type WorkspacePlanUsage = {
|
||||
__typename?: 'WorkspacePlanUsage';
|
||||
modelCount: Scalars['Int']['output'];
|
||||
projectCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export enum WorkspacePlans {
|
||||
Academia = 'academia',
|
||||
Business = 'business',
|
||||
@@ -4789,6 +4799,8 @@ export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
export type WorkspaceProjectsFilter = {
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only return workspace projects that the active user has an explicit project role in */
|
||||
withProjectRoleOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectsUpdatedMessage = {
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
"vue-tippy": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/cli": "^5.0.5",
|
||||
"@graphql-codegen/client-preset": "^4.3.0",
|
||||
"@nuxt/eslint": "^0.3.13",
|
||||
"@nuxtjs/tailwindcss": "^6.7.0",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@types/apollo-upload-client": "^17.0.1",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
|
||||
@@ -42,11 +42,8 @@ import { workspaceRoute } from '~/lib/common/helpers/route'
|
||||
graphql(`
|
||||
fragment ProjectPageProjectHeader on Project {
|
||||
id
|
||||
role
|
||||
name
|
||||
description
|
||||
visibility
|
||||
allowPublicComments
|
||||
workspace {
|
||||
id
|
||||
slug
|
||||
|
||||
@@ -62,7 +62,6 @@ import { graphql } from '~~/lib/common/generated/gql'
|
||||
graphql(`
|
||||
fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {
|
||||
id
|
||||
role
|
||||
name
|
||||
description
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<template>
|
||||
<LayoutDialog v-model:open="isOpen" max-width="sm">
|
||||
<template #header>Manage Project</template>
|
||||
<div class="flex flex-col text-foreground"></div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { ProjectPageTeamDialogFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
import type { OpenSectionType } from '~~/lib/projects/helpers/components'
|
||||
|
||||
graphql(`
|
||||
fragment ProjectPageTeamDialog on Project {
|
||||
id
|
||||
name
|
||||
role
|
||||
allowPublicComments
|
||||
visibility
|
||||
team {
|
||||
id
|
||||
role
|
||||
user {
|
||||
...LimitedUserAvatar
|
||||
role
|
||||
}
|
||||
}
|
||||
invitedTeam {
|
||||
id
|
||||
title
|
||||
inviteId
|
||||
role
|
||||
user {
|
||||
...LimitedUserAvatar
|
||||
role
|
||||
}
|
||||
}
|
||||
...ProjectsPageTeamDialogManagePermissions_Project
|
||||
}
|
||||
`)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:open', v: boolean): void
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
project: ProjectPageTeamDialogFragment
|
||||
openSection?: OpenSectionType
|
||||
}>()
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => props.open,
|
||||
set: (newVal) => emit('update:open', newVal)
|
||||
})
|
||||
</script>
|
||||
@@ -103,7 +103,7 @@ const {
|
||||
filter: {
|
||||
search: (search.value || '').trim() || null,
|
||||
onlyWithRoles: selectedRoles.value?.length ? selectedRoles.value : null,
|
||||
workspaceId: isWorkspaceNewPlansEnabled ? (null as Nullable<string>) : undefined
|
||||
personalOnly: isWorkspaceNewPlansEnabled.value
|
||||
},
|
||||
cursor: null as Nullable<string>
|
||||
}))
|
||||
|
||||
@@ -110,7 +110,6 @@ graphql(`
|
||||
cursor
|
||||
items {
|
||||
...ProjectsMoveToWorkspaceDialog_Project
|
||||
role
|
||||
workspace {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -29,10 +29,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<FormCheckbox
|
||||
v-model="enableDomainDiscoverabilityModel"
|
||||
name="enableDomainDiscoverability"
|
||||
:label="`Allow users with the @${verifiedDomain} domain to request to join workspace`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 mt-4 w-full md:max-w-96">
|
||||
<FormButton size="lg" submit full-width>
|
||||
{{ nextButtonText }}
|
||||
</FormButton>
|
||||
<FormButton size="lg" submit full-width>{{ nextButtonText }}</FormButton>
|
||||
<FormButton color="subtle" size="lg" full-width @click.stop="goToPreviousStep">
|
||||
Back
|
||||
</FormButton>
|
||||
@@ -47,11 +53,14 @@ import { PlusIcon } from '@heroicons/vue/24/outline'
|
||||
import { isEmailOrEmpty } from '~~/lib/common/helpers/validation'
|
||||
import { useForm, useFieldArray } from 'vee-validate'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
import { useVerifiedUserEmailDomains } from '~/lib/workspaces/composables/security'
|
||||
import { isUndefined } from 'lodash-es'
|
||||
|
||||
interface InviteForm {
|
||||
fields: string[]
|
||||
}
|
||||
|
||||
const { domains } = useVerifiedUserEmailDomains()
|
||||
const { goToNextStep, goToPreviousStep, state } = useWorkspacesWizard()
|
||||
const mixpanel = useMixpanel()
|
||||
const { handleSubmit } = useForm<InviteForm>({
|
||||
@@ -61,10 +70,26 @@ const { handleSubmit } = useForm<InviteForm>({
|
||||
})
|
||||
const { fields, push } = useFieldArray<string>('fields')
|
||||
|
||||
const nextButtonText = computed(() =>
|
||||
fields.value.filter((field) => !!field.value).length > 0 ? 'Continue' : 'Skip'
|
||||
const enableDomainDiscoverabilityModel = ref<true | undefined>(
|
||||
!isUndefined(state.value.enableDomainDiscoverabilityForDomain)
|
||||
? state.value.enableDomainDiscoverabilityForDomain !== null
|
||||
? true
|
||||
: undefined
|
||||
: true
|
||||
)
|
||||
|
||||
const nextButtonText = computed(() =>
|
||||
fields.value.filter((field) => !!field.value).length > 0
|
||||
? 'Continue'
|
||||
: 'Continue without inviting'
|
||||
)
|
||||
|
||||
const verifiedDomain = computed(() => {
|
||||
// only support enabling domain discoverability if there's one verified unblocked domain
|
||||
if (domains.value.length !== 1) return undefined
|
||||
return domains.value[0]
|
||||
})
|
||||
|
||||
const onAddInvite = () => {
|
||||
push('')
|
||||
}
|
||||
@@ -76,6 +101,12 @@ const onSubmit = handleSubmit(() => {
|
||||
|
||||
state.value.invites = validInvites
|
||||
|
||||
if (enableDomainDiscoverabilityModel.value && verifiedDomain.value) {
|
||||
state.value.enableDomainDiscoverabilityForDomain = verifiedDomain.value
|
||||
} else {
|
||||
state.value.enableDomainDiscoverabilityForDomain = null
|
||||
}
|
||||
|
||||
mixpanel.track('Workspace Invites Step Completed', {
|
||||
inviteCount: validInvites
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ export const activeUserQuery = graphql(`
|
||||
email
|
||||
emails {
|
||||
id
|
||||
email
|
||||
verified
|
||||
}
|
||||
company
|
||||
|
||||
@@ -65,7 +65,7 @@ type Documents = {
|
||||
"\n fragment ProjectModelPageDialogMoveToVersion on Version {\n id\n message\n }\n": typeof types.ProjectModelPageDialogMoveToVersionFragmentDoc,
|
||||
"\n fragment ProjectsModelPageEmbed_Project on Project {\n id\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": typeof types.ProjectsModelPageEmbed_ProjectFragmentDoc,
|
||||
"\n fragment ProjectModelPageVersionsCardVersion on Version {\n id\n message\n authorUser {\n ...LimitedUserAvatar\n }\n createdAt\n previewUrl\n sourceApplication\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectModelPageDialogDeleteVersion\n ...ProjectModelPageDialogMoveToVersion\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": typeof types.ProjectModelPageVersionsCardVersionFragmentDoc,
|
||||
"\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n": typeof types.ProjectPageProjectHeaderFragmentDoc,
|
||||
"\n fragment ProjectPageProjectHeader on Project {\n id\n name\n description\n workspace {\n id\n slug\n name\n logo\n }\n }\n": typeof types.ProjectPageProjectHeaderFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevision on AutomationRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n type\n model {\n id\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctions_AutomationFragmentDoc,
|
||||
@@ -91,8 +91,7 @@ type Documents = {
|
||||
"\n fragment ProjectPageSettingsGeneralBlockDelete_Project on Project {\n ...ProjectsDeleteDialog_Project\n }\n": typeof types.ProjectPageSettingsGeneralBlockDelete_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockDiscussions_Project on Project {\n id\n visibility\n allowPublicComments\n }\n": typeof types.ProjectPageSettingsGeneralBlockDiscussions_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockLeave_Project on Project {\n id\n name\n role\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n workspace {\n id\n }\n }\n": typeof types.ProjectPageSettingsGeneralBlockLeave_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n role\n name\n description\n }\n": typeof types.ProjectPageSettingsGeneralBlockProjectInfo_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": typeof types.ProjectPageTeamDialogFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n name\n description\n }\n": typeof types.ProjectPageSettingsGeneralBlockProjectInfo_ProjectFragmentDoc,
|
||||
"\n fragment ProjectsPageTeamDialogManagePermissions_Project on Project {\n id\n visibility\n role\n }\n": typeof types.ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc,
|
||||
"\n fragment ProjectsDashboard_UserProjectCollection on UserProjectCollection {\n numberOfHidden\n }\n": typeof types.ProjectsDashboard_UserProjectCollectionFragmentDoc,
|
||||
"\n fragment ProjectsDashboardFilledProject on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": typeof types.ProjectsDashboardFilledProjectFragmentDoc,
|
||||
@@ -135,7 +134,7 @@ type Documents = {
|
||||
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": typeof types.ViewerCommentsListItemFragmentDoc,
|
||||
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": typeof types.ViewerModelVersionCardItemFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_Workspace on Workspace {\n id\n ...ProjectsMoveToWorkspaceDialog_Workspace\n projects {\n items {\n id\n }\n }\n }\n": typeof types.MoveProjectsDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n": typeof types.MoveProjectsDialog_UserFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n workspace {\n id\n }\n }\n }\n }\n": typeof types.MoveProjectsDialog_UserFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n": typeof types.WorkspaceProjectList_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": typeof types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...BillingAlert_Workspace\n slug\n readOnly\n }\n": typeof types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
@@ -147,7 +146,7 @@ type Documents = {
|
||||
"\n fragment WorkspaceSidebar_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n slug\n plan {\n status\n }\n }\n": typeof types.WorkspaceSidebar_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceWizard_Workspace on Workspace {\n creationState {\n completed\n state\n }\n name\n slug\n }\n": typeof types.WorkspaceWizard_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
|
||||
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": typeof types.ActiveUserMainMetadataDocument,
|
||||
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n email\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": typeof types.ActiveUserMainMetadataDocument,
|
||||
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.CreateOnboardingProjectDocument,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n": typeof types.FullPermissionCheckResultFragmentDoc,
|
||||
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": typeof types.FinishOnboardingDocument,
|
||||
@@ -209,6 +208,7 @@ type Documents = {
|
||||
"\n query NavigationInvites {\n activeUser {\n id\n projectInvites {\n ...HeaderNavNotificationsProjectInvite_PendingStreamCollaborator\n }\n workspaceInvites {\n ...HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator\n }\n }\n }\n": typeof types.NavigationInvitesDocument,
|
||||
"\n fragment ProjectPageTeamInternals_Project on Project {\n id\n role\n invitedTeam {\n id\n title\n role\n inviteId\n user {\n role\n ...LimitedUserAvatar\n }\n }\n team {\n role\n seatType\n user {\n id\n role\n ...LimitedUserAvatar\n }\n }\n }\n": typeof types.ProjectPageTeamInternals_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageTeamInternals_Workspace on Workspace {\n id\n team {\n items {\n id\n role\n user {\n id\n }\n }\n }\n }\n": typeof types.ProjectPageTeamInternals_WorkspaceFragmentDoc,
|
||||
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": typeof types.ProjectPageTeamDialogFragmentDoc,
|
||||
"\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n id\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": typeof types.ProjectDashboardItemNoModelsFragmentDoc,
|
||||
"\n fragment ProjectDashboardItem on Project {\n id\n ...ProjectDashboardItemNoModels\n models(limit: 4) {\n totalCount\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n workspace {\n id\n slug\n name\n logo\n readOnly\n }\n pendingImportedModels(limit: 4) {\n ...PendingFileUpload\n }\n }\n": typeof types.ProjectDashboardItemFragmentDoc,
|
||||
"\n fragment PendingFileUpload on FileUpload {\n id\n projectId\n modelName\n convertedStatus\n convertedMessage\n uploadDate\n convertedLastUpdate\n fileType\n fileName\n }\n": typeof types.PendingFileUploadFragmentDoc,
|
||||
@@ -315,7 +315,7 @@ type Documents = {
|
||||
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter, limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersSearchDocument,
|
||||
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesInvitesSearchDocument,
|
||||
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": typeof types.SettingsWorkspacesProjectsDocument,
|
||||
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n": typeof types.SettingsWorkspaceSecurityDocument,
|
||||
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n": typeof types.SettingsWorkspaceSecurityDocument,
|
||||
"\n fragment AppAuthorAvatar on AppAuthor {\n id\n name\n avatar\n }\n": typeof types.AppAuthorAvatarFragmentDoc,
|
||||
"\n fragment LimitedUserAvatar on LimitedUser {\n id\n name\n avatar\n }\n": typeof types.LimitedUserAvatarFragmentDoc,
|
||||
"\n fragment ActiveUserAvatar on User {\n id\n name\n avatar\n }\n": typeof types.ActiveUserAvatarFragmentDoc,
|
||||
@@ -411,7 +411,7 @@ type Documents = {
|
||||
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": typeof types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n\n fragment SettingsWorkspacesSecurity_User on User {\n id\n emails {\n id\n email\n verified\n }\n }\n": typeof types.SettingsWorkspacesSecurity_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n": typeof types.SettingsWorkspacesSecurity_WorkspaceFragmentDoc,
|
||||
};
|
||||
const documents: Documents = {
|
||||
"\n fragment AuthLoginWithEmailBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n email\n user {\n id\n }\n }\n": types.AuthLoginWithEmailBlock_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
@@ -465,7 +465,7 @@ const documents: Documents = {
|
||||
"\n fragment ProjectModelPageDialogMoveToVersion on Version {\n id\n message\n }\n": types.ProjectModelPageDialogMoveToVersionFragmentDoc,
|
||||
"\n fragment ProjectsModelPageEmbed_Project on Project {\n id\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": types.ProjectsModelPageEmbed_ProjectFragmentDoc,
|
||||
"\n fragment ProjectModelPageVersionsCardVersion on Version {\n id\n message\n authorUser {\n ...LimitedUserAvatar\n }\n createdAt\n previewUrl\n sourceApplication\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectModelPageDialogDeleteVersion\n ...ProjectModelPageDialogMoveToVersion\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": types.ProjectModelPageVersionsCardVersionFragmentDoc,
|
||||
"\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n": types.ProjectPageProjectHeaderFragmentDoc,
|
||||
"\n fragment ProjectPageProjectHeader on Project {\n id\n name\n description\n workspace {\n id\n slug\n name\n logo\n }\n }\n": types.ProjectPageProjectHeaderFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevision on AutomationRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n type\n model {\n id\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": types.ProjectPageAutomationFunctions_AutomationFragmentDoc,
|
||||
@@ -491,8 +491,7 @@ const documents: Documents = {
|
||||
"\n fragment ProjectPageSettingsGeneralBlockDelete_Project on Project {\n ...ProjectsDeleteDialog_Project\n }\n": types.ProjectPageSettingsGeneralBlockDelete_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockDiscussions_Project on Project {\n id\n visibility\n allowPublicComments\n }\n": types.ProjectPageSettingsGeneralBlockDiscussions_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockLeave_Project on Project {\n id\n name\n role\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n workspace {\n id\n }\n }\n": types.ProjectPageSettingsGeneralBlockLeave_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n role\n name\n description\n }\n": types.ProjectPageSettingsGeneralBlockProjectInfo_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": types.ProjectPageTeamDialogFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n name\n description\n }\n": types.ProjectPageSettingsGeneralBlockProjectInfo_ProjectFragmentDoc,
|
||||
"\n fragment ProjectsPageTeamDialogManagePermissions_Project on Project {\n id\n visibility\n role\n }\n": types.ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc,
|
||||
"\n fragment ProjectsDashboard_UserProjectCollection on UserProjectCollection {\n numberOfHidden\n }\n": types.ProjectsDashboard_UserProjectCollectionFragmentDoc,
|
||||
"\n fragment ProjectsDashboardFilledProject on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": types.ProjectsDashboardFilledProjectFragmentDoc,
|
||||
@@ -535,7 +534,7 @@ const documents: Documents = {
|
||||
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": types.ViewerCommentsListItemFragmentDoc,
|
||||
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_Workspace on Workspace {\n id\n ...ProjectsMoveToWorkspaceDialog_Workspace\n projects {\n items {\n id\n }\n }\n }\n": types.MoveProjectsDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n": types.MoveProjectsDialog_UserFragmentDoc,
|
||||
"\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n workspace {\n id\n }\n }\n }\n }\n": types.MoveProjectsDialog_UserFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n": types.WorkspaceProjectList_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...BillingAlert_Workspace\n slug\n readOnly\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
@@ -547,7 +546,7 @@ const documents: Documents = {
|
||||
"\n fragment WorkspaceSidebar_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n slug\n plan {\n status\n }\n }\n": types.WorkspaceSidebar_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceWizard_Workspace on Workspace {\n creationState {\n completed\n state\n }\n name\n slug\n }\n": types.WorkspaceWizard_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
|
||||
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": types.ActiveUserMainMetadataDocument,
|
||||
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n email\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": types.ActiveUserMainMetadataDocument,
|
||||
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": types.CreateOnboardingProjectDocument,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n": types.FullPermissionCheckResultFragmentDoc,
|
||||
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": types.FinishOnboardingDocument,
|
||||
@@ -609,6 +608,7 @@ const documents: Documents = {
|
||||
"\n query NavigationInvites {\n activeUser {\n id\n projectInvites {\n ...HeaderNavNotificationsProjectInvite_PendingStreamCollaborator\n }\n workspaceInvites {\n ...HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator\n }\n }\n }\n": types.NavigationInvitesDocument,
|
||||
"\n fragment ProjectPageTeamInternals_Project on Project {\n id\n role\n invitedTeam {\n id\n title\n role\n inviteId\n user {\n role\n ...LimitedUserAvatar\n }\n }\n team {\n role\n seatType\n user {\n id\n role\n ...LimitedUserAvatar\n }\n }\n }\n": types.ProjectPageTeamInternals_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageTeamInternals_Workspace on Workspace {\n id\n team {\n items {\n id\n role\n user {\n id\n }\n }\n }\n }\n": types.ProjectPageTeamInternals_WorkspaceFragmentDoc,
|
||||
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": types.ProjectPageTeamDialogFragmentDoc,
|
||||
"\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n id\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": types.ProjectDashboardItemNoModelsFragmentDoc,
|
||||
"\n fragment ProjectDashboardItem on Project {\n id\n ...ProjectDashboardItemNoModels\n models(limit: 4) {\n totalCount\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n workspace {\n id\n slug\n name\n logo\n readOnly\n }\n pendingImportedModels(limit: 4) {\n ...PendingFileUpload\n }\n }\n": types.ProjectDashboardItemFragmentDoc,
|
||||
"\n fragment PendingFileUpload on FileUpload {\n id\n projectId\n modelName\n convertedStatus\n convertedMessage\n uploadDate\n convertedLastUpdate\n fileType\n fileName\n }\n": types.PendingFileUploadFragmentDoc,
|
||||
@@ -715,7 +715,7 @@ const documents: Documents = {
|
||||
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter, limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersSearchDocument,
|
||||
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesInvitesSearchDocument,
|
||||
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": types.SettingsWorkspacesProjectsDocument,
|
||||
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n": types.SettingsWorkspaceSecurityDocument,
|
||||
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n": types.SettingsWorkspaceSecurityDocument,
|
||||
"\n fragment AppAuthorAvatar on AppAuthor {\n id\n name\n avatar\n }\n": types.AppAuthorAvatarFragmentDoc,
|
||||
"\n fragment LimitedUserAvatar on LimitedUser {\n id\n name\n avatar\n }\n": types.LimitedUserAvatarFragmentDoc,
|
||||
"\n fragment ActiveUserAvatar on User {\n id\n name\n avatar\n }\n": types.ActiveUserAvatarFragmentDoc,
|
||||
@@ -811,7 +811,7 @@ const documents: Documents = {
|
||||
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n\n fragment SettingsWorkspacesSecurity_User on User {\n id\n emails {\n id\n email\n verified\n }\n }\n": types.SettingsWorkspacesSecurity_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n": types.SettingsWorkspacesSecurity_WorkspaceFragmentDoc,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1035,7 +1035,7 @@ export function graphql(source: "\n fragment ProjectModelPageVersionsCardVersio
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n"): (typeof documents)["\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageProjectHeader on Project {\n id\n name\n description\n workspace {\n id\n slug\n name\n logo\n }\n }\n"): (typeof documents)["\n fragment ProjectPageProjectHeader on Project {\n id\n name\n description\n workspace {\n id\n slug\n name\n logo\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1139,11 +1139,7 @@ export function graphql(source: "\n fragment ProjectPageSettingsGeneralBlockLea
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n role\n name\n description\n }\n"): (typeof documents)["\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n role\n name\n description\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n"): (typeof documents)["\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n name\n description\n }\n"): (typeof documents)["\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n name\n description\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1315,7 +1311,7 @@ export function graphql(source: "\n fragment MoveProjectsDialog_Workspace on Wo
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n workspace {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n fragment MoveProjectsDialog_User on User {\n projects(cursor: $cursor, filter: $filter, limit: 10) {\n totalCount\n cursor\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n workspace {\n id\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1363,7 +1359,7 @@ export function graphql(source: "\n fragment WorkspaceWizardStepRegion_ServerIn
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n"): (typeof documents)["\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n email\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n"): (typeof documents)["\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n email\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1608,6 +1604,10 @@ export function graphql(source: "\n fragment ProjectPageTeamInternals_Project o
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageTeamInternals_Workspace on Workspace {\n id\n team {\n items {\n id\n role\n user {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageTeamInternals_Workspace on Workspace {\n id\n team {\n items {\n id\n role\n user {\n id\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n"): (typeof documents)["\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2035,7 +2035,7 @@ export function graphql(source: "\n query SettingsWorkspacesProjects(\n $slu
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n"): (typeof documents)["\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n"];
|
||||
export function graphql(source: "\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2419,7 +2419,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesRegions_ServerIn
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n\n fragment SettingsWorkspacesSecurity_User on User {\n id\n emails {\n id\n email\n verified\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n\n fragment SettingsWorkspacesSecurity_User on User {\n id\n emails {\n id\n email\n verified\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,34 @@
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
|
||||
export const projectPageTeamDialogFragment = graphql(`
|
||||
fragment ProjectPageTeamDialog on Project {
|
||||
id
|
||||
name
|
||||
role
|
||||
allowPublicComments
|
||||
visibility
|
||||
team {
|
||||
id
|
||||
role
|
||||
user {
|
||||
...LimitedUserAvatar
|
||||
role
|
||||
}
|
||||
}
|
||||
invitedTeam {
|
||||
id
|
||||
title
|
||||
inviteId
|
||||
role
|
||||
user {
|
||||
...LimitedUserAvatar
|
||||
role
|
||||
}
|
||||
}
|
||||
...ProjectsPageTeamDialogManagePermissions_Project
|
||||
}
|
||||
`)
|
||||
|
||||
export const projectDashboardItemNoModelsFragment = graphql(`
|
||||
fragment ProjectDashboardItemNoModels on Project {
|
||||
id
|
||||
|
||||
@@ -126,8 +126,5 @@ export const settingsWorkspacesSecurityQuery = graphql(`
|
||||
workspaceBySlug(slug: $slug) {
|
||||
...SettingsWorkspacesSecurity_Workspace
|
||||
}
|
||||
activeUser {
|
||||
...SettingsWorkspacesSecurity_User
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { blockedDomains } from '@speckle/shared'
|
||||
|
||||
export const useVerifiedUserEmailDomains = (
|
||||
options?: Partial<{
|
||||
/**
|
||||
* Whether to filter out blocked domains from the list
|
||||
*/
|
||||
filterBlocked: boolean
|
||||
}>
|
||||
) => {
|
||||
const { filterBlocked = true } = options || {}
|
||||
const { activeUser } = useActiveUser()
|
||||
|
||||
const domains = computed(() => {
|
||||
return (activeUser.value?.emails || [])
|
||||
.filter((email) => email.verified)
|
||||
.map((email) => email.email.split('@')[1])
|
||||
.filter(
|
||||
(domain) => domain && (!filterBlocked || !blockedDomains.includes(domain))
|
||||
)
|
||||
})
|
||||
|
||||
return { domains }
|
||||
}
|
||||
@@ -28,7 +28,8 @@ const emptyState: WorkspaceWizardState = {
|
||||
plan: null,
|
||||
billingInterval: BillingInterval.Monthly,
|
||||
id: '',
|
||||
region: null
|
||||
region: null,
|
||||
enableDomainDiscoverabilityForDomain: undefined
|
||||
}
|
||||
|
||||
const steps: readonly WizardSteps[] = [
|
||||
@@ -129,7 +130,9 @@ export const useWorkspacesWizard = () => {
|
||||
const newWorkspaceResult = await createWorkspace(
|
||||
{
|
||||
name: wizardState.value.state.name,
|
||||
slug: wizardState.value.state.slug
|
||||
slug: wizardState.value.state.slug,
|
||||
enableDomainDiscoverabilityForDomain:
|
||||
wizardState.value.state.enableDomainDiscoverabilityForDomain || null
|
||||
},
|
||||
{ navigateOnSuccess: false, hideNotifications: true }
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type {
|
||||
BillingInterval,
|
||||
WorkspacePlans,
|
||||
@@ -38,6 +39,7 @@ export type WorkspaceWizardState = {
|
||||
billingInterval: BillingInterval | null
|
||||
id: string
|
||||
region: SettingsWorkspacesRegionsSelect_ServerRegionItemFragment | null
|
||||
enableDomainDiscoverabilityForDomain: MaybeNullOrUndefined<string>
|
||||
}
|
||||
|
||||
export enum WizardSteps {
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"@nuxt/eslint": "^1.1.0",
|
||||
"@nuxt/image": "^1.8.1",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@speckle/tailwind-theme": "workspace:^",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
|
||||
@@ -132,6 +132,7 @@ import {
|
||||
workspaceUpdateDomainProtectionMutation,
|
||||
workspaceUpdateDiscoverabilityMutation
|
||||
} from '~/lib/workspaces/graphql/mutations'
|
||||
import { useVerifiedUserEmailDomains } from '~/lib/workspaces/composables/security'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesSecurity_Workspace on Workspace {
|
||||
@@ -146,15 +147,6 @@ graphql(`
|
||||
domainBasedMembershipProtectionEnabled
|
||||
discoverabilityEnabled
|
||||
}
|
||||
|
||||
fragment SettingsWorkspacesSecurity_User on User {
|
||||
id
|
||||
emails {
|
||||
id
|
||||
email
|
||||
verified
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
definePageMeta({
|
||||
@@ -167,6 +159,9 @@ useHead({
|
||||
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const { domains: userEmailDomains } = useVerifiedUserEmailDomains({
|
||||
filterBlocked: false
|
||||
})
|
||||
const route = useRoute()
|
||||
const addWorkspaceDomain = useAddWorkspaceDomain()
|
||||
const isSsoEnabled = useIsWorkspacesSsoEnabled()
|
||||
@@ -198,10 +193,7 @@ const verifiedUserDomains = computed(() => {
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
(result.value?.activeUser?.emails ?? [])
|
||||
.filter((email) => email.verified)
|
||||
.map((email) => email.email.split('@')[1])
|
||||
.filter((domain) => !workspaceDomainSet.has(domain))
|
||||
userEmailDomains.value.filter((domain) => !workspaceDomainSet.has(domain))
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
@@ -208,7 +208,15 @@ input UserProjectsFilter {
|
||||
"""
|
||||
onlyWithRoles: [String!]
|
||||
|
||||
"""
|
||||
Only include projects in the specified workspace
|
||||
"""
|
||||
workspaceId: ID
|
||||
|
||||
"""
|
||||
Only include personal projects (not in any workspace)
|
||||
"""
|
||||
personalOnly: Boolean
|
||||
}
|
||||
|
||||
enum UserProjectsUpdatedMessageType {
|
||||
|
||||
@@ -45,6 +45,10 @@ input WorkspaceCreateInput {
|
||||
Logo image as base64-encoded string
|
||||
"""
|
||||
logo: String
|
||||
"""
|
||||
Add this domain to the workspace as a verified domain and enable domain discoverability
|
||||
"""
|
||||
enableDomainDiscoverabilityForDomain: String
|
||||
}
|
||||
|
||||
input WorkspaceUpdateInput {
|
||||
@@ -138,12 +142,8 @@ type WorkspaceMutations {
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
leave(id: ID!): Boolean! @hasServerRole(role: SERVER_GUEST)
|
||||
join(input: JoinWorkspaceInput!): Workspace! @hasScope(scope: "workspace:update")
|
||||
# TODO: this mutation should have an hasWorkspaceRole directive to authorize only workspace admin
|
||||
# We are, for the moment, doing the check in the resolver
|
||||
addDomain(input: AddDomainToWorkspaceInput!): Workspace!
|
||||
@hasScope(scope: "workspace:update")
|
||||
# TODO: this mutation should have an hasWorkspaceRole directive to authorize only workspace admin
|
||||
# We are, for the moment, doing the check in the resolver
|
||||
deleteDomain(input: WorkspaceDomainDeleteInput!): Workspace!
|
||||
@hasScope(scope: "workspace:update")
|
||||
deleteSsoProvider(workspaceId: String!): Boolean!
|
||||
@@ -406,6 +406,11 @@ input WorkspaceProjectsFilter {
|
||||
Filter out projects by name
|
||||
"""
|
||||
search: String
|
||||
|
||||
"""
|
||||
Only return workspace projects that the active user has an explicit project role in
|
||||
"""
|
||||
withProjectRoleOnly: Boolean
|
||||
}
|
||||
|
||||
input WorkspaceTeamFilter {
|
||||
@@ -550,6 +555,15 @@ extend type User {
|
||||
|
||||
extend type Project {
|
||||
workspace: Workspace
|
||||
"""
|
||||
Returns information about the potential effects of moving a project to a given workspace.
|
||||
"""
|
||||
moveToWorkspaceDryRun(workspaceId: String!): ProjectMoveToWorkspaceDryRun!
|
||||
}
|
||||
|
||||
type ProjectMoveToWorkspaceDryRun {
|
||||
addedToWorkspace(limit: Int): [LimitedUser!]!
|
||||
addedToWorkspaceTotalCount: Int!
|
||||
}
|
||||
|
||||
type ServerWorkspacesInfo {
|
||||
|
||||
Vendored
+1
@@ -50,6 +50,7 @@ const startDebugger = process.env.START_DEBUGGER
|
||||
if ((isTestEnv() || isDevEnv()) && startDebugger) {
|
||||
const inspector = require('node:inspector')
|
||||
if (!inspector.url()) {
|
||||
console.log('Debugger starting on process ' + process.pid)
|
||||
inspector.open(0, undefined, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ generates:
|
||||
WorkspaceSubscriptionSeats: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionSeatsGraphQLReturn'
|
||||
WorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn'
|
||||
LimitedWorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceJoinRequestGraphQLReturn'
|
||||
ProjectMoveToWorkspaceDryRun: '@/modules/workspacesCore/helpers/graphTypes#ProjectMoveToWorkspaceDryRunGraphQLReturn'
|
||||
Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn'
|
||||
SmartTextEditorValue: '@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn'
|
||||
BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem'
|
||||
|
||||
@@ -54,7 +54,12 @@ const configs = [
|
||||
'@typescript-eslint/unbound-method': 'off', // too many false positives
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off', // false positives - sometimes they are actually necessary
|
||||
'@typescript-eslint/no-empty-object-type': 'off', // too restrictive
|
||||
'@typescript-eslint/only-throw-error': ['error', { allow: ['AssertionError'] }],
|
||||
'@typescript-eslint/only-throw-error': [
|
||||
'error',
|
||||
{
|
||||
allow: ['AssertionError']
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import cryptoRandomString from 'crypto-random-string'
|
||||
import Redis from 'ioredis'
|
||||
import { get, has, isObjectLike } from 'lodash'
|
||||
import { Logger } from 'pino'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
|
||||
export enum AuthCodePayloadAction {
|
||||
CreateAutomation = 'createAutomation',
|
||||
@@ -87,7 +88,7 @@ export const validateStoredAuthCodeFactory =
|
||||
// Token is valid, confirm user is authorized to access specified resources.
|
||||
if (resources?.workspaceId) {
|
||||
await emit({
|
||||
eventName: 'workspace.authorized',
|
||||
eventName: WorkspaceEvents.Authorizing,
|
||||
payload: { userId: payload.userId, workspaceId: resources?.workspaceId }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { defineModuleLoaders } from '@/modules/loaders'
|
||||
import { getStreamFactory } from '@/modules/core/repositories/streams'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
adminOverrideEnabled,
|
||||
getFeatureFlags
|
||||
} from '@/modules/shared/helpers/envHelper'
|
||||
import { db } from '@/db/knex'
|
||||
|
||||
// TODO: Move everything to use dataLoaders
|
||||
@@ -8,6 +11,7 @@ export default defineModuleLoaders(async () => {
|
||||
const getStream = getStreamFactory({ db })
|
||||
|
||||
return {
|
||||
getAdminOverrideEnabled: async () => adminOverrideEnabled(),
|
||||
getEnv: async () => getFeatureFlags(),
|
||||
getProject: async ({ projectId }, { dataLoaders }) => {
|
||||
return await dataLoaders.streams.getStream.load(projectId)
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { ProjectTeamMember } from '@/modules/core/domain/projects/types'
|
||||
import { Project } from '@/modules/core/domain/streams/types'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { MaybeNullOrUndefined, StreamRoles } from '@speckle/shared'
|
||||
|
||||
export type GetProject = (args: { projectId: string }) => Promise<Project | null>
|
||||
|
||||
export type GetProjectCollaborators = (args: {
|
||||
projectId: string
|
||||
}) => Promise<ProjectTeamMember[]>
|
||||
|
||||
export type UpdateProject = (args: {
|
||||
projectUpdate: Pick<StreamRecord, 'id' | 'workspaceId'>
|
||||
}) => Promise<StreamRecord>
|
||||
|
||||
@@ -178,7 +178,15 @@ export type BaseUserStreamsQueryParams = {
|
||||
* Only allow streams with the specified IDs to be returned
|
||||
*/
|
||||
streamIdWhitelist?: string[]
|
||||
workspaceId?: string | null
|
||||
/**
|
||||
* Only allow streams in the specified workspace to be returned
|
||||
*/
|
||||
workspaceId?: MaybeNullOrUndefined<string>
|
||||
|
||||
/**
|
||||
* Only allow personal (non-workspace) streams to be returned
|
||||
*/
|
||||
personalOnly?: MaybeNullOrUndefined<boolean>
|
||||
|
||||
/**
|
||||
* Only with active sso session
|
||||
|
||||
@@ -123,6 +123,7 @@ export type CreateValidatedUser = (
|
||||
},
|
||||
options?: Partial<{
|
||||
skipPropertyValidation: boolean
|
||||
allowPersonalEmail: boolean
|
||||
}>
|
||||
) => Promise<string>
|
||||
|
||||
|
||||
@@ -12,29 +12,13 @@ import {
|
||||
import { GraphqlDirectiveBuilder } from '@/modules/core/graph/helpers/directiveHelper'
|
||||
import { getRolesFactory } from '@/modules/shared/repositories/roles'
|
||||
import { db } from '@/db/knex'
|
||||
import { authorizeResolverFactory } from '@/modules/shared/services/auth'
|
||||
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
getUserAclRoleFactory,
|
||||
getUserServerRoleFactory
|
||||
} from '@/modules/shared/repositories/acl'
|
||||
import { getStreamFactory } from '@/modules/core/repositories/streams'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
|
||||
const getStream = getStreamFactory({ db })
|
||||
const throwForNotHavingServerRole = throwForNotHavingServerRoleFactory({
|
||||
validateServerRole: validateServerRoleBuilderFactory({
|
||||
getRoles: getRolesFactory({ db })
|
||||
})
|
||||
})
|
||||
const authorizeResolver = authorizeResolverFactory({
|
||||
getRoles: getRolesFactory({ db }),
|
||||
adminOverrideEnabled,
|
||||
getUserServerRole: getUserServerRoleFactory({ db }),
|
||||
getStream,
|
||||
getUserAclRole: getUserAclRoleFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
/**
|
||||
* Ensure that the user has the specified SERVER role (e.g. server user, admin etc.)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from
|
||||
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes';
|
||||
import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types';
|
||||
import { AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes';
|
||||
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceJoinRequestMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, LimitedWorkspaceJoinRequestGraphQLReturn, ProjectRoleGraphQLReturn, WorkspacePermissionChecksGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
|
||||
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceJoinRequestMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, LimitedWorkspaceJoinRequestGraphQLReturn, ProjectMoveToWorkspaceDryRunGraphQLReturn, ProjectRoleGraphQLReturn, WorkspacePermissionChecksGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
|
||||
import { WorkspacePlanGraphQLReturn, WorkspacePlanUsageGraphQLReturn, PriceGraphQLReturn } from '@/modules/gatekeeperCore/helpers/graphTypes';
|
||||
import { WorkspaceBillingMutationsGraphQLReturn, WorkspaceSubscriptionSeatsGraphQLReturn, WorkspaceSubscriptionGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes';
|
||||
import { WebhookGraphQLReturn } from '@/modules/webhooks/helpers/graphTypes';
|
||||
@@ -2031,6 +2031,8 @@ export type Project = {
|
||||
* real or fake (e.g., with a foo/bar model, it will be nested under foo even if such a model doesn't actually exist)
|
||||
*/
|
||||
modelsTree: ModelsTreeItemCollection;
|
||||
/** Returns information about the potential effects of moving a project to a given workspace. */
|
||||
moveToWorkspaceDryRun: ProjectMoveToWorkspaceDryRun;
|
||||
name: Scalars['String']['output'];
|
||||
object?: Maybe<Object>;
|
||||
/** Pending project access requests */
|
||||
@@ -2129,6 +2131,11 @@ export type ProjectModelsTreeArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectObjectArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
@@ -2449,6 +2456,17 @@ export const ProjectModelsUpdatedMessageType = {
|
||||
} as const;
|
||||
|
||||
export type ProjectModelsUpdatedMessageType = typeof ProjectModelsUpdatedMessageType[keyof typeof ProjectModelsUpdatedMessageType];
|
||||
export type ProjectMoveToWorkspaceDryRun = {
|
||||
__typename?: 'ProjectMoveToWorkspaceDryRun';
|
||||
addedToWorkspace: Array<LimitedUser>;
|
||||
addedToWorkspaceTotalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunAddedToWorkspaceArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectMutations = {
|
||||
__typename?: 'ProjectMutations';
|
||||
/** Access request related mutations */
|
||||
@@ -4058,8 +4076,11 @@ export type UserProjectCollection = {
|
||||
export type UserProjectsFilter = {
|
||||
/** Only include projects where user has the specified roles */
|
||||
onlyWithRoles?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
/** Only include personal projects (not in any workspace) */
|
||||
personalOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only include projects in the specified workspace */
|
||||
workspaceId?: InputMaybe<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
@@ -4478,6 +4499,8 @@ export type WorkspaceCollection = {
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Add this domain to the workspace as a verified domain and enable domain discoverability */
|
||||
enableDomainDiscoverabilityForDomain?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Logo image as base64-encoded string */
|
||||
logo?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -4841,6 +4864,8 @@ export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
export type WorkspaceProjectsFilter = {
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only return workspace projects that the active user has an explicit project role in */
|
||||
withProjectRoleOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectsUpdatedMessage = {
|
||||
@@ -5226,6 +5251,7 @@ export type ResolversTypes = {
|
||||
ProjectModelsTreeFilter: ProjectModelsTreeFilter;
|
||||
ProjectModelsUpdatedMessage: ResolverTypeWrapper<Omit<ProjectModelsUpdatedMessage, 'model'> & { model?: Maybe<ResolversTypes['Model']> }>;
|
||||
ProjectModelsUpdatedMessageType: ProjectModelsUpdatedMessageType;
|
||||
ProjectMoveToWorkspaceDryRun: ResolverTypeWrapper<ProjectMoveToWorkspaceDryRunGraphQLReturn>;
|
||||
ProjectMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
|
||||
ProjectPendingModelsUpdatedMessage: ResolverTypeWrapper<Omit<ProjectPendingModelsUpdatedMessage, 'model'> & { model: ResolversTypes['FileUpload'] }>;
|
||||
ProjectPendingModelsUpdatedMessageType: ProjectPendingModelsUpdatedMessageType;
|
||||
@@ -5545,6 +5571,7 @@ export type ResolversParentTypes = {
|
||||
ProjectModelsFilter: ProjectModelsFilter;
|
||||
ProjectModelsTreeFilter: ProjectModelsTreeFilter;
|
||||
ProjectModelsUpdatedMessage: Omit<ProjectModelsUpdatedMessage, 'model'> & { model?: Maybe<ResolversParentTypes['Model']> };
|
||||
ProjectMoveToWorkspaceDryRun: ProjectMoveToWorkspaceDryRunGraphQLReturn;
|
||||
ProjectMutations: MutationsObjectGraphQLReturn;
|
||||
ProjectPendingModelsUpdatedMessage: Omit<ProjectPendingModelsUpdatedMessage, 'model'> & { model: ResolversParentTypes['FileUpload'] };
|
||||
ProjectPendingVersionsUpdatedMessage: Omit<ProjectPendingVersionsUpdatedMessage, 'version'> & { version: ResolversParentTypes['FileUpload'] };
|
||||
@@ -6458,6 +6485,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
modelChildrenTree?: Resolver<Array<ResolversTypes['ModelsTreeItem']>, ParentType, ContextType, RequireFields<ProjectModelChildrenTreeArgs, 'fullName'>>;
|
||||
models?: Resolver<ResolversTypes['ModelCollection'], ParentType, ContextType, RequireFields<ProjectModelsArgs, 'limit'>>;
|
||||
modelsTree?: Resolver<ResolversTypes['ModelsTreeItemCollection'], ParentType, ContextType, RequireFields<ProjectModelsTreeArgs, 'limit'>>;
|
||||
moveToWorkspaceDryRun?: Resolver<ResolversTypes['ProjectMoveToWorkspaceDryRun'], ParentType, ContextType, RequireFields<ProjectMoveToWorkspaceDryRunArgs, 'workspaceId'>>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
object?: Resolver<Maybe<ResolversTypes['Object']>, ParentType, ContextType, RequireFields<ProjectObjectArgs, 'id'>>;
|
||||
pendingAccessRequests?: Resolver<Maybe<Array<ResolversTypes['ProjectAccessRequest']>>, ParentType, ContextType>;
|
||||
@@ -6564,6 +6592,12 @@ export type ProjectModelsUpdatedMessageResolvers<ContextType = GraphQLContext, P
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectMoveToWorkspaceDryRun'] = ResolversParentTypes['ProjectMoveToWorkspaceDryRun']> = {
|
||||
addedToWorkspace?: Resolver<Array<ResolversTypes['LimitedUser']>, ParentType, ContextType, Partial<ProjectMoveToWorkspaceDryRunAddedToWorkspaceArgs>>;
|
||||
addedToWorkspaceTotalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ProjectMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectMutations'] = ResolversParentTypes['ProjectMutations']> = {
|
||||
accessRequestMutations?: Resolver<ResolversTypes['ProjectAccessRequestMutations'], ParentType, ContextType>;
|
||||
automationMutations?: Resolver<ResolversTypes['ProjectAutomationMutations'], ParentType, ContextType, RequireFields<ProjectMutationsAutomationMutationsArgs, 'projectId'>>;
|
||||
@@ -7492,6 +7526,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
ProjectFileImportUpdatedMessage?: ProjectFileImportUpdatedMessageResolvers<ContextType>;
|
||||
ProjectInviteMutations?: ProjectInviteMutationsResolvers<ContextType>;
|
||||
ProjectModelsUpdatedMessage?: ProjectModelsUpdatedMessageResolvers<ContextType>;
|
||||
ProjectMoveToWorkspaceDryRun?: ProjectMoveToWorkspaceDryRunResolvers<ContextType>;
|
||||
ProjectMutations?: ProjectMutationsResolvers<ContextType>;
|
||||
ProjectPendingModelsUpdatedMessage?: ProjectPendingModelsUpdatedMessageResolvers<ContextType>;
|
||||
ProjectPendingVersionsUpdatedMessage?: ProjectPendingVersionsUpdatedMessageResolvers<ContextType>;
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
insertCommentsFactory
|
||||
} from '@/modules/comments/repositories/comments'
|
||||
import { RateLimitError } from '@/modules/core/errors/ratelimit'
|
||||
import { StreamNotFoundError } from '@/modules/core/errors/stream'
|
||||
import {
|
||||
ProjectVisibility,
|
||||
Resolvers,
|
||||
@@ -88,10 +87,7 @@ import {
|
||||
UserSubscriptions
|
||||
} from '@/modules/shared/utils/subscriptions'
|
||||
import { has } from 'lodash'
|
||||
import { throwUncoveredError } from '@speckle/shared'
|
||||
import { ForbiddenError } from '@/modules/shared/errors'
|
||||
import { Authz } from '@speckle/shared'
|
||||
import { SsoSessionMissingOrExpiredError } from '@/modules/workspacesCore/errors'
|
||||
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUsers = getUsersFactory({ db })
|
||||
@@ -184,27 +180,7 @@ export = {
|
||||
projectId: args.id,
|
||||
userId: context.userId
|
||||
})
|
||||
|
||||
if (!canQuery.isOk) {
|
||||
switch (canQuery.error.code) {
|
||||
case Authz.ProjectNotFoundError.code:
|
||||
throw new StreamNotFoundError('Project not found')
|
||||
case Authz.ProjectNoAccessError.code:
|
||||
case Authz.WorkspaceNoAccessError.code:
|
||||
throw new ForbiddenError(canQuery.error.message)
|
||||
case Authz.WorkspaceSsoSessionNoAccessError.code:
|
||||
throw new SsoSessionMissingOrExpiredError(canQuery.error.message, {
|
||||
info: {
|
||||
workspaceSlug: canQuery.error.payload.workspaceSlug
|
||||
}
|
||||
})
|
||||
case Authz.ServerNoAccessError.code:
|
||||
case Authz.ServerNoSessionError.code:
|
||||
throw new ForbiddenError(canQuery.error.message)
|
||||
default:
|
||||
throwUncoveredError(canQuery.error)
|
||||
}
|
||||
}
|
||||
throwIfAuthNotOk(canQuery)
|
||||
|
||||
const project = await getStream({ streamId: args.id })
|
||||
|
||||
@@ -284,6 +260,11 @@ export = {
|
||||
throw new RateLimitError(rateLimitResult)
|
||||
}
|
||||
|
||||
const canCreate = await context.authPolicies.project.canCreateLegacy({
|
||||
userId: context.userId
|
||||
})
|
||||
throwIfAuthNotOk(canCreate)
|
||||
|
||||
const regionKey = await getValidDefaultProjectRegionKey()
|
||||
const projectDb = await getDb({ regionKey })
|
||||
|
||||
@@ -344,7 +325,8 @@ export = {
|
||||
searchQuery: args.filter?.search || undefined,
|
||||
withRoles: (args.filter?.onlyWithRoles || []) as StreamRoles[],
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
workspaceId: args.filter?.workspaceId
|
||||
workspaceId: args.filter?.workspaceId,
|
||||
personalOnly: args.filter?.personalOnly
|
||||
}),
|
||||
getUserStreamsCount({
|
||||
userId: ctx.userId!,
|
||||
@@ -353,7 +335,8 @@ export = {
|
||||
withRoles: (args.filter?.onlyWithRoles || []) as StreamRoles[],
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
onlyWithActiveSsoSession: true,
|
||||
workspaceId: args.filter?.workspaceId
|
||||
workspaceId: args.filter?.workspaceId,
|
||||
personalOnly: args.filter?.personalOnly
|
||||
}),
|
||||
getUserStreams({
|
||||
userId: ctx.userId!,
|
||||
@@ -364,7 +347,8 @@ export = {
|
||||
withRoles: (args.filter?.onlyWithRoles || []) as StreamRoles[],
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
onlyWithActiveSsoSession: true,
|
||||
workspaceId: args.filter?.workspaceId
|
||||
workspaceId: args.filter?.workspaceId,
|
||||
personalOnly: args.filter?.personalOnly
|
||||
})
|
||||
])
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApolloServerOptions, BaseContext } from '@apollo/server'
|
||||
import { Authz } from '@speckle/shared'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import _ from 'lodash'
|
||||
import _, { isObjectLike } from 'lodash'
|
||||
import { VError } from 'verror'
|
||||
import { ZodError } from 'zod'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
@@ -35,29 +36,34 @@ export function buildErrorFormatter(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// If error isn't a VError child, don't do anything extra
|
||||
if (!(realError instanceof VError)) {
|
||||
return formattedError
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let extensions: { [key: string]: any } = {
|
||||
...(formattedError.extensions || {})
|
||||
}
|
||||
|
||||
// Converting VError based error to Apollo's format
|
||||
const extensions = {
|
||||
...(formattedError.extensions || {}),
|
||||
...(VError.info(realError) || {})
|
||||
if (realError instanceof VError) {
|
||||
extensions = _.omit(
|
||||
{
|
||||
...extensions,
|
||||
...(VError.info(realError) || {}),
|
||||
stacktrace: VError.fullStack(realError)
|
||||
},
|
||||
VERROR_TRASH_PROPS
|
||||
)
|
||||
} else if (Authz.isAuthPolicyError(realError)) {
|
||||
extensions = {
|
||||
...extensions,
|
||||
code: realError.code,
|
||||
...(isObjectLike(realError.payload)
|
||||
? realError.payload
|
||||
: { payload: realError.payload })
|
||||
}
|
||||
}
|
||||
|
||||
// Getting rid of redundant info
|
||||
delete extensions.originalError
|
||||
|
||||
// Updating exception metadata in extensions
|
||||
if (extensions.exception) {
|
||||
extensions.exception = _.omit(extensions.exception, VERROR_TRASH_PROPS)
|
||||
|
||||
if (includeStacktraceInErrorResponses) {
|
||||
extensions.exception.stacktrace = VError.fullStack(realError)
|
||||
} else {
|
||||
delete extensions.exception.stacktrace
|
||||
}
|
||||
if (!includeStacktraceInErrorResponses) {
|
||||
delete extensions.stacktrace
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -57,7 +57,6 @@ import { metaHelpers } from '@/modules/core/helpers/meta'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
import {
|
||||
DeleteProjectRole,
|
||||
GetProjectCollaborators,
|
||||
UpdateProject,
|
||||
GetRolesByUserId,
|
||||
UpsertProjectRole,
|
||||
@@ -686,12 +685,6 @@ export const getStreamCollaboratorsFactory =
|
||||
return items
|
||||
}
|
||||
|
||||
export const getProjectCollaboratorsFactory =
|
||||
(deps: { db: Knex }): GetProjectCollaborators =>
|
||||
async ({ projectId }) => {
|
||||
return await getStreamCollaboratorsFactory(deps)(projectId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base query for finding or counting user streams
|
||||
*/
|
||||
@@ -705,7 +698,8 @@ const getUserStreamsQueryBaseFactory =
|
||||
withRoles,
|
||||
streamIdWhitelist,
|
||||
workspaceId,
|
||||
onlyWithActiveSsoSession
|
||||
onlyWithActiveSsoSession,
|
||||
personalOnly
|
||||
}: BaseUserStreamsQueryParams) => {
|
||||
const query = tables
|
||||
.streamAcl(deps.db)
|
||||
@@ -740,8 +734,10 @@ const getUserStreamsQueryBaseFactory =
|
||||
})
|
||||
}
|
||||
|
||||
if (!isUndefined(workspaceId)) {
|
||||
if (workspaceId?.length) {
|
||||
query.andWhere(Streams.col.workspaceId, workspaceId)
|
||||
} else if (personalOnly) {
|
||||
query.andWhere(Streams.col.workspaceId, null)
|
||||
}
|
||||
|
||||
if (ownedOnly || withRoles?.length) {
|
||||
|
||||
@@ -157,7 +157,10 @@ export const createUserFactory =
|
||||
}): CreateValidatedUser =>
|
||||
async (user, options = undefined) => {
|
||||
// ONLY ALLOW SKIPPING WHEN CREATING USERS FOR TESTS, IT'S UNSAFE OTHERWISE
|
||||
const { skipPropertyValidation = false } = options || {}
|
||||
const {
|
||||
skipPropertyValidation = false,
|
||||
allowPersonalEmail = !FF_NO_PERSONAL_EMAILS_ENABLED
|
||||
} = options || {}
|
||||
|
||||
const signUpCtx = user.signUpContext
|
||||
|
||||
@@ -175,8 +178,7 @@ export const createUserFactory =
|
||||
const isBlockedDomain = blockedDomains.includes(
|
||||
finalUser.email.split('@')[1]?.toLowerCase()
|
||||
)
|
||||
const requireWorkDomain =
|
||||
!user?.signUpContext?.isInvite && FF_NO_PERSONAL_EMAILS_ENABLED
|
||||
const requireWorkDomain = !user?.signUpContext?.isInvite && !allowPersonalEmail
|
||||
|
||||
if (requireWorkDomain && isBlockedDomain) throw new BlockedEmailDomainError()
|
||||
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
/* istanbul ignore file */
|
||||
const { mockRequireModule } = require('@/test/mockHelper')
|
||||
const envHelperMock = mockRequireModule(
|
||||
[
|
||||
'@/modules/shared/helpers/envHelper',
|
||||
require.resolve('../../shared/helpers/envHelper')
|
||||
],
|
||||
['@/modules/shared/index']
|
||||
)
|
||||
const expect = require('chai').expect
|
||||
|
||||
const { beforeEachContext } = require('@/test/hooks')
|
||||
@@ -73,6 +65,7 @@ const {
|
||||
finalizeInvitedServerRegistrationFactory
|
||||
} = require('@/modules/serverinvites/services/processing')
|
||||
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
|
||||
const { mockAdminOverride } = require('@/test/mocks/global')
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = getUserFactory({ db })
|
||||
@@ -133,6 +126,7 @@ const createUser = createUserFactory({
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const adminOverrideMock = mockAdminOverride()
|
||||
|
||||
describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
before(async () => {
|
||||
@@ -265,11 +259,10 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
envHelperMock.disable()
|
||||
adminOverrideMock.disable()
|
||||
})
|
||||
after(() => {
|
||||
envHelperMock.destroy()
|
||||
envHelperMock.resetMockedFunctions()
|
||||
adminOverrideMock.disable()
|
||||
})
|
||||
it('should allow stream:owners to be stream:owners', async () => {
|
||||
await authorizeResolver(
|
||||
@@ -281,8 +274,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
})
|
||||
|
||||
it('should get the passed in role for server:admins if override enabled', async () => {
|
||||
envHelperMock.enable()
|
||||
envHelperMock.mockFunction('adminOverrideEnabled', () => true)
|
||||
adminOverrideMock.enable(true)
|
||||
await authorizeResolver(
|
||||
serverOwner.id,
|
||||
myStream.id,
|
||||
@@ -305,8 +297,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
})
|
||||
|
||||
it('should allow server:admins to be anything if adminOverride is enabled', async () => {
|
||||
envHelperMock.enable()
|
||||
envHelperMock.mockFunction('adminOverrideEnabled', () => true)
|
||||
adminOverrideMock.enable(true)
|
||||
|
||||
await authorizeResolver(
|
||||
serverOwner.id,
|
||||
@@ -331,8 +322,8 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
})
|
||||
|
||||
it('should not allow server:users to be anything if adminOverride is enabled', async () => {
|
||||
envHelperMock.enable()
|
||||
envHelperMock.mockFunction('adminOverrideEnabled', () => true)
|
||||
adminOverrideMock.enable(true)
|
||||
|
||||
try {
|
||||
await authorizeResolver(
|
||||
otherGuy.id,
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('Projects GraphQL @core', () => {
|
||||
createProjectNonInWorkspaceRes.data!.projectMutations.create
|
||||
|
||||
const userProjectsRes = await apollo.execute(ActiveUserProjectsDocument, {
|
||||
filter: { workspaceId: null }
|
||||
filter: { personalOnly: true }
|
||||
})
|
||||
expect(userProjectsRes).to.not.haveGraphQLErrors()
|
||||
|
||||
|
||||
@@ -2011,6 +2011,8 @@ export type Project = {
|
||||
* real or fake (e.g., with a foo/bar model, it will be nested under foo even if such a model doesn't actually exist)
|
||||
*/
|
||||
modelsTree: ModelsTreeItemCollection;
|
||||
/** Returns information about the potential effects of moving a project to a given workspace. */
|
||||
moveToWorkspaceDryRun: ProjectMoveToWorkspaceDryRun;
|
||||
name: Scalars['String']['output'];
|
||||
object?: Maybe<Object>;
|
||||
/** Pending project access requests */
|
||||
@@ -2109,6 +2111,11 @@ export type ProjectModelsTreeArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectObjectArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
@@ -2429,6 +2436,17 @@ export const ProjectModelsUpdatedMessageType = {
|
||||
} as const;
|
||||
|
||||
export type ProjectModelsUpdatedMessageType = typeof ProjectModelsUpdatedMessageType[keyof typeof ProjectModelsUpdatedMessageType];
|
||||
export type ProjectMoveToWorkspaceDryRun = {
|
||||
__typename?: 'ProjectMoveToWorkspaceDryRun';
|
||||
addedToWorkspace: Array<LimitedUser>;
|
||||
addedToWorkspaceTotalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunAddedToWorkspaceArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectMutations = {
|
||||
__typename?: 'ProjectMutations';
|
||||
/** Access request related mutations */
|
||||
@@ -4038,8 +4056,11 @@ export type UserProjectCollection = {
|
||||
export type UserProjectsFilter = {
|
||||
/** Only include projects where user has the specified roles */
|
||||
onlyWithRoles?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
/** Only include personal projects (not in any workspace) */
|
||||
personalOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only include projects in the specified workspace */
|
||||
workspaceId?: InputMaybe<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
@@ -4458,6 +4479,8 @@ export type WorkspaceCollection = {
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Add this domain to the workspace as a verified domain and enable domain discoverability */
|
||||
enableDomainDiscoverabilityForDomain?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Logo image as base64-encoded string */
|
||||
logo?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -4821,6 +4844,8 @@ export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
export type WorkspaceProjectsFilter = {
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only return workspace projects that the active user has an explicit project role in */
|
||||
withProjectRoleOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectsUpdatedMessage = {
|
||||
|
||||
@@ -2,7 +2,11 @@ import {
|
||||
WorkspacePlanProductPrices,
|
||||
WorkspacePricingProducts
|
||||
} from '@/modules/gatekeeperCore/domain/billing'
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceSeat,
|
||||
WorkspaceSeatType
|
||||
} from '@/modules/workspacesCore/domain/types'
|
||||
import {
|
||||
Nullable,
|
||||
Optional,
|
||||
@@ -16,6 +20,12 @@ import {
|
||||
import { OverrideProperties } from 'type-fest'
|
||||
import { z } from 'zod'
|
||||
|
||||
export { WorkspaceSeat, WorkspaceSeatType }
|
||||
export {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats
|
||||
} from '@/modules/workspacesCore/domain/operations'
|
||||
|
||||
export type GetWorkspacePlan = (args: {
|
||||
workspaceId: string
|
||||
}) => Promise<WorkspacePlan | null>
|
||||
@@ -199,20 +209,6 @@ export type ReconcileSubscriptionData = (args: {
|
||||
prorationBehavior: 'always_invoice' | 'create_prorations' | 'none'
|
||||
}) => Promise<void>
|
||||
|
||||
export const WorkspaceSeatType = <const>{
|
||||
Viewer: 'viewer',
|
||||
Editor: 'editor'
|
||||
}
|
||||
export type WorkspaceSeatType =
|
||||
(typeof WorkspaceSeatType)[keyof typeof WorkspaceSeatType]
|
||||
|
||||
export type WorkspaceSeat = {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
type: WorkspaceSeatType
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
// Prices
|
||||
export type GetRecurringPrices = () => Promise<
|
||||
{
|
||||
@@ -224,26 +220,3 @@ export type GetRecurringPrices = () => Promise<
|
||||
>
|
||||
|
||||
export type GetWorkspacePlanProductPrices = () => Promise<WorkspacePlanProductPrices>
|
||||
|
||||
export type GetWorkspaceRolesAndSeats = (params: {
|
||||
workspaceId: string
|
||||
userIds?: string[]
|
||||
}) => Promise<{
|
||||
[userId: string]: {
|
||||
role: WorkspaceAcl
|
||||
seat: Nullable<WorkspaceSeat>
|
||||
userId: string
|
||||
}
|
||||
}>
|
||||
|
||||
export type GetWorkspaceRoleAndSeat = (params: {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
}) => Promise<
|
||||
| {
|
||||
role: WorkspaceAcl
|
||||
seat: Nullable<WorkspaceSeat>
|
||||
userId: string
|
||||
}
|
||||
| undefined
|
||||
>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { buildTableHelper, StreamAcl, Streams } from '@/modules/core/dbSchema'
|
||||
import { StreamAcl, Streams } from '@/modules/core/dbSchema'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats,
|
||||
WorkspaceSeat
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
import { WorkspaceSeat } from '@/modules/gatekeeper/domain/billing'
|
||||
import {
|
||||
CountSeatsByTypeInWorkspace,
|
||||
CreateWorkspaceSeat,
|
||||
@@ -14,18 +10,13 @@ import {
|
||||
GetWorkspaceUserSeat,
|
||||
GetWorkspaceUserSeats
|
||||
} from '@/modules/gatekeeper/domain/operations'
|
||||
import { formatJsonArrayRecords } from '@/modules/shared/helpers/dbHelper'
|
||||
import { WorkspaceAcl as WorkspaceAclRecord } from '@/modules/workspacesCore/domain/types'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/helpers/db'
|
||||
import { WorkspaceAcl, WorkspaceSeats } from '@/modules/workspacesCore/helpers/db'
|
||||
import { Knex } from 'knex'
|
||||
|
||||
const WorkspaceSeats = buildTableHelper('workspace_seats', [
|
||||
'workspaceId',
|
||||
'userId',
|
||||
'type',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
])
|
||||
export {
|
||||
getWorkspaceRoleAndSeatFactory,
|
||||
getWorkspaceRolesAndSeatsFactory
|
||||
} from '@/modules/workspacesCore/repositories/rolesSeats'
|
||||
|
||||
const tables = {
|
||||
workspaceSeats: (db: Knex) => db<WorkspaceSeat>(WorkspaceSeats.name),
|
||||
@@ -93,55 +84,6 @@ export const getWorkspaceUserSeatFactory =
|
||||
return seats[userId]
|
||||
}
|
||||
|
||||
export const getWorkspaceRolesAndSeatsFactory =
|
||||
(deps: { db: Knex }): GetWorkspaceRolesAndSeats =>
|
||||
async ({ workspaceId, userIds }) => {
|
||||
const q = tables
|
||||
.workspaceAcl(deps.db)
|
||||
.select<Array<{ seats: WorkspaceSeat[]; roles: WorkspaceAclRecord[] }>>([
|
||||
// There's only ever gonna be 1 role and seat per user, but this way we can avoid having to group
|
||||
// by many columns and we can get everything in 1 query
|
||||
WorkspaceAcl.groupArray('roles'),
|
||||
WorkspaceSeats.groupArray('seats')
|
||||
])
|
||||
.leftJoin(WorkspaceSeats.name, (j1) => {
|
||||
j1.on(WorkspaceSeats.col.userId, WorkspaceAcl.col.userId).andOnVal(
|
||||
WorkspaceSeats.col.workspaceId,
|
||||
workspaceId
|
||||
)
|
||||
})
|
||||
.where(WorkspaceAcl.col.workspaceId, workspaceId)
|
||||
.groupBy(WorkspaceAcl.col.userId)
|
||||
|
||||
if (userIds?.length) {
|
||||
q.whereIn(WorkspaceAcl.col.userId, userIds)
|
||||
}
|
||||
|
||||
const res = await q
|
||||
return res.reduce((acc, row) => {
|
||||
const role = formatJsonArrayRecords(row.roles)[0]
|
||||
if (!role) return acc
|
||||
|
||||
acc[role.userId] = {
|
||||
role,
|
||||
seat: formatJsonArrayRecords(row.seats || [])[0] || null,
|
||||
userId: role.userId
|
||||
}
|
||||
return acc
|
||||
}, {} as Awaited<ReturnType<GetWorkspaceRolesAndSeats>>)
|
||||
}
|
||||
|
||||
export const getWorkspaceRoleAndSeatFactory =
|
||||
(deps: { db: Knex }): GetWorkspaceRoleAndSeat =>
|
||||
async ({ workspaceId, userId }) => {
|
||||
const getWorkspaceRolesAndSeats = getWorkspaceRolesAndSeatsFactory(deps)
|
||||
const rolesAndSeats = await getWorkspaceRolesAndSeats({
|
||||
workspaceId,
|
||||
userIds: [userId]
|
||||
})
|
||||
return rolesAndSeats[userId]
|
||||
}
|
||||
|
||||
export const getWorkspacesUsersSeatsFactory =
|
||||
(deps: { db: Knex }): GetWorkspacesUsersSeats =>
|
||||
async (params) => {
|
||||
|
||||
@@ -138,6 +138,8 @@ export const init = async (params: { app: Express; metricsRegister: Registry })
|
||||
await module.finalize?.({ app, isInitial, metricsRegister })
|
||||
}
|
||||
|
||||
// Reset some caches
|
||||
|
||||
// Validate & cache authz loaders
|
||||
await moduleAuthLoaders({
|
||||
dataLoaders: undefined
|
||||
|
||||
@@ -9,7 +9,7 @@ export const commandFactory =
|
||||
}: {
|
||||
db: Knex
|
||||
eventBus?: EventBus
|
||||
operationFactory: (arg: { db: Knex; emit: EventBusEmit }) => TOperation
|
||||
operationFactory: (arg: { db: Knex; trx: Knex; emit: EventBusEmit }) => TOperation
|
||||
}) =>
|
||||
async (...args: Parameters<TOperation>): Promise<Awaited<ReturnType<TOperation>>> => {
|
||||
const events: EmitArg[] = []
|
||||
@@ -19,7 +19,7 @@ export const commandFactory =
|
||||
|
||||
const trx = await db.transaction()
|
||||
try {
|
||||
const result = await operationFactory({ db, emit })(...args)
|
||||
const result = await operationFactory({ db, trx, emit })(...args)
|
||||
|
||||
await trx.commit()
|
||||
if (eventBus) {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import { StreamNotFoundError } from '@/modules/core/errors/stream'
|
||||
import { WorkspacesModuleDisabledError } from '@/modules/core/errors/workspaces'
|
||||
import { BaseError, ForbiddenError } from '@/modules/shared/errors'
|
||||
import { SsoSessionMissingOrExpiredError } from '@/modules/workspacesCore/errors'
|
||||
import { Authz, ensureError, throwUncoveredError } from '@speckle/shared'
|
||||
import { VError } from 'verror'
|
||||
|
||||
/**
|
||||
@@ -15,3 +19,38 @@ export function getCause(e: Error) {
|
||||
}
|
||||
|
||||
export { ensureError }
|
||||
|
||||
/**
|
||||
* Global mapping for mapping any kind of auth error to a server thrown error
|
||||
*/
|
||||
export const mapAuthToServerError = (e: Authz.AllAuthErrors): BaseError => {
|
||||
switch (e.code) {
|
||||
case Authz.ProjectNotFoundError.code:
|
||||
return new StreamNotFoundError(e.message)
|
||||
case Authz.ProjectNoAccessError.code:
|
||||
case Authz.WorkspaceNoAccessError.code:
|
||||
case Authz.WorkspaceNotEnoughPermissionsError.code:
|
||||
case Authz.WorkspaceReadOnlyError.code:
|
||||
case Authz.WorkspaceLimitsReachedError.code:
|
||||
case Authz.WorkspaceNoEditorSeatError.code:
|
||||
return new ForbiddenError(e.message)
|
||||
case Authz.WorkspaceSsoSessionNoAccessError.code:
|
||||
throw new SsoSessionMissingOrExpiredError(e.message, {
|
||||
info: {
|
||||
workspaceSlug: e.payload.workspaceSlug
|
||||
}
|
||||
})
|
||||
case Authz.ServerNoAccessError.code:
|
||||
case Authz.ServerNoSessionError.code:
|
||||
return new ForbiddenError(e.message)
|
||||
case Authz.WorkspacesNotEnabledError.code:
|
||||
return new WorkspacesModuleDisabledError()
|
||||
default:
|
||||
throwUncoveredError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export const throwIfAuthNotOk = (result: Authz.AuthPolicyResult) => {
|
||||
if (result.isOk) return
|
||||
throw mapAuthToServerError(result.error)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
CommitSubscriptions,
|
||||
BranchSubscriptions
|
||||
} from '@/modules/shared/utils/subscriptions'
|
||||
import { getWorkspaceRoleAndSeatFactory } from '@/modules/workspacesCore/repositories/rolesSeats'
|
||||
|
||||
export {
|
||||
pubsub,
|
||||
@@ -32,5 +33,6 @@ export const authorizeResolver = authorizeResolverFactory({
|
||||
getUserServerRole: getUserServerRoleFactory({ db }),
|
||||
getStream: getStreamFactory({ db }),
|
||||
getUserAclRole: getUserAclRoleFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
emitWorkspaceEvent: getEventBus().emit,
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db })
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ForbiddenError } from '@/modules/shared/errors'
|
||||
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
|
||||
import { EventBusEmit } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import { GetWorkspaceRoleAndSeat } from '@/modules/workspacesCore/domain/operations'
|
||||
import { isNullOrUndefined, Roles } from '@speckle/shared'
|
||||
import { OperationTypeNode } from 'graphql'
|
||||
|
||||
@@ -31,6 +32,12 @@ export const validateScopesFactory = (): ValidateScopes => async (scopes, scope)
|
||||
throw new ForbiddenError(errMsg, { info: { scope } })
|
||||
}
|
||||
|
||||
const workspaceRoleImplicitProjectRoleMap = <const>{
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Reviewer,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the userId against the resource's acl.
|
||||
*/
|
||||
@@ -41,6 +48,7 @@ export const authorizeResolverFactory =
|
||||
getUserServerRole: GetUserServerRole
|
||||
getStream: GetStream
|
||||
getUserAclRole: GetUserAclRole
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
emitWorkspaceEvent: EventBusEmit
|
||||
}): AuthorizeResolver =>
|
||||
async (userId, resourceId, requiredRole, userResourceAccessLimits, operationType) => {
|
||||
@@ -97,7 +105,7 @@ export const authorizeResolverFactory =
|
||||
targetWorkspaceId = resourceId
|
||||
}
|
||||
|
||||
const userAclRole = userId
|
||||
let userAclRole = userId
|
||||
? await deps.getUserAclRole({
|
||||
aclTableName: role.aclTableName,
|
||||
userId,
|
||||
@@ -106,7 +114,27 @@ export const authorizeResolverFactory =
|
||||
: null
|
||||
|
||||
if (!userAclRole) {
|
||||
throw new ForbiddenError('You are not authorized to access this resource.')
|
||||
// TODO: Could be more optimized (caching?) but we're moving away from this towards
|
||||
// auth policies anyway
|
||||
// Check if workspace role allows for stream actions
|
||||
if (
|
||||
role.resourceTarget === RoleResourceTargets.Streams &&
|
||||
targetWorkspaceId &&
|
||||
userId
|
||||
) {
|
||||
const workspaceRoleAndSeat = await deps.getWorkspaceRoleAndSeat({
|
||||
workspaceId: targetWorkspaceId,
|
||||
userId
|
||||
})
|
||||
const implicitStreamRole =
|
||||
workspaceRoleAndSeat?.role.role &&
|
||||
workspaceRoleImplicitProjectRoleMap[workspaceRoleAndSeat.role.role]
|
||||
userAclRole = implicitStreamRole
|
||||
}
|
||||
|
||||
if (!userAclRole) {
|
||||
throw new ForbiddenError('You are not authorized to access this resource.')
|
||||
}
|
||||
}
|
||||
|
||||
const fullRole = roles.find((r) => r.name === userAclRole)
|
||||
@@ -117,7 +145,7 @@ export const authorizeResolverFactory =
|
||||
|
||||
if (!isNullOrUndefined(targetWorkspaceId)) {
|
||||
await deps.emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.Authorized,
|
||||
eventName: WorkspaceEvents.Authorizing,
|
||||
payload: {
|
||||
workspaceId: targetWorkspaceId,
|
||||
userId
|
||||
|
||||
@@ -26,6 +26,7 @@ import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types'
|
||||
import { ServerRegion } from '@/modules/multiregion/domain/types'
|
||||
import { SetOptional } from 'type-fest'
|
||||
import { WorkspaceSeat, WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
|
||||
import { UserRecord } from '@/modules/core/helpers/userHelper'
|
||||
|
||||
/** Workspace */
|
||||
|
||||
@@ -240,6 +241,51 @@ export type GetWorkspacesProjectsCounts = (params: {
|
||||
[workspaceId: string]: number
|
||||
}>
|
||||
|
||||
export type GetPaginatedWorkspaceProjectsArgs = {
|
||||
workspaceId: string
|
||||
/**
|
||||
* If set, will take the user's workspace role into account when fetching projects.
|
||||
* E.g. guests will only see projects they have explicit access to.
|
||||
*/
|
||||
userId?: string
|
||||
cursor?: MaybeNullOrUndefined<string>
|
||||
/**
|
||||
* Defaults to 25, if unset
|
||||
*/
|
||||
limit?: MaybeNullOrUndefined<number>
|
||||
filter?: MaybeNullOrUndefined<
|
||||
Partial<{
|
||||
/**
|
||||
* Search for projects by name
|
||||
*/
|
||||
search: MaybeNullOrUndefined<string>
|
||||
/**
|
||||
* Only get projects that the active user has an explicit role in
|
||||
*/
|
||||
withProjectRoleOnly: MaybeNullOrUndefined<boolean>
|
||||
}>
|
||||
>
|
||||
}
|
||||
|
||||
export type GetPaginatedWorkspaceProjectsItems = (
|
||||
params: GetPaginatedWorkspaceProjectsArgs
|
||||
) => Promise<{
|
||||
items: Stream[]
|
||||
cursor: string | null
|
||||
}>
|
||||
|
||||
export type GetPaginatedWorkspaceProjectsTotalCount = (
|
||||
params: Omit<GetPaginatedWorkspaceProjectsArgs, 'cursor' | 'limit'>
|
||||
) => Promise<number>
|
||||
|
||||
export type GetPaginatedWorkspaceProjects = (
|
||||
params: GetPaginatedWorkspaceProjectsArgs
|
||||
) => Promise<{
|
||||
cursor: string | null
|
||||
items: Stream[]
|
||||
totalCount: number
|
||||
}>
|
||||
|
||||
/** Workspace Project Roles */
|
||||
|
||||
type GrantWorkspaceProjectRolesArgs = {
|
||||
@@ -445,3 +491,8 @@ export type SetUserActiveWorkspace = (args: {
|
||||
/** Is the user in a "personal project" outside of a workspace? */
|
||||
isProjectsActive?: boolean
|
||||
}) => Promise<void>
|
||||
|
||||
export type IntersectProjectCollaboratorsAndWorkspaceCollaborators = (params: {
|
||||
projectId: string
|
||||
workspaceId: string
|
||||
}) => Promise<UserRecord[]>
|
||||
|
||||
@@ -499,7 +499,7 @@ export const workspaceTrackingFactory =
|
||||
break
|
||||
case 'gatekeeper.workspace-trial-expired':
|
||||
break
|
||||
case 'workspace.authorized':
|
||||
case WorkspaceEvents.Authorizing:
|
||||
break
|
||||
case 'workspace.created':
|
||||
// we're setting workspace props and attributing to speckle users
|
||||
@@ -732,7 +732,7 @@ export const initializeEventListenersFactory =
|
||||
getWorkspaceSubscription: getWorkspaceSubscriptionFactory({ db })
|
||||
})(payload)
|
||||
}),
|
||||
eventBus.listen(WorkspaceEvents.Authorized, async ({ payload }) => {
|
||||
eventBus.listen(WorkspaceEvents.Authorizing, async ({ payload }) => {
|
||||
const onWorkspaceAuthorized = onWorkspaceAuthorizedFactory({
|
||||
getWorkspace,
|
||||
getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({ db }),
|
||||
|
||||
@@ -3,10 +3,12 @@ import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getPaginatedItemsFactory } from '@/modules/shared/services/paginatedItems'
|
||||
import { WorkspaceTeamMember } from '@/modules/workspaces/domain/types'
|
||||
import { intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory } from '@/modules/workspaces/repositories/projects'
|
||||
import {
|
||||
countInvitableCollaboratorsByProjectIdFactory,
|
||||
getInvitableCollaboratorsByProjectIdFactory
|
||||
} from '@/modules/workspaces/repositories/users'
|
||||
import { getMoveProjectToWorkspaceDryRunFactory } from '@/modules/workspaces/services/projects'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -46,6 +48,25 @@ export default FF_WORKSPACES_MODULE_ENABLED
|
||||
cursor: args.cursor ?? undefined,
|
||||
limit: args.limit
|
||||
})
|
||||
},
|
||||
moveToWorkspaceDryRun: async (parent, args) => {
|
||||
const { id: projectId } = parent
|
||||
const { workspaceId } = args
|
||||
|
||||
const { addedToWorkspace } = await getMoveProjectToWorkspaceDryRunFactory({
|
||||
intersectProjectCollaboratorsAndWorkspaceCollaborators:
|
||||
intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory({ db })
|
||||
})({ projectId, workspaceId })
|
||||
|
||||
return addedToWorkspace
|
||||
}
|
||||
},
|
||||
ProjectMoveToWorkspaceDryRun: {
|
||||
addedToWorkspace: async (parent, args) => {
|
||||
return args.limit ? parent.slice(0, args.limit) : parent
|
||||
},
|
||||
addedToWorkspaceTotalCount: async (parent) => {
|
||||
return parent.length
|
||||
}
|
||||
}
|
||||
} as Resolvers)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { db } from '@/db/knex'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
import {
|
||||
getProjectCollaboratorsFactory,
|
||||
updateProjectFactory,
|
||||
upsertProjectRoleFactory,
|
||||
getRolesByUserIdFactory,
|
||||
@@ -11,8 +10,7 @@ import {
|
||||
revokeStreamPermissionsFactory,
|
||||
grantStreamPermissionsFactory,
|
||||
legacyGetStreamsFactory,
|
||||
getUserStreamsPageFactory,
|
||||
getUserStreamsCountFactory
|
||||
getStreamCollaboratorsFactory
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
|
||||
import {
|
||||
@@ -74,7 +72,8 @@ import {
|
||||
upsertWorkspaceCreationStateFactory,
|
||||
queryWorkspacesFactory,
|
||||
countWorkspacesFactory,
|
||||
countWorkspaceRoleWithOptionalProjectRoleFactory
|
||||
countWorkspaceRoleWithOptionalProjectRoleFactory,
|
||||
getPaginatedWorkspaceProjectsFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
buildWorkspaceInviteEmailContentsFactory,
|
||||
@@ -98,7 +97,6 @@ import {
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import {
|
||||
createWorkspaceProjectFactory,
|
||||
getWorkspaceProjectsFactory,
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
|
||||
moveProjectToWorkspaceFactory,
|
||||
queryAllWorkspaceProjectsFactory
|
||||
@@ -288,8 +286,6 @@ const updateStreamRoleAndNotify = updateStreamRoleAndNotifyFactory({
|
||||
}),
|
||||
removeStreamCollaborator
|
||||
})
|
||||
const getUserStreams = getUserStreamsPageFactory({ db })
|
||||
const getUserStreamsCount = getUserStreamsCountFactory({ db })
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED, FF_MOVE_PROJECT_REGION_ENABLED } =
|
||||
getFeatureFlags()
|
||||
@@ -439,37 +435,90 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
create: async (_parent, args, context) => {
|
||||
const { name, description, logo, slug } = args.input
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
logo,
|
||||
slug,
|
||||
enableDomainDiscoverabilityForDomain
|
||||
} = args.input
|
||||
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
generateValidSlug: generateValidSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit,
|
||||
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db }),
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db }),
|
||||
eventEmit: getEventBus().emit
|
||||
})
|
||||
const createWorkspace = commandFactory({
|
||||
db,
|
||||
eventBus,
|
||||
operationFactory: ({ trx, emit }) => {
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx })
|
||||
}),
|
||||
generateValidSlug: generateValidSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx })
|
||||
}),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db: trx }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }),
|
||||
emitWorkspaceEvent: emit,
|
||||
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db: trx }),
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: trx }),
|
||||
eventEmit: emit
|
||||
})
|
||||
})
|
||||
|
||||
const updateWorkspace = updateWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx })
|
||||
}),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db: trx }),
|
||||
getWorkspaceSsoProviderRecord: getWorkspaceSsoProviderFactory({
|
||||
db: trx,
|
||||
decrypt: getDecryptor()
|
||||
}),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db: trx }),
|
||||
emitWorkspaceEvent: emit
|
||||
})
|
||||
|
||||
const addDomain = addDomainToWorkspaceFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db: trx }),
|
||||
findEmailsByUserId: findEmailsByUserIdFactory({ db: trx }),
|
||||
storeWorkspaceDomain: storeWorkspaceDomainFactory({ db: trx }),
|
||||
getDomains: getWorkspaceDomainsFactory({ db: trx }),
|
||||
emitWorkspaceEvent: emit
|
||||
})
|
||||
|
||||
return async () => {
|
||||
let workspace = await createWorkspace({
|
||||
userId: context.userId!,
|
||||
workspaceInput: {
|
||||
name,
|
||||
slug,
|
||||
description: description ?? null,
|
||||
logo: logo ?? null
|
||||
},
|
||||
userResourceAccessLimits: context.resourceAccessRules
|
||||
})
|
||||
|
||||
if (enableDomainDiscoverabilityForDomain) {
|
||||
// Add domain & enable discoverability
|
||||
await addDomain({
|
||||
workspaceId: workspace.id,
|
||||
userId: context.userId!,
|
||||
domain: enableDomainDiscoverabilityForDomain
|
||||
})
|
||||
|
||||
workspace = await updateWorkspace({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: {
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return workspace
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const workspace = await createWorkspace({
|
||||
userId: context.userId!,
|
||||
workspaceInput: {
|
||||
name,
|
||||
slug,
|
||||
description: description ?? null,
|
||||
logo: logo ?? null
|
||||
},
|
||||
userResourceAccessLimits: context.resourceAccessRules
|
||||
})
|
||||
|
||||
return workspace
|
||||
return await createWorkspace()
|
||||
},
|
||||
delete: async (_parent, args, context) => {
|
||||
const { workspaceId } = args
|
||||
@@ -1027,7 +1076,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
getProject: getProjectFactory({ db }),
|
||||
updateProject: updateProjectFactory({ db }),
|
||||
upsertProjectRole: upsertProjectRoleFactory({ db }),
|
||||
getProjectCollaborators: getProjectCollaboratorsFactory({ db }),
|
||||
getProjectCollaborators: getStreamCollaboratorsFactory({ db }),
|
||||
getWorkspaceRolesAndSeats: getWorkspaceRolesAndSeatsFactory({ db }),
|
||||
updateWorkspaceRole: updateWorkspaceRoleFactory({
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
|
||||
@@ -1154,33 +1203,12 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return await getPendingTeam({ workspaceId: parent.id, filter: args.filter })
|
||||
},
|
||||
projects: async (parent, args, ctx) => {
|
||||
if (!ctx.userId) return []
|
||||
const getWorkspaceProjects = getWorkspaceProjectsFactory({
|
||||
getStreams: getUserStreams
|
||||
const getWorkspaceProjects = getPaginatedWorkspaceProjectsFactory({ db })
|
||||
return await getWorkspaceProjects({
|
||||
workspaceId: parent.id,
|
||||
userId: ctx.userId!,
|
||||
...args
|
||||
})
|
||||
const filter = {
|
||||
...(args.filter || {}),
|
||||
userId: ctx.userId,
|
||||
workspaceId: parent.id
|
||||
}
|
||||
const { items, cursor } = await getWorkspaceProjects(
|
||||
{
|
||||
workspaceId: parent.id
|
||||
},
|
||||
{
|
||||
limit: args.limit || 25,
|
||||
cursor: args.cursor || null,
|
||||
filter
|
||||
}
|
||||
)
|
||||
return {
|
||||
items,
|
||||
cursor,
|
||||
totalCount: await getUserStreamsCount({
|
||||
...filter,
|
||||
searchQuery: filter.search || undefined
|
||||
})
|
||||
}
|
||||
},
|
||||
automateFunctions: async (parent, args, context) => {
|
||||
try {
|
||||
@@ -1519,7 +1547,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
getTotalCount: getWorkspaceCollaboratorsTotalCountFactory({ db })
|
||||
})({
|
||||
workspaceId: parent.id,
|
||||
limit: args.limit,
|
||||
limit: args.limit ?? 100,
|
||||
cursor: args.cursor ?? undefined
|
||||
})
|
||||
return team
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { StreamAcl, Users } from '@/modules/core/dbSchema'
|
||||
import { StreamAclRecord } from '@/modules/core/helpers/types'
|
||||
import { UserRecord } from '@/modules/core/helpers/userHelper'
|
||||
import { IntersectProjectCollaboratorsAndWorkspaceCollaborators } from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/helpers/db'
|
||||
import { Knex } from 'knex'
|
||||
|
||||
const tables = {
|
||||
streamAcl: (db: Knex) => db.table<StreamAclRecord>('stream_acl')
|
||||
}
|
||||
|
||||
export const intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory =
|
||||
(deps: { db: Knex }): IntersectProjectCollaboratorsAndWorkspaceCollaborators =>
|
||||
async ({ projectId, workspaceId }) => {
|
||||
return await tables
|
||||
.streamAcl(deps.db)
|
||||
.select<UserRecord[]>(...Users.cols)
|
||||
.join(Users.name, Users.col.id, StreamAcl.col.userId)
|
||||
.where(StreamAcl.col.resourceId, projectId)
|
||||
.except((builder) => {
|
||||
return builder
|
||||
.select(...Users.cols)
|
||||
.from(WorkspaceAcl.name)
|
||||
.join(Users.name, Users.col.id, WorkspaceAcl.col.userId)
|
||||
.where(WorkspaceAcl.col.workspaceId, workspaceId)
|
||||
})
|
||||
}
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
DeleteWorkspace,
|
||||
DeleteWorkspaceDomain,
|
||||
DeleteWorkspaceRole,
|
||||
GetPaginatedWorkspaceProjects,
|
||||
GetPaginatedWorkspaceProjectsArgs,
|
||||
GetPaginatedWorkspaceProjectsItems,
|
||||
GetPaginatedWorkspaceProjectsTotalCount,
|
||||
GetUserDiscoverableWorkspaces,
|
||||
GetUserIdsWithRoleInWorkspace,
|
||||
GetWorkspace,
|
||||
@@ -36,6 +40,7 @@ import {
|
||||
import { Knex } from 'knex'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import {
|
||||
ServerAclRecord,
|
||||
BranchRecord,
|
||||
StreamAclRecord,
|
||||
StreamRecord
|
||||
@@ -60,16 +65,22 @@ import {
|
||||
InvitesRetrievalValidityFilter
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants'
|
||||
import { clamp } from 'lodash'
|
||||
import { clamp, has, isObjectLike } from 'lodash'
|
||||
import {
|
||||
WorkspaceCreationState,
|
||||
WorkspaceTeamMember
|
||||
} from '@/modules/workspaces/domain/types'
|
||||
import {
|
||||
decodeCompositeCursor,
|
||||
encodeCompositeCursor
|
||||
} from '@/modules/shared/helpers/graphqlHelper'
|
||||
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const tables = {
|
||||
branches: (db: Knex) => db<BranchRecord>('branches'),
|
||||
streams: (db: Knex) => db<StreamRecord>('streams'),
|
||||
streamAcl: (db: Knex) => db<StreamAclRecord>('stream_acl'),
|
||||
serverAcl: (db: Knex) => db<ServerAclRecord>(ServerAcl.name),
|
||||
workspaces: (db: Knex) => db<Workspace>('workspaces'),
|
||||
workspaceDomains: (db: Knex) => db<WorkspaceDomain>('workspace_domains'),
|
||||
workspacesAcl: (db: Knex) => db<WorkspaceAcl>('workspace_acl'),
|
||||
@@ -542,3 +553,152 @@ export const getWorkspacesProjectsCountsFactory =
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
const getPaginatedWorkspaceProjectsBaseQueryFactory =
|
||||
(deps: { db: Knex }) =>
|
||||
(params: Omit<GetPaginatedWorkspaceProjectsArgs, 'cursor' | 'limit'>) => {
|
||||
const { workspaceId, userId, filter } = params
|
||||
const { search, withProjectRoleOnly } = filter || {}
|
||||
|
||||
const query = tables
|
||||
.streams(deps.db)
|
||||
.where(Streams.col.workspaceId, workspaceId)
|
||||
.select<StreamRecord[]>(Streams.cols)
|
||||
|
||||
/**
|
||||
* If userId is set:
|
||||
* - If no workspace role, user should be server admin w/ admin override enabled
|
||||
* - If workspace role is guest, user should have explicit stream roles
|
||||
* - If workspace role other than guest, just get all workspace streams
|
||||
*
|
||||
* If withProjectRoleOnly is set: Require project role always
|
||||
*/
|
||||
if (userId) {
|
||||
query
|
||||
.leftJoin(DbWorkspaceAcl.name, (j) => {
|
||||
j.on(DbWorkspaceAcl.col.workspaceId, Streams.col.workspaceId).andOnVal(
|
||||
DbWorkspaceAcl.col.userId,
|
||||
userId
|
||||
)
|
||||
})
|
||||
.andWhere((w) => {
|
||||
// Check server_acl exist first, so subsequent checks can be optimized away
|
||||
if (adminOverrideEnabled() && !withProjectRoleOnly) {
|
||||
w.whereExists(
|
||||
tables
|
||||
.serverAcl(deps.db)
|
||||
.select('*')
|
||||
.where(ServerAcl.col.userId, userId)
|
||||
.andWhere(ServerAcl.col.role, Roles.Server.Admin)
|
||||
)
|
||||
}
|
||||
|
||||
w.orWhere((w2) => {
|
||||
// Ensure workspace role exists and its not guest or the user has explicit stream roles
|
||||
w2.whereNotNull(DbWorkspaceAcl.col.role).andWhere((w3) => {
|
||||
if (!withProjectRoleOnly) {
|
||||
w3.whereNot(DbWorkspaceAcl.col.role, Roles.Workspace.Guest)
|
||||
}
|
||||
|
||||
w3.orWhereExists(
|
||||
tables
|
||||
.streamAcl(deps.db)
|
||||
.select('*')
|
||||
.where(StreamAcl.col.userId, userId)
|
||||
.andWhere(StreamAcl.col.resourceId, knex.ref(Streams.col.id))
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (search?.length) {
|
||||
query.andWhere((w) => {
|
||||
w.where(Streams.col.name, 'ILIKE', `%${search}%`).orWhere(
|
||||
Streams.col.description,
|
||||
'ILIKE',
|
||||
`%${search}%`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
export const getPaginatedWorkspaceProjectsItemsFactory =
|
||||
(deps: { db: Knex }): GetPaginatedWorkspaceProjectsItems =>
|
||||
async (params) => {
|
||||
type CursorType = { updatedAt: string; id: string }
|
||||
const query = getPaginatedWorkspaceProjectsBaseQueryFactory(deps)(params)
|
||||
|
||||
const limit = clamp(params.limit || 25, 1, 50)
|
||||
const cursor = decodeCompositeCursor<CursorType>(
|
||||
params.cursor,
|
||||
(c) => isObjectLike(c) && has(c, 'id') && has(c, 'updatedAt')
|
||||
)
|
||||
|
||||
if (cursor) {
|
||||
// filter by date, and if there's duplicate dates, filter by id too
|
||||
query.andWhereRaw('(??, ??) < (?, ?)', [
|
||||
Streams.col.updatedAt,
|
||||
Streams.col.id,
|
||||
cursor.updatedAt,
|
||||
cursor.id
|
||||
])
|
||||
}
|
||||
|
||||
query
|
||||
.orderBy([
|
||||
{ column: Streams.col.updatedAt, order: 'desc' },
|
||||
{ column: Streams.col.id, order: 'desc' }
|
||||
])
|
||||
.limit(limit)
|
||||
|
||||
const rows = await query
|
||||
const newCursorRow = rows.at(-1)
|
||||
const newCursor = newCursorRow
|
||||
? encodeCompositeCursor<CursorType>({
|
||||
updatedAt: newCursorRow.updatedAt.toISOString(),
|
||||
id: newCursorRow.id
|
||||
})
|
||||
: null
|
||||
|
||||
return {
|
||||
items: rows,
|
||||
cursor: newCursor
|
||||
}
|
||||
}
|
||||
|
||||
export const getPaginatedWorkspaceProjectsTotalCountFactory =
|
||||
(deps: { db: Knex }): GetPaginatedWorkspaceProjectsTotalCount =>
|
||||
async (params) => {
|
||||
const query = getPaginatedWorkspaceProjectsBaseQueryFactory(deps)(params)
|
||||
const [res] = await query.clearSelect().count()
|
||||
const count = parseInt(res.count.toString())
|
||||
return count
|
||||
}
|
||||
|
||||
export const getPaginatedWorkspaceProjectsFactory =
|
||||
(deps: { db: Knex }): GetPaginatedWorkspaceProjects =>
|
||||
async (params) => {
|
||||
const getItems = getPaginatedWorkspaceProjectsItemsFactory(deps)
|
||||
const getTotalCount = getPaginatedWorkspaceProjectsTotalCountFactory(deps)
|
||||
|
||||
const [items, totalCount] = await Promise.all([
|
||||
params.limit !== 0 ? getItems(params) : undefined,
|
||||
getTotalCount(params)
|
||||
])
|
||||
|
||||
if (!items) {
|
||||
return {
|
||||
items: [],
|
||||
cursor: null,
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...items,
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
GetDefaultRegion,
|
||||
GetWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
GetWorkspaceSeatTypeToProjectRoleMapping,
|
||||
IntersectProjectCollaboratorsAndWorkspaceCollaborators,
|
||||
QueryAllWorkspaceProjects,
|
||||
UpdateWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
GetProject,
|
||||
GetProjectCollaborators,
|
||||
UpdateProject,
|
||||
UpsertProjectRole
|
||||
} from '@/modules/core/domain/projects/operations'
|
||||
@@ -22,7 +22,7 @@ import { Roles, StreamRoles } from '@speckle/shared'
|
||||
import { orderByWeight } from '@/modules/shared/domain/rolesAndScopes/logic'
|
||||
import coreUserRoles from '@/modules/core/roles'
|
||||
import {
|
||||
GetUserStreamsPage,
|
||||
GetStreamCollaborators,
|
||||
LegacyGetStreams
|
||||
} from '@/modules/core/domain/streams/operations'
|
||||
import { ProjectNotFoundError } from '@/modules/core/errors/projects'
|
||||
@@ -86,44 +86,6 @@ export const queryAllWorkspaceProjectsFactory = ({
|
||||
} while (!!cursor)
|
||||
}
|
||||
|
||||
type GetWorkspaceProjectsArgs = {
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
type GetWorkspaceProjectsOptions = {
|
||||
limit: number | null
|
||||
cursor: string | null
|
||||
filter: {
|
||||
search?: string | null
|
||||
userId: string
|
||||
}
|
||||
}
|
||||
|
||||
type GetWorkspaceProjectsReturnValue = {
|
||||
items: StreamRecord[]
|
||||
cursor: string | null
|
||||
}
|
||||
|
||||
export const getWorkspaceProjectsFactory =
|
||||
({ getStreams }: { getStreams: GetUserStreamsPage }) =>
|
||||
async (
|
||||
args: GetWorkspaceProjectsArgs,
|
||||
opts: GetWorkspaceProjectsOptions
|
||||
): Promise<GetWorkspaceProjectsReturnValue> => {
|
||||
const { streams, cursor } = await getStreams({
|
||||
cursor: opts.cursor,
|
||||
limit: opts.limit || 25,
|
||||
searchQuery: opts.filter?.search || undefined,
|
||||
workspaceId: args.workspaceId,
|
||||
userId: opts.filter.userId
|
||||
})
|
||||
|
||||
return {
|
||||
items: streams,
|
||||
cursor
|
||||
}
|
||||
}
|
||||
|
||||
type MoveProjectToWorkspaceArgs = {
|
||||
projectId: string
|
||||
workspaceId: string
|
||||
@@ -144,7 +106,7 @@ export const moveProjectToWorkspaceFactory =
|
||||
getProject: GetProject
|
||||
updateProject: UpdateProject
|
||||
upsertProjectRole: UpsertProjectRole
|
||||
getProjectCollaborators: GetProjectCollaborators
|
||||
getProjectCollaborators: GetStreamCollaborators
|
||||
getWorkspaceRolesAndSeats: GetWorkspaceRolesAndSeats
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
updateWorkspaceRole: UpdateWorkspaceRole
|
||||
@@ -168,7 +130,7 @@ export const moveProjectToWorkspaceFactory =
|
||||
// Update roles for current project members
|
||||
const [workspace, projectTeam, workspaceTeam] = await Promise.all([
|
||||
getWorkspaceWithPlan({ workspaceId }),
|
||||
getProjectCollaborators({ projectId }),
|
||||
getProjectCollaborators(projectId),
|
||||
getWorkspaceRolesAndSeats({ workspaceId })
|
||||
])
|
||||
if (!workspace) throw new WorkspaceNotFoundError()
|
||||
@@ -357,3 +319,17 @@ export const createWorkspaceProjectFactory =
|
||||
|
||||
return project
|
||||
}
|
||||
|
||||
export const getMoveProjectToWorkspaceDryRunFactory =
|
||||
(deps: {
|
||||
intersectProjectCollaboratorsAndWorkspaceCollaborators: IntersectProjectCollaboratorsAndWorkspaceCollaborators
|
||||
}) =>
|
||||
async (args: { projectId: string; workspaceId: string }) => {
|
||||
const addedToWorkspace =
|
||||
await deps.intersectProjectCollaboratorsAndWorkspaceCollaborators({
|
||||
projectId: args.projectId,
|
||||
workspaceId: args.workspaceId
|
||||
})
|
||||
|
||||
return { addedToWorkspace }
|
||||
}
|
||||
|
||||
@@ -342,12 +342,17 @@ export const unassignFromWorkspaces = async (
|
||||
}
|
||||
|
||||
export const assignToWorkspaces = async (
|
||||
pairs: [BasicTestWorkspace, BasicTestUser, MaybeNullOrUndefined<WorkspaceRoles>][]
|
||||
pairs: [
|
||||
BasicTestWorkspace,
|
||||
BasicTestUser,
|
||||
MaybeNullOrUndefined<WorkspaceRoles>,
|
||||
seatType?: MaybeNullOrUndefined<WorkspaceSeatType>
|
||||
][]
|
||||
) => {
|
||||
// Serial execution is somehow faster with bigger batch sizes, assignToWorkspace
|
||||
// may be quite heavy on the DB
|
||||
for (const [workspace, user, role] of pairs) {
|
||||
await assignToWorkspace(workspace, user, role || undefined)
|
||||
for (const [workspace, user, role, seatType] of pairs) {
|
||||
await assignToWorkspace(workspace, user, role || undefined, seatType || undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
||||
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
|
||||
import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
|
||||
import { getWorkspaceUserSeatsFactory } from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { WorkspaceInvalidRoleError } from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
assignToWorkspace,
|
||||
assignToWorkspaces,
|
||||
BasicTestWorkspace,
|
||||
createTestWorkspace
|
||||
} from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { describeEach } from '@/test/assertionHelper'
|
||||
import {
|
||||
BasicTestUser,
|
||||
createAuthTokenForUser,
|
||||
createTestUser,
|
||||
createTestUsers
|
||||
} from '@/test/authHelper'
|
||||
import { describeEach, itEach } from '@/test/assertionHelper'
|
||||
import { BasicTestUser, createTestUser, createTestUsers } from '@/test/authHelper'
|
||||
import {
|
||||
ActiveUserProjectsWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument,
|
||||
GetProjectDocument,
|
||||
GetWorkspaceProjectsDocument,
|
||||
GetWorkspaceProjectsQuery,
|
||||
GetWorkspaceTeamDocument,
|
||||
MoveProjectToWorkspaceDocument,
|
||||
ProjectUpdateRoleInput,
|
||||
@@ -27,22 +24,26 @@ import {
|
||||
UpdateWorkspaceProjectRoleDocument
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import {
|
||||
createTestContext,
|
||||
ExecuteOperationResponse,
|
||||
testApolloServer,
|
||||
TestApolloServer
|
||||
} from '@/test/graphqlHelper'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { mockAdminOverride } from '@/test/mocks/global'
|
||||
import {
|
||||
addToStream,
|
||||
BasicTestStream,
|
||||
createTestStream,
|
||||
getUserStreamRole
|
||||
} from '@/test/speckle-helpers/streamHelper'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { isNonNullable, Nullable, Optional, Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import dayjs from 'dayjs'
|
||||
import { times } from 'lodash'
|
||||
|
||||
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
|
||||
const adminOverrideMock = mockAdminOverride()
|
||||
|
||||
describe('Workspace project GQL CRUD', () => {
|
||||
let apollo: TestApolloServer
|
||||
@@ -71,15 +72,8 @@ describe('Workspace project GQL CRUD', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
await createTestUsers([serverAdminUser, serverMemberUser])
|
||||
const token = await createAuthTokenForUser(serverAdminUser.id, AllScopes)
|
||||
apollo = await testApolloServer({
|
||||
context: await createTestContext({
|
||||
auth: true,
|
||||
userId: serverAdminUser.id,
|
||||
token,
|
||||
role: serverAdminUser.role,
|
||||
scopes: AllScopes
|
||||
})
|
||||
authUserId: serverAdminUser.id
|
||||
})
|
||||
|
||||
await createTestWorkspace(workspace, serverAdminUser)
|
||||
@@ -282,72 +276,446 @@ describe('Workspace project GQL CRUD', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when querying workspace projects', () => {
|
||||
it('should return multiple projects', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: workspace.id
|
||||
})
|
||||
describe('when querying projects', () => {
|
||||
const PAGE_SIZE = 5
|
||||
const PAGE_COUNT = 3
|
||||
const TOTAL_COUNT = PAGE_COUNT * PAGE_SIZE
|
||||
const GUEST_PROJECT_COUNT = PAGE_SIZE + 1
|
||||
const NON_WORKSPACE_PROJECT_COUNT = 5
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.items.length).to.be.greaterThanOrEqual(3)
|
||||
const queryWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'Query Workspace'
|
||||
}
|
||||
|
||||
const workspaceGuest: BasicTestUser = {
|
||||
id: '',
|
||||
email: '',
|
||||
name: 'Query Workspace Guest'
|
||||
}
|
||||
const workspaceAdmin = serverMemberUser
|
||||
const workspaceMember: BasicTestUser = {
|
||||
id: '',
|
||||
email: '',
|
||||
name: 'Query Workspace Member'
|
||||
}
|
||||
let projects: BasicTestStream[]
|
||||
let nonWorkspaceProjects: BasicTestStream[]
|
||||
let apollo: TestApolloServer
|
||||
|
||||
before(async () => {
|
||||
await createTestUsers([workspaceGuest, workspaceMember])
|
||||
await createTestWorkspace(queryWorkspace, workspaceAdmin, {
|
||||
addPlan: { name: 'team', status: 'valid' }
|
||||
})
|
||||
await assignToWorkspaces([
|
||||
[
|
||||
queryWorkspace,
|
||||
workspaceGuest,
|
||||
Roles.Workspace.Guest,
|
||||
WorkspaceSeatType.Editor
|
||||
],
|
||||
[
|
||||
queryWorkspace,
|
||||
workspaceMember,
|
||||
Roles.Workspace.Member,
|
||||
WorkspaceSeatType.Editor
|
||||
]
|
||||
])
|
||||
projects = times(
|
||||
TOTAL_COUNT,
|
||||
(i): BasicTestStream => ({
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: `Query Workspace Project - #${i}`,
|
||||
isPublic: false, // have to be private for tests below
|
||||
workspaceId: queryWorkspace.id
|
||||
})
|
||||
)
|
||||
nonWorkspaceProjects = times(
|
||||
NON_WORKSPACE_PROJECT_COUNT,
|
||||
(i): BasicTestStream => ({
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: `Non Workspace Project - #${i}`,
|
||||
isPublic: false
|
||||
})
|
||||
)
|
||||
|
||||
// CREATE CONCURRENTLY TO TEST COMPOSITE CURSOR (same updatedAt)
|
||||
await Promise.all([
|
||||
...projects.map((project) => createTestStream(project, workspaceAdmin)),
|
||||
...nonWorkspaceProjects.map((project) =>
|
||||
createTestStream(project, workspaceGuest)
|
||||
)
|
||||
])
|
||||
|
||||
// ONLY ADD EXPLICIT PROJECT ASSIGNMENTS TO GUEST
|
||||
const projectsToAssign = projects.slice(0, GUEST_PROJECT_COUNT)
|
||||
await Promise.all(
|
||||
projectsToAssign.map((project) =>
|
||||
addToStream(project, workspaceGuest, Roles.Stream.Contributor)
|
||||
)
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
// Add explicit single assignment to workspaceMember to 1st non-workspace project
|
||||
addToStream(nonWorkspaceProjects[0], workspaceMember, Roles.Stream.Contributor),
|
||||
// Add explicit single assignment to workspaceMember to 1st workspace project
|
||||
addToStream(projects[0], workspaceMember, Roles.Stream.Contributor)
|
||||
])
|
||||
|
||||
apollo = await testApolloServer({
|
||||
authUserId: workspaceAdmin.id
|
||||
})
|
||||
})
|
||||
|
||||
it('should respect limits', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: workspace.id,
|
||||
limit: 1
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.items.length).to.equal(1)
|
||||
afterEach(async () => {
|
||||
adminOverrideMock.disable()
|
||||
})
|
||||
|
||||
it('should respect pagination', async () => {
|
||||
const resA = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: workspace.id,
|
||||
limit: 10
|
||||
})
|
||||
describe('through Workspace.projects', () => {
|
||||
it('should return all projects for workspace members', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: 999 // get everything
|
||||
})
|
||||
|
||||
const resB = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: workspace.id,
|
||||
limit: 10,
|
||||
cursor: resA.data?.workspace.projects.cursor
|
||||
})
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.workspace.projects
|
||||
expect(collection?.items.length).to.equal(TOTAL_COUNT)
|
||||
expect(collection?.cursor).to.be.ok
|
||||
expect(collection?.totalCount).to.eq(TOTAL_COUNT)
|
||||
|
||||
const projectA = resA.data?.workspace.projects.items[0]
|
||||
const projectB = resB.data?.workspace.projects.items[0]
|
||||
|
||||
expect(resA).to.not.haveGraphQLErrors()
|
||||
expect(resB).to.not.haveGraphQLErrors()
|
||||
expect(projectA).to.exist
|
||||
expect(projectB).to.not.exist
|
||||
expect(projectA?.name).to.not.equal(projectB?.name)
|
||||
})
|
||||
|
||||
it('should respect search filters', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: workspace.id,
|
||||
limit: 1,
|
||||
filter: {
|
||||
search: 'Workspace Project B'
|
||||
// validate sorting
|
||||
const projects = collection?.items || []
|
||||
let lastUpdatedAt: Optional<string> = undefined
|
||||
for (const project of projects) {
|
||||
const date = project.updatedAt
|
||||
if (!lastUpdatedAt) {
|
||||
lastUpdatedAt = date
|
||||
continue
|
||||
}
|
||||
expect(
|
||||
dayjs(date).isSame(dayjs(lastUpdatedAt)) ||
|
||||
dayjs(date).isBefore(dayjs(lastUpdatedAt))
|
||||
).to.be.true
|
||||
lastUpdatedAt = date
|
||||
}
|
||||
})
|
||||
|
||||
const project = res.data?.workspace.projects.items[0]
|
||||
itEach(
|
||||
[{ adminOverrideEnabled: true }, { adminOverrideEnabled: false }],
|
||||
({ adminOverrideEnabled }) =>
|
||||
adminOverrideEnabled
|
||||
? 'should return all projects for server admins if override enabled'
|
||||
: 'should fail retrieving projects for server admins if no override enabled',
|
||||
async ({ adminOverrideEnabled }) => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: serverAdminUser.id
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(project).to.exist
|
||||
expect(project?.name).to.equal('Workspace Project B')
|
||||
adminOverrideMock.enable(adminOverrideEnabled)
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: 999 // get everything
|
||||
})
|
||||
|
||||
if (adminOverrideEnabled) {
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.workspace.projects
|
||||
expect(collection?.items.length).to.equal(TOTAL_COUNT)
|
||||
expect(collection?.cursor).to.be.ok
|
||||
expect(collection?.totalCount).to.eq(TOTAL_COUNT)
|
||||
} else {
|
||||
expect(res).to.haveGraphQLErrors()
|
||||
const collection = res.data?.workspace.projects
|
||||
expect(collection).to.not.be.ok
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it('should return only explicitly assigned projects for guests', async () => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: workspaceGuest.id
|
||||
})
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: 999 // get everything
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.workspace.projects
|
||||
expect(collection?.items.length).to.equal(GUEST_PROJECT_COUNT)
|
||||
expect(collection?.cursor).to.be.ok
|
||||
expect(collection?.totalCount).to.equal(GUEST_PROJECT_COUNT)
|
||||
})
|
||||
|
||||
it('should respect limits', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: 1
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.items.length).to.equal(1)
|
||||
expect(res.data?.workspace.projects.cursor).to.be.ok
|
||||
expect(res.data?.workspace.projects.totalCount).to.equal(TOTAL_COUNT)
|
||||
})
|
||||
|
||||
it('should only return totalCount if limit === 0', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: 0
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.items.length).to.equal(0)
|
||||
expect(res.data?.workspace.projects.cursor).to.be.null
|
||||
expect(res.data?.workspace.projects.totalCount).to.equal(TOTAL_COUNT)
|
||||
})
|
||||
|
||||
it('should respect pagination', async () => {
|
||||
let newCursor: Nullable<string> = null
|
||||
for (let page = 1; page <= PAGE_COUNT + 1; page++) {
|
||||
const res: ExecuteOperationResponse<GetWorkspaceProjectsQuery> =
|
||||
await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: newCursor
|
||||
})
|
||||
newCursor = res.data?.workspace.projects.cursor || null
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.totalCount).to.equal(TOTAL_COUNT)
|
||||
|
||||
if (page <= PAGE_COUNT) {
|
||||
expect(res.data?.workspace.projects.items.length).to.equal(PAGE_SIZE)
|
||||
expect(res.data?.workspace.projects.cursor).to.be.ok
|
||||
} else {
|
||||
expect(res.data?.workspace.projects.items.length).to.eq(0)
|
||||
expect(res.data?.workspace.projects.cursor).to.be.null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should respect search filters', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
filter: {
|
||||
search: 'Query Workspace Project - #0'
|
||||
}
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.projects.items.length).to.equal(1)
|
||||
expect(res.data?.workspace.projects.totalCount).to.equal(1)
|
||||
expect(res.data?.workspace.projects.cursor).to.be.ok
|
||||
|
||||
const project = res.data?.workspace.projects.items[0]
|
||||
expect(project).to.exist
|
||||
expect(project?.name).to.equal('Query Workspace Project - #0')
|
||||
})
|
||||
|
||||
it('should respect withProjectRoleOnly flag', async () => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: workspaceMember.id
|
||||
})
|
||||
const res = await apollo.execute(GetWorkspaceProjectsDocument, {
|
||||
id: queryWorkspace.id,
|
||||
filter: {
|
||||
withProjectRoleOnly: true
|
||||
}
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.workspace.projects
|
||||
expect(collection).to.be.ok
|
||||
expect(collection?.items.length).to.equal(1)
|
||||
expect(collection?.items[0].id).to.equal(projects[0].id)
|
||||
expect(collection?.totalCount).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should return workspace info on project types', async () => {
|
||||
const res = await apollo.execute(ActiveUserProjectsWorkspaceDocument, {})
|
||||
describe('for a specific one', () => {
|
||||
const randomServerGuy: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'Random Server Guy',
|
||||
email: ''
|
||||
}
|
||||
|
||||
const projects = res.data?.activeUser?.projects.items
|
||||
before(async () => {
|
||||
await createTestUser(randomServerGuy)
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(projects).to.exist
|
||||
expect(projects?.every((project) => !!project?.workspace?.id)).to.be.ok
|
||||
// projects at the end have no explicit project assignments,
|
||||
// and first X ones are explicitly assigned to guest user
|
||||
const implicitProject = () => projects.at(-1)!
|
||||
const explicitGuestProject = () => projects.at(0)!
|
||||
|
||||
it('it should be accessible to workspace member', async () => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: workspaceMember.id
|
||||
})
|
||||
const res = await apollo.execute(GetProjectDocument, {
|
||||
id: implicitProject().id
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.project.id).to.be.ok
|
||||
})
|
||||
|
||||
it('it should not be accessible to random outside workspace guy', async () => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: randomServerGuy.id
|
||||
})
|
||||
const res = await apollo.execute(GetProjectDocument, {
|
||||
id: implicitProject().id
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors()
|
||||
expect(res.data?.project).to.not.be.ok
|
||||
})
|
||||
|
||||
itEach(
|
||||
[{ explicit: false }, { explicit: true }],
|
||||
({ explicit }) =>
|
||||
explicit
|
||||
? 'it should be accessible to workspace guest with explicit project role'
|
||||
: 'it should not be accessible to workspace guest without explicit project role',
|
||||
async ({ explicit }) => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: workspaceGuest.id
|
||||
})
|
||||
const res = await apollo.execute(GetProjectDocument, {
|
||||
id: explicit ? explicitGuestProject().id : implicitProject().id
|
||||
})
|
||||
|
||||
if (explicit) {
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.project.id).to.be.ok
|
||||
} else {
|
||||
expect(res).to.haveGraphQLErrors()
|
||||
expect(res.data?.project).to.not.be.ok
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
itEach(
|
||||
[{ adminOverrideEnabled: true }, { adminOverrideEnabled: false }],
|
||||
({ adminOverrideEnabled }) =>
|
||||
adminOverrideEnabled
|
||||
? 'it should return project for server admins if override enabled'
|
||||
: 'it should not return project for server admins if override disabled',
|
||||
async ({ adminOverrideEnabled }) => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: serverAdminUser.id
|
||||
})
|
||||
|
||||
adminOverrideMock.enable(adminOverrideEnabled)
|
||||
const res = await apollo.execute(GetProjectDocument, {
|
||||
id: implicitProject().id
|
||||
})
|
||||
|
||||
if (adminOverrideEnabled) {
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.project.id).to.be.ok
|
||||
} else {
|
||||
expect(res).to.haveGraphQLErrors()
|
||||
expect(res.data?.project).to.not.be.ok
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('through ActiveUser.projects', () => {
|
||||
let apollo: TestApolloServer
|
||||
|
||||
before(async () => {
|
||||
apollo = await testApolloServer({
|
||||
authUserId: workspaceGuest.id
|
||||
})
|
||||
})
|
||||
|
||||
it('should return all projects user is explicitly assigned to', async () => {
|
||||
// guest
|
||||
const apolloGuest = await testApolloServer({
|
||||
authUserId: workspaceGuest.id
|
||||
})
|
||||
const guestRes = await apolloGuest.execute(
|
||||
ActiveUserProjectsWorkspaceDocument,
|
||||
{ limit: 999 },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const guestCollection = guestRes.data?.activeUser?.projects
|
||||
const expectedGuestCount = GUEST_PROJECT_COUNT + NON_WORKSPACE_PROJECT_COUNT
|
||||
expect(guestCollection).to.be.ok
|
||||
expect(guestCollection!.totalCount).to.equal(expectedGuestCount)
|
||||
expect(guestCollection!.items.length).to.equal(expectedGuestCount)
|
||||
expect(
|
||||
guestCollection!.items.map((i) => i.workspace?.id).filter(isNonNullable)
|
||||
).to.have.length(GUEST_PROJECT_COUNT)
|
||||
|
||||
// member
|
||||
const apolloMember = await testApolloServer({
|
||||
authUserId: workspaceMember.id
|
||||
})
|
||||
const memberRes = await apolloMember.execute(
|
||||
ActiveUserProjectsWorkspaceDocument,
|
||||
{ limit: 999 },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
const memberCollection = memberRes.data?.activeUser?.projects
|
||||
const expectedMemberCount = 2 // only 2 explicit assignments
|
||||
expect(memberCollection).to.be.ok
|
||||
expect(memberCollection!.totalCount).to.equal(expectedMemberCount)
|
||||
expect(memberCollection!.items.length).to.equal(expectedMemberCount)
|
||||
expect([
|
||||
memberCollection!.items[0].id,
|
||||
memberCollection!.items[1].id
|
||||
]).to.deep.equalInAnyOrder([nonWorkspaceProjects[0].id, projects[0].id])
|
||||
})
|
||||
|
||||
it('should only return workspace projects if filter set', async () => {
|
||||
const res = await apollo.execute(ActiveUserProjectsWorkspaceDocument, {
|
||||
filter: {
|
||||
workspaceId: queryWorkspace.id
|
||||
},
|
||||
limit: 999
|
||||
})
|
||||
|
||||
const expectedCount = GUEST_PROJECT_COUNT
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.activeUser?.projects
|
||||
expect(collection).to.be.ok
|
||||
expect(collection?.items.length).to.equal(expectedCount)
|
||||
expect(collection?.totalCount).to.equal(expectedCount)
|
||||
expect(
|
||||
collection?.items.map((i) => i.workspace?.id).filter(isNonNullable)
|
||||
).to.have.length(expectedCount)
|
||||
})
|
||||
|
||||
it('should only return non-workspace projects if filter set', async () => {
|
||||
const res = await apollo.execute(ActiveUserProjectsWorkspaceDocument, {
|
||||
filter: {
|
||||
personalOnly: true
|
||||
},
|
||||
limit: 999
|
||||
})
|
||||
|
||||
const expectedCount = NON_WORKSPACE_PROJECT_COUNT
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
const collection = res.data?.activeUser?.projects
|
||||
expect(collection).to.be.ok
|
||||
expect(collection?.items.length).to.equal(expectedCount)
|
||||
expect(collection?.totalCount).to.equal(expectedCount)
|
||||
expect(
|
||||
collection?.items.map((i) => i.workspace?.id).filter((v) => !v)
|
||||
).to.have.length(expectedCount)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { createRandomEmail } from '@/modules/core/helpers/testHelpers'
|
||||
import { intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory } from '@/modules/workspaces/repositories/projects'
|
||||
import {
|
||||
assignToWorkspaces,
|
||||
BasicTestWorkspace,
|
||||
createTestWorkspace
|
||||
} from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { BasicTestUser, createTestUser, createTestUsers } from '@/test/authHelper'
|
||||
import {
|
||||
addAllToStream,
|
||||
BasicTestStream,
|
||||
createTestStream
|
||||
} from '@/test/speckle-helpers/streamHelper'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { db } from '@/db/knex'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory returns a function, that', () => {
|
||||
const adminUser: BasicTestUser = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'Mr. Workspace'
|
||||
}
|
||||
|
||||
const projectUsers: BasicTestUser[] = [
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John A. Speckle'
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John B. Speckle'
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John C. Speckle'
|
||||
}
|
||||
]
|
||||
|
||||
const workspaceUsers: BasicTestUser[] = [
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John X. Speckle'
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John Y. Speckle'
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'John Z. Speckle'
|
||||
}
|
||||
]
|
||||
|
||||
const project: BasicTestStream = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: cryptoRandomString({ length: 9 }),
|
||||
isPublic: true
|
||||
}
|
||||
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: cryptoRandomString({ length: 9 }),
|
||||
slug: ''
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await createTestUser(adminUser)
|
||||
await createTestUsers([...projectUsers, ...workspaceUsers])
|
||||
|
||||
await createTestStream(project, adminUser)
|
||||
await addAllToStream(project, projectUsers)
|
||||
await createTestWorkspace(workspace, adminUser)
|
||||
await assignToWorkspaces(workspaceUsers.map((user) => [workspace, user, null]))
|
||||
})
|
||||
|
||||
it('returns users that are project members but not members of the target workspace', async () => {
|
||||
const result = await intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory({
|
||||
db
|
||||
})({
|
||||
projectId: project.id,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
expect(result.length).to.equal(3)
|
||||
expect(
|
||||
result.every((resultUser) =>
|
||||
projectUsers.some((projectUser) => projectUser.id === resultUser.id)
|
||||
)
|
||||
).to.equal(true)
|
||||
})
|
||||
|
||||
it('does not return project users that are already members of the workspace', async () => {
|
||||
const result = await intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory({
|
||||
db
|
||||
})({
|
||||
projectId: project.id,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
expect(
|
||||
result.some((resultUser) =>
|
||||
workspaceUsers.some((workspaceUser) => workspaceUser.id === resultUser.id)
|
||||
)
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
it('does not return workspace admin or project owner', async () => {
|
||||
const result = await intersectProjectCollaboratorsAndWorkspaceCollaboratorsFactory({
|
||||
db
|
||||
})({
|
||||
projectId: project.id,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
expect(result.some((user) => user.id === adminUser.id)).to.equal(false)
|
||||
})
|
||||
})
|
||||
@@ -56,11 +56,46 @@ import {
|
||||
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
|
||||
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getUserFactory } from '@/modules/core/repositories/users'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { itEach } from '@/test/assertionHelper'
|
||||
import { assignWorkspaceSeatFactory } from '@/modules/workspaces/services/workspaceSeat'
|
||||
import { createWorkspaceSeatFactory } from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
|
||||
|
||||
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
|
||||
const validateAndCreateUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
sendEmail,
|
||||
renderEmail
|
||||
})
|
||||
})
|
||||
const { FF_GATEKEEPER_FORCE_FREE_PLAN } = getFeatureFlags()
|
||||
|
||||
describe('Workspaces GQL CRUD', () => {
|
||||
@@ -883,6 +918,107 @@ describe('Workspaces GQL CRUD', () => {
|
||||
expect(getRes.data?.workspace?.name).to.equal(workspaceName)
|
||||
expect(getRes.data?.workspace?.slug).to.equal(workspaceSlug)
|
||||
})
|
||||
|
||||
describe('when attempting to enable domain discoverability', () => {
|
||||
const guyWithNoVerifiedEmails: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'Guy with no verified emails',
|
||||
email: 'guy-with-no-verified-emails@bozo1.org',
|
||||
verified: false
|
||||
}
|
||||
|
||||
const guyWithMultipleVerifiedEmails: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'Guy with multiple verified emails',
|
||||
email: 'guy-with-multiple-verified-emails@bozo2.org',
|
||||
verified: true
|
||||
}
|
||||
|
||||
const guyWithOneVerifiedEmail: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'Guy with one verified email',
|
||||
email: 'guy-with-one-verified-email@bozo3.org',
|
||||
verified: true
|
||||
}
|
||||
|
||||
const guyWithOneBlockedVerifiedEmail: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'Guy with one blocked verified email',
|
||||
email: 'guy-with-one-blocked-verified-email@gmail.com',
|
||||
verified: true,
|
||||
allowPersonalEmail: true
|
||||
}
|
||||
|
||||
const getDomain = (user: BasicTestUser) => user.email.split('@')[1]
|
||||
|
||||
before(async () => {
|
||||
await createTestUsers([
|
||||
guyWithNoVerifiedEmails,
|
||||
guyWithMultipleVerifiedEmails,
|
||||
guyWithOneVerifiedEmail,
|
||||
guyWithOneBlockedVerifiedEmail
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
validateAndCreateUserEmail({
|
||||
userEmail: {
|
||||
userId: guyWithMultipleVerifiedEmails.id,
|
||||
email: 'guy-with-multiple-verified-emails@bozo22.org',
|
||||
verified: true
|
||||
}
|
||||
}),
|
||||
validateAndCreateUserEmail({
|
||||
userEmail: {
|
||||
userId: guyWithMultipleVerifiedEmails.id,
|
||||
email: 'guy-with-multiple-verified-emails@bozo23.org',
|
||||
verified: true
|
||||
}
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
itEach(
|
||||
[guyWithOneVerifiedEmail, guyWithMultipleVerifiedEmails],
|
||||
(user) => `${user.name} can create with enabled domain discoverability`,
|
||||
async (user) => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: user.id
|
||||
})
|
||||
const createRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: {
|
||||
name: `${user.name} Domain Discoverability Workspace`,
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
enableDomainDiscoverabilityForDomain: getDomain(user)
|
||||
}
|
||||
})
|
||||
|
||||
expect(createRes).to.not.haveGraphQLErrors()
|
||||
expect(createRes.data?.workspaceMutations.create.id).to.be.ok
|
||||
expect(createRes.data!.workspaceMutations.create.discoverabilityEnabled).to
|
||||
.be.true
|
||||
}
|
||||
)
|
||||
|
||||
itEach(
|
||||
[guyWithNoVerifiedEmails, guyWithOneBlockedVerifiedEmail],
|
||||
(user) => `${user.name} can not create with enabled domain discoverability`,
|
||||
async (user) => {
|
||||
const apollo = await testApolloServer({
|
||||
authUserId: user.id
|
||||
})
|
||||
const createRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: {
|
||||
name: `${user.name} Domain Discoverability Workspace`,
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
enableDomainDiscoverabilityForDomain: getDomain(user)
|
||||
}
|
||||
})
|
||||
|
||||
expect(createRes).to.haveGraphQLErrors()
|
||||
expect(createRes.data?.workspaceMutations.create).to.not.be.ok
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutation workspaceMutations.delete', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const workspaceEventNamespace = 'workspace' as const
|
||||
const eventPrefix = `${workspaceEventNamespace}.` as const
|
||||
|
||||
export const WorkspaceEvents = {
|
||||
Authorized: `${eventPrefix}authorized`,
|
||||
Authorizing: `${eventPrefix}authorizing`,
|
||||
Created: `${eventPrefix}created`,
|
||||
Updated: `${eventPrefix}updated`,
|
||||
Deleted: `${eventPrefix}deleted`,
|
||||
@@ -47,7 +47,7 @@ type WorkspaceJoinedFromDiscoveryPayload = {
|
||||
}
|
||||
|
||||
export type WorkspaceEventsPayloads = {
|
||||
[WorkspaceEvents.Authorized]: WorkspaceAuthorizedPayload
|
||||
[WorkspaceEvents.Authorizing]: WorkspaceAuthorizedPayload
|
||||
[WorkspaceEvents.Created]: WorkspaceCreatedPayload
|
||||
[WorkspaceEvents.Updated]: WorkspaceUpdatedPayload
|
||||
[WorkspaceEvents.Deleted]: { workspaceId: string }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { WorkspaceAcl, WorkspaceSeat } from '@/modules/workspacesCore/domain/types'
|
||||
import { Nullable } from '@speckle/shared'
|
||||
|
||||
export type GetWorkspaceRolesAndSeats = (params: {
|
||||
workspaceId: string
|
||||
userIds?: string[]
|
||||
}) => Promise<{
|
||||
[userId: string]: {
|
||||
role: WorkspaceAcl
|
||||
seat: Nullable<WorkspaceSeat>
|
||||
userId: string
|
||||
}
|
||||
}>
|
||||
|
||||
export type GetWorkspaceRoleAndSeat = (params: {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
}) => Promise<
|
||||
| {
|
||||
role: WorkspaceAcl
|
||||
seat: Nullable<WorkspaceSeat>
|
||||
userId: string
|
||||
}
|
||||
| undefined
|
||||
>
|
||||
@@ -72,3 +72,18 @@ export type WorkspaceJoinRequest = {
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export const WorkspaceSeatType = <const>{
|
||||
Viewer: 'viewer',
|
||||
Editor: 'editor'
|
||||
}
|
||||
export type WorkspaceSeatType =
|
||||
(typeof WorkspaceSeatType)[keyof typeof WorkspaceSeatType]
|
||||
|
||||
export type WorkspaceSeat = {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
type: WorkspaceSeatType
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
@@ -36,3 +36,11 @@ export const WorkspaceJoinRequests = buildTableHelper('workspace_join_requests',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
])
|
||||
|
||||
export const WorkspaceSeats = buildTableHelper('workspace_seats', [
|
||||
'workspaceId',
|
||||
'userId',
|
||||
'type',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
])
|
||||
|
||||
@@ -40,3 +40,5 @@ export type WorkspaceCollaboratorGraphQLReturn = WorkspaceTeamMember
|
||||
export type WorkspacePermissionChecksGraphQLReturn = {
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunGraphQLReturn = LimitedUserRecord[]
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { formatJsonArrayRecords } from '@/modules/shared/helpers/dbHelper'
|
||||
import {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats
|
||||
} from '@/modules/workspacesCore/domain/operations'
|
||||
import {
|
||||
WorkspaceSeat,
|
||||
WorkspaceAcl as WorkspaceAclRecord
|
||||
} from '@/modules/workspacesCore/domain/types'
|
||||
import { WorkspaceAcl, WorkspaceSeats } from '@/modules/workspacesCore/helpers/db'
|
||||
import { Knex } from 'knex'
|
||||
|
||||
const tables = {
|
||||
workspaceSeats: (db: Knex) => db<WorkspaceSeat>(WorkspaceSeats.name),
|
||||
workspaceAcl: (db: Knex) => db<WorkspaceAclRecord>(WorkspaceAcl.name)
|
||||
}
|
||||
|
||||
export const getWorkspaceRolesAndSeatsFactory =
|
||||
(deps: { db: Knex }): GetWorkspaceRolesAndSeats =>
|
||||
async ({ workspaceId, userIds }) => {
|
||||
const q = tables
|
||||
.workspaceAcl(deps.db)
|
||||
.select<Array<{ seats: WorkspaceSeat[]; roles: WorkspaceAclRecord[] }>>([
|
||||
// There's only ever gonna be 1 role and seat per user, but this way we can avoid having to group
|
||||
// by many columns and we can get everything in 1 query
|
||||
WorkspaceAcl.groupArray('roles'),
|
||||
WorkspaceSeats.groupArray('seats')
|
||||
])
|
||||
.leftJoin(WorkspaceSeats.name, (j1) => {
|
||||
j1.on(WorkspaceSeats.col.userId, WorkspaceAcl.col.userId).andOnVal(
|
||||
WorkspaceSeats.col.workspaceId,
|
||||
workspaceId
|
||||
)
|
||||
})
|
||||
.where(WorkspaceAcl.col.workspaceId, workspaceId)
|
||||
.groupBy(WorkspaceAcl.col.userId)
|
||||
|
||||
if (userIds?.length) {
|
||||
q.whereIn(WorkspaceAcl.col.userId, userIds)
|
||||
}
|
||||
|
||||
const res = await q
|
||||
return res.reduce((acc, row) => {
|
||||
const role = formatJsonArrayRecords(row.roles)[0]
|
||||
if (!role) return acc
|
||||
|
||||
acc[role.userId] = {
|
||||
role,
|
||||
seat: formatJsonArrayRecords(row.seats || [])[0] || null,
|
||||
userId: role.userId
|
||||
}
|
||||
return acc
|
||||
}, {} as Awaited<ReturnType<GetWorkspaceRolesAndSeats>>)
|
||||
}
|
||||
|
||||
export const getWorkspaceRoleAndSeatFactory =
|
||||
(deps: { db: Knex }): GetWorkspaceRoleAndSeat =>
|
||||
async ({ workspaceId, userId }) => {
|
||||
const getWorkspaceRolesAndSeats = getWorkspaceRolesAndSeatsFactory(deps)
|
||||
const rolesAndSeats = await getWorkspaceRolesAndSeats({
|
||||
workspaceId,
|
||||
userIds: [userId]
|
||||
})
|
||||
return rolesAndSeats[userId]
|
||||
}
|
||||
@@ -139,12 +139,12 @@
|
||||
"@apollo/rover": "^0.23.0",
|
||||
"@bull-board/express": "^4.2.2",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@graphql-codegen/cli": "^5.0.3",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.11",
|
||||
"@graphql-codegen/typescript": "^4.1.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.3.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.4.0",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@graphql-codegen/cli": "^5.0.5",
|
||||
"@graphql-codegen/typed-document-node": "^5.1.1",
|
||||
"@graphql-codegen/typescript": "^4.1.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.6.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.5.0",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@swc/core": "^1.11.11",
|
||||
"@tiptap/core": "^2.0.0-beta.176",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
|
||||
@@ -85,6 +85,10 @@ export type BasicTestUser = {
|
||||
*/
|
||||
id: string
|
||||
role?: ServerRoles
|
||||
/**
|
||||
* Even if disabled server-wide, allow personal emails for this user
|
||||
*/
|
||||
allowPersonalEmail?: boolean
|
||||
} & Partial<UserRecord>
|
||||
|
||||
const initTestUser = (user: Partial<BasicTestUser>): BasicTestUser => ({
|
||||
@@ -119,7 +123,10 @@ export async function createTestUser(userObj?: Partial<BasicTestUser>) {
|
||||
setVal('email', `${kebabCase(baseUser.name)}@example.org`)
|
||||
}
|
||||
|
||||
const id = await createUser(omit(baseUser, ['id']), { skipPropertyValidation: true })
|
||||
const id = await createUser(omit(baseUser, ['id', 'allowPersonalEmail']), {
|
||||
skipPropertyValidation: true,
|
||||
allowPersonalEmail: baseUser.allowPersonalEmail
|
||||
})
|
||||
setVal('id', id)
|
||||
|
||||
return baseUser
|
||||
|
||||
@@ -2012,6 +2012,8 @@ export type Project = {
|
||||
* real or fake (e.g., with a foo/bar model, it will be nested under foo even if such a model doesn't actually exist)
|
||||
*/
|
||||
modelsTree: ModelsTreeItemCollection;
|
||||
/** Returns information about the potential effects of moving a project to a given workspace. */
|
||||
moveToWorkspaceDryRun: ProjectMoveToWorkspaceDryRun;
|
||||
name: Scalars['String']['output'];
|
||||
object?: Maybe<Object>;
|
||||
/** Pending project access requests */
|
||||
@@ -2110,6 +2112,11 @@ export type ProjectModelsTreeArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectObjectArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
@@ -2430,6 +2437,17 @@ export const ProjectModelsUpdatedMessageType = {
|
||||
} as const;
|
||||
|
||||
export type ProjectModelsUpdatedMessageType = typeof ProjectModelsUpdatedMessageType[keyof typeof ProjectModelsUpdatedMessageType];
|
||||
export type ProjectMoveToWorkspaceDryRun = {
|
||||
__typename?: 'ProjectMoveToWorkspaceDryRun';
|
||||
addedToWorkspace: Array<LimitedUser>;
|
||||
addedToWorkspaceTotalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunAddedToWorkspaceArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectMutations = {
|
||||
__typename?: 'ProjectMutations';
|
||||
/** Access request related mutations */
|
||||
@@ -4039,8 +4057,11 @@ export type UserProjectCollection = {
|
||||
export type UserProjectsFilter = {
|
||||
/** Only include projects where user has the specified roles */
|
||||
onlyWithRoles?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
/** Only include personal projects (not in any workspace) */
|
||||
personalOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only include projects in the specified workspace */
|
||||
workspaceId?: InputMaybe<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
@@ -4459,6 +4480,8 @@ export type WorkspaceCollection = {
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Add this domain to the workspace as a verified domain and enable domain discoverability */
|
||||
enableDomainDiscoverabilityForDomain?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Logo image as base64-encoded string */
|
||||
logo?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -4822,6 +4845,8 @@ export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
export type WorkspaceProjectsFilter = {
|
||||
/** Filter out projects by name */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Only return workspace projects that the active user has an explicit project role in */
|
||||
withProjectRoleOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectsUpdatedMessage = {
|
||||
@@ -5948,7 +5973,7 @@ export type MarkProjectVersionReceivedMutationVariables = Exact<{
|
||||
|
||||
export type MarkProjectVersionReceivedMutation = { __typename?: 'Mutation', versionMutations: { __typename?: 'VersionMutations', markReceived: boolean } };
|
||||
|
||||
export type TestWorkspaceFragment = { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean };
|
||||
export type TestWorkspaceFragment = { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean };
|
||||
|
||||
export type TestWorkspaceCollaboratorFragment = { __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', name: string }, projectRoles: Array<{ __typename?: 'ProjectRole', role: string, project: { __typename?: 'Project', id: string, name: string } }> };
|
||||
|
||||
@@ -5959,7 +5984,7 @@ export type CreateWorkspaceMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', create: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean } } };
|
||||
export type CreateWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', create: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean } } };
|
||||
|
||||
export type DeleteWorkspaceMutationVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
@@ -5973,14 +5998,14 @@ export type GetWorkspaceQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWorkspaceQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', name: string }, projectRoles: Array<{ __typename?: 'ProjectRole', role: string, project: { __typename?: 'Project', id: string, name: string } }> }> } } };
|
||||
export type GetWorkspaceQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', name: string }, projectRoles: Array<{ __typename?: 'ProjectRole', role: string, project: { __typename?: 'Project', id: string, name: string } }> }> } } };
|
||||
|
||||
export type GetWorkspaceBySlugQueryVariables = Exact<{
|
||||
workspaceSlug: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWorkspaceBySlugQuery = { __typename?: 'Query', workspaceBySlug: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', name: string }, projectRoles: Array<{ __typename?: 'ProjectRole', role: string, project: { __typename?: 'Project', id: string, name: string } }> }> } } };
|
||||
export type GetWorkspaceBySlugQuery = { __typename?: 'Query', workspaceBySlug: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', name: string }, projectRoles: Array<{ __typename?: 'ProjectRole', role: string, project: { __typename?: 'Project', id: string, name: string } }> }> } } };
|
||||
|
||||
export type GetActiveUserDiscoverableWorkspacesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -5992,12 +6017,12 @@ export type UpdateWorkspaceMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', update: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean } } };
|
||||
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', update: { __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean } } };
|
||||
|
||||
export type GetActiveUserWorkspacesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetActiveUserWorkspacesQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', workspaces: { __typename?: 'WorkspaceCollection', items: Array<{ __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean }> } } | null };
|
||||
export type GetActiveUserWorkspacesQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', workspaces: { __typename?: 'WorkspaceCollection', items: Array<{ __typename?: 'Workspace', id: string, name: string, slug: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, readOnly: boolean, discoverabilityEnabled: boolean }> } } | null };
|
||||
|
||||
export type UpdateWorkspaceRoleMutationVariables = Exact<{
|
||||
input: WorkspaceRoleUpdateInput;
|
||||
@@ -6047,10 +6072,14 @@ export type ActiveUserLeaveWorkspaceMutationVariables = Exact<{
|
||||
|
||||
export type ActiveUserLeaveWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', leave: boolean } };
|
||||
|
||||
export type ActiveUserProjectsWorkspaceQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
export type ActiveUserProjectsWorkspaceQueryVariables = Exact<{
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserProjectsFilter>;
|
||||
}>;
|
||||
|
||||
|
||||
export type ActiveUserProjectsWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'UserProjectCollection', items: Array<{ __typename?: 'Project', id: string, workspace?: { __typename?: 'Workspace', id: string, name: string } | null }> } } | null };
|
||||
export type ActiveUserProjectsWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'UserProjectCollection', totalCount: number, items: Array<{ __typename?: 'Project', id: string, workspace?: { __typename?: 'Workspace', id: string, name: string } | null }> } } | null };
|
||||
|
||||
export type ActiveUserExpiredSsoSessionsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -6082,7 +6111,7 @@ export const BasicStreamFieldsFragmentDoc = {"kind":"Document","definitions":[{"
|
||||
export const UserWithEmailsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserWithEmails"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"emails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"primary"}}]}}]}}]} as unknown as DocumentNode<UserWithEmailsFragment, unknown>;
|
||||
export const BaseUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BaseUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode<BaseUserFieldsFragment, unknown>;
|
||||
export const BaseLimitedUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BaseLimitedUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}}]}}]} as unknown as DocumentNode<BaseLimitedUserFieldsFragment, unknown>;
|
||||
export const TestWorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<TestWorkspaceFragment, unknown>;
|
||||
export const TestWorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}}]} as unknown as DocumentNode<TestWorkspaceFragment, unknown>;
|
||||
export const TestWorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<TestWorkspaceCollaboratorFragment, unknown>;
|
||||
export const TestWorkspaceProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode<TestWorkspaceProjectFragment, unknown>;
|
||||
export const CreateObjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateObject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<CreateObjectMutation, CreateObjectMutationVariables>;
|
||||
@@ -6216,19 +6245,19 @@ export const UserActiveResourcesDocument = {"kind":"Document","definitions":[{"k
|
||||
export const SetUserActiveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetUserActiveWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isProjectsActive"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUserMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setActiveWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}},{"kind":"Argument","name":{"kind":"Name","value":"isProjectsActive"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isProjectsActive"}}}]}]}}]}}]} as unknown as DocumentNode<SetUserActiveWorkspaceMutation, SetUserActiveWorkspaceMutationVariables>;
|
||||
export const CreateProjectVersionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectVersion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateVersionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}}]}}]} as unknown as DocumentNode<CreateProjectVersionMutation, CreateProjectVersionMutationVariables>;
|
||||
export const MarkProjectVersionReceivedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkProjectVersionReceived"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MarkReceivedVersionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markReceived"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<MarkProjectVersionReceivedMutation, MarkProjectVersionReceivedMutationVariables>;
|
||||
export const CreateWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<CreateWorkspaceMutation, CreateWorkspaceMutationVariables>;
|
||||
export const CreateWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}}]} as unknown as DocumentNode<CreateWorkspaceMutation, CreateWorkspaceMutationVariables>;
|
||||
export const DeleteWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"delete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}]}]}}]}}]} as unknown as DocumentNode<DeleteWorkspaceMutation, DeleteWorkspaceMutationVariables>;
|
||||
export const GetWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceQuery, GetWorkspaceQueryVariables>;
|
||||
export const GetWorkspaceBySlugDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceBySlug"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceBySlugQuery, GetWorkspaceBySlugQueryVariables>;
|
||||
export const GetWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceQuery, GetWorkspaceQueryVariables>;
|
||||
export const GetWorkspaceBySlugDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceBySlug"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceBySlugQuery, GetWorkspaceBySlugQueryVariables>;
|
||||
export const GetActiveUserDiscoverableWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getActiveUserDiscoverableWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"discoverableWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetActiveUserDiscoverableWorkspacesQuery, GetActiveUserDiscoverableWorkspacesQueryVariables>;
|
||||
export const UpdateWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<UpdateWorkspaceMutation, UpdateWorkspaceMutationVariables>;
|
||||
export const GetActiveUserWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<GetActiveUserWorkspacesQuery, GetActiveUserWorkspacesQueryVariables>;
|
||||
export const UpdateWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}}]} as unknown as DocumentNode<UpdateWorkspaceMutation, UpdateWorkspaceMutationVariables>;
|
||||
export const GetActiveUserWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspace"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}}]}}]} as unknown as DocumentNode<GetActiveUserWorkspacesQuery, GetActiveUserWorkspacesQueryVariables>;
|
||||
export const UpdateWorkspaceRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceRoleUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateRole"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<UpdateWorkspaceRoleMutation, UpdateWorkspaceRoleMutationVariables>;
|
||||
export const CreateWorkspaceProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspaceProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceProject"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode<CreateWorkspaceProjectMutation, CreateWorkspaceProjectMutationVariables>;
|
||||
export const GetWorkspaceProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceProject"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceProjectsQuery, GetWorkspaceProjectsQueryVariables>;
|
||||
export const GetWorkspaceSsoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceSso"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sso"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"provider"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"session"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceSsoQuery, GetWorkspaceSsoQueryVariables>;
|
||||
export const GetWorkspaceTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceTeamFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceTeamQuery, GetWorkspaceTeamQueryVariables>;
|
||||
export const ActiveUserLeaveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActiveUserLeaveWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leave"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]}}]} as unknown as DocumentNode<ActiveUserLeaveWorkspaceMutation, ActiveUserLeaveWorkspaceMutationVariables>;
|
||||
export const ActiveUserProjectsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserProjectsWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<ActiveUserProjectsWorkspaceQuery, ActiveUserProjectsWorkspaceQueryVariables>;
|
||||
export const ActiveUserProjectsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserProjectsWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<ActiveUserProjectsWorkspaceQuery, ActiveUserProjectsWorkspaceQueryVariables>;
|
||||
export const ActiveUserExpiredSsoSessionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserExpiredSsoSessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"expiredSsoSessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]}}]} as unknown as DocumentNode<ActiveUserExpiredSsoSessionsQuery, ActiveUserExpiredSsoSessionsQueryVariables>;
|
||||
export const MoveProjectToWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveProjectToWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveToWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<MoveProjectToWorkspaceMutation, MoveProjectToWorkspaceMutationVariables>;
|
||||
@@ -10,6 +10,7 @@ export const workspaceFragment = gql`
|
||||
updatedAt
|
||||
logo
|
||||
readOnly
|
||||
discoverabilityEnabled
|
||||
}
|
||||
`
|
||||
|
||||
@@ -229,9 +230,14 @@ export const leaveWorkspaceMutation = gql`
|
||||
`
|
||||
|
||||
export const getProjectWorkspaceQuery = gql`
|
||||
query ActiveUserProjectsWorkspace {
|
||||
query ActiveUserProjectsWorkspace(
|
||||
$limit: Int
|
||||
$cursor: String
|
||||
$filter: UserProjectsFilter
|
||||
) {
|
||||
activeUser {
|
||||
projects {
|
||||
projects(filter: $filter, limit: $limit, cursor: $cursor) {
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
workspace {
|
||||
|
||||
@@ -27,3 +27,25 @@ export const MultiRegionConfigMock = mockRequireModule<
|
||||
export const StripeClientMock = mockRequireModule<
|
||||
typeof import('@/modules/gatekeeper/clients/stripe')
|
||||
>(['@/modules/gatekeeper/clients/stripe'])
|
||||
|
||||
export const EnvHelperMock = mockRequireModule<
|
||||
typeof import('@/modules/shared/helpers/envHelper')
|
||||
>(
|
||||
[
|
||||
'@/modules/shared/helpers/envHelper',
|
||||
require.resolve('../../modules/shared/helpers/envHelper')
|
||||
],
|
||||
['@/modules/shared/index']
|
||||
)
|
||||
|
||||
export const mockAdminOverride = () => {
|
||||
const enable = (enabled: boolean) => {
|
||||
EnvHelperMock.mockFunction('adminOverrideEnabled', () => enabled)
|
||||
}
|
||||
|
||||
const disable = () => {
|
||||
EnvHelperMock.resetMockedFunction('adminOverrideEnabled')
|
||||
}
|
||||
|
||||
return { enable, disable }
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export type BasicTestStream = {
|
||||
export async function createTestStreams(
|
||||
streamOwnerPairs: [BasicTestStream, BasicTestUser][]
|
||||
) {
|
||||
await Promise.all(streamOwnerPairs.map((p) => createTestStream(p[0], p[1])))
|
||||
return await Promise.all(streamOwnerPairs.map((p) => createTestStream(p[0], p[1])))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,6 +145,7 @@ export async function createTestStream(
|
||||
|
||||
streamObj.id = id
|
||||
streamObj.ownerId = owner.id
|
||||
return streamObj
|
||||
}
|
||||
|
||||
export async function leaveStream(streamObj: BasicTestStream, user: BasicTestUser) {
|
||||
|
||||
@@ -13,6 +13,9 @@ const configs = [
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['**/html/**']
|
||||
},
|
||||
...tseslint.configs.recommendedTypeChecked.map((c) => ({
|
||||
...c,
|
||||
files: [...(c.files || []), '**/*.ts', '**/*.d.ts']
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { hasMinimumServerRole, canUseAdminOverride } from './serverRole.js'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { parseFeatureFlags } from '../../environment/index.js'
|
||||
|
||||
describe('hasMinimumServerRole returns a function, that', () => {
|
||||
it('turns non existing server roles into false ', async () => {
|
||||
@@ -33,7 +32,7 @@ describe('hasMinimumServerRole returns a function, that', () => {
|
||||
describe('canUseAdminOverride returns a function, that', () => {
|
||||
it('returns false for admins if admin override is not enabled', async () => {
|
||||
const result = await canUseAdminOverride({
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getServerRole: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
@@ -42,21 +41,21 @@ describe('canUseAdminOverride returns a function, that', () => {
|
||||
})
|
||||
it('returns false for non admins if admin override is not enabled', async () => {
|
||||
const result = await canUseAdminOverride({
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getServerRole: async () => 'server:user'
|
||||
})({ userId: cryptoRandomString({ length: 10 }) })
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
it('returns false for non admins if admin override is enabled', async () => {
|
||||
const result = await canUseAdminOverride({
|
||||
getEnv: async () => parseFeatureFlags({ FF_ADMIN_OVERRIDE_ENABLED: 'true' }),
|
||||
getAdminOverrideEnabled: async () => true,
|
||||
getServerRole: async () => 'server:user'
|
||||
})({ userId: cryptoRandomString({ length: 10 }) })
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
it('returns true for admins if admin override is enabled', async () => {
|
||||
const result = await canUseAdminOverride({
|
||||
getEnv: async () => parseFeatureFlags({ FF_ADMIN_OVERRIDE_ENABLED: 'true' }),
|
||||
getAdminOverrideEnabled: async () => true,
|
||||
getServerRole: async () => 'server:admin'
|
||||
})({ userId: cryptoRandomString({ length: 10 }) })
|
||||
expect(result).toEqual(true)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Roles, ServerRoles } from '../../core/constants.js'
|
||||
import { UserContext } from '../domain/context.js'
|
||||
import { Loaders } from '../domain/loaders.js'
|
||||
import { isMinimumServerRole } from '../domain/logic/roles.js'
|
||||
import { AuthPolicyCheck } from '../domain/policies.js'
|
||||
|
||||
@@ -15,13 +16,13 @@ export const hasMinimumServerRole: AuthPolicyCheck<
|
||||
}
|
||||
|
||||
export const canUseAdminOverride: AuthPolicyCheck<
|
||||
'getEnv' | 'getServerRole',
|
||||
typeof Loaders.getAdminOverrideEnabled | 'getServerRole',
|
||||
UserContext
|
||||
> =
|
||||
(loaders) =>
|
||||
async ({ userId }) => {
|
||||
const { FF_ADMIN_OVERRIDE_ENABLED } = await loaders.getEnv()
|
||||
if (!FF_ADMIN_OVERRIDE_ENABLED) return false
|
||||
const adminOverrideEnabled = await loaders.getAdminOverrideEnabled()
|
||||
if (!adminOverrideEnabled) return false
|
||||
return await hasMinimumServerRole(loaders)({
|
||||
userId,
|
||||
role: Roles.Server.Admin
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { get, isObjectLike } from '#lodash'
|
||||
import { ValueOf } from 'type-fest'
|
||||
import { WorkspaceLimits } from '../../workspaces/helpers/limits.js'
|
||||
|
||||
export type AuthError<ErrorCode extends string = string, Payload = undefined> = {
|
||||
readonly code: ErrorCode
|
||||
readonly message: string
|
||||
readonly payload: Payload
|
||||
}
|
||||
} & Error
|
||||
|
||||
export const defineAuthError = <
|
||||
ErrorCode extends string,
|
||||
@@ -20,10 +22,11 @@ export const defineAuthError = <
|
||||
): AuthError<ErrorCode, Payload>
|
||||
code: ErrorCode
|
||||
} => {
|
||||
return class AuthErrorClass {
|
||||
return class AuthErrorClass extends Error {
|
||||
readonly message: string
|
||||
readonly code: ErrorCode
|
||||
readonly payload: Payload
|
||||
readonly isAuthPolicyError = true
|
||||
|
||||
static code: ErrorCode = definition.code
|
||||
|
||||
@@ -33,15 +36,22 @@ export const defineAuthError = <
|
||||
: [params: { payload: Payload; message?: string }]
|
||||
) {
|
||||
const [params] = args
|
||||
const message = params?.message || definition.message
|
||||
super(message)
|
||||
|
||||
this.code = definition.code
|
||||
this.payload =
|
||||
params && 'payload' in params ? params.payload : (undefined as Payload)
|
||||
this.message = params?.message || definition.message
|
||||
this.name = definition.code + 'Error'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthPolicyError = (err: unknown): err is AuthError => {
|
||||
return isObjectLike(err) && get(err, 'isAuthPolicyError') === true
|
||||
}
|
||||
|
||||
export const ProjectNotFoundError = defineAuthError({
|
||||
code: 'ProjectNotFound',
|
||||
message: 'Project not found'
|
||||
@@ -104,3 +114,13 @@ export const ServerNoSessionError = defineAuthError({
|
||||
code: 'ServerNoSession',
|
||||
message: 'You are not logged in to this server'
|
||||
})
|
||||
|
||||
// Resolve all exported error types
|
||||
export type AllAuthErrors = ValueOf<{
|
||||
[key in keyof typeof import('./authErrors.js')]: typeof import('./authErrors.js')[key] extends new (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...args: any[]
|
||||
) => infer R
|
||||
? R
|
||||
: never
|
||||
}>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MaybeAsync } from '../../core/index.js'
|
||||
import type { GetServerRole } from './core/operations.js'
|
||||
import type { GetProject, GetProjectRole } from './projects/operations.js'
|
||||
import type {
|
||||
GetAdminOverrideEnabled,
|
||||
GetEnv,
|
||||
GetWorkspace,
|
||||
GetWorkspaceLimits,
|
||||
@@ -54,8 +55,10 @@ export const AuthCheckContextLoaderKeys = <const>{
|
||||
getWorkspacePlan: 'getWorkspacePlan',
|
||||
getWorkspaceLimits: 'getWorkspaceLimits',
|
||||
getWorkspaceSsoProvider: 'getWorkspaceSsoProvider',
|
||||
getWorkspaceSsoSession: 'getWorkspaceSsoSession'
|
||||
getWorkspaceSsoSession: 'getWorkspaceSsoSession',
|
||||
getAdminOverrideEnabled: 'getAdminOverrideEnabled'
|
||||
}
|
||||
export const Loaders = AuthCheckContextLoaderKeys // shorter alias
|
||||
/* v8 ignore end */
|
||||
|
||||
export type AuthCheckContextLoaderKeys =
|
||||
@@ -63,6 +66,7 @@ export type AuthCheckContextLoaderKeys =
|
||||
|
||||
export type AllAuthCheckContextLoaders = AuthContextLoaderMappingDefinition<{
|
||||
getEnv: GetEnv
|
||||
getAdminOverrideEnabled: GetAdminOverrideEnabled
|
||||
getProject: GetProject
|
||||
getProjectRole: GetProjectRole
|
||||
getServerRole: GetServerRole
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import Result from 'true-myth/result'
|
||||
import Unit from 'true-myth/unit'
|
||||
import { AuthError } from './authErrors.js'
|
||||
import { AllAuthErrors, AuthError } from './authErrors.js'
|
||||
import { AuthCheckContextLoaderKeys, AuthCheckContextLoaders } from './loaders.js'
|
||||
import Maybe from 'true-myth/maybe'
|
||||
|
||||
export type AuthPolicyResult<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ExpectedAuthErrors extends AuthError<any, any> = AllAuthErrors
|
||||
> = Result<Unit, ExpectedAuthErrors>
|
||||
|
||||
// a complete policy always returns a full result
|
||||
export type AuthPolicy<
|
||||
LoaderKeys extends AuthCheckContextLoaderKeys,
|
||||
|
||||
@@ -34,3 +34,5 @@ export type GetWorkspaceSsoSession = (
|
||||
) => Promise<WorkspaceSsoSession | null>
|
||||
|
||||
export type GetEnv = () => Promise<FeatureFlags>
|
||||
|
||||
export type GetAdminOverrideEnabled = () => Promise<boolean>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { maybeMemberRoleWithValidSsoSessionIfNeeded } from './workspaceSso.js'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import {
|
||||
WorkspaceNoAccessError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../domain/authErrors.js'
|
||||
import { just, nothing } from 'true-myth/maybe'
|
||||
|
||||
describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that', () => {
|
||||
it('hides non existing workspaces behind a WorkspaceNoAccessError', async () => {
|
||||
@@ -25,7 +23,9 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
await expect(result).resolves.toStrictEqual(just(err(new WorkspaceNoAccessError())))
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('returns WorkspaceNoAccessError if the user does not have a workspace role', async () => {
|
||||
const result = await maybeMemberRoleWithValidSsoSessionIfNeeded({
|
||||
@@ -44,7 +44,9 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(result).toStrictEqual(just(err(new WorkspaceNoAccessError())))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('returns nothing if user does not have a minimum workspace:member role', async () => {
|
||||
const result = await maybeMemberRoleWithValidSsoSessionIfNeeded({
|
||||
@@ -63,7 +65,7 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(result).toStrictEqual(nothing())
|
||||
expect(result).toBeNothingResult()
|
||||
})
|
||||
it('returns just(ok()) if user is a member and workspace has no SSO provider', async () => {
|
||||
const result = await maybeMemberRoleWithValidSsoSessionIfNeeded({
|
||||
@@ -80,7 +82,7 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(result).toStrictEqual(just(ok()))
|
||||
expect(result).toBeAuthOKResult()
|
||||
})
|
||||
it('returns WorkspaceSsoSessionInvalidError if user does not have an SSO session', async () => {
|
||||
const result = maybeMemberRoleWithValidSsoSessionIfNeeded({
|
||||
@@ -97,11 +99,11 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
await expect(result).resolves.toStrictEqual(
|
||||
just(
|
||||
err(new WorkspaceSsoSessionNoAccessError({ payload: { workspaceSlug: 'bbb' } }))
|
||||
)
|
||||
)
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code,
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
})
|
||||
it('returns WorkspaceSsoSessionInvalidError if user has an expired sso session', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
@@ -125,11 +127,11 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId,
|
||||
workspaceId
|
||||
})
|
||||
expect(result).toStrictEqual(
|
||||
just(
|
||||
err(new WorkspaceSsoSessionNoAccessError({ payload: { workspaceSlug: 'bbb' } }))
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code,
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
})
|
||||
it('returns true if user has a valid sso session', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
@@ -153,6 +155,6 @@ describe('maybeMemberRoleWithValidSsoSessionIfNeeded returns a function, that',
|
||||
userId,
|
||||
workspaceId
|
||||
})
|
||||
expect(result).toStrictEqual(just(ok()))
|
||||
expect(result).toBeAuthOKResult()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,3 +6,4 @@ export {
|
||||
} from './domain/loaders.js'
|
||||
export * from './helpers/graphql.js'
|
||||
export * from './domain/authErrors.js'
|
||||
export { AuthPolicyResult } from './domain/policies.js'
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { AllAuthCheckContextLoaders } from '../domain/loaders.js'
|
||||
import { canCreateWorkspaceProjectPolicy } from './canCreateWorkspaceProject.js'
|
||||
import { canReadProjectPolicy } from './canReadProject.js'
|
||||
import { canCreateWorkspaceProjectPolicy } from './workspace/canCreateWorkspaceProject.js'
|
||||
import { canReadProjectPolicy } from './project/canReadProject.js'
|
||||
import { canCreateProjectPolicy } from './project/canCreate.js'
|
||||
|
||||
export const authPoliciesFactory = (loaders: AllAuthCheckContextLoaders) => ({
|
||||
project: {
|
||||
canRead: canReadProjectPolicy(loaders)
|
||||
canRead: canReadProjectPolicy(loaders),
|
||||
canCreateLegacy: canCreateProjectPolicy(loaders)
|
||||
},
|
||||
workspace: {
|
||||
canCreateProject: canCreateWorkspaceProjectPolicy(loaders)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { canCreateProjectPolicy } from './canCreate.js'
|
||||
import { parseFeatureFlags } from '../../../environment/index.js'
|
||||
import {
|
||||
ProjectNoAccessError,
|
||||
ServerNoAccessError,
|
||||
ServerNoSessionError
|
||||
} from '../../domain/authErrors.js'
|
||||
|
||||
const buildSUT = (overrides?: Partial<Parameters<typeof canCreateProjectPolicy>[0]>) =>
|
||||
canCreateProjectPolicy({
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'false'
|
||||
}),
|
||||
getServerRole: async () => 'server:user',
|
||||
...(overrides || {})
|
||||
})
|
||||
|
||||
describe('canCreateProject', () => {
|
||||
it('returns error if user is not logged in', async () => {
|
||||
const canCreateProject = buildSUT()
|
||||
|
||||
const result = await canCreateProject({ userId: undefined })
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: ServerNoSessionError.code
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Re-enable when ready
|
||||
it.skip('returns error if workspaces module is enabled', async () => {
|
||||
const canCreateProject = buildSUT({
|
||||
getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true' })
|
||||
})
|
||||
|
||||
const result = await canCreateProject({ userId: 'user-id' })
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: ProjectNoAccessError.code
|
||||
})
|
||||
})
|
||||
|
||||
it('returns error if user is a server guest', async () => {
|
||||
const canCreateProject = buildSUT({
|
||||
getServerRole: async () => 'server:guest'
|
||||
})
|
||||
|
||||
const result = await canCreateProject({ userId: 'user-id' })
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: ServerNoAccessError.code
|
||||
})
|
||||
})
|
||||
|
||||
it('returns ok if user is a server user', async () => {
|
||||
const canCreateProject = buildSUT()
|
||||
const result = await canCreateProject({ userId: 'user-id' })
|
||||
expect(result).toBeAuthOKResult()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import {
|
||||
ProjectNoAccessError,
|
||||
ServerNoAccessError,
|
||||
ServerNoSessionError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../../domain/authErrors.js'
|
||||
import { MaybeUserContext } from '../../domain/context.js'
|
||||
import { Loaders } from '../../domain/loaders.js'
|
||||
import { AuthPolicy } from '../../domain/policies.js'
|
||||
import { hasMinimumServerRole } from '../../checks/serverRole.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
|
||||
export const canCreateProjectPolicy: AuthPolicy<
|
||||
typeof Loaders.getServerRole | typeof Loaders.getEnv,
|
||||
MaybeUserContext,
|
||||
InstanceType<
|
||||
| typeof ServerNoSessionError
|
||||
| typeof ServerNoAccessError
|
||||
| typeof ProjectNoAccessError
|
||||
| typeof WorkspaceSsoSessionNoAccessError
|
||||
>
|
||||
> =
|
||||
(loaders) =>
|
||||
async ({ userId }) => {
|
||||
const env = await loaders.getEnv()
|
||||
if (!userId?.length) return err(new ServerNoSessionError())
|
||||
if (env.FF_WORKSPACES_MODULE_ENABLED) {
|
||||
// TODO: We're not ready to enforce this yet, there's a bunch of tests that would break
|
||||
// return err(
|
||||
// new ProjectNoAccessError({
|
||||
// message: "Projects can't be created outside of workspaces"
|
||||
// })
|
||||
// )
|
||||
}
|
||||
|
||||
const isActiveServerUser = await hasMinimumServerRole(loaders)({
|
||||
userId,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
if (!isActiveServerUser) return err(new ServerNoAccessError())
|
||||
return ok()
|
||||
}
|
||||
+80
-39
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it, assert } from 'vitest'
|
||||
import { canReadProjectPolicy } from './canReadProject.js'
|
||||
import { parseFeatureFlags } from '../../environment/index.js'
|
||||
import { parseFeatureFlags } from '../../../environment/index.js'
|
||||
import crs from 'crypto-random-string'
|
||||
import { Roles } from '../../core/constants.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
import {
|
||||
ProjectNoAccessError,
|
||||
ProjectNotFoundError,
|
||||
@@ -10,11 +10,10 @@ import {
|
||||
ServerNoSessionError,
|
||||
WorkspaceNoAccessError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../domain/authErrors.js'
|
||||
import { getProjectFake } from '../../tests/fakes.js'
|
||||
import { err, ok } from 'true-myth/result'
|
||||
} from '../../domain/authErrors.js'
|
||||
import { getProjectFake } from '../../../tests/fakes.js'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { AuthCheckContextLoaders } from '../domain/loaders.js'
|
||||
import { AuthCheckContextLoaders } from '../../domain/loaders.js'
|
||||
|
||||
const canReadProjectArgs = () => {
|
||||
const projectId = crs({ length: 10 })
|
||||
@@ -32,6 +31,7 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
it('converts not found projects into ProjectNotFoundError', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getWorkspace,
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getProject: async () => null,
|
||||
getProjectRole: () => {
|
||||
@@ -51,12 +51,15 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
|
||||
await expect(result).resolves.toStrictEqual(err(new ProjectNotFoundError()))
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: ProjectNotFoundError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('project visibility', () => {
|
||||
it('allows anyone on a public project', async () => {
|
||||
const canReadProject = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getProject: getProjectFake({ isPublic: true }),
|
||||
getProjectRole: () => {
|
||||
@@ -78,10 +81,11 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
}
|
||||
})
|
||||
const canQuery = await canReadProject(canReadProjectArgs())
|
||||
expect(canQuery.isOk).toBe(true)
|
||||
expect(canQuery).toBeAuthOKResult()
|
||||
})
|
||||
it('allows anyone on a linkShareable project', async () => {
|
||||
const canReadProject = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getProject: getProjectFake({ isDiscoverable: true }),
|
||||
getProjectRole: () => {
|
||||
@@ -102,13 +106,14 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
}
|
||||
})
|
||||
const canQuery = await canReadProject(canReadProjectArgs())
|
||||
expect(canQuery.isOk).toBe(true)
|
||||
expect(canQuery).toBeAuthOKResult()
|
||||
})
|
||||
})
|
||||
|
||||
describe('server roles', () => {
|
||||
it('allows access for archived server users with a project role on a public project', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: true }),
|
||||
@@ -125,10 +130,12 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
it('does not allow access for archived server users with a project role', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
@@ -145,10 +152,14 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(err(new ServerNoAccessError()))
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: ServerNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('does not allow access for non public projects for unknown users', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
@@ -166,7 +177,10 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})({ userId: undefined, projectId: cryptoRandomString({ length: 10 }) })
|
||||
await expect(result).resolves.toStrictEqual(err(new ServerNoSessionError()))
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: ServerNoSessionError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -175,6 +189,7 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
'allows access for active server users to private projects with %s role',
|
||||
async (role) => {
|
||||
const canReadProject = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
@@ -191,12 +206,14 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})
|
||||
|
||||
const canQuery = await canReadProject(canReadProjectArgs())
|
||||
expect(canQuery.isOk).toBe(true)
|
||||
expect(canQuery).toBeAuthOKResult()
|
||||
}
|
||||
)
|
||||
it('does not allow access to private projects without a project role', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
@@ -213,13 +230,17 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(err(new ProjectNoAccessError()))
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: ProjectNoAccessError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('admin override', () => {
|
||||
it('allows server admins without project roles on private projects if admin override is enabled', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getEnv: async () => parseFeatureFlags({ FF_ADMIN_OVERRIDE_ENABLED: 'true' }),
|
||||
getAdminOverrideEnabled: async () => true,
|
||||
getEnv: async () => parseFeatureFlags({}),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
getServerRole: async () => Roles.Server.Admin,
|
||||
getProjectRole: () => {
|
||||
@@ -236,14 +257,15 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
|
||||
it('does not allow server admins without project roles on private projects if admin override is disabled', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_ADMIN_OVERRIDE_ENABLED: 'false',
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'false'
|
||||
}),
|
||||
getProject: getProjectFake({ isDiscoverable: false, isPublic: false }),
|
||||
@@ -261,12 +283,16 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(err(new ProjectNoAccessError()))
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: ProjectNoAccessError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('the workspace world', () => {
|
||||
it('does not check workspace rules if the workspaces module is not enabled', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'false' }),
|
||||
getProject: getProjectFake({
|
||||
@@ -288,10 +314,12 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
it('does not allow project access without a workspace role', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -313,10 +341,14 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('allows project access via workspace role if user does not have project role', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -335,10 +367,12 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
getWorkspace,
|
||||
getWorkspaceSsoProvider: async () => null
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
it('does not check SSO sessions if user is workspace guest', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -359,10 +393,12 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
assert.fail()
|
||||
}
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
it('does not check SSO sessions if workspace does not have it enabled', async () => {
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -381,10 +417,12 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
},
|
||||
getWorkspaceSsoProvider: async () => null
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
it('does not allow project access if SSO session is missing', async () => {
|
||||
const canReadProject = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -401,14 +439,18 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
getWorkspaceSsoSession: async () => null,
|
||||
getWorkspaceSsoProvider: async () => ({ providerId: 'foo' })
|
||||
})
|
||||
|
||||
const canQuery = await canReadProject(canReadProjectArgs())
|
||||
expect(canQuery.isOk).toBe(false)
|
||||
expect(canQuery).toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('does not allow project access if SSO session is not found', async () => {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - 1)
|
||||
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -425,19 +467,18 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
getWorkspaceSsoSession: async () => null,
|
||||
getWorkspaceSsoProvider: async () => ({ providerId: 'foo' })
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(
|
||||
err(
|
||||
new WorkspaceSsoSessionNoAccessError({
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code,
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
})
|
||||
it('does not allow project access if SSO session is expired', async () => {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - 1)
|
||||
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -458,19 +499,18 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
}),
|
||||
getWorkspaceSsoProvider: async () => ({ providerId: 'foo' })
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(
|
||||
err(
|
||||
new WorkspaceSsoSessionNoAccessError({
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
await expect(result).resolves.toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code,
|
||||
payload: { workspaceSlug: 'bbb' }
|
||||
})
|
||||
})
|
||||
it('allows project access if SSO session is valid', async () => {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + 1)
|
||||
|
||||
const result = canReadProjectPolicy({
|
||||
getAdminOverrideEnabled: async () => false,
|
||||
getEnv: async () =>
|
||||
parseFeatureFlags({
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
@@ -491,7 +531,8 @@ describe('canReadProjectPolicy creates a function, that handles ', () => {
|
||||
}),
|
||||
getWorkspaceSsoProvider: async () => ({ providerId: 'foo' })
|
||||
})(canReadProjectArgs())
|
||||
await expect(result).resolves.toStrictEqual(ok())
|
||||
|
||||
await expect(result).resolves.toBeAuthOKResult()
|
||||
})
|
||||
})
|
||||
})
|
||||
+13
-9
@@ -1,5 +1,8 @@
|
||||
import { Roles } from '../../core/constants.js'
|
||||
import { hasMinimumProjectRole, isPubliclyReadableProject } from '../checks/projects.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
import {
|
||||
hasMinimumProjectRole,
|
||||
isPubliclyReadableProject
|
||||
} from '../../checks/projects.js'
|
||||
import {
|
||||
ProjectNoAccessError,
|
||||
ProjectNotFoundError,
|
||||
@@ -7,16 +10,17 @@ import {
|
||||
ServerNoSessionError,
|
||||
WorkspaceNoAccessError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../domain/authErrors.js'
|
||||
} from '../../domain/authErrors.js'
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import { AuthCheckContextLoaderKeys } from '../domain/loaders.js'
|
||||
import { AuthPolicy } from '../domain/policies.js'
|
||||
import { canUseAdminOverride, hasMinimumServerRole } from '../checks/serverRole.js'
|
||||
import { hasAnyWorkspaceRole } from '../checks/workspaceRole.js'
|
||||
import { maybeMemberRoleWithValidSsoSessionIfNeeded } from '../fragments/workspaceSso.js'
|
||||
import { MaybeUserContext, ProjectContext } from '../domain/context.js'
|
||||
import { AuthCheckContextLoaderKeys } from '../../domain/loaders.js'
|
||||
import { AuthPolicy } from '../../domain/policies.js'
|
||||
import { canUseAdminOverride, hasMinimumServerRole } from '../../checks/serverRole.js'
|
||||
import { hasAnyWorkspaceRole } from '../../checks/workspaceRole.js'
|
||||
import { maybeMemberRoleWithValidSsoSessionIfNeeded } from '../../fragments/workspaceSso.js'
|
||||
import { MaybeUserContext, ProjectContext } from '../../domain/context.js'
|
||||
|
||||
export const canReadProjectPolicy: AuthPolicy<
|
||||
| typeof AuthCheckContextLoaderKeys.getAdminOverrideEnabled
|
||||
| typeof AuthCheckContextLoaderKeys.getEnv
|
||||
| typeof AuthCheckContextLoaderKeys.getProject
|
||||
| typeof AuthCheckContextLoaderKeys.getProjectRole
|
||||
+49
-35
@@ -9,14 +9,13 @@ import {
|
||||
WorkspaceReadOnlyError,
|
||||
WorkspacesNotEnabledError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../domain/authErrors.js'
|
||||
} from '../../domain/authErrors.js'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { canCreateWorkspaceProjectPolicy } from './canCreateWorkspaceProject.js'
|
||||
import { parseFeatureFlags } from '../../environment/index.js'
|
||||
import { parseFeatureFlags } from '../../../environment/index.js'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { WorkspacePlan } from '../../workspaces/index.js'
|
||||
import { Workspace, WorkspaceSsoProvider } from '../domain/workspaces/types.js'
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import { WorkspacePlan } from '../../../workspaces/index.js'
|
||||
import { Workspace, WorkspaceSsoProvider } from '../../domain/workspaces/types.js'
|
||||
|
||||
const canCreateArgs = () => ({
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
@@ -60,7 +59,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspacesNotEnabledError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspacesNotEnabledError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -97,7 +98,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})({ workspaceId: '' })
|
||||
|
||||
expect(result).toStrictEqual(err(new ServerNoSessionError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: ServerNoSessionError.code
|
||||
})
|
||||
})
|
||||
it('forbids creation for anyone not having minimum server:user role', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -131,7 +134,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new ServerNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: ServerNoAccessError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -170,7 +175,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
|
||||
it('forbids creation when sso session is not found', async () => {
|
||||
@@ -207,15 +214,12 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(
|
||||
err(
|
||||
new WorkspaceSsoSessionNoAccessError({
|
||||
payload: { workspaceSlug }
|
||||
})
|
||||
)
|
||||
)
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceSsoSessionNoAccessError.code,
|
||||
payload: { workspaceSlug }
|
||||
})
|
||||
})
|
||||
it('throws UncoveredError from unexpected sso session errors')
|
||||
// it('throws UncoveredError from unexpected sso session errors')
|
||||
})
|
||||
|
||||
describe('user workspace roles', () => {
|
||||
@@ -251,7 +255,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('WorkspaceNotEnoughPermissionsError for workspace guests', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -285,13 +291,10 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(
|
||||
err(
|
||||
new WorkspaceNotEnoughPermissionsError({
|
||||
message: 'Guests cannot create projects in the workspace'
|
||||
})
|
||||
)
|
||||
)
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNotEnoughPermissionsError.code,
|
||||
message: 'Guests cannot create projects in the workspace'
|
||||
})
|
||||
})
|
||||
it('forbids non-editor seats from creating projects', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -328,7 +331,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoEditorSeatError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoEditorSeatError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -365,7 +370,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('forbids creation if plan is read-only', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -401,7 +408,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceReadOnlyError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceReadOnlyError.code
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -440,7 +449,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('allows creation if plan has no limits', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -479,7 +490,7 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(ok())
|
||||
expect(result).toBeAuthOKResult()
|
||||
})
|
||||
it('forbids creation if current project count fails to load', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -518,7 +529,9 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(err(new WorkspaceNoAccessError()))
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceNoAccessError.code
|
||||
})
|
||||
})
|
||||
it('allows creation if new project is within plan limits', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -557,7 +570,7 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(ok())
|
||||
expect(result).toBeAuthOKResult()
|
||||
})
|
||||
it('forbids creation if new project is not within plan limits', async () => {
|
||||
const result = await canCreateWorkspaceProjectPolicy({
|
||||
@@ -596,9 +609,10 @@ describe('canCreateWorkspaceProjectPolicy creates a function, that handles', ()
|
||||
}
|
||||
})(canCreateArgs())
|
||||
|
||||
expect(result).toStrictEqual(
|
||||
err(new WorkspaceLimitsReachedError({ payload: { limit: 'projectCount' } }))
|
||||
)
|
||||
expect(result).toBeAuthErrorResult({
|
||||
code: WorkspaceLimitsReachedError.code,
|
||||
payload: { limit: 'projectCount' }
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
+9
-9
@@ -1,4 +1,4 @@
|
||||
import { AuthPolicy } from '../domain/policies.js'
|
||||
import { AuthPolicy } from '../../domain/policies.js'
|
||||
import {
|
||||
ServerNoAccessError,
|
||||
ServerNoSessionError,
|
||||
@@ -9,18 +9,18 @@ import {
|
||||
WorkspaceReadOnlyError,
|
||||
WorkspacesNotEnabledError,
|
||||
WorkspaceSsoSessionNoAccessError
|
||||
} from '../domain/authErrors.js'
|
||||
} from '../../domain/authErrors.js'
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import { hasMinimumServerRole } from '../checks/serverRole.js'
|
||||
import { Roles } from '../../core/constants.js'
|
||||
import { maybeMemberRoleWithValidSsoSessionIfNeeded } from '../fragments/workspaceSso.js'
|
||||
import { throwUncoveredError } from '../../core/index.js'
|
||||
import { hasEditorSeat } from '../checks/workspaceSeat.js'
|
||||
import { MaybeUserContext, WorkspaceContext } from '../domain/context.js'
|
||||
import { hasMinimumServerRole } from '../../checks/serverRole.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
import { maybeMemberRoleWithValidSsoSessionIfNeeded } from '../../fragments/workspaceSso.js'
|
||||
import { throwUncoveredError } from '../../../core/index.js'
|
||||
import { hasEditorSeat } from '../../checks/workspaceSeat.js'
|
||||
import { MaybeUserContext, WorkspaceContext } from '../../domain/context.js'
|
||||
import {
|
||||
isNewWorkspacePlan,
|
||||
isWorkspacePlanStatusReadOnly
|
||||
} from '../../workspaces/index.js'
|
||||
} from '../../../workspaces/index.js'
|
||||
|
||||
export const canCreateWorkspaceProjectPolicy: AuthPolicy<
|
||||
| 'getEnv'
|
||||
@@ -13,11 +13,6 @@ export const parseFeatureFlags = (
|
||||
//INFO
|
||||
// As a convention all feature flags should be prefixed with a FF_
|
||||
const res = parseEnv(input, {
|
||||
// Enables the admin override feature
|
||||
FF_ADMIN_OVERRIDE_ENABLED: {
|
||||
schema: z.boolean(),
|
||||
defaults: { production: false, _: false }
|
||||
},
|
||||
// Enables the automate module.
|
||||
FF_AUTOMATE_MODULE_ENABLED: {
|
||||
schema: z.boolean(),
|
||||
@@ -99,7 +94,6 @@ export const parseFeatureFlags = (
|
||||
let parsedFlags: FeatureFlags | undefined
|
||||
|
||||
export type FeatureFlags = {
|
||||
FF_ADMIN_OVERRIDE_ENABLED: boolean
|
||||
FF_AUTOMATE_MODULE_ENABLED: boolean
|
||||
FF_GENDOAI_MODULE_ENABLED: boolean
|
||||
FF_WORKSPACES_MODULE_ENABLED: boolean
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
import { expect } from 'vitest'
|
||||
import { AllAuthErrors, isAuthPolicyError } from '../authz/index.js'
|
||||
import { isInstance as isResult } from 'true-myth/result'
|
||||
import { isInstance as isMaybe } from 'true-myth/maybe'
|
||||
|
||||
// Augment vitest types w/ new matchers
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toBeAuthOKResult: () => R
|
||||
toBeAuthErrorResult: (params: {
|
||||
/**
|
||||
* Check for specific error code
|
||||
*/
|
||||
code?: AllAuthErrors['code']
|
||||
|
||||
/**
|
||||
* Check for specific error message (includes not equals)
|
||||
*/
|
||||
message?: string
|
||||
|
||||
/**
|
||||
* Check for a specific payload
|
||||
*/
|
||||
payload?: unknown
|
||||
}) => R
|
||||
toBeNothingResult: () => R
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
}
|
||||
|
||||
// Extend w/ extra matchers
|
||||
expect.extend({
|
||||
toBeAuthOKResult(received: unknown) {
|
||||
if (isMaybe(received) && received.isJust) {
|
||||
received = received.value
|
||||
}
|
||||
|
||||
if (!isResult(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be a Result structure`
|
||||
}
|
||||
}
|
||||
|
||||
if (!received.isOk) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be an OK Result`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `${received} is an OK Result`
|
||||
}
|
||||
},
|
||||
toBeAuthErrorResult(
|
||||
received: unknown,
|
||||
expected: Parameters<CustomMatchers['toBeAuthErrorResult']>[0]
|
||||
) {
|
||||
const { code, message, payload } = expected
|
||||
if (!code?.length && !message?.length && !payload) {
|
||||
throw new Error(
|
||||
'No expected value provided. Either code or message or payload must be set.'
|
||||
)
|
||||
}
|
||||
|
||||
if (isMaybe(received) && received.isJust) {
|
||||
received = received.value
|
||||
}
|
||||
|
||||
if (!isResult(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be a Result structure`
|
||||
}
|
||||
}
|
||||
|
||||
if (!received.isErr) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be an Error Result`
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAuthPolicyError(received.error)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be an Error Result with an AuthError`
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity checks done, now do actual expectation checks
|
||||
if (expected.code && received.error.code !== expected.code) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`Expected ${received} to be an Auth Error Result with code ${expected.code}`,
|
||||
expected: expected.code,
|
||||
actual: received.error.code
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.message && !received.error.message.includes(expected.message)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`Expected ${received} to be an Auth Error Result with message substring '${expected.message}'`,
|
||||
expected: expected.message,
|
||||
actual: received.error.message
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.payload) {
|
||||
const errPayload = received.error.payload
|
||||
const equals = this.equals(errPayload, expected.payload)
|
||||
if (!equals) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`Expected ${received} to be an Auth Error Result with payload ${expected.payload}`,
|
||||
expected: expected.payload,
|
||||
actual: errPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `${received} is an Auth Error Result with code ${expected.code}`
|
||||
}
|
||||
},
|
||||
toBeNothingResult(received: unknown) {
|
||||
if (!isMaybe(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be a Maybe structure`
|
||||
}
|
||||
}
|
||||
|
||||
if (received.isJust) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${received} to be a Nothing Result`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `${received} is a Nothing Result`
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
enabled: true,
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.{ts,tsx}']
|
||||
}
|
||||
},
|
||||
setupFiles: ['./src/tests/setup.ts']
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10629,114 +10629,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/cli@npm:^5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "@graphql-codegen/cli@npm:5.0.2"
|
||||
dependencies:
|
||||
"@babel/generator": "npm:^7.18.13"
|
||||
"@babel/template": "npm:^7.18.10"
|
||||
"@babel/types": "npm:^7.18.13"
|
||||
"@graphql-codegen/client-preset": "npm:^4.2.2"
|
||||
"@graphql-codegen/core": "npm:^4.0.2"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.3"
|
||||
"@graphql-tools/apollo-engine-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/code-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/git-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/github-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/graphql-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/json-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/load": "npm:^8.0.0"
|
||||
"@graphql-tools/prisma-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/url-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
"@whatwg-node/fetch": "npm:^0.8.0"
|
||||
chalk: "npm:^4.1.0"
|
||||
cosmiconfig: "npm:^8.1.3"
|
||||
debounce: "npm:^1.2.0"
|
||||
detect-indent: "npm:^6.0.0"
|
||||
graphql-config: "npm:^5.0.2"
|
||||
inquirer: "npm:^8.0.0"
|
||||
is-glob: "npm:^4.0.1"
|
||||
jiti: "npm:^1.17.1"
|
||||
json-to-pretty-yaml: "npm:^1.2.2"
|
||||
listr2: "npm:^4.0.5"
|
||||
log-symbols: "npm:^4.0.0"
|
||||
micromatch: "npm:^4.0.5"
|
||||
shell-quote: "npm:^1.7.3"
|
||||
string-env-interpolation: "npm:^1.0.1"
|
||||
ts-log: "npm:^2.2.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
yaml: "npm:^2.3.1"
|
||||
yargs: "npm:^17.0.0"
|
||||
peerDependencies:
|
||||
"@parcel/watcher": ^2.1.0
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
peerDependenciesMeta:
|
||||
"@parcel/watcher":
|
||||
optional: true
|
||||
bin:
|
||||
gql-gen: cjs/bin.js
|
||||
graphql-code-generator: cjs/bin.js
|
||||
graphql-codegen: cjs/bin.js
|
||||
graphql-codegen-esm: esm/bin.js
|
||||
checksum: 10/24f5a4d441e4af2f0cae1818c8643a5400718cc1f08ca829a9110a35d99cb5529b567991ce826544b5a2aab36d0be3b10309dc112343bab1232d7c6f2fa14008
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/cli@npm:^5.0.3":
|
||||
version: 5.0.3
|
||||
resolution: "@graphql-codegen/cli@npm:5.0.3"
|
||||
dependencies:
|
||||
"@babel/generator": "npm:^7.18.13"
|
||||
"@babel/template": "npm:^7.18.10"
|
||||
"@babel/types": "npm:^7.18.13"
|
||||
"@graphql-codegen/client-preset": "npm:^4.4.0"
|
||||
"@graphql-codegen/core": "npm:^4.0.2"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.3"
|
||||
"@graphql-tools/apollo-engine-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/code-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/git-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/github-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/graphql-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/json-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/load": "npm:^8.0.0"
|
||||
"@graphql-tools/prisma-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/url-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
"@whatwg-node/fetch": "npm:^0.9.20"
|
||||
chalk: "npm:^4.1.0"
|
||||
cosmiconfig: "npm:^8.1.3"
|
||||
debounce: "npm:^1.2.0"
|
||||
detect-indent: "npm:^6.0.0"
|
||||
graphql-config: "npm:^5.1.1"
|
||||
inquirer: "npm:^8.0.0"
|
||||
is-glob: "npm:^4.0.1"
|
||||
jiti: "npm:^1.17.1"
|
||||
json-to-pretty-yaml: "npm:^1.2.2"
|
||||
listr2: "npm:^4.0.5"
|
||||
log-symbols: "npm:^4.0.0"
|
||||
micromatch: "npm:^4.0.5"
|
||||
shell-quote: "npm:^1.7.3"
|
||||
string-env-interpolation: "npm:^1.0.1"
|
||||
ts-log: "npm:^2.2.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
yaml: "npm:^2.3.1"
|
||||
yargs: "npm:^17.0.0"
|
||||
peerDependencies:
|
||||
"@parcel/watcher": ^2.1.0
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
peerDependenciesMeta:
|
||||
"@parcel/watcher":
|
||||
optional: true
|
||||
bin:
|
||||
gql-gen: cjs/bin.js
|
||||
graphql-code-generator: cjs/bin.js
|
||||
graphql-codegen: cjs/bin.js
|
||||
graphql-codegen-esm: esm/bin.js
|
||||
checksum: 10/c3359668f824246e78656d26af506b5b279d50e08a56f54db87da492bd4d0a8e8b6540a6119402d7f5026c137babfd79e628897c6038e199ee6322f688eec757
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/cli@npm:^5.0.5":
|
||||
version: 5.0.5
|
||||
resolution: "@graphql-codegen/cli@npm:5.0.5"
|
||||
@@ -10791,7 +10683,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/client-preset@npm:^4.2.2, @graphql-codegen/client-preset@npm:^4.3.0":
|
||||
"@graphql-codegen/client-preset@npm:^4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "@graphql-codegen/client-preset@npm:4.3.0"
|
||||
dependencies:
|
||||
@@ -10814,29 +10706,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/client-preset@npm:^4.4.0":
|
||||
version: 4.5.0
|
||||
resolution: "@graphql-codegen/client-preset@npm:4.5.0"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": "npm:^7.20.2"
|
||||
"@babel/template": "npm:^7.20.7"
|
||||
"@graphql-codegen/add": "npm:^5.0.3"
|
||||
"@graphql-codegen/gql-tag-operations": "npm:4.0.11"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.0.11"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.3.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:^5.5.0"
|
||||
"@graphql-tools/documents": "npm:^1.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
"@graphql-typed-document-node/core": "npm:3.2.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/bbbbaa255f6cb1248cd143b54e06f6fc553cdd9f7ca002977bbf42b92cf9d5c6fe052eda1ae1233eab3d50dd80fbb04609bfeeb29132019faead04300e61ddc0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/client-preset@npm:^4.6.0, @graphql-codegen/client-preset@npm:^4.6.4":
|
||||
version: 4.6.4
|
||||
resolution: "@graphql-codegen/client-preset@npm:4.6.4"
|
||||
@@ -10874,21 +10743,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/gql-tag-operations@npm:4.0.11":
|
||||
version: 4.0.11
|
||||
resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.11"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/cc277d1af9da611dbd37c00f18d08e8fdc634632c0fba6789a1027931f8e3b925ad64af27a6fa7c23ed44afdef131f9c03025ca9b077cd6e95e5c9823751c6a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/gql-tag-operations@npm:4.0.16":
|
||||
version: 4.0.16
|
||||
resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.16"
|
||||
@@ -10964,21 +10818,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typed-document-node@npm:^5.0.11":
|
||||
version: 5.0.11
|
||||
resolution: "@graphql-codegen/typed-document-node@npm:5.0.11"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/9320fbc9ccf13d0b0ecc7b57f1b0799629ce93a4e0cf95a76cdeb38981e2da92775734daa7bf68a9383e3d01f9a47f4b35cb870aef710f5dc137234b93b9d7cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typed-document-node@npm:^5.0.15":
|
||||
version: 5.0.15
|
||||
resolution: "@graphql-codegen/typed-document-node@npm:5.0.15"
|
||||
@@ -11009,6 +10848,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typed-document-node@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "@graphql-codegen/typed-document-node@npm:5.1.1"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.8.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/213983a6c10173dc61041f331c19d6152a7e1bb07ab10c70355d49a7048a367ccee30428eda652b514c32431f938606b1db5c04f0d2c9dd93abb0096187cb91a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-operations@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "@graphql-codegen/typescript-operations@npm:4.2.1"
|
||||
@@ -11024,21 +10878,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-operations@npm:^4.3.1":
|
||||
version: 4.3.1
|
||||
resolution: "@graphql-codegen/typescript-operations@npm:4.3.1"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/cdad24e16aa9b369e3ef2434032f2527fd1363e82256dd09d2e9aa6d9a55539eeea15665a4289e7695145f7417a9a765ad73979054a97c606d757ee060780819
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-operations@npm:^4.5.1":
|
||||
version: 4.5.1
|
||||
resolution: "@graphql-codegen/typescript-operations@npm:4.5.1"
|
||||
@@ -11054,19 +10893,36 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-resolvers@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "@graphql-codegen/typescript-resolvers@npm:4.4.0"
|
||||
"@graphql-codegen/typescript-operations@npm:^4.6.0":
|
||||
version: 4.6.0
|
||||
resolution: "@graphql-codegen/typescript-operations@npm:4.6.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.6"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.8.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
graphql-sock: ^1.0.0
|
||||
checksum: 10/6b835dce1db8f73f9f1d51ff8258f1cccbd40618a3582c923eb9ee1761a481502beaea719869d96741e5dd3a4cc2bb87c4fd0f6ab369f7dc22fbc838f6e7751f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-resolvers@npm:^4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "@graphql-codegen/typescript-resolvers@npm:4.5.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.6"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.8.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/26efe707c89a9612da0ff8be56ebdb91227fe6afb3889b22f49bbec2cd12ba677d58f7946871179dc64c8279acbad773987086fd1c388980c9a7932fd7c15e32
|
||||
graphql-sock: ^1.0.0
|
||||
checksum: 10/02986bacf81da793a3501c5bbf810a0eb53b56cf42fbb760f62e8d1a9a6d929a35dff22d01bc1120a075f23a6ded35a6fd14d0c4cbb3d49effe7b4ecb9234ad4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11085,21 +10941,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@graphql-codegen/typescript@npm:4.1.1"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/schema-ast": "npm:^4.0.2"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/a47fabef00832122f4981fecbbcfd1e90e2567bdc7fc1d63520b018ae1a6db5217eb42f4f4744265cc492e64cd134b87b7bcfdaddfd7b3e35ce5c47d4548225d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript@npm:^4.1.5":
|
||||
version: 4.1.5
|
||||
resolution: "@graphql-codegen/typescript@npm:4.1.5"
|
||||
@@ -11115,6 +10956,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript@npm:^4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@graphql-codegen/typescript@npm:4.1.6"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/schema-ast": "npm:^4.0.2"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.8.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/a32804685743fb5561d397515c9ddc92031f97712add706ad7ca38c628c3a52f9455f0880a425a8bdc8377c4ee39f80c8be2097fefeaa499b19c03e4e6abb584
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.2.0, @graphql-codegen/visitor-plugin-common@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.2.0"
|
||||
@@ -11135,26 +10991,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.5.0, @graphql-codegen/visitor-plugin-common@npm:^5.5.0":
|
||||
version: 5.5.0
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.5.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-tools/optimize": "npm:^2.0.0"
|
||||
"@graphql-tools/relay-operation-optimizer": "npm:^7.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
dependency-graph: "npm:^0.11.0"
|
||||
graphql-tag: "npm:^2.11.0"
|
||||
parse-filepath: "npm:^1.0.2"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/f923c40ae996a2accf3a951d302b3da9b3c063f4b1c66b159bf3f74910e18ea592e87b3f35495a84f6c36d1198d880dd07f6e8c3fe94b0d6dba0f2f77522cb5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.7.1, @graphql-codegen/visitor-plugin-common@npm:^5.7.1":
|
||||
version: 5.7.1
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.7.1"
|
||||
@@ -11175,6 +11011,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.8.0":
|
||||
version: 5.8.0
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.8.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-tools/optimize": "npm:^2.0.0"
|
||||
"@graphql-tools/relay-operation-optimizer": "npm:^7.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
dependency-graph: "npm:^0.11.0"
|
||||
graphql-tag: "npm:^2.11.0"
|
||||
parse-filepath: "npm:^1.0.2"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/6d9c5e40d3dcda1f1c209440be5a7530581eac2ec00f787f3e45218bba024a5eeda8f2cb8811e05c6d69d114df0097f2377cbfceedb7786fb0e558d4511904e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/apollo-engine-loader@npm:^8.0.0":
|
||||
version: 8.0.1
|
||||
resolution: "@graphql-tools/apollo-engine-loader@npm:8.0.1"
|
||||
@@ -14112,6 +13968,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-android-arm64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-android-arm64@npm:2.5.1"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-darwin-arm64@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-darwin-arm64@npm:2.4.1"
|
||||
@@ -14119,6 +13982,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-darwin-arm64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-darwin-arm64@npm:2.5.1"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-darwin-x64@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-darwin-x64@npm:2.4.1"
|
||||
@@ -14126,6 +13996,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-darwin-x64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-darwin-x64@npm:2.5.1"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-freebsd-x64@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-freebsd-x64@npm:2.4.1"
|
||||
@@ -14133,6 +14010,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-freebsd-x64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-freebsd-x64@npm:2.5.1"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-linux-arm-glibc@npm:2.4.1"
|
||||
@@ -14140,6 +14024,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.1"
|
||||
conditions: os=linux & cpu=arm & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm-musl@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.1"
|
||||
conditions: os=linux & cpu=arm & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.4.1"
|
||||
@@ -14147,6 +14045,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-linux-arm64-musl@npm:2.4.1"
|
||||
@@ -14154,6 +14059,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-linux-x64-glibc@npm:2.4.1"
|
||||
@@ -14161,6 +14073,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.1"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-x64-musl@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-linux-x64-musl@npm:2.4.1"
|
||||
@@ -14168,6 +14087,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-linux-x64-musl@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.1"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-wasm@npm:^2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-wasm@npm:2.4.1"
|
||||
@@ -14186,6 +14112,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-win32-arm64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-win32-arm64@npm:2.5.1"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-win32-ia32@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-win32-ia32@npm:2.4.1"
|
||||
@@ -14193,6 +14126,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-win32-ia32@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-win32-ia32@npm:2.5.1"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-win32-x64@npm:2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher-win32-x64@npm:2.4.1"
|
||||
@@ -14200,6 +14140,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-win32-x64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-win32-x64@npm:2.5.1"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher@npm:^2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@parcel/watcher@npm:2.4.1"
|
||||
@@ -14250,36 +14197,56 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-schema@npm:^2.1.6":
|
||||
version: 2.2.0
|
||||
resolution: "@peculiar/asn1-schema@npm:2.2.0"
|
||||
"@parcel/watcher@npm:^2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher@npm:2.5.1"
|
||||
dependencies:
|
||||
asn1js: "npm:^3.0.5"
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/1f467f387b0a1efa709377e0ed8c5b562f7bc3b6aa5dcf4df9a2ab20bba464b20a05c71870be802b77edc0df881b89427ad6afcd4196176fdbffdafd1fdd7484
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/json-schema@npm:^1.1.12":
|
||||
version: 1.1.12
|
||||
resolution: "@peculiar/json-schema@npm:1.1.12"
|
||||
dependencies:
|
||||
tslib: "npm:^2.0.0"
|
||||
checksum: 10/dfec178afe63a02b6d45da8a18e51ef417e9f5412a8c2809c9a07b29b9376fadee1b4f2ea2d92d4e5a7b8eba76d9e99afbef6d7e9a27bd85257f69c4da228cbc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/webcrypto@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@peculiar/webcrypto@npm:1.4.0"
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema": "npm:^2.1.6"
|
||||
"@peculiar/json-schema": "npm:^1.1.12"
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
tslib: "npm:^2.4.0"
|
||||
webcrypto-core: "npm:^1.7.4"
|
||||
checksum: 10/125596cdc92c1b5aad1486c503e108648f7654912da8b73484857bb81b8c9ca1e03833b4fdc8d797a7b40f1107d129a6c7d541fd67ab9d8dd4d146d528ea0126
|
||||
"@parcel/watcher-android-arm64": "npm:2.5.1"
|
||||
"@parcel/watcher-darwin-arm64": "npm:2.5.1"
|
||||
"@parcel/watcher-darwin-x64": "npm:2.5.1"
|
||||
"@parcel/watcher-freebsd-x64": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-arm-glibc": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-arm-musl": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-arm64-glibc": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-arm64-musl": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-x64-glibc": "npm:2.5.1"
|
||||
"@parcel/watcher-linux-x64-musl": "npm:2.5.1"
|
||||
"@parcel/watcher-win32-arm64": "npm:2.5.1"
|
||||
"@parcel/watcher-win32-ia32": "npm:2.5.1"
|
||||
"@parcel/watcher-win32-x64": "npm:2.5.1"
|
||||
detect-libc: "npm:^1.0.3"
|
||||
is-glob: "npm:^4.0.3"
|
||||
micromatch: "npm:^4.0.5"
|
||||
node-addon-api: "npm:^7.0.0"
|
||||
node-gyp: "npm:latest"
|
||||
dependenciesMeta:
|
||||
"@parcel/watcher-android-arm64":
|
||||
optional: true
|
||||
"@parcel/watcher-darwin-arm64":
|
||||
optional: true
|
||||
"@parcel/watcher-darwin-x64":
|
||||
optional: true
|
||||
"@parcel/watcher-freebsd-x64":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-arm-glibc":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-arm-musl":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-arm64-glibc":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-arm64-musl":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-x64-glibc":
|
||||
optional: true
|
||||
"@parcel/watcher-linux-x64-musl":
|
||||
optional: true
|
||||
"@parcel/watcher-win32-arm64":
|
||||
optional: true
|
||||
"@parcel/watcher-win32-ia32":
|
||||
optional: true
|
||||
"@parcel/watcher-win32-x64":
|
||||
optional: true
|
||||
checksum: 10/2cc1405166fb3016b34508661902ab08b6dec59513708165c633c84a4696fff64f9b99ea116e747c121215e09619f1decab6f0350d1cb26c9210b98eb28a6a56
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -16452,13 +16419,13 @@ __metadata:
|
||||
resolution: "@speckle/dui3@workspace:packages/dui3"
|
||||
dependencies:
|
||||
"@apollo/client": "npm:^3.7.14"
|
||||
"@graphql-codegen/cli": "npm:^5.0.2"
|
||||
"@graphql-codegen/cli": "npm:^5.0.5"
|
||||
"@graphql-codegen/client-preset": "npm:^4.3.0"
|
||||
"@headlessui/vue": "npm:^1.7.13"
|
||||
"@heroicons/vue": "npm:^2.0.12"
|
||||
"@nuxt/eslint": "npm:^0.3.13"
|
||||
"@nuxtjs/tailwindcss": "npm:^6.7.0"
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
"@parcel/watcher": "npm:^2.5.1"
|
||||
"@pinia/nuxt": "npm:^0.4.11"
|
||||
"@speckle/shared": "workspace:^"
|
||||
"@speckle/ui-components": "workspace:^"
|
||||
@@ -16564,7 +16531,7 @@ __metadata:
|
||||
"@nuxt/eslint": "npm:^1.1.0"
|
||||
"@nuxt/image": "npm:^1.8.1"
|
||||
"@nuxtjs/tailwindcss": "npm:^6.12.2"
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
"@parcel/watcher": "npm:^2.5.1"
|
||||
"@speckle/shared": "workspace:^"
|
||||
"@speckle/tailwind-theme": "workspace:^"
|
||||
"@speckle/ui-components": "workspace:^"
|
||||
@@ -16837,11 +16804,11 @@ __metadata:
|
||||
"@bull-board/express": "npm:^4.2.2"
|
||||
"@faker-js/faker": "npm:^8.4.1"
|
||||
"@godaddy/terminus": "npm:^4.9.0"
|
||||
"@graphql-codegen/cli": "npm:^5.0.3"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.0.11"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.3.1"
|
||||
"@graphql-codegen/typescript-resolvers": "npm:^4.4.0"
|
||||
"@graphql-codegen/cli": "npm:^5.0.5"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.1.1"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.6"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.6.0"
|
||||
"@graphql-codegen/typescript-resolvers": "npm:^4.5.0"
|
||||
"@graphql-tools/mock": "npm:^9.0.4"
|
||||
"@graphql-tools/schema": "npm:^10.0.6"
|
||||
"@isaacs/ttlcache": "npm:^1.4.1"
|
||||
@@ -16855,7 +16822,7 @@ __metadata:
|
||||
"@opentelemetry/instrumentation-knex": "npm:^0.40.0"
|
||||
"@opentelemetry/instrumentation-pino": "npm:^0.42.0"
|
||||
"@opentelemetry/sdk-trace-node": "npm:^1.26.0"
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
"@parcel/watcher": "npm:^2.5.1"
|
||||
"@speckle/objectloader": "workspace:^"
|
||||
"@speckle/shared": "workspace:^"
|
||||
"@swc/core": "npm:^1.11.11"
|
||||
@@ -22463,13 +22430,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/events@npm:^0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "@whatwg-node/events@npm:0.0.3"
|
||||
checksum: 10/af26f40d4d0a0f5f0ee45fc6124afb8d6b33988dae96ab0fb87aa5e66d1ff08a749491b9da533ea524bbaebd4a770736f254d574a91ab4455386aa098cee8c77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/events@npm:^0.1.0":
|
||||
version: 0.1.1
|
||||
resolution: "@whatwg-node/events@npm:0.1.1"
|
||||
@@ -22487,19 +22447,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/fetch@npm:^0.8.0":
|
||||
version: 0.8.8
|
||||
resolution: "@whatwg-node/fetch@npm:0.8.8"
|
||||
dependencies:
|
||||
"@peculiar/webcrypto": "npm:^1.4.0"
|
||||
"@whatwg-node/node-fetch": "npm:^0.3.6"
|
||||
busboy: "npm:^1.6.0"
|
||||
urlpattern-polyfill: "npm:^8.0.0"
|
||||
web-streams-polyfill: "npm:^3.2.1"
|
||||
checksum: 10/4d04f28a3db1886a5ab6070af0d8d6b90c891596495e62417aa296dcdf65506703fb5f76937f7a7b7f4125721ef80f4ac9204a948588c33517dc064c746d7a42
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/fetch@npm:^0.9.0":
|
||||
version: 0.9.18
|
||||
resolution: "@whatwg-node/fetch@npm:0.9.18"
|
||||
@@ -22510,29 +22457,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/fetch@npm:^0.9.20":
|
||||
version: 0.9.23
|
||||
resolution: "@whatwg-node/fetch@npm:0.9.23"
|
||||
dependencies:
|
||||
"@whatwg-node/node-fetch": "npm:^0.6.0"
|
||||
urlpattern-polyfill: "npm:^10.0.0"
|
||||
checksum: 10/6024a3fcc2175de6a20ea4833c009d0488cf68c01cd235541ec0dba0ce59bb0b0befcd4cd788db0e65b99a5a8755bc00d490dc9d7beeb0c2f35058ef46732fe0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.3.6":
|
||||
version: 0.3.6
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.3.6"
|
||||
dependencies:
|
||||
"@whatwg-node/events": "npm:^0.0.3"
|
||||
busboy: "npm:^1.6.0"
|
||||
fast-querystring: "npm:^1.1.1"
|
||||
fast-url-parser: "npm:^1.1.3"
|
||||
tslib: "npm:^2.3.1"
|
||||
checksum: 10/8284e385cf50f4479f19a5be8feb0d55f448af3bb7a62ec654ec9e4232ce3f0858191494f508f5196a94b16017d5e08f8e0bce9f49af4dc133a39d5047b8e369
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.5.7":
|
||||
version: 0.5.11
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.5.11"
|
||||
@@ -22546,18 +22470,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.6.0"
|
||||
dependencies:
|
||||
"@kamilkisiela/fast-url-parser": "npm:^1.1.4"
|
||||
busboy: "npm:^1.6.0"
|
||||
fast-querystring: "npm:^1.1.1"
|
||||
tslib: "npm:^2.6.3"
|
||||
checksum: 10/87ad7c4cc68b24499089166617d16cbe25d9107b4d9354c804232f8c53c4fc27d1e2166471d878390442620e09588aa1d8705a8e2ea5bcc2d728a558ad1156c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.7.11":
|
||||
version: 0.7.11
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.7.11"
|
||||
@@ -23625,17 +23537,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asn1js@npm:^3.0.1, asn1js@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "asn1js@npm:3.0.5"
|
||||
dependencies:
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
pvutils: "npm:^1.1.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/17fb0302432186631550de9606a4622ec366646d072cde9cdf4bcafa47bd2425e157eeb7b1377ee6520f8b46687b4ecaee31cf0ad2fa494361a1938b2ed53194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assert-never@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "assert-never@npm:1.2.1"
|
||||
@@ -33220,31 +33121,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-config@npm:^5.0.2":
|
||||
version: 5.0.3
|
||||
resolution: "graphql-config@npm:5.0.3"
|
||||
dependencies:
|
||||
"@graphql-tools/graphql-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/json-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/load": "npm:^8.0.0"
|
||||
"@graphql-tools/merge": "npm:^9.0.0"
|
||||
"@graphql-tools/url-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
cosmiconfig: "npm:^8.1.0"
|
||||
jiti: "npm:^1.18.2"
|
||||
minimatch: "npm:^4.2.3"
|
||||
string-env-interpolation: "npm:^1.0.1"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
cosmiconfig-toml-loader: ^1.0.0
|
||||
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
peerDependenciesMeta:
|
||||
cosmiconfig-toml-loader:
|
||||
optional: true
|
||||
checksum: 10/be7667ea260c7db3e8b02c0d73d2a2bc214683d91886f883c73465e07aa204f9ae6bff494eaa253def31abd2bbe59e78c6b418ed456e06d2274050dbc45e33e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-config@npm:^5.1.1":
|
||||
version: 5.1.3
|
||||
resolution: "graphql-config@npm:5.1.3"
|
||||
@@ -38912,15 +38788,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^4.2.3":
|
||||
version: 4.2.3
|
||||
resolution: "minimatch@npm:4.2.3"
|
||||
dependencies:
|
||||
brace-expansion: "npm:^1.1.7"
|
||||
checksum: 10/02bacb187448c6aeeed5a794a64799fb1d1dd0274694da97272a9d3b919c7dfba6987d2089f6465191450d36d767c47fd7958227610b919121ccaed7de7f8924
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "minimatch@npm:5.1.0"
|
||||
@@ -45333,22 +45200,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pvtsutils@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "pvtsutils@npm:1.3.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/3e89fea1836dd9027446d65923f7240372a1040b777b2e6adfc319bfeb3cacfd56dccb708652651e85ad6a5c87f61728b697226c105d441140b648f3e4167872
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pvutils@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "pvutils@npm:1.1.3"
|
||||
checksum: 10/e5201b8f78ece68eae414a938c844bc45fb3f0de298178eed1775a217eedfd897c4346e5e54f410bb4d7466e09ceb262e85f20fd64239b8bb2595f14c52fa95e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"qs@npm:6.11.0, qs@npm:^6.10.0":
|
||||
version: 6.11.0
|
||||
resolution: "qs@npm:6.11.0"
|
||||
@@ -52051,7 +51902,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"urlpattern-polyfill@npm:8.0.2, urlpattern-polyfill@npm:^8.0.0":
|
||||
"urlpattern-polyfill@npm:8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "urlpattern-polyfill@npm:8.0.2"
|
||||
checksum: 10/fd86b5c55473f3abbf9ed317b953c9cbb4fa6b3f75f681a1d982fe9c17bbc8d9bcf988f4cf3bda35e2e5875984086c97e177f97f076bb80dfa2beb85d1dd7b23
|
||||
@@ -53418,26 +53269,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"web-streams-polyfill@npm:^3.2.1":
|
||||
version: 3.3.3
|
||||
resolution: "web-streams-polyfill@npm:3.3.3"
|
||||
checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webcrypto-core@npm:^1.7.4":
|
||||
version: 1.7.5
|
||||
resolution: "webcrypto-core@npm:1.7.5"
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema": "npm:^2.1.6"
|
||||
"@peculiar/json-schema": "npm:^1.1.12"
|
||||
asn1js: "npm:^3.0.1"
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/f322c6ec494102bb0ad8d242915e7d838341f4555b6d9940c0686fd492a5a1f3ecb78825c4d4e75a1136677dac2e96f138acece22ef8f9f5d36c0e88a9f3a20f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webgl-sdf-generator@npm:1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "webgl-sdf-generator@npm:1.1.1"
|
||||
|
||||
Reference in New Issue
Block a user