Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-1171-change-the-users-repository-to-update-the-verified-field-in

This commit is contained in:
Alessandro Magionami 2024-07-10 12:12:00 +02:00
Родитель bb964cd457 d12e80035d
Коммит 295b6d0621
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: EC367516F896CBA4
69 изменённых файлов: 3311 добавлений и 368 удалений

Просмотреть файл

@ -41,7 +41,7 @@ repos:
- id: helm-documentation
name: Helm Json Schema
language: system
files: utils/helm/speckle-server/values.yaml
files: utils\/helm\/speckle\-server\/values\.yaml
entry: utils/helm/update-schema-json.sh
description: If this fails it is because the values.yaml file was updated. Or has missing or incorrect documentation.

Просмотреть файл

@ -61,7 +61,7 @@ const deleteConfirmed = async () => {
},
{
update: (cache, { data }) => {
if (data?.streamsDelete) {
if (data?.projectMutations.batchDelete) {
// Remove project from cache
const cacheId = getCacheId('Project', projectId)
cache.evict({
@ -103,7 +103,7 @@ const deleteConfirmed = async () => {
}
).catch(convertThrowIntoFetchResult)
if (result?.data?.streamsDelete) {
if (result?.data?.projectMutations.batchDelete) {
triggerNotification({
type: ToastNotificationType.Success,
title: 'Project deleted',

Просмотреть файл

@ -275,6 +275,16 @@ import { useFunctionRunsStatusSummary } from '~/lib/automate/composables/runStat
const isGendoEnabled = useIsGendoModuleEnabled()
enum ViewerKeyboardActions {
ToggleModels = 'ToggleModels',
ToggleExplorer = 'ToggleExplorer',
ToggleDiscussions = 'ToggleDiscussions',
ToggleMeasurements = 'ToggleMeasurements',
ToggleProjection = 'ToggleProjection',
ToggleSectionBox = 'ToggleSectionBox',
ZoomExtentsOrSelection = 'ZoomExtentsOrSelection'
}
const width = ref(360)
const scrollableControlsContainer = ref(null as Nullable<HTMLDivElement>)
@ -364,28 +374,72 @@ const {
diff: { enabled }
} = useInjectedViewerInterfaceState()
const map: Record<ViewerKeyboardActions, [ModifierKeys[], string]> = {
[ViewerKeyboardActions.ToggleModels]: [[ModifierKeys.Shift], 'm'],
[ViewerKeyboardActions.ToggleExplorer]: [[ModifierKeys.Shift], 'e'],
[ViewerKeyboardActions.ToggleDiscussions]: [[ModifierKeys.Shift], 't'],
[ViewerKeyboardActions.ToggleMeasurements]: [[ModifierKeys.Shift], 'r'],
[ViewerKeyboardActions.ToggleProjection]: [[ModifierKeys.Shift], 'p'],
[ViewerKeyboardActions.ToggleSectionBox]: [[ModifierKeys.Shift], 'b'],
[ViewerKeyboardActions.ZoomExtentsOrSelection]: [[ModifierKeys.Shift], 'space']
}
const getShortcutTitle = (action: ViewerKeyboardActions) =>
`(${getKeyboardShortcutTitle([...map[action][0], map[action][1]])})`
const modelsShortcut = ref(
`Models (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'm'])})`
`Models ${getShortcutTitle(ViewerKeyboardActions.ToggleModels)}`
)
const explorerShortcut = ref(
`Scene Explorer (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'e'])})`
`Scene Explorer ${getShortcutTitle(ViewerKeyboardActions.ToggleExplorer)}`
)
const discussionsShortcut = ref(
`Discussions (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 't'])})`
`Discussions ${getShortcutTitle(ViewerKeyboardActions.ToggleDiscussions)}`
)
const zoomExtentsShortcut = ref(
`Fit to screen (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'Space'])})`
`Fit to screen ${getShortcutTitle(ViewerKeyboardActions.ZoomExtentsOrSelection)}`
)
const projectionShortcut = ref(
`Projection (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'p'])})`
`Projection ${getShortcutTitle(ViewerKeyboardActions.ToggleProjection)}`
)
const sectionBoxShortcut = ref(
`Section Box (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'b'])})`
`Section Box ${getShortcutTitle(ViewerKeyboardActions.ToggleSectionBox)}`
)
const measureShortcut = ref(
`Measure Mode (${getKeyboardShortcutTitle([ModifierKeys.AltOrOpt, 'd'])})`
`Measure Mode ${getShortcutTitle(ViewerKeyboardActions.ToggleMeasurements)}`
)
const handleKeyboardAction = (action: ViewerKeyboardActions) => {
switch (action) {
case ViewerKeyboardActions.ToggleModels:
toggleActiveControl('models')
break
case ViewerKeyboardActions.ToggleExplorer:
toggleActiveControl('explorer')
break
case ViewerKeyboardActions.ToggleDiscussions:
toggleActiveControl('discussions')
break
case ViewerKeyboardActions.ToggleMeasurements:
toggleMeasurements()
break
case ViewerKeyboardActions.ToggleProjection:
trackAndtoggleProjection()
break
case ViewerKeyboardActions.ToggleSectionBox:
toggleSectionBox()
break
case ViewerKeyboardActions.ZoomExtentsOrSelection:
trackAndzoomExtentsOrSelection()
break
}
}
Object.entries(map).forEach(([actionKey, [modifiers, key]]) => {
const action = actionKey as ViewerKeyboardActions
onKeyboardShortcut(modifiers, key, () => handleKeyboardAction(action))
})
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const toggleActiveControl = (control: ActiveControl) => {
@ -396,33 +450,6 @@ const toggleActiveControl = (control: ActiveControl) => {
activeControl.value = activeControl.value === control ? 'none' : control
}
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'm', () => {
toggleActiveControl('models')
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'e', () => {
toggleActiveControl('explorer')
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'f', () => {
toggleActiveControl('filters')
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], ['t'], () => {
toggleActiveControl('discussions')
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'd', () => {
toggleActiveControl('measurements')
})
// Viewer actions kbd shortcuts
onKeyboardShortcut([ModifierKeys.AltOrOpt], ' ', () => {
trackAndzoomExtentsOrSelection()
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'p', () => {
toggleProjection()
})
onKeyboardShortcut([ModifierKeys.AltOrOpt], 'b', () => {
toggleSectionBox()
})
const mp = useMixpanel()
watch(activeControl, (newVal) => {
mp.track('Viewer Action', { type: 'action', name: 'controls-toggle', action: newVal })

Просмотреть файл

@ -26,7 +26,7 @@ const props = defineProps<{
}>()
const { result, loading, load } = useLazyQuery(viewerRawObjectQuery, () => ({
streamId: projectId.value,
projectId: projectId.value,
objectId: props.object['referencedId'] as string
}))
@ -35,7 +35,7 @@ if (props.object['referencedId']) {
}
const kvps = computed(() => {
const obj = (result.value?.stream?.object?.data || props.object) as Record<
const obj = (result.value?.project?.object?.data || props.object) as Record<
string,
unknown
>

Просмотреть файл

@ -307,8 +307,18 @@ const isNonEmptyObjectArray = (x: unknown) => isNonEmptyArray(x) && isObject(x[0
const isObject = (x: unknown) =>
typeof x === 'object' && !Array.isArray(x) && x !== null
const isAllowedType = (node: ExplorerNode) =>
!['Objects.Other.DisplayStyle'].includes(node.raw?.speckle_type || '')
const hiddenSpeckleTypes = [
'Objects.Other.DisplayStyle',
'Objects.Other.Revit.RevitMaterial',
'Objects.BuiltElements.Revit.ProjectInfo',
'Objects.BuiltElements.View',
'Objects.BuiltElements.View3D'
]
const isAllowedType = (node: ExplorerNode) => {
const speckleType = node.raw?.speckle_type || ''
return !hiddenSpeckleTypes.some((substring) => speckleType.includes(substring))
}
const unfold = ref(false)

Просмотреть файл

@ -194,7 +194,7 @@ const documents = {
"\n subscription OnProjectAutomationsUpdated($id: String!) {\n projectAutomationsUpdated(projectId: $id) {\n type\n automationId\n automation {\n id\n ...ProjectPageAutomationPage_Automation\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n": types.OnProjectAutomationsUpdatedDocument,
"\n mutation ServerInfoUpdate($info: ServerInfoUpdateInput!) {\n serverInfoUpdate(info: $info)\n }\n": types.ServerInfoUpdateDocument,
"\n mutation AdminPanelDeleteUser($userConfirmation: UserDeleteInput!) {\n adminDeleteUser(userConfirmation: $userConfirmation)\n }\n": types.AdminPanelDeleteUserDocument,
"\n mutation AdminPanelDeleteProject($ids: [String!]) {\n streamsDelete(ids: $ids)\n }\n": types.AdminPanelDeleteProjectDocument,
"\n mutation AdminPanelDeleteProject($ids: [String!]!) {\n projectMutations {\n batchDelete(ids: $ids)\n }\n }\n": types.AdminPanelDeleteProjectDocument,
"\n mutation AdminPanelResendInvite($inviteId: String!) {\n inviteResend(inviteId: $inviteId)\n }\n": types.AdminPanelResendInviteDocument,
"\n mutation AdminPanelDeleteInvite($inviteId: String!) {\n inviteDelete(inviteId: $inviteId)\n }\n": types.AdminPanelDeleteInviteDocument,
"\n mutation AdminChangeUseRole($userRoleInput: UserRoleInput!) {\n userRoleChange(userRoleInput: $userRoleInput)\n }\n": types.AdminChangeUseRoleDocument,
@ -224,14 +224,14 @@ const documents = {
"\n query ViewerModelVersions(\n $projectId: String!\n $modelId: String!\n $versionsCursor: String\n ) {\n project(id: $projectId) {\n id\n role\n model(id: $modelId) {\n id\n versions(cursor: $versionsCursor, limit: 5) {\n totalCount\n cursor\n items {\n ...ViewerModelVersionCardItem\n }\n }\n }\n }\n }\n": types.ViewerModelVersionsDocument,
"\n query ViewerDiffVersions(\n $projectId: String!\n $modelId: String!\n $versionAId: String!\n $versionBId: String!\n ) {\n project(id: $projectId) {\n id\n model(id: $modelId) {\n id\n versionA: version(id: $versionAId) {\n ...ViewerModelVersionCardItem\n }\n versionB: version(id: $versionBId) {\n ...ViewerModelVersionCardItem\n }\n }\n }\n }\n": types.ViewerDiffVersionsDocument,
"\n query ViewerLoadedThreads(\n $projectId: String!\n $filter: ProjectCommentsFilter!\n $cursor: String\n $limit: Int = 25\n ) {\n project(id: $projectId) {\n id\n commentThreads(filter: $filter, cursor: $cursor, limit: $limit) {\n totalCount\n totalArchivedCount\n items {\n ...ViewerCommentThread\n ...LinkableComment\n }\n }\n }\n }\n": types.ViewerLoadedThreadsDocument,
"\n query Stream($streamId: String!, $objectId: String!) {\n stream(id: $streamId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n": types.StreamDocument,
"\n query ViewerRawProjectObject($projectId: String!, $objectId: String!) {\n project(id: $projectId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n": types.ViewerRawProjectObjectDocument,
"\n subscription OnViewerUserActivityBroadcasted(\n $target: ViewerUpdateTrackingTarget!\n $sessionId: String!\n ) {\n viewerUserActivityBroadcasted(target: $target, sessionId: $sessionId) {\n userName\n userId\n user {\n ...LimitedUserAvatar\n }\n state\n status\n sessionId\n }\n }\n": types.OnViewerUserActivityBroadcastedDocument,
"\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": types.OnViewerCommentsUpdatedDocument,
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": types.LinkableCommentFragmentDoc,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n stream(id: $streamId) {\n branch(name: $branchName) {\n id\n }\n }\n }\n": types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n stream(id: $streamId) {\n commit(id: $commitId) {\n id\n branch {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument,
"\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n stream(id: $streamId) {\n id\n commits(limit: 1) {\n totalCount\n items {\n id\n branch {\n id\n }\n }\n }\n }\n }\n": types.LegacyViewerStreamRedirectMetadataDocument,
"\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n comment(id: $commentId, streamId: $projectId) {\n ...LinkableComment\n }\n }\n": types.ResolveCommentLinkDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument,
"\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\n }\n }\n }\n }\n": types.LegacyViewerStreamRedirectMetadataDocument,
"\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n project(id: $projectId) {\n comment(id: $commentId) {\n id\n ...LinkableComment\n }\n }\n }\n": types.ResolveCommentLinkDocument,
"\n fragment AutomateFunctionPage_AutomateFunction on AutomateFunction {\n id\n name\n description\n logo\n supportedSourceApps\n tags\n ...AutomateFunctionPageHeader_Function\n ...AutomateFunctionPageInfo_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n creator {\n id\n }\n }\n": types.AutomateFunctionPage_AutomateFunctionFragmentDoc,
"\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n": types.AutomateFunctionPageDocument,
"\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n": types.AutomateFunctionsPageDocument,
@ -982,7 +982,7 @@ export function graphql(source: "\n mutation AdminPanelDeleteUser($userConfirma
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation AdminPanelDeleteProject($ids: [String!]) {\n streamsDelete(ids: $ids)\n }\n"): (typeof documents)["\n mutation AdminPanelDeleteProject($ids: [String!]) {\n streamsDelete(ids: $ids)\n }\n"];
export function graphql(source: "\n mutation AdminPanelDeleteProject($ids: [String!]!) {\n projectMutations {\n batchDelete(ids: $ids)\n }\n }\n"): (typeof documents)["\n mutation AdminPanelDeleteProject($ids: [String!]!) {\n projectMutations {\n batchDelete(ids: $ids)\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@ -1102,7 +1102,7 @@ export function graphql(source: "\n query ViewerLoadedThreads(\n $projectId:
/**
* 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 Stream($streamId: String!, $objectId: String!) {\n stream(id: $streamId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n"): (typeof documents)["\n query Stream($streamId: String!, $objectId: String!) {\n stream(id: $streamId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n"];
export function graphql(source: "\n query ViewerRawProjectObject($projectId: String!, $objectId: String!) {\n project(id: $projectId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n"): (typeof documents)["\n query ViewerRawProjectObject($projectId: String!, $objectId: String!) {\n project(id: $projectId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@ -1118,19 +1118,19 @@ export function graphql(source: "\n fragment LinkableComment on Comment {\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 query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n stream(id: $streamId) {\n branch(name: $branchName) {\n id\n }\n }\n }\n"): (typeof documents)["\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n stream(id: $streamId) {\n branch(name: $branchName) {\n id\n }\n }\n }\n"];
export function graphql(source: "\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n"): (typeof documents)["\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\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 query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n stream(id: $streamId) {\n commit(id: $commitId) {\n id\n branch {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n stream(id: $streamId) {\n commit(id: $commitId) {\n id\n branch {\n id\n }\n }\n }\n }\n"];
export function graphql(source: "\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\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 query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n stream(id: $streamId) {\n id\n commits(limit: 1) {\n totalCount\n items {\n id\n branch {\n id\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n stream(id: $streamId) {\n id\n commits(limit: 1) {\n totalCount\n items {\n id\n branch {\n id\n }\n }\n }\n }\n }\n"];
export function graphql(source: "\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\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 query ResolveCommentLink($commentId: String!, $projectId: String!) {\n comment(id: $commentId, streamId: $projectId) {\n ...LinkableComment\n }\n }\n"): (typeof documents)["\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n comment(id: $commentId, streamId: $projectId) {\n ...LinkableComment\n }\n }\n"];
export function graphql(source: "\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n project(id: $projectId) {\n comment(id: $commentId) {\n id\n ...LinkableComment\n }\n }\n }\n"): (typeof documents)["\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n project(id: $projectId) {\n comment(id: $commentId) {\n id\n ...LinkableComment\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -13,8 +13,10 @@ export const adminDeleteUserMutation = graphql(`
`)
export const adminDeleteProjectMutation = graphql(`
mutation AdminPanelDeleteProject($ids: [String!]) {
streamsDelete(ids: $ids)
mutation AdminPanelDeleteProject($ids: [String!]!) {
projectMutations {
batchDelete(ids: $ids)
}
}
`)

Просмотреть файл

@ -136,8 +136,8 @@ export const viewerLoadedThreadsQuery = graphql(`
`)
export const viewerRawObjectQuery = graphql(`
query Stream($streamId: String!, $objectId: String!) {
stream(id: $streamId) {
query ViewerRawProjectObject($projectId: String!, $objectId: String!) {
project(id: $projectId) {
id
object(id: $objectId) {
id

Просмотреть файл

@ -14,8 +14,8 @@ import { ViewerHashStateKeys } from '~/lib/viewer/composables/setup/urlHashState
const legacyBranchMetadataQuery = graphql(`
query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {
stream(id: $streamId) {
branch(name: $branchName) {
project(id: $streamId) {
modelByName(name: $branchName) {
id
}
}
@ -24,10 +24,10 @@ const legacyBranchMetadataQuery = graphql(`
const legacyViewerCommitMetadataQuery = graphql(`
query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {
stream(id: $streamId) {
commit(id: $commitId) {
project(id: $streamId) {
version(id: $commitId) {
id
branch {
model {
id
}
}
@ -37,13 +37,13 @@ const legacyViewerCommitMetadataQuery = graphql(`
const legacyViewerStreamMetadataQuery = graphql(`
query LegacyViewerStreamRedirectMetadata($streamId: String!) {
stream(id: $streamId) {
project(id: $streamId) {
id
commits(limit: 1) {
versions(limit: 1) {
totalCount
items {
id
branch {
model {
id
}
}
@ -68,6 +68,7 @@ const adminPageRgx = /^\/admin\/?/
*/
export default defineNuxtRouteMiddleware(async (to) => {
const logger = useLogger()
const path = to.path
const apollo = useApolloClientFromNuxt()
const resourceBuilder = () => SpeckleViewer.ViewerRoute.resourceBuilder()
@ -91,23 +92,33 @@ export default defineNuxtRouteMiddleware(async (to) => {
const resourceIdString = resourceIdStringBuilder.addObject(viewerId).toString()
return navigateTo(modelRoute(viewerStreamId, resourceIdString, hashState))
} else {
const { data } = await apollo
const { data, errors } = await apollo
.query({
query: legacyViewerCommitMetadataQuery,
variables: { streamId: viewerStreamId, commitId: viewerId }
})
.catch(convertThrowIntoFetchResult)
const branchId = data?.stream?.commit?.branch?.id
const branchId = data?.project?.version?.model?.id
return navigateTo(
branchId
? modelRoute(
viewerStreamId,
resourceIdStringBuilder.addModel(branchId, viewerId).toString(),
hashState
)
: projectRoute(viewerStreamId)
)
if (branchId) {
return navigateTo(
modelRoute(
viewerStreamId,
resourceIdStringBuilder.addModel(branchId, viewerId).toString(),
hashState
)
)
} else {
logger.warn(
{
errors,
streamId: viewerStreamId,
commitId: viewerId
},
"Couldn't resolve legacy viewer redirect commit metadata"
)
return navigateTo(projectRoute(viewerStreamId))
}
}
}
@ -131,6 +142,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
const branchName = to.query['branch'] as Optional<string> // get first branch commit
if (!streamId?.length) {
logger.warn('No stream ID provided for embed viewer redirect')
return navigateTo(homeRoute)
}
@ -141,27 +153,37 @@ export default defineNuxtRouteMiddleware(async (to) => {
})
)
} else if (commitId?.length) {
const { data } = await apollo
const { data, errors } = await apollo
.query({
query: legacyViewerCommitMetadataQuery,
variables: { streamId, commitId }
})
.catch(convertThrowIntoFetchResult)
const branchId = data?.stream?.commit?.branch?.id
const branchId = data?.project?.version?.model?.id
return navigateTo(
branchId
? modelRoute(
streamId,
resourceBuilder().addModel(branchId, commitId).toString(),
{
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
}
)
: projectRoute(viewerStreamId)
)
if (branchId) {
return navigateTo(
modelRoute(
streamId,
resourceBuilder().addModel(branchId, commitId).toString(),
{
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
}
)
)
} else {
logger.warn(
{
errors,
streamId,
commitId
},
"Couldn't resolve legacy commit embed redirect metadata"
)
return navigateTo(projectRoute(streamId))
}
} else if (branchName?.length) {
const { data } = await apollo
const { data, errors } = await apollo
.query({
query: legacyBranchMetadataQuery,
variables: {
@ -171,44 +193,63 @@ export default defineNuxtRouteMiddleware(async (to) => {
})
.catch(convertThrowIntoFetchResult)
return navigateTo(
data?.stream?.branch?.id
? modelRoute(
streamId,
resourceBuilder().addModel(data.stream.branch.id).toString(),
{
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
}
)
: projectRoute(streamId)
)
const branchId = data?.project?.modelByName?.id
if (branchId) {
return navigateTo(
modelRoute(streamId, resourceBuilder().addModel(branchId).toString(), {
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
})
)
} else {
logger.warn(
{
errors,
streamId,
branchName: decodeURIComponent(branchName)
},
"Couldn't resolve legacy branch embed redirect metadata"
)
return navigateTo(projectRoute(streamId))
}
} else {
const { data } = await apollo
const { data, errors } = await apollo
.query({ query: legacyViewerStreamMetadataQuery, variables: { streamId } })
.catch(convertThrowIntoFetchResult)
return navigateTo(
data?.stream?.commits?.items?.length && data.stream.commits.items[0].branch
? modelRoute(
data.stream.id,
SpeckleViewer.ViewerRoute.resourceBuilder()
.addModel(
data.stream.commits.items[0].branch.id,
data.stream.commits.items[0].id
)
.toString(),
{
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
}
)
: projectRoute(streamId)
)
if (
data?.project?.versions?.items?.length &&
data.project.versions.items[0].model
) {
return navigateTo(
modelRoute(
data.project.id,
SpeckleViewer.ViewerRoute.resourceBuilder()
.addModel(
data.project.versions.items[0].model.id,
data.project.versions.items[0].id
)
.toString(),
{
[ViewerHashStateKeys.EmbedOptions]: JSON.stringify(embedOptions)
}
)
)
} else {
logger.warn(
{
errors,
streamId
},
"Couldn't resolve legacy stream embed redirect metadata"
)
return navigateTo(projectRoute(streamId))
}
}
}
const [, branchStreamId, branchName] = path.match(streamBranchPageRgx) || []
if (branchStreamId && branchName) {
const { data } = await apollo
const { data, errors } = await apollo
.query({
query: legacyBranchMetadataQuery,
variables: {
@ -217,13 +258,22 @@ export default defineNuxtRouteMiddleware(async (to) => {
}
})
.catch(convertThrowIntoFetchResult)
const branchId = data?.stream?.branch?.id
const branchId = data?.project?.modelByName?.id
return navigateTo(
branchId
? modelVersionsRoute(branchStreamId, branchId)
: projectRoute(branchStreamId)
)
if (branchId) {
return navigateTo(modelVersionsRoute(branchStreamId, branchId))
} else {
logger.warn(
{
errors,
streamId: branchStreamId,
branchName: decodeURIComponent(branchName)
},
"Couldn't resolve legacy branch redirect metadata"
)
return navigateTo(projectRoute(branchStreamId))
}
}
const [, streamId] = path.match(streamPageRgx) || []

Просмотреть файл

@ -5,8 +5,11 @@ import { getLinkToThread } from '~/lib/viewer/helpers/comments'
const resolveLinkQuery = graphql(`
query ResolveCommentLink($commentId: String!, $projectId: String!) {
comment(id: $commentId, streamId: $projectId) {
...LinkableComment
project(id: $projectId) {
comment(id: $commentId) {
id
...LinkableComment
}
}
}
`)
@ -26,7 +29,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
})
.catch(convertThrowIntoFetchResult)
const comment = res.data?.comment
const comment = res.data?.project?.comment
if (!comment) {
return abortNavigation(
createError({

Просмотреть файл

@ -5,10 +5,19 @@ extend type Query {
streamAccessRequest(streamId: String!): StreamAccessRequest
@hasServerRole(role: SERVER_GUEST)
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use User.projectAccessRequest instead."
)
}
extend type User {
"""
Get pending project access request, that the user made
"""
projectAccessRequest(projectId: String!): ProjectAccessRequest
@hasServerRole(role: SERVER_GUEST)
@isOwner
}
extend type Stream {
"""
Pending stream access requests
@ -16,10 +25,33 @@ extend type Stream {
pendingAccessRequests: [StreamAccessRequest!]
@hasStreamRole(role: STREAM_OWNER)
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use Project.pendingAccessRequests instead."
)
}
extend type Project {
"""
Pending project access requests
"""
pendingAccessRequests: [ProjectAccessRequest!] @hasStreamRole(role: STREAM_OWNER)
}
type ProjectAccessRequestMutations {
"""
Request access to a specific project
"""
create(projectId: String!): ProjectAccessRequest!
"""
Accept or decline a project access request. Must be a project owner to invoke this.
"""
use(
requestId: String!
accept: Boolean!
role: StreamRole! = STREAM_CONTRIBUTOR
): Project!
}
extend type Mutation {
"""
Accept or decline a stream access request. Must be a stream owner to invoke this.
@ -32,7 +64,7 @@ extend type Mutation {
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "users:invite")
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.use instead."
)
"""
@ -42,10 +74,19 @@ extend type Mutation {
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "users:invite")
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.create instead."
)
}
extend type ProjectMutations {
"""
Access request related mutations
"""
accessRequestMutations: ProjectAccessRequestMutations!
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "users:invite")
}
"""
Created when a user requests to become a contributor on a stream
"""
@ -60,3 +101,18 @@ type StreamAccessRequest {
stream: Stream!
createdAt: DateTime!
}
"""
Created when a user requests to become a contributor on a project
"""
type ProjectAccessRequest {
id: ID!
requester: LimitedUser!
requesterId: String!
projectId: String!
"""
Can only be selected if authed user has proper access
"""
project: Project!
createdAt: DateTime!
}

Просмотреть файл

@ -1,8 +1,9 @@
extend type Query {
comment(id: String!, streamId: String!): Comment
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use Project.comment instead."
)
"""
This query can be used in the following ways:
- get all the comments for a stream: **do not pass in any resource identifiers**.
@ -27,6 +28,11 @@ extend type Project {
limit: Int! = 25
filter: ProjectCommentsFilter
): ProjectCommentCollection!
"""
Get specific project comment/thread by ID
"""
comment(id: String!): Comment
}
extend type Version {

Просмотреть файл

@ -13,7 +13,7 @@ extend type Stream {
)
branch(name: String = "main"): Branch
@deprecated(
reason: "Part of the old API surface and will be removed in the future. Use Project.model instead."
reason: "Part of the old API surface and will be removed in the future. Use Project.model or Project.modelByName instead."
)
}
@ -106,7 +106,7 @@ extend type Mutation {
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "streams:write")
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use VersionMutations.create instead."
)
commitUpdate(commit: CommitUpdateInput!): Boolean!
@ -120,7 +120,7 @@ extend type Mutation {
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "streams:read")
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use VersionMutations.markReceived instead."
)
commitDelete(commit: CommitDeleteInput!): Boolean!

Просмотреть файл

@ -40,6 +40,11 @@ extend type Project {
Retrieve a specific project version by its ID
"""
version(id: String!): Version
"""
Retrieve a specific project model by its ID
"""
modelByName(name: String!): Model!
}
extend type User {
@ -174,10 +179,29 @@ input UpdateVersionInput {
message: String
}
input CreateVersionInput {
projectId: String!
modelId: String!
objectId: String!
message: String
sourceApplication: String
totalChildrenCount: Int
parents: [String!]
}
input MarkReceivedVersionInput {
projectId: String!
versionId: String!
sourceApplication: String!
message: String
}
type VersionMutations {
moveToModel(input: MoveVersionsInput!): Model!
delete(input: DeleteVersionsInput!): Boolean!
update(input: UpdateVersionInput!): Version!
create(input: CreateVersionInput!): Version!
markReceived(input: MarkReceivedVersionInput!): Boolean!
}
extend type Mutation {

Просмотреть файл

@ -1,14 +1,18 @@
extend type Stream {
object(id: String!): Object
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use Project.object instead."
)
}
extend type Project {
object(id: String!): Object
}
type Object {
id: String!
speckleType: String
applicationId: String
applicationId: String @deprecated(reason: "Not implemented.")
createdAt: DateTime
totalChildrenCount: Int
"""
@ -32,11 +36,13 @@ type Object {
type ObjectCollection {
totalCount: Int!
cursor: String
objects: [Object]!
objects: [Object!]!
}
extend type Mutation {
objectCreate(objectInput: ObjectCreateInput!): [String]!
objectCreate(objectInput: ObjectCreateInput!): [String!]!
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "streams:write")
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
)

Просмотреть файл

@ -99,6 +99,11 @@ type ProjectMutations {
"""
delete(id: String!): Boolean! @hasServerRole(role: SERVER_USER)
"""
Batch delete projects
"""
batchDelete(ids: [String!]!): Boolean! @hasServerRole(role: SERVER_ADMIN)
"""
Updates an existing project
"""

Просмотреть файл

@ -206,7 +206,7 @@ extend type Mutation {
streamsDelete(ids: [String!]): Boolean!
@hasServerRole(role: SERVER_ADMIN)
@deprecated(
reason: "Part of the old API surface and will be removed in the future."
reason: "Part of the old API surface and will be removed in the future. Use ProjectMutations.batchDelete instead."
)
"""

Просмотреть файл

@ -13,11 +13,14 @@ generates:
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn'
Commit: '@/modules/core/helpers/graphTypes#CommitGraphQLReturn'
Project: '@/modules/core/helpers/graphTypes#ProjectGraphQLReturn'
Object: '@/modules/core/helpers/graphTypes#ObjectGraphQLReturn'
Version: '@/modules/core/helpers/graphTypes#VersionGraphQLReturn'
ServerInvite: '@/modules/core/helpers/graphTypes#ServerInviteGraphQLReturnType'
Model: '@/modules/core/helpers/graphTypes#ModelGraphQLReturn'
ModelsTreeItem: '@/modules/core/helpers/graphTypes#ModelsTreeItemGraphQLReturn'
StreamAccessRequest: '@/modules/accessrequests/helpers/graphTypes#StreamAccessRequestGraphQLReturn'
ProjectAccessRequest: '@/modules/accessrequests/helpers/graphTypes#ProjectAccessRequestGraphQLReturn'
ProjectAccessRequestMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
LimitedUser: '@/modules/core/helpers/graphTypes#LimitedUserGraphQLReturn'
ActiveUserMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
ProjectMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'

Просмотреть файл

@ -0,0 +1,95 @@
/**
Adapted from prom-client: https://github.com/siimon/prom-client/tree/master/lib/metrics
Copyright 2015 Simon Nyberg
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Histogram, Registry } from 'prom-client'
import type { Metric } from '@/logging/highFrequencyMetrics/highfrequencyMonitoring'
const NODEJS_HEAP_SIZE_TOTAL = 'nodejs_heap_size_total_bytes_high_frequency'
const NODEJS_HEAP_SIZE_USED = 'nodejs_heap_size_used_bytes_high_frequency'
const NODEJS_EXTERNAL_MEMORY = 'nodejs_external_memory_bytes_high_frequency'
type BucketName =
| typeof NODEJS_HEAP_SIZE_TOTAL
| typeof NODEJS_HEAP_SIZE_USED
| typeof NODEJS_EXTERNAL_MEMORY
const DEFAULT_NODEJS_HEAP_SIZE_BUCKETS = {
NODEJS_HEAP_SIZE_TOTAL: [0, 0.1e9, 0.25e9, 0.5e9, 0.75e9, 1e9, 2e9], //TODO: check if this is the right default
NODEJS_HEAP_SIZE_USED: [0, 0.1e9, 0.25e9, 0.5e9, 0.75e9, 1e9, 2e9], //TODO: check if this is the right default
NODEJS_EXTERNAL_MEMORY: [0, 0.1e9, 0.25e9, 0.5e9, 0.75e9, 1e9, 2e9] //TODO: check if this is the right default
}
type MetricConfig = {
prefix?: string
labels?: Record<string, string>
buckets?: Record<BucketName, number[]>
}
export const heapSizeAndUsed = (
registry: Registry,
config: MetricConfig = {}
): Metric => {
const registers = registry ? [registry] : undefined
const namePrefix = config.prefix ?? ''
const labels = config.labels ?? {}
const labelNames = Object.keys(labels)
const buckets = { ...DEFAULT_NODEJS_HEAP_SIZE_BUCKETS, ...config.buckets }
const heapSizeTotal = new Histogram({
name: namePrefix + NODEJS_HEAP_SIZE_TOTAL,
help: 'Process heap size from Node.js in bytes. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.NODEJS_HEAP_SIZE_TOTAL,
labelNames
})
const heapSizeUsed = new Histogram({
name: namePrefix + NODEJS_HEAP_SIZE_USED,
help: 'Process heap size used from Node.js in bytes. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.NODEJS_HEAP_SIZE_USED,
labelNames
})
const externalMemUsed = new Histogram({
name: namePrefix + NODEJS_EXTERNAL_MEMORY,
help: 'Node.js external memory size in bytes. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.NODEJS_EXTERNAL_MEMORY,
labelNames
})
return {
collect: () => {
const memUsage = safeMemoryUsage()
if (memUsage) {
heapSizeTotal.observe(labels, memUsage.heapTotal)
heapSizeUsed.observe(labels, memUsage.heapUsed)
if (memUsage.external !== undefined) {
externalMemUsed.observe(labels, memUsage.external)
}
}
}
}
}
function safeMemoryUsage() {
try {
return process.memoryUsage()
} catch {
return
}
}

Просмотреть файл

@ -0,0 +1,72 @@
/**
* High frequency monitoring, collects data related to CPU, memory, and network usage
* at a higher frequency than the default prometheus monitoring. It makes the data
* available to Prometheus via an histogram.
*/
import { Histogram, Registry } from 'prom-client'
import { processCpuTotal } from '@/logging/highFrequencyMetrics/processCPUTotal'
import { heapSizeAndUsed } from '@/logging/highFrequencyMetrics/heapSizeAndUsed'
type MetricConfig = {
prefix?: string
labels?: Record<string, string>
buckets?: Record<string, number[]>
}
type HighFrequencyMonitor = {
start: () => () => void
}
export const initHighFrequencyMonitoring = (params: {
register: Registry
collectionPeriodMilliseconds: number
config?: MetricConfig
}): HighFrequencyMonitor => {
const { register, collectionPeriodMilliseconds } = params
const config = params.config ?? {}
const registers = register ? [register] : undefined
const namePrefix = config.prefix ?? ''
const labels = config.labels ?? {}
const labelNames = Object.keys(labels)
const metrics = [processCpuTotal(register, config), heapSizeAndUsed(register, config)]
const selfMonitor = new Histogram({
name: namePrefix + 'self_monitor_time_high_frequency',
help: 'The time taken to collect all of the high frequency metrics, seconds.',
registers,
buckets: [0, 0.001, 0.01, 0.025, 0.05, 0.1, 0.2],
labelNames
})
return {
start: collectHighFrequencyMetrics({
selfMonitor,
metrics,
collectionPeriodMilliseconds
})
}
}
export interface Metric {
collect: () => void
}
const collectHighFrequencyMetrics = (params: {
selfMonitor: Histogram<string>
collectionPeriodMilliseconds: number
metrics: Metric[]
}) => {
const { selfMonitor, metrics, collectionPeriodMilliseconds } = params
return () => {
const intervalId = setInterval(() => {
const end = selfMonitor.startTimer()
for (const metric of metrics) {
metric.collect()
}
end()
}, collectionPeriodMilliseconds)
return () => clearInterval(intervalId)
}
}

Просмотреть файл

@ -0,0 +1,91 @@
/**
* Adapted from prom-client: https://github.com/siimon/prom-client/tree/master/lib/metrics
*
Copyright 2015 Simon Nyberg
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Histogram, Registry } from 'prom-client'
import type { Metric } from '@/logging/highFrequencyMetrics/highfrequencyMonitoring'
const PROCESS_CPU_USER_SECONDS = 'process_cpu_user_seconds_total_high_frequency'
const PROCESS_CPU_SYSTEM_SECONDS = 'process_cpu_system_seconds_total_high_frequency'
const PROCESS_CPU_SECONDS = 'process_cpu_seconds_total_high_frequency'
type BucketName =
| typeof PROCESS_CPU_USER_SECONDS
| typeof PROCESS_CPU_SYSTEM_SECONDS
| typeof PROCESS_CPU_SECONDS
const DEFAULT_CPU_TOTAL_BUCKETS = {
PROCESS_CPU_SECONDS: [0, 0.1, 0.25, 0.5, 0.75, 1, 2], //TODO: check if this is the right default
PROCESS_CPU_USER_SECONDS: [0, 0.1, 0.25, 0.5, 0.75, 1, 2], //TODO: check if this is the right default
PROCESS_CPU_SYSTEM_SECONDS: [0, 0.1, 0.25, 0.5, 0.75, 1, 2] //TODO: check if this is the right default
}
type MetricConfig = {
prefix?: string
labels?: Record<string, string>
buckets?: Record<BucketName, number[]>
}
export const processCpuTotal = (
registry: Registry,
config: MetricConfig = {}
): Metric => {
const registers = registry ? [registry] : undefined
const namePrefix = config.prefix ?? ''
const labels = config.labels ?? {}
const labelNames = Object.keys(labels)
const buckets = { ...DEFAULT_CPU_TOTAL_BUCKETS, ...config.buckets }
const cpuUserUsageHistogram = new Histogram({
name: namePrefix + PROCESS_CPU_USER_SECONDS,
help: 'Total user CPU time spent in seconds. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
labelNames,
buckets: buckets.PROCESS_CPU_USER_SECONDS,
registers
})
const cpuSystemUsageHistogram = new Histogram({
name: namePrefix + PROCESS_CPU_SYSTEM_SECONDS,
help: 'Total system CPU time spent in seconds. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.PROCESS_CPU_SYSTEM_SECONDS,
labelNames
})
const cpuUsageHistogram = new Histogram({
name: namePrefix + PROCESS_CPU_SECONDS,
help: 'Total user and system CPU time spent in seconds. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.PROCESS_CPU_USER_SECONDS,
labelNames
})
let lastCpuUsage = process.cpuUsage()
return {
collect: () => {
const cpuUsage = process.cpuUsage()
const userUsageMicros = cpuUsage.user - lastCpuUsage.user
const systemUsageMicros = cpuUsage.system - lastCpuUsage.system
lastCpuUsage = cpuUsage
cpuUserUsageHistogram.observe(labels, userUsageMicros / 1e6)
cpuSystemUsageHistogram.observe(labels, systemUsageMicros / 1e6)
cpuUsageHistogram.observe(labels, (userUsageMicros + systemUsageMicros) / 1e6)
}
}
}

Просмотреть файл

@ -5,7 +5,10 @@ const { getMachineId } = require('./machineId')
const prometheusClient = require('prom-client')
const promBundle = require('express-prom-bundle')
const { initKnexPrometheusMetrics } = require('./knexMonitoring')
const { initKnexPrometheusMetrics } = require('@/logging/knexMonitoring')
const {
initHighFrequencyMonitoring
} = require('@/logging/highFrequencyMetrics/highfrequencyMonitoring')
let prometheusInitialized = false
@ -20,6 +23,11 @@ module.exports = function (app) {
app: 'server'
})
prometheusClient.collectDefaultMetrics()
const highfrequencyMonitoring = initHighFrequencyMonitoring({
register: prometheusClient.register,
collectionPeriodMilliseconds: 100
})
highfrequencyMonitoring.start()
initKnexPrometheusMetrics()
const expressMetricsMiddleware = promBundle({

Просмотреть файл

@ -1,7 +1,12 @@
import { AccessRequestType } from '@/modules/accessrequests/repositories'
import {
getPendingProjectRequests,
getPendingStreamRequests,
getUserProjectAccessRequest,
getUserStreamAccessRequest,
processPendingProjectRequest,
processPendingStreamRequest,
requestProjectAccess,
requestStreamAccess
} from '@/modules/accessrequests/services/stream'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
@ -35,6 +40,35 @@ const resolvers: Resolvers = {
return await requestStreamAccess(userId, streamId)
}
},
ProjectMutations: {
accessRequestMutations: () => ({})
},
ProjectAccessRequestMutations: {
async create(_parent, args, ctx) {
const { userId } = ctx
const { projectId } = args
return await requestProjectAccess(userId!, projectId)
},
async use(_parent, args, ctx) {
const { userId, resourceAccessRules } = ctx
const { requestId, accept, role } = args
const usedReq = await processPendingProjectRequest(
userId!,
requestId,
accept,
mapStreamRoleToValue(role),
resourceAccessRules
)
const project = await ctx.loaders.streams.getStream.load(usedReq.resourceId)
if (!project) {
throw new LogicError('Unexpectedly unable to find request project')
}
return project
}
},
Query: {
async streamAccessRequest(_, args, ctx) {
const { streamId } = args
@ -44,12 +78,26 @@ const resolvers: Resolvers = {
return await getUserStreamAccessRequest(userId, streamId)
}
},
User: {
async projectAccessRequest(parent, args) {
const { id: userId } = parent
const { projectId } = args
return await getUserProjectAccessRequest(userId, projectId)
}
},
Stream: {
async pendingAccessRequests(parent) {
const { id } = parent
return await getPendingStreamRequests(id)
}
},
Project: {
async pendingAccessRequests(parent) {
const { id } = parent
return await getPendingProjectRequests(id)
}
},
StreamAccessRequest: {
async requester(parent, _args, ctx) {
const { requesterId } = parent
@ -77,6 +125,45 @@ const resolvers: Resolvers = {
return stream
}
},
ProjectAccessRequest: {
async requester(parent, _args, ctx) {
const { requesterId } = parent
const user = await ctx.loaders.users.getUser.load(requesterId)
if (!user) {
throw new LogicError('Unable to find requester')
}
return user
},
async projectId(parent) {
const { resourceId, resourceType } = parent
if (resourceType !== AccessRequestType.Stream) {
throw new LogicError('Unexpectedly returned invalid resource type')
}
return resourceId
},
async project(parent, _args, ctx) {
const { resourceId, resourceType } = parent
if (resourceType !== AccessRequestType.Stream) {
throw new LogicError('Unexpectedly returned invalid resource type')
}
const project = await ctx.loaders.streams.getStream.load(resourceId)
if (!project) {
throw new LogicError('Unable to find request project')
}
await validateStreamAccess(
ctx.userId,
project.id,
Roles.Stream.Reviewer,
ctx.resourceAccessRules
)
return project
}
}
}

Просмотреть файл

@ -1,6 +1,9 @@
import { StreamAccessRequestRecord } from '@/modules/accessrequests/repositories'
import { StreamAccessRequest } from '@/modules/core/graph/generated/graphql'
export type StreamAccessRequestGraphQLReturn = Omit<
StreamAccessRequest,
'requester' | 'stream'
>
export type ProjectAccessRequestGraphQLReturn = StreamAccessRequestRecord

Просмотреть файл

@ -12,7 +12,8 @@ import {
getPendingAccessRequest,
getPendingAccessRequests,
getUsersPendingAccessRequest,
ServerAccessRequestRecord
ServerAccessRequestRecord,
StreamAccessRequestRecord
} from '@/modules/accessrequests/repositories'
import { StreamInvalidAccessError } from '@/modules/core/errors/stream'
import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types'
@ -36,44 +37,52 @@ function buildStreamAccessRequestGraphQLReturn(
}
}
export async function getUserProjectAccessRequest(
userId: string,
projectId: string
): Promise<Nullable<StreamAccessRequestRecord>> {
const req = await getUsersPendingAccessRequest(
userId,
AccessRequestType.Stream,
projectId
)
return req || null
}
export async function getUserStreamAccessRequest(
userId: string,
streamId: string
): Promise<Nullable<StreamAccessRequestGraphQLReturn>> {
const req = await getUsersPendingAccessRequest(
userId,
AccessRequestType.Stream,
streamId
)
const req = await getUserProjectAccessRequest(userId, streamId)
if (!req) return null
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Create new stream access request
* Create new project access request
*/
export async function requestStreamAccess(userId: string, streamId: string) {
export async function requestProjectAccess(userId: string, projectId: string) {
const [stream, existingRequest] = await Promise.all([
getStream({ userId, streamId }),
getUserStreamAccessRequest(userId, streamId)
getStream({ userId, streamId: projectId }),
getUserStreamAccessRequest(userId, projectId)
])
if (existingRequest) {
throw new AccessRequestCreationError(
'User already has a pending access request for this stream'
'User already has a pending access request for this resource'
)
}
if (!stream) {
throw new AccessRequestCreationError(
"Can't request access to a non-existant stream"
"Can't request access to a non-existant resource"
)
}
if (stream.role) {
throw new AccessRequestCreationError(
'User already has access to the specified stream'
'User already has access to the specified resource'
)
}
@ -81,23 +90,40 @@ export async function requestStreamAccess(userId: string, streamId: string) {
id: generateId(),
requesterId: userId,
resourceType: AccessRequestType.Stream,
resourceId: streamId
resourceId: projectId
})
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Created, {
request: req
})
return req
}
/**
* Create new stream access request
*/
export async function requestStreamAccess(userId: string, streamId: string) {
const req = await requestProjectAccess(userId, streamId)
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Get pending project access requests
*/
export async function getPendingProjectRequests(
projectId: string
): Promise<StreamAccessRequestRecord[]> {
return await getPendingAccessRequests(AccessRequestType.Stream, projectId)
}
/**
* Get pending stream access requests
*/
export async function getPendingStreamRequests(
streamId: string
): Promise<StreamAccessRequestGraphQLReturn[]> {
const reqs = await getPendingAccessRequests(AccessRequestType.Stream, streamId)
const reqs = await getPendingProjectRequests(streamId)
return reqs.map(buildStreamAccessRequestGraphQLReturn)
}
@ -152,4 +178,8 @@ export async function processPendingStreamRequest(
approved: accept ? { role } : undefined,
finalizedBy: userId
})
return req
}
export const processPendingProjectRequest = processPendingStreamRequest

Просмотреть файл

@ -0,0 +1,417 @@
import {
deleteRequestById,
getPendingAccessRequest
} from '@/modules/accessrequests/repositories'
import { requestProjectAccess } from '@/modules/accessrequests/services/stream'
import { ActionTypes } from '@/modules/activitystream/helpers/types'
import {
ServerAccessRequests,
StreamActivity,
Streams,
Users
} from '@/modules/core/dbSchema'
import { StreamAccessUpdateError } from '@/modules/core/errors/stream'
import { mapStreamRoleToValue } from '@/modules/core/helpers/graphTypes'
import { Roles } from '@/modules/core/helpers/mainConstants'
import { getStreamCollaborators } from '@/modules/core/repositories/streams'
import {
addOrUpdateStreamCollaborator,
removeStreamCollaborator
} from '@/modules/core/services/streams/streamAccessService'
import { NotificationType } from '@/modules/notifications/helpers/types'
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
CreateProjectAccessRequestDocument,
GetActiveUserFullProjectAccessRequestDocument,
GetActiveUserProjectAccessRequestDocument,
GetPendingProjectAccessRequestsDocument,
StreamRole,
UseProjectAccessRequestDocument
} from '@/test/graphql/generated/graphql'
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
import { truncateTables } from '@/test/hooks'
import { EmailSendingServiceMock } from '@/test/mocks/global'
import {
buildNotificationsStateTracker,
NotificationsStateManager
} from '@/test/notificationsHelper'
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { expect } from 'chai'
import { noop } from 'lodash'
const isNotCollaboratorError = (e: unknown) =>
e instanceof StreamAccessUpdateError &&
e.message.includes('User is not a stream collaborator')
const createReqAndGetId = async (userId: string, streamId: string) => {
const createReqRes = await requestProjectAccess(userId, streamId)
return createReqRes.id
}
const cleanup = async () => {
await truncateTables([Streams.name, ServerAccessRequests.name, Users.name])
}
describe('Project access requests', () => {
let apollo: TestApolloServer
let notificationsStateManager: NotificationsStateManager
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const otherGuy: BasicTestUser = {
name: 'and im the other guy, hi!',
email: '',
id: ''
}
const anotherGuy: BasicTestUser = {
name: 'and im another guy lol',
email: '',
id: ''
}
const otherGuysPrivateStream: BasicTestStream = {
name: 'other guys test stream #1',
isPublic: false,
ownerId: '',
id: ''
}
const otherGuysPublicStream: BasicTestStream = {
name: 'other guys public test stream #2',
isPublic: true,
ownerId: '',
id: ''
}
const myPrivateStream: BasicTestStream = {
name: 'this is my private stream #1',
isPublic: false,
ownerId: '',
id: ''
}
before(async () => {
await cleanup()
await createTestUsers([me, otherGuy, anotherGuy])
await createTestStreams([
[otherGuysPrivateStream, otherGuy],
[otherGuysPublicStream, otherGuy],
[myPrivateStream, me]
])
apollo = await testApolloServer({
authUserId: me.id
})
notificationsStateManager = buildNotificationsStateTracker()
})
after(async () => {
notificationsStateManager.destroy()
})
const createReq = async (projectId: string) =>
await apollo.execute(CreateProjectAccessRequestDocument, {
projectId
})
const getActiveUserReq = async (projectId: string) =>
await apollo.execute(GetActiveUserProjectAccessRequestDocument, {
projectId
})
const getPendingProjectReqs = async (projectId: string) =>
await apollo.execute(GetPendingProjectAccessRequestsDocument, {
projectId
})
const getFullActiveUserAccessRequest = async (projectId: string) =>
await apollo.execute(GetActiveUserFullProjectAccessRequestDocument, {
projectId
})
const useReq = async (
requestId: string,
accept: boolean,
role: StreamRole = StreamRole.StreamContributor
) =>
await apollo.execute(UseProjectAccessRequestDocument, {
requestId,
accept,
role
})
describe('when being created', () => {
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name, StreamActivity.name])
})
afterEach(async () => {
// Ensure me doesnt have any roles on stream1
await removeStreamCollaborator(otherGuysPrivateStream.id, me.id, me.id).catch(
(e) => {
if (!isNotCollaboratorError(e)) throw e
}
)
})
it('operation succeeds', async () => {
const sendEmailCall = EmailSendingServiceMock.hijackFunction(
'sendEmail',
async () => true
)
const waitForAck = notificationsStateManager.waitForAck(
(e) => e.result?.type === NotificationType.NewStreamAccessRequest
)
const results = await createReq(otherGuysPrivateStream.id)
// req gets created
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.projectMutations.accessRequestMutations.create.id).to.be.ok
expect(results.data?.projectMutations.accessRequestMutations.create.createdAt).to
.be.ok
expect(results.data?.projectMutations.accessRequestMutations.create.requesterId)
.to.be.ok
expect(
results.data?.projectMutations.accessRequestMutations.create.requester.id
).to.eq(results.data?.projectMutations.accessRequestMutations.create.requesterId)
expect(results.data?.projectMutations.accessRequestMutations.create.projectId).to
.be.ok
await waitForAck
// email gets sent out
expect(sendEmailCall.args?.[0]?.[0]).to.be.ok
const emailParams = sendEmailCall.args[0][0]
expect(emailParams.subject).to.contain('A user requested access to your project')
expect(emailParams.html).to.be.ok
expect(emailParams.text).to.be.ok
expect(emailParams.to).to.eq(otherGuy.email)
// activity stream item inserted
const streamActivity = await getStreamActivities(otherGuysPrivateStream.id, {
actionType: ActionTypes.Stream.AccessRequestSent,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
})
it('operation fails if request already exists', async () => {
const firstResults = await createReq(otherGuysPrivateStream.id)
expect(firstResults).to.not.haveGraphQLErrors()
expect(firstResults.data?.projectMutations.accessRequestMutations.create.id).to.be
.ok
const secondResults = await createReq(otherGuysPrivateStream.id)
expect(secondResults).to.haveGraphQLErrors('already has a pending access request')
expect(secondResults.data?.projectMutations.accessRequestMutations.create.id).to
.be.not.ok
})
it('operation fails if stream is nonexistant', async () => {
const secondResults = await createReq('abcdef123')
expect(secondResults).to.haveGraphQLErrors('non-existant resource')
expect(secondResults.data?.projectMutations.accessRequestMutations.create.id).to
.be.not.ok
})
it('operation fails if user already has a role on the stream', async () => {
await addOrUpdateStreamCollaborator(
otherGuysPrivateStream.id,
me.id,
Roles.Stream.Contributor,
otherGuy.id
)
const secondResults = await createReq(otherGuysPrivateStream.id)
expect(secondResults).to.haveGraphQLErrors('user already has access')
expect(secondResults.data?.projectMutations.accessRequestMutations.create.id).to
.be.not.ok
})
})
describe('when being read', () => {
let myRequestId: string
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let myPublicReqId: string
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let anotherGuysRequestId: string
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name])
const [myNewReqId, anotherGuysNewReqId, myNewPublicReqId] = await Promise.all([
createReqAndGetId(me.id, otherGuysPrivateStream.id),
createReqAndGetId(anotherGuy.id, otherGuysPrivateStream.id),
createReqAndGetId(me.id, otherGuysPublicStream.id)
])
myRequestId = myNewReqId
anotherGuysRequestId = anotherGuysNewReqId
myPublicReqId = myNewPublicReqId
})
it('returns the request correctly', async () => {
const results = await getActiveUserReq(otherGuysPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.activeUser?.projectAccessRequest?.id).to.eq(myRequestId)
expect(results.data?.activeUser?.projectAccessRequest?.createdAt).to.be.ok
expect(results.data?.activeUser?.projectAccessRequest?.requesterId).to.be.ok
expect(results.data?.activeUser?.projectAccessRequest?.requester.id).to.eq(
results.data?.activeUser?.projectAccessRequest?.requesterId
)
expect(results.data?.activeUser?.projectAccessRequest?.projectId).to.be.ok
})
it('returns null if no req found', async () => {
await deleteRequestById(myRequestId)
const results = await getActiveUserReq(otherGuysPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.activeUser?.projectAccessRequest).to.eq(null)
})
it('throws error if attempting to read private stream metadata before has access to it', async () => {
const results = await getFullActiveUserAccessRequest(otherGuysPrivateStream.id)
expect(results).to.haveGraphQLErrors(
'User does not have required access to stream'
)
})
it('doesnt throw if attempting to read stream metadata on accessible stream', async () => {
const results = await getFullActiveUserAccessRequest(otherGuysPublicStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.activeUser?.projectAccessRequest?.project.id).to.be.ok
})
})
describe('when being read from a stream', () => {
before(async () => {
await truncateTables([ServerAccessRequests.name])
await addOrUpdateStreamCollaborator(
otherGuysPublicStream.id,
me.id,
Roles.Stream.Contributor,
otherGuy.id
)
await Promise.all([
createReqAndGetId(otherGuy.id, myPrivateStream.id),
createReqAndGetId(anotherGuy.id, myPrivateStream.id)
])
})
after(async () => {
await removeStreamCollaborator(otherGuysPublicStream.id, me.id, me.id).catch(noop)
})
it(`operation fails if reading from a non-owned stream`, async () => {
const results = await getPendingProjectReqs(otherGuysPublicStream.id)
expect(results).to.haveGraphQLErrors('not authorized')
expect(results.data?.project?.pendingAccessRequests).to.be.not.ok
expect(results.data?.project?.id).to.be.ok
})
it('operation succeeds', async () => {
const results = await getPendingProjectReqs(myPrivateStream.id)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.project?.pendingAccessRequests).to.have.lengthOf(2)
for (const pendingReq of results.data!.project!.pendingAccessRequests!) {
expect(pendingReq.id).to.be.ok
expect(pendingReq.createdAt).to.be.ok
expect(pendingReq.requesterId).to.be.ok
expect(pendingReq.projectId).to.be.ok
expect(pendingReq.project.id).to.eq(results.data!.project!.id)
expect(pendingReq.requester.id).to.eq(pendingReq.requesterId)
expect([otherGuy.id, anotherGuy.id].includes(pendingReq.requesterId)).to.be.true
}
})
})
describe('when being processed', () => {
let validReqId: string
beforeEach(async () => {
await truncateTables([ServerAccessRequests.name, StreamActivity.name])
await removeStreamCollaborator(
myPrivateStream.id,
otherGuy.id,
otherGuy.id
).catch((e) => {
if (!isNotCollaboratorError(e)) throw e
})
validReqId = await createReqAndGetId(otherGuy.id, myPrivateStream.id)
})
it('processing fails when pointing to nonexistant req', async () => {
const results = await useReq('abcd', true)
expect(results).to.haveGraphQLErrors('no request with this id exists')
expect(results.data?.projectMutations.accessRequestMutations.use).to.be.not.ok
})
it('processing fails when pointing to a req the user doesnt have access to', async () => {
const inaccessibleReqId = await createReqAndGetId(
anotherGuy.id,
otherGuysPrivateStream.id
)
const results = await useReq(inaccessibleReqId, true)
expect(results).to.haveGraphQLErrors('you must own the stream')
expect(results.data?.projectMutations.accessRequestMutations.use).to.be.not.ok
})
const validProcessingDataSet = [
{ display: 'declining', accept: false },
{ display: 'approving', accept: true },
{
display: 'approving with custom role',
accept: true,
role: StreamRole.StreamReviewer
}
]
validProcessingDataSet.forEach(({ display, accept, role }) => {
it(`${display} works`, async () => {
const results = await useReq(validReqId, accept, role)
expect(results).to.not.haveGraphQLErrors()
expect(results.data?.projectMutations.accessRequestMutations.use).to.be.ok
// req should be deleted
const req = await getPendingAccessRequest(validReqId)
expect(req).to.not.be.ok
// activity stream item should be inserted
if (accept) {
const streamActivity = await getStreamActivities(myPrivateStream.id, {
actionType: ActionTypes.Stream.PermissionsAdd,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
const collaborators = await getStreamCollaborators(myPrivateStream.id)
const newCollaborator = collaborators.find((c) => c.id === otherGuy.id)
expect(newCollaborator).to.be.ok
expect(newCollaborator?.streamRole).to.eq(
role ? mapStreamRoleToValue(role) : Roles.Stream.Contributor
)
} else {
const streamActivity = await getStreamActivities(myPrivateStream.id, {
actionType: ActionTypes.Stream.AccessRequestDeclined,
userId: me.id
})
expect(streamActivity).to.have.lengthOf(1)
}
})
})
})
})

Просмотреть файл

@ -194,7 +194,7 @@ describe('Stream access requests', () => {
it('operation fails if stream is nonexistant', async () => {
const secondResults = await createReq('abcdef123')
expect(secondResults).to.haveGraphQLErrors('non-existant stream')
expect(secondResults).to.haveGraphQLErrors('non-existant resource')
expect(secondResults.data?.streamAccessRequestCreate.id).to.be.not.ok
})

Просмотреть файл

@ -5,7 +5,6 @@ const { getStream } = require('@/modules/core/services/streams')
const { Roles } = require('@/modules/core/helpers/mainConstants')
const {
getComment,
getComments,
getResourceCommentCount,
createComment,
@ -15,6 +14,7 @@ const {
editComment,
streamResourceCheck
} = require('@/modules/comments/services/index')
const { getComment } = require('@/modules/comments/repositories/comments')
const {
ensureCommentSchema
} = require('@/modules/comments/services/commentTextService')
@ -62,19 +62,27 @@ const {
convertLegacyDataToState
} = require('@/modules/comments/services/data')
const getStreamComment = async ({ streamId, commentId }, ctx) => {
await authorizeProjectCommentsAccess({
projectId: streamId,
authCtx: ctx
})
const comment = await getComment({ id: commentId, userId: ctx.userId })
if (comment.streamId !== streamId)
throw new ApolloForbiddenError('You do not have access to this comment.')
return comment
}
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
module.exports = {
Query: {
async comment(parent, args, context) {
await authorizeProjectCommentsAccess({
projectId: args.streamId,
authCtx: context
})
const comment = await getComment({ id: args.id, userId: context.userId })
if (comment.streamId !== args.streamId)
throw new ApolloForbiddenError('You do not have access to this comment.')
return comment
async comment(_parent, args, context) {
return await getStreamComment(
{ streamId: args.streamId, commentId: args.id },
context
)
},
async comments(parent, args, context) {
@ -210,6 +218,12 @@ module.exports = {
threadsOnly: true
}
})
},
async comment(parent, args, context) {
return await getStreamComment(
{ streamId: parent.id, commentId: args.id },
context
)
}
},
Version: {

Просмотреть файл

@ -0,0 +1,91 @@
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
CreateCommentInput,
CreateProjectCommentDocument
} from '@/test/graphql/generated/graphql'
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
import { beforeEachContext } from '@/test/hooks'
import {
BasicTestBranch,
createTestBranches
} from '@/test/speckle-helpers/branchHelper'
import { BasicTestCommit, createTestCommits } from '@/test/speckle-helpers/commitHelper'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { SpeckleViewer } from '@speckle/shared'
import { RichTextEditor } from '@speckle/shared'
import { expect } from 'chai'
const resourceUrlBuilder = SpeckleViewer.ViewerRoute.resourceBuilder
describe('Project Comments', () => {
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const myStream: BasicTestStream = {
name: 'this is my great stream #1',
isPublic: true,
ownerId: '',
id: ''
}
const myBranch: BasicTestBranch = {
name: 'nice branch!!',
streamId: '',
id: '',
authorId: ''
}
const myCommit: BasicTestCommit = {
id: '',
objectId: '',
streamId: '',
authorId: '',
message: 'this is my nice commit :)))',
branchName: myBranch.name
}
before(async () => {
await beforeEachContext()
await createTestUsers([me])
await createTestStreams([[myStream, me]])
await createTestBranches([{ branch: myBranch, stream: myStream, owner: me }])
await createTestCommits([myCommit], { stream: myStream, owner: me })
})
describe('in GraphQL API', () => {
let apollo: TestApolloServer
before(async () => {
apollo = await testApolloServer({
authUserId: me.id
})
})
const createProjectComment = async (input: CreateCommentInput) =>
await apollo.execute(CreateProjectCommentDocument, { input })
it('can be created', async () => {
const input: CreateCommentInput = {
projectId: myStream.id,
resourceIdString: resourceUrlBuilder()
.addModel(myBranch.id, myCommit.id)
.toString(),
content: {
doc: RichTextEditor.convertBasicStringToDocument('hello world')
}
}
const res = await createProjectComment(input)
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.commentMutations.create.id).to.be.ok
expect(res.data?.commentMutations.create.rawText).to.equal('hello world')
expect(res.data?.commentMutations.create.text.doc).to.be.ok
expect(res.data?.commentMutations.create.authorId).to.equal(me.id)
})
describe('after creation', () => {
it.skip('can be retrieved through Project.comment')
})
})
})

Просмотреть файл

@ -15,6 +15,11 @@ export class CommitCreateError extends BaseError {
static code = 'COMMIT_CREATE_ERROR'
}
export class CommitReceiveError extends BaseError {
static defaultMessage = 'An issue occurred while receiving a commit'
static code = 'COMMIT_RECEIVE_ERROR'
}
export class CommitUpdateError extends BaseError {
static defaultMessage = 'An issue occurred while updating a commit'
static code = 'COMMIT_UPDATE_ERROR'

Просмотреть файл

@ -1,6 +1,6 @@
import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
import { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, LimitedUserGraphQLReturn, MutationsObjectGraphQLReturn, GraphQLEmptyReturn } from '@/modules/core/helpers/graphTypes';
import { StreamAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes';
import { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, ObjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, MutationsObjectGraphQLReturn, LimitedUserGraphQLReturn, GraphQLEmptyReturn } from '@/modules/core/helpers/graphTypes';
import { StreamAccessRequestGraphQLReturn, ProjectAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes';
import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from '@/modules/comments/helpers/graphTypes';
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes';
import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types';
@ -26,7 +26,6 @@ export type Scalars = {
BigInt: { input: bigint; output: bigint; }
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
DateTime: { input: Date; output: Date; }
EmailAddress: { input: any; output: any; }
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSONObject: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
@ -811,6 +810,16 @@ export type CreateModelInput = {
projectId: Scalars['ID']['input'];
};
export type CreateVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
modelId: Scalars['String']['input'];
objectId: Scalars['String']['input'];
parents?: InputMaybe<Array<Scalars['String']['input']>>;
projectId: Scalars['String']['input'];
sourceApplication?: InputMaybe<Scalars['String']['input']>;
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};
export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
@ -1001,6 +1010,13 @@ export type LimitedUserTimelineArgs = {
limit?: Scalars['Int']['input'];
};
export type MarkReceivedVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
projectId: Scalars['String']['input'];
sourceApplication: Scalars['String']['input'];
versionId: Scalars['String']['input'];
};
export type Model = {
__typename?: 'Model';
author: LimitedUser;
@ -1170,11 +1186,11 @@ export type Mutation = {
* @deprecated Use commentMutations version
*/
commentView: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.create instead. */
commitCreate: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.delete instead. */
commitDelete: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.markReceived instead. */
commitReceive: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.update/moveToModel instead. */
commitUpdate: Scalars['Boolean']['output'];
@ -1200,7 +1216,7 @@ export type Mutation = {
inviteResend: Scalars['Boolean']['output'];
modelMutations: ModelMutations;
/** @deprecated Part of the old API surface and will be removed in the future. */
objectCreate: Array<Maybe<Scalars['String']['output']>>;
objectCreate: Array<Scalars['String']['output']>;
projectMutations: ProjectMutations;
/** (Re-)send the account verification e-mail */
requestVerification: Scalars['Boolean']['output'];
@ -1212,12 +1228,12 @@ export type Mutation = {
serverInviteCreate: Scalars['Boolean']['output'];
/**
* Request access to a specific stream
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.create instead.
*/
streamAccessRequestCreate: StreamAccessRequest;
/**
* Accept or decline a stream access request. Must be a stream owner to invoke this.
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.use instead.
*/
streamAccessRequestUse: Scalars['Boolean']['output'];
/**
@ -1274,7 +1290,7 @@ export type Mutation = {
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.updateRole instead.
*/
streamUpdatePermission?: Maybe<Scalars['Boolean']['output']>;
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.batchDelete instead. */
streamsDelete: Scalars['Boolean']['output'];
/**
* Used for broadcasting real time typing status in comment threads. Does not persist any info.
@ -1591,6 +1607,7 @@ export type MutationWebhookUpdateArgs = {
export type Object = {
__typename?: 'Object';
/** @deprecated Not implemented. */
applicationId?: Maybe<Scalars['String']['output']>;
/**
* Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
@ -1630,7 +1647,7 @@ export type ObjectChildrenArgs = {
export type ObjectCollection = {
__typename?: 'ObjectCollection';
cursor?: Maybe<Scalars['String']['output']>;
objects: Array<Maybe<Object>>;
objects: Array<Object>;
totalCount: Scalars['Int']['output'];
};
@ -1691,6 +1708,8 @@ export type Project = {
blob?: Maybe<BlobMetadata>;
/** Get the metadata collection of blobs stored for this stream. */
blobs?: Maybe<BlobMetadataCollection>;
/** Get specific project comment/thread by ID */
comment?: Maybe<Comment>;
/** All comment threads in this project */
commentThreads: ProjectCommentCollection;
createdAt: Scalars['DateTime']['output'];
@ -1700,6 +1719,8 @@ export type Project = {
invitedTeam?: Maybe<Array<PendingStreamCollaborator>>;
/** Returns a specific model by its ID */
model: Model;
/** Retrieve a specific project model by its ID */
modelByName: Model;
/** Return a model tree of children for the specified model name */
modelChildrenTree: Array<ModelsTreeItem>;
/** Returns a flat list of all models */
@ -1710,6 +1731,9 @@ export type Project = {
*/
modelsTree: ModelsTreeItemCollection;
name: Scalars['String']['output'];
object?: Maybe<Object>;
/** Pending project access requests */
pendingAccessRequests?: Maybe<Array<ProjectAccessRequest>>;
/** Returns a list models that are being created from a file import */
pendingImportedModels: Array<FileUpload>;
/** Active user's role for this project. `null` if request is not authenticated, or the project is not explicitly shared with you. */
@ -1753,6 +1777,11 @@ export type ProjectBlobsArgs = {
};
export type ProjectCommentArgs = {
id: Scalars['String']['input'];
};
export type ProjectCommentThreadsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<ProjectCommentsFilter>;
@ -1765,6 +1794,11 @@ export type ProjectModelArgs = {
};
export type ProjectModelByNameArgs = {
name: Scalars['String']['input'];
};
export type ProjectModelChildrenTreeArgs = {
fullName: Scalars['String']['input'];
};
@ -1784,6 +1818,11 @@ export type ProjectModelsTreeArgs = {
};
export type ProjectObjectArgs = {
id: Scalars['String']['input'];
};
export type ProjectPendingImportedModelsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>;
};
@ -1810,6 +1849,38 @@ export type ProjectWebhooksArgs = {
id?: InputMaybe<Scalars['String']['input']>;
};
/** Created when a user requests to become a contributor on a project */
export type ProjectAccessRequest = {
__typename?: 'ProjectAccessRequest';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
/** Can only be selected if authed user has proper access */
project: Project;
projectId: Scalars['String']['output'];
requester: LimitedUser;
requesterId: Scalars['String']['output'];
};
export type ProjectAccessRequestMutations = {
__typename?: 'ProjectAccessRequestMutations';
/** Request access to a specific project */
create: ProjectAccessRequest;
/** Accept or decline a project access request. Must be a project owner to invoke this. */
use: Project;
};
export type ProjectAccessRequestMutationsCreateArgs = {
projectId: Scalars['String']['input'];
};
export type ProjectAccessRequestMutationsUseArgs = {
accept: Scalars['Boolean']['input'];
requestId: Scalars['String']['input'];
role?: StreamRole;
};
export type ProjectAutomationCreateInput = {
enabled: Scalars['Boolean']['input'];
name: Scalars['String']['input'];
@ -2052,7 +2123,11 @@ export enum ProjectModelsUpdatedMessageType {
export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Access request related mutations */
accessRequestMutations: ProjectAccessRequestMutations;
automationMutations: ProjectAutomationMutations;
/** Batch delete projects */
batchDelete: Scalars['Boolean']['output'];
/** Create new project */
create: Project;
/**
@ -2078,6 +2153,11 @@ export type ProjectMutationsAutomationMutationsArgs = {
};
export type ProjectMutationsBatchDeleteArgs = {
ids: Array<Scalars['String']['input']>;
};
export type ProjectMutationsCreateArgs = {
input?: InputMaybe<ProjectCreateInput>;
};
@ -2241,7 +2321,7 @@ export type Query = {
automateFunctions: AutomateFunctionCollection;
/** Part of the automation/function creation handshake mechanism */
automateValidateAuthCode: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.comment instead. */
comment?: Maybe<Comment>;
/**
* This query can be used in the following ways:
@ -2280,7 +2360,7 @@ export type Query = {
stream?: Maybe<Stream>;
/**
* Get authed user's stream access request
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use User.projectAccessRequest instead.
*/
streamAccessRequest?: Maybe<StreamAccessRequest>;
/**
@ -2645,7 +2725,7 @@ export type Stream = {
* @deprecated Part of the old API surface and will be removed in the future. Use Project.blobs instead.
*/
blobs?: Maybe<BlobMetadataCollection>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model instead. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model or Project.modelByName instead. */
branch?: Maybe<Branch>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.models or Project.modelsTree instead. */
branches?: Maybe<BranchCollection>;
@ -2690,11 +2770,11 @@ export type Stream = {
/** Whether the stream can be viewed by non-contributors */
isPublic: Scalars['Boolean']['output'];
name: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.object instead. */
object?: Maybe<Object>;
/**
* Pending stream access requests
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use Project.pendingAccessRequests instead.
*/
pendingAccessRequests?: Maybe<Array<StreamAccessRequest>>;
/** Collaborators who have been invited, but not yet accepted. */
@ -3198,6 +3278,8 @@ export type User = {
name: Scalars['String']['output'];
notificationPreferences: Scalars['JSONObject']['output'];
profiles?: Maybe<Scalars['JSONObject']['output']>;
/** Get pending project access request, that the user made */
projectAccessRequest?: Maybe<ProjectAccessRequest>;
/** Get all invitations to projects that the active user has */
projectInvites: Array<PendingStreamCollaborator>;
/** Get projects that the user participates in */
@ -3263,6 +3345,15 @@ export type UserFavoriteStreamsArgs = {
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
*/
export type UserProjectAccessRequestArgs = {
projectId: Scalars['String']['input'];
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
@ -3407,18 +3498,30 @@ export type VersionCreatedTriggerDefinition = {
export type VersionMutations = {
__typename?: 'VersionMutations';
create: Version;
delete: Scalars['Boolean']['output'];
markReceived: Scalars['Boolean']['output'];
moveToModel: Model;
requestGendoAIRender: Scalars['Boolean']['output'];
update: Version;
};
export type VersionMutationsCreateArgs = {
input: CreateVersionInput;
};
export type VersionMutationsDeleteArgs = {
input: DeleteVersionsInput;
};
export type VersionMutationsMarkReceivedArgs = {
input: MarkReceivedVersionInput;
};
export type VersionMutationsMoveToModelArgs = {
input: MoveVersionsInput;
};
@ -3700,13 +3803,13 @@ export type ResolversTypes = {
CreateCommentInput: CreateCommentInput;
CreateCommentReplyInput: CreateCommentReplyInput;
CreateModelInput: CreateModelInput;
CreateVersionInput: CreateVersionInput;
DateTime: ResolverTypeWrapper<Scalars['DateTime']['output']>;
DeleteModelInput: DeleteModelInput;
DeleteVersionsInput: DeleteVersionsInput;
DiscoverableStreamsSortType: DiscoverableStreamsSortType;
DiscoverableStreamsSortingInput: DiscoverableStreamsSortingInput;
EditCommentInput: EditCommentInput;
EmailAddress: ResolverTypeWrapper<Scalars['EmailAddress']['output']>;
FileUpload: ResolverTypeWrapper<FileUploadGraphQLReturn>;
Float: ResolverTypeWrapper<Scalars['Float']['output']>;
GendoAIRender: ResolverTypeWrapper<GendoAiRender>;
@ -3717,6 +3820,7 @@ export type ResolversTypes = {
JSONObject: ResolverTypeWrapper<Scalars['JSONObject']['output']>;
LegacyCommentViewerData: ResolverTypeWrapper<LegacyCommentViewerData>;
LimitedUser: ResolverTypeWrapper<LimitedUserGraphQLReturn>;
MarkReceivedVersionInput: MarkReceivedVersionInput;
Model: ResolverTypeWrapper<ModelGraphQLReturn>;
ModelCollection: ResolverTypeWrapper<Omit<ModelCollection, 'items'> & { items: Array<ResolversTypes['Model']> }>;
ModelMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
@ -3725,13 +3829,15 @@ export type ResolversTypes = {
ModelsTreeItemCollection: ResolverTypeWrapper<Omit<ModelsTreeItemCollection, 'items'> & { items: Array<ResolversTypes['ModelsTreeItem']> }>;
MoveVersionsInput: MoveVersionsInput;
Mutation: ResolverTypeWrapper<{}>;
Object: ResolverTypeWrapper<Object>;
ObjectCollection: ResolverTypeWrapper<ObjectCollection>;
Object: ResolverTypeWrapper<ObjectGraphQLReturn>;
ObjectCollection: ResolverTypeWrapper<Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversTypes['Object']> }>;
ObjectCreateInput: ObjectCreateInput;
PasswordStrengthCheckFeedback: ResolverTypeWrapper<PasswordStrengthCheckFeedback>;
PasswordStrengthCheckResults: ResolverTypeWrapper<PasswordStrengthCheckResults>;
PendingStreamCollaborator: ResolverTypeWrapper<PendingStreamCollaboratorGraphQLReturn>;
Project: ResolverTypeWrapper<ProjectGraphQLReturn>;
ProjectAccessRequest: ResolverTypeWrapper<ProjectAccessRequestGraphQLReturn>;
ProjectAccessRequestMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
ProjectAutomationCreateInput: ProjectAutomationCreateInput;
ProjectAutomationMutations: ResolverTypeWrapper<ProjectAutomationMutationsGraphQLReturn>;
ProjectAutomationRevisionCreateInput: ProjectAutomationRevisionCreateInput;
@ -3813,7 +3919,7 @@ export type ResolversTypes = {
UpdateAutomateFunctionInput: UpdateAutomateFunctionInput;
UpdateModelInput: UpdateModelInput;
UpdateVersionInput: UpdateVersionInput;
User: ResolverTypeWrapper<Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectInvites' | 'projects' | 'streams'> & { automateInfo: ResolversTypes['UserAutomateInfo'], commits?: Maybe<ResolversTypes['CommitCollection']>, favoriteStreams: ResolversTypes['StreamCollection'], projectInvites: Array<ResolversTypes['PendingStreamCollaborator']>, projects: ResolversTypes['ProjectCollection'], streams: ResolversTypes['StreamCollection'] }>;
User: ResolverTypeWrapper<Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectAccessRequest' | 'projectInvites' | 'projects' | 'streams'> & { automateInfo: ResolversTypes['UserAutomateInfo'], commits?: Maybe<ResolversTypes['CommitCollection']>, favoriteStreams: ResolversTypes['StreamCollection'], projectAccessRequest?: Maybe<ResolversTypes['ProjectAccessRequest']>, projectInvites: Array<ResolversTypes['PendingStreamCollaborator']>, projects: ResolversTypes['ProjectCollection'], streams: ResolversTypes['StreamCollection'] }>;
UserAutomateInfo: ResolverTypeWrapper<UserAutomateInfoGraphQLReturn>;
UserDeleteInput: UserDeleteInput;
UserProjectsFilter: UserProjectsFilter;
@ -3915,12 +4021,12 @@ export type ResolversParentTypes = {
CreateCommentInput: CreateCommentInput;
CreateCommentReplyInput: CreateCommentReplyInput;
CreateModelInput: CreateModelInput;
CreateVersionInput: CreateVersionInput;
DateTime: Scalars['DateTime']['output'];
DeleteModelInput: DeleteModelInput;
DeleteVersionsInput: DeleteVersionsInput;
DiscoverableStreamsSortingInput: DiscoverableStreamsSortingInput;
EditCommentInput: EditCommentInput;
EmailAddress: Scalars['EmailAddress']['output'];
FileUpload: FileUploadGraphQLReturn;
Float: Scalars['Float']['output'];
GendoAIRender: GendoAiRender;
@ -3931,6 +4037,7 @@ export type ResolversParentTypes = {
JSONObject: Scalars['JSONObject']['output'];
LegacyCommentViewerData: LegacyCommentViewerData;
LimitedUser: LimitedUserGraphQLReturn;
MarkReceivedVersionInput: MarkReceivedVersionInput;
Model: ModelGraphQLReturn;
ModelCollection: Omit<ModelCollection, 'items'> & { items: Array<ResolversParentTypes['Model']> };
ModelMutations: MutationsObjectGraphQLReturn;
@ -3939,13 +4046,15 @@ export type ResolversParentTypes = {
ModelsTreeItemCollection: Omit<ModelsTreeItemCollection, 'items'> & { items: Array<ResolversParentTypes['ModelsTreeItem']> };
MoveVersionsInput: MoveVersionsInput;
Mutation: {};
Object: Object;
ObjectCollection: ObjectCollection;
Object: ObjectGraphQLReturn;
ObjectCollection: Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversParentTypes['Object']> };
ObjectCreateInput: ObjectCreateInput;
PasswordStrengthCheckFeedback: PasswordStrengthCheckFeedback;
PasswordStrengthCheckResults: PasswordStrengthCheckResults;
PendingStreamCollaborator: PendingStreamCollaboratorGraphQLReturn;
Project: ProjectGraphQLReturn;
ProjectAccessRequest: ProjectAccessRequestGraphQLReturn;
ProjectAccessRequestMutations: MutationsObjectGraphQLReturn;
ProjectAutomationCreateInput: ProjectAutomationCreateInput;
ProjectAutomationMutations: ProjectAutomationMutationsGraphQLReturn;
ProjectAutomationRevisionCreateInput: ProjectAutomationRevisionCreateInput;
@ -4012,7 +4121,7 @@ export type ResolversParentTypes = {
UpdateAutomateFunctionInput: UpdateAutomateFunctionInput;
UpdateModelInput: UpdateModelInput;
UpdateVersionInput: UpdateVersionInput;
User: Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectInvites' | 'projects' | 'streams'> & { automateInfo: ResolversParentTypes['UserAutomateInfo'], commits?: Maybe<ResolversParentTypes['CommitCollection']>, favoriteStreams: ResolversParentTypes['StreamCollection'], projectInvites: Array<ResolversParentTypes['PendingStreamCollaborator']>, projects: ResolversParentTypes['ProjectCollection'], streams: ResolversParentTypes['StreamCollection'] };
User: Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectAccessRequest' | 'projectInvites' | 'projects' | 'streams'> & { automateInfo: ResolversParentTypes['UserAutomateInfo'], commits?: Maybe<ResolversParentTypes['CommitCollection']>, favoriteStreams: ResolversParentTypes['StreamCollection'], projectAccessRequest?: Maybe<ResolversParentTypes['ProjectAccessRequest']>, projectInvites: Array<ResolversParentTypes['PendingStreamCollaborator']>, projects: ResolversParentTypes['ProjectCollection'], streams: ResolversParentTypes['StreamCollection'] };
UserAutomateInfo: UserAutomateInfoGraphQLReturn;
UserDeleteInput: UserDeleteInput;
UserProjectsFilter: UserProjectsFilter;
@ -4459,10 +4568,6 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversT
name: 'DateTime';
}
export interface EmailAddressScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['EmailAddress'], any> {
name: 'EmailAddress';
}
export type FileUploadResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['FileUpload'] = ResolversParentTypes['FileUpload']> = {
branchName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
convertedCommitId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@ -4618,7 +4723,7 @@ export type MutationResolvers<ContextType = GraphQLContext, ParentType extends R
inviteDelete?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationInviteDeleteArgs, 'inviteId'>>;
inviteResend?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationInviteResendArgs, 'inviteId'>>;
modelMutations?: Resolver<ResolversTypes['ModelMutations'], ParentType, ContextType>;
objectCreate?: Resolver<Array<Maybe<ResolversTypes['String']>>, ParentType, ContextType, RequireFields<MutationObjectCreateArgs, 'objectInput'>>;
objectCreate?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType, RequireFields<MutationObjectCreateArgs, 'objectInput'>>;
projectMutations?: Resolver<ResolversTypes['ProjectMutations'], ParentType, ContextType>;
requestVerification?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
requestVerificationByEmail?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationRequestVerificationByEmailArgs, 'email'>>;
@ -4665,7 +4770,7 @@ export type ObjectResolvers<ContextType = GraphQLContext, ParentType extends Res
export type ObjectCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ObjectCollection'] = ResolversParentTypes['ObjectCollection']> = {
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
objects?: Resolver<Array<Maybe<ResolversTypes['Object']>>, ParentType, ContextType>;
objects?: Resolver<Array<ResolversTypes['Object']>, ParentType, ContextType>;
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@ -4703,16 +4808,20 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
automations?: Resolver<ResolversTypes['AutomationCollection'], ParentType, ContextType, Partial<ProjectAutomationsArgs>>;
blob?: Resolver<Maybe<ResolversTypes['BlobMetadata']>, ParentType, ContextType, RequireFields<ProjectBlobArgs, 'id'>>;
blobs?: Resolver<Maybe<ResolversTypes['BlobMetadataCollection']>, ParentType, ContextType, RequireFields<ProjectBlobsArgs, 'cursor' | 'limit' | 'query'>>;
comment?: Resolver<Maybe<ResolversTypes['Comment']>, ParentType, ContextType, RequireFields<ProjectCommentArgs, 'id'>>;
commentThreads?: Resolver<ResolversTypes['ProjectCommentCollection'], ParentType, ContextType, RequireFields<ProjectCommentThreadsArgs, 'limit'>>;
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
invitedTeam?: Resolver<Maybe<Array<ResolversTypes['PendingStreamCollaborator']>>, ParentType, ContextType>;
model?: Resolver<ResolversTypes['Model'], ParentType, ContextType, RequireFields<ProjectModelArgs, 'id'>>;
modelByName?: Resolver<ResolversTypes['Model'], ParentType, ContextType, RequireFields<ProjectModelByNameArgs, 'name'>>;
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'>>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
object?: Resolver<Maybe<ResolversTypes['Object']>, ParentType, ContextType, RequireFields<ProjectObjectArgs, 'id'>>;
pendingAccessRequests?: Resolver<Maybe<Array<ResolversTypes['ProjectAccessRequest']>>, ParentType, ContextType>;
pendingImportedModels?: Resolver<Array<ResolversTypes['FileUpload']>, ParentType, ContextType, RequireFields<ProjectPendingImportedModelsArgs, 'limit'>>;
role?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
sourceApps?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
@ -4726,6 +4835,22 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ProjectAccessRequestResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectAccessRequest'] = ResolversParentTypes['ProjectAccessRequest']> = {
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
project?: Resolver<ResolversTypes['Project'], ParentType, ContextType>;
projectId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
requester?: Resolver<ResolversTypes['LimitedUser'], ParentType, ContextType>;
requesterId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ProjectAccessRequestMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectAccessRequestMutations'] = ResolversParentTypes['ProjectAccessRequestMutations']> = {
create?: Resolver<ResolversTypes['ProjectAccessRequest'], ParentType, ContextType, RequireFields<ProjectAccessRequestMutationsCreateArgs, 'projectId'>>;
use?: Resolver<ResolversTypes['Project'], ParentType, ContextType, RequireFields<ProjectAccessRequestMutationsUseArgs, 'accept' | 'requestId' | 'role'>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ProjectAutomationMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectAutomationMutations'] = ResolversParentTypes['ProjectAutomationMutations']> = {
create?: Resolver<ResolversTypes['Automation'], ParentType, ContextType, RequireFields<ProjectAutomationMutationsCreateArgs, 'input'>>;
createRevision?: Resolver<ResolversTypes['AutomationRevision'], ParentType, ContextType, RequireFields<ProjectAutomationMutationsCreateRevisionArgs, 'input'>>;
@ -4796,7 +4921,9 @@ export type ProjectModelsUpdatedMessageResolvers<ContextType = GraphQLContext, P
};
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'>>;
batchDelete?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<ProjectMutationsBatchDeleteArgs, 'ids'>>;
create?: Resolver<ResolversTypes['Project'], ParentType, ContextType, Partial<ProjectMutationsCreateArgs>>;
createForOnboarding?: Resolver<ResolversTypes['Project'], ParentType, ContextType>;
delete?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<ProjectMutationsDeleteArgs, 'id'>>;
@ -5144,6 +5271,7 @@ export type UserResolvers<ContextType = GraphQLContext, ParentType extends Resol
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
notificationPreferences?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
profiles?: Resolver<Maybe<ResolversTypes['JSONObject']>, ParentType, ContextType>;
projectAccessRequest?: Resolver<Maybe<ResolversTypes['ProjectAccessRequest']>, ParentType, ContextType, RequireFields<UserProjectAccessRequestArgs, 'projectId'>>;
projectInvites?: Resolver<Array<ResolversTypes['PendingStreamCollaborator']>, ParentType, ContextType>;
projects?: Resolver<ResolversTypes['ProjectCollection'], ParentType, ContextType, RequireFields<UserProjectsArgs, 'limit'>>;
role?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@ -5213,7 +5341,9 @@ export type VersionCreatedTriggerDefinitionResolvers<ContextType = GraphQLContex
};
export type VersionMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['VersionMutations'] = ResolversParentTypes['VersionMutations']> = {
create?: Resolver<ResolversTypes['Version'], ParentType, ContextType, RequireFields<VersionMutationsCreateArgs, 'input'>>;
delete?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<VersionMutationsDeleteArgs, 'input'>>;
markReceived?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<VersionMutationsMarkReceivedArgs, 'input'>>;
moveToModel?: Resolver<ResolversTypes['Model'], ParentType, ContextType, RequireFields<VersionMutationsMoveToModelArgs, 'input'>>;
requestGendoAIRender?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<VersionMutationsRequestGendoAiRenderArgs, 'input'>>;
update?: Resolver<ResolversTypes['Version'], ParentType, ContextType, RequireFields<VersionMutationsUpdateArgs, 'input'>>;
@ -5325,7 +5455,6 @@ export type Resolvers<ContextType = GraphQLContext> = {
CommitCollection?: CommitCollectionResolvers<ContextType>;
CountOnlyCollection?: CountOnlyCollectionResolvers<ContextType>;
DateTime?: GraphQLScalarType;
EmailAddress?: GraphQLScalarType;
FileUpload?: FileUploadResolvers<ContextType>;
GendoAIRender?: GendoAiRenderResolvers<ContextType>;
GendoAIRenderCollection?: GendoAiRenderCollectionResolvers<ContextType>;
@ -5344,6 +5473,8 @@ export type Resolvers<ContextType = GraphQLContext> = {
PasswordStrengthCheckResults?: PasswordStrengthCheckResultsResolvers<ContextType>;
PendingStreamCollaborator?: PendingStreamCollaboratorResolvers<ContextType>;
Project?: ProjectResolvers<ContextType>;
ProjectAccessRequest?: ProjectAccessRequestResolvers<ContextType>;
ProjectAccessRequestMutations?: ProjectAccessRequestMutationsResolvers<ContextType>;
ProjectAutomationMutations?: ProjectAutomationMutationsResolvers<ContextType>;
ProjectAutomationsUpdatedMessage?: ProjectAutomationsUpdatedMessageResolvers<ContextType>;
ProjectCollaborator?: ProjectCollaboratorResolvers<ContextType>;

Просмотреть файл

@ -22,13 +22,9 @@ const {
const {
createCommitByBranchName,
updateCommitAndNotify,
deleteCommitAndNotify
deleteCommitAndNotify,
markCommitReceivedAndNotify
} = require('@/modules/core/services/commit/management')
const {
addCommitReceivedActivity
} = require('@/modules/activitystream/services/commitActivity')
const { getUser } = require('../../services/users')
const { RateLimitError } = require('@/modules/core/errors/ratelimit')
const {
@ -231,18 +227,12 @@ module.exports = {
context.resourceAccessRules
)
const commit = await getCommitById({
streamId: args.input.streamId,
id: args.input.commitId
await markCommitReceivedAndNotify({
input: args.input,
userId: context.userId
})
const user = await getUser(context.userId)
if (commit && user) {
await addCommitReceivedActivity({ input: args.input, userId: user.id })
return true
}
return false
return true
},
async commitDelete(_parent, args, context) {

Просмотреть файл

@ -58,6 +58,16 @@ export = {
return model
},
async modelByName(parent, args, ctx) {
const model = await ctx.loaders.streams.getStreamBranchByName
.forStream(parent.id)
.load(args.name)
if (!model) {
throw new BranchNotFoundError('Model not found')
}
return model
},
async modelsTree(parent, args) {
return await getProjectTopLevelModelsTree(parent.id, args)
},

Просмотреть файл

@ -1,24 +1,26 @@
'use strict'
const { validateScopes, authorizeResolver } = require('@/modules/shared')
import { authorizeResolver } from '@/modules/shared'
const {
import {
createObjects,
getObject,
getObjectChildren,
getObjectChildrenQuery
} = require('../../services/objects')
const { Roles, Scopes } = require('@speckle/shared')
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
} from '../../services/objects'
module.exports = {
import { Roles } from '@speckle/shared'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import { getObject } from '@/modules/core/repositories/objects'
const getStreamObject: NonNullable<Resolvers['Stream']>['object'] =
async function object(parent, args) {
return (await getObject(args.id, parent.id)) || null
}
export = {
Stream: {
async object(parent, args) {
const obj = await getObject({ streamId: parent.id, objectId: args.id })
if (!obj) return null
obj.streamId = parent.id
return obj
}
object: getStreamObject
},
Project: {
object: getStreamObject
},
Object: {
async children(parent, args) {
@ -34,7 +36,7 @@ module.exports = {
})
result.objects.forEach((x) => (x.streamId = parent.streamId))
return {
totalCount: parent.totalChildrenCount || '0',
totalCount: parent.totalChildrenCount || 0,
cursor: result.cursor,
objects: result.objects
}
@ -56,9 +58,7 @@ module.exports = {
}
},
Mutation: {
async objectCreate(parent, args, context) {
await throwForNotHavingServerRole(context, Roles.Server.Guest)
await validateScopes(context.scopes, Scopes.Streams.Write)
async objectCreate(_parent, args, context) {
await authorizeResolver(
context.userId,
args.objectInput.streamId,
@ -73,4 +73,4 @@ module.exports = {
return ids
}
}
}
} as Resolvers

Просмотреть файл

@ -89,6 +89,16 @@ export = {
projectMutations: () => ({})
},
ProjectMutations: {
async batchDelete(_parent, args, ctx) {
const results = await Promise.all(
args.ids.map((id) =>
deleteStreamAndNotify(id, ctx.userId!, ctx.resourceAccessRules, {
skipAccessChecks: true
})
)
)
return results.every((res) => res === true)
},
async delete(_parent, { id }, { userId, resourceAccessRules }) {
return await deleteStreamAndNotify(id, userId!, resourceAccessRules)
},

Просмотреть файл

@ -11,7 +11,16 @@ import {
batchMoveCommits
} from '@/modules/core/services/commit/batchCommitActions'
import { CommitUpdateError } from '@/modules/core/errors/commit'
import { updateCommitAndNotify } from '@/modules/core/services/commit/management'
import {
createCommitByBranchId,
markCommitReceivedAndNotify,
updateCommitAndNotify
} from '@/modules/core/services/commit/management'
import {
getRateLimitResult,
isRateLimitBreached
} from '@/modules/core/services/ratelimiter'
import { RateLimitError } from '@/modules/core/errors/ratelimit'
export = {
Project: {
@ -62,6 +71,47 @@ export = {
ctx.resourceAccessRules
)
return await updateCommitAndNotify(args.input, ctx.userId!)
},
async create(_parent, args, ctx) {
await authorizeResolver(
ctx.userId,
args.input.projectId,
Roles.Stream.Contributor,
ctx.resourceAccessRules
)
const rateLimitResult = await getRateLimitResult('COMMIT_CREATE', ctx.userId!)
if (isRateLimitBreached(rateLimitResult)) {
throw new RateLimitError(rateLimitResult)
}
const commit = await createCommitByBranchId({
authorId: ctx.userId!,
streamId: args.input.projectId,
branchId: args.input.modelId,
message: args.input.message || null,
sourceApplication: args.input.sourceApplication || null,
objectId: args.input.objectId,
parents: args.input.parents || []
})
return commit
},
async markReceived(_parent, args, ctx) {
await authorizeResolver(
ctx.userId,
args.input.projectId,
Roles.Stream.Reviewer,
ctx.resourceAccessRules
)
await markCommitReceivedAndNotify({
input: args.input,
userId: ctx.userId!
})
return true
}
},
Subscription: {

Просмотреть файл

@ -13,8 +13,6 @@ exports.scalarResolvers = {
exports.scalarSchemas = `
scalar DateTime
scalar EmailAddress
scalar BigInt
scalar JSONObject

Просмотреть файл

@ -7,7 +7,12 @@ import {
Commit
} from '@/modules/core/graph/generated/graphql'
import { Roles, ServerRoles, StreamRoles } from '@/modules/core/helpers/mainConstants'
import { BranchRecord, CommitRecord, StreamRecord } from '@/modules/core/helpers/types'
import {
BranchRecord,
CommitRecord,
ObjectRecord,
StreamRecord
} from '@/modules/core/helpers/types'
import { Nullable } from '@speckle/shared'
/**
@ -72,6 +77,8 @@ export type ModelsTreeItemGraphQLReturn = Omit<ModelsTreeItem, 'model' | 'childr
projectId: string
}
export type ObjectGraphQLReturn = ObjectRecord
/**
* Return type for top-level mutations groupings like `projectMutations`, `activeUserMutations` etc.
*/

Просмотреть файл

@ -5,7 +5,6 @@ import {
import { TokenCreateError } from '@/modules/core/errors/user'
import { TokenResourceAccessRecord } from '@/modules/core/helpers/types'
import { ResourceTargets } from '@/modules/serverinvites/helpers/inviteHelper'
import {} from '@/modules/serverinvites/services/operations'
import { MaybeNullOrUndefined, Nullable, Optional, Scopes } from '@speckle/shared'
import { differenceBy } from 'lodash'

Просмотреть файл

@ -1,16 +1,20 @@
import {
addCommitCreatedActivity,
addCommitDeletedActivity,
addCommitReceivedActivity,
addCommitUpdatedActivity
} from '@/modules/activitystream/services/commitActivity'
import {
CommitCreateError,
CommitDeleteError,
CommitReceiveError,
CommitUpdateError
} from '@/modules/core/errors/commit'
import { VersionEvents, VersionsEmitter } from '@/modules/core/events/versionsEmitter'
import {
CommitReceivedInput,
CommitUpdateInput,
MarkReceivedVersionInput,
UpdateVersionInput
} from '@/modules/core/graph/generated/graphql'
import { CommitRecord } from '@/modules/core/helpers/types'
@ -38,9 +42,35 @@ import {
import { ensureError, MaybeNullOrUndefined, Nullable, Roles } from '@speckle/shared'
import { has } from 'lodash'
/**
* Note: Doesn't notify subscriptions or save activityStream due to missing branchName
*/
export async function markCommitReceivedAndNotify(params: {
input: MarkReceivedVersionInput | CommitReceivedInput
userId: string
}) {
const { input, userId } = params
const oldInput: CommitReceivedInput =
'projectId' in input
? {
...input,
streamId: input.projectId,
commitId: input.versionId
}
: input
const commit = await getCommit(oldInput.commitId, { streamId: oldInput.streamId })
if (!commit) {
throw new CommitReceiveError(
`Failed to find commit with id ${oldInput.commitId} in stream ${oldInput.streamId}.`,
{ info: params }
)
}
await addCommitReceivedActivity({
input: oldInput,
userId
})
}
export async function createCommitByBranchId(
params: {
streamId: string

Просмотреть файл

@ -169,6 +169,9 @@ module.exports = {
return ids
},
/**
* @returns {Promise<string[]>}
*/
async createObjects(streamId, objects) {
// TODO: Switch to knex batch inserting functionality
// see http://knexjs.org/#Utility-BatchInsert
@ -248,6 +251,9 @@ module.exports = {
return ids
},
/**
* @returns {Promise<Omit<import('@/modules/core/helpers/types').ObjectRecord, 'streamId'>>}
*/
async getObject({ streamId, objectId }) {
const res = await Objects().where({ streamId, id: objectId }).select('*').first()
if (!res) return null
@ -288,6 +294,9 @@ module.exports = {
return q.stream({ highWaterMark: 500 })
},
/**
* @returns {Promise<{objects: import('@/modules/core/helpers/types').ObjectRecord[], cursor: string | null}>}
*/
async getObjectChildren({ streamId, objectId, limit, depth, select, cursor }) {
limit = parseInt(limit) || 50
depth = parseInt(depth) || 1000
@ -370,8 +379,13 @@ module.exports = {
return { objects: rows, cursor: lastId }
},
// This query is inefficient on larger sets (n * 10k objects) as we need to return the total count on an arbitrarily (user) defined selection of objects.
// A possible future optimisation route would be to cache the total count of a query (as objects are immutable, it will not change) on a first run, and, if found on a subsequent round, do a simpler query and merge the total count result.
/**
*
* This query is inefficient on larger sets (n * 10k objects) as we need to return the total count on an arbitrarily (user) defined selection of objects.
* A possible future optimisation route would be to cache the total count of a query (as objects are immutable, it will not change) on a first run, and, if found on a subsequent round, do a simpler query and merge the total count result.
* @param {*} param0
* @returns {Promise<{objects: import('@/modules/core/helpers/types').ObjectRecord[], cursor: string | null, totalCount: number}>}
*/
async getObjectChildrenQuery({
streamId,
objectId,

Просмотреть файл

@ -41,7 +41,7 @@ async function isStreamCollaborator(userId, streamId) {
* @param {string} [userId] If falsy, will throw for non-public streams
* @param {string} streamId
* @param {string} [expectedRole] Defaults to reviewer
* @param {import('@/modules/serverinvites/services/operations').TokenResourceIdentifier[] | undefined | null} [userResourceAccessLimits]
* @param {import('@/modules/core/domain/tokens/types').TokenResourceIdentifier[] | undefined | null} [userResourceAccessLimits]
* @returns {Promise<boolean>}
*/
async function validateStreamAccess(
@ -90,7 +90,7 @@ async function validateStreamAccess(
* @param {string} streamId
* @param {string} userId ID of user that should be removed
* @param {string} removedById ID of user that is doing the removing
* @param {import('@/modules/serverinvites/services/operations').TokenResourceIdentifier[] | undefined | null} [removerResourceAccessRules] Resource access rules (if any) for the user doing the removing
* @param {import('@/modules/core/domain/tokens/types').TokenResourceIdentifier[] | undefined | null} [removerResourceAccessRules] Resource access rules (if any) for the user doing the removing
*/
async function removeStreamCollaborator(
streamId,
@ -134,7 +134,7 @@ async function removeStreamCollaborator(
* @param {string} userId ID of user who is being added
* @param {string} role
* @param {string} addedById ID of user who is adding the new collaborator
* @param {import('@/modules/serverinvites/services/operations').TokenResourceIdentifier[] | undefined | null} [adderResourceAccessRules] Resource access rules (if any) for the user doing the adding
* @param {import('@/modules/core/domain/tokens/types').TokenResourceIdentifier[] | undefined | null} [adderResourceAccessRules] Resource access rules (if any) for the user doing the adding
* @param {{
* fromInvite?: boolean,
* }} param4

Просмотреть файл

@ -0,0 +1,89 @@
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
CreateModelInput,
CreateProjectModelDocument,
FindProjectModelByNameDocument
} from '@/test/graphql/generated/graphql'
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
import { beforeEachContext } from '@/test/hooks'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { expect } from 'chai'
import { omit } from 'lodash'
import { before, describe } from 'mocha'
describe('Models', () => {
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const myPrivateStream: BasicTestStream = {
name: 'this is my private stream #1',
isPublic: false,
ownerId: '',
id: ''
}
before(async () => {
await beforeEachContext()
await createTestUsers([me])
await createTestStreams([[myPrivateStream, me]])
})
describe('in GraphQL API', () => {
let apollo: TestApolloServer
const createModel = async (input: CreateModelInput) =>
await apollo.execute(CreateProjectModelDocument, {
input
})
before(async () => {
apollo = await testApolloServer({
authUserId: me.id
})
})
it('can be created', async () => {
const input: CreateModelInput = {
projectId: myPrivateStream.id,
name: 'my first model',
description: 'ayyooo'
}
const res = await createModel(input)
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.modelMutations.create.id).to.be.ok
expect(res.data?.modelMutations.create.name).to.equal(input.name)
expect(res.data?.modelMutations.create.description).to.equal(input.description)
})
describe('after creation', () => {
let firstModel: CreateModelInput & { id: string }
before(async () => {
firstModel = {
projectId: myPrivateStream.id,
name: 'anutha model #1',
description: 'ayyooo!!',
id: ''
}
const res = await createModel(omit(firstModel, ['id']))
firstModel.id = res.data!.modelMutations.create.id
expect(firstModel.id).to.be.ok
})
it('can be found by name', async () => {
const res = await apollo.execute(FindProjectModelByNameDocument, {
projectId: myPrivateStream.id,
name: firstModel.name
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.project.modelByName.id).to.equal(firstModel.id)
expect(res.data?.project.modelByName.name).to.equal(firstModel.name)
})
})
})
})

Просмотреть файл

@ -0,0 +1,132 @@
import { before, describe } from 'mocha'
import { expect } from 'chai'
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { beforeEachContext } from '@/test/hooks'
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
import {
BatchDeleteProjectsDocument,
CreateProjectDocument,
GetProjectObjectDocument,
ProjectCreateInput
} from '@/test/graphql/generated/graphql'
import { createTestObject } from '@/test/speckle-helpers/commitHelper'
import { times } from 'lodash'
import { Roles } from '@speckle/shared'
describe('Projects', () => {
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const otherUser: BasicTestUser = {
name: 'hello itsa some otha guy',
email: '',
id: ''
}
before(async () => {
await beforeEachContext()
await createTestUsers([me, otherUser])
})
describe('in GraphQL API', () => {
let apollo: TestApolloServer
before(async () => {
apollo = await testApolloServer({
authUserId: me.id
})
})
it('can be created', async () => {
const input: ProjectCreateInput = {
name: 'my first project',
description: 'ayyooo'
}
const res = await apollo.execute(CreateProjectDocument, {
input
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.projectMutations.create.id).to.be.ok
expect(res.data?.projectMutations.create.name).to.equal(input.name)
expect(res.data?.projectMutations.create.description).to.equal(input.description)
})
describe('after creation', () => {
const myStream: BasicTestStream = {
name: 'this is my great stream #1',
isPublic: true,
ownerId: '',
id: ''
}
const getMyStreamObject = async (objectId: string) =>
await apollo.execute(GetProjectObjectDocument, {
projectId: myStream.id,
objectId
})
before(async () => {
await createTestStreams([[myStream, me]])
})
it('returns null if querying for a non-existant object()', async () => {
const res = await getMyStreamObject('non-existant-object-id')
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.project.object).to.be.null
})
it('can have their objects retrieved through object()', async () => {
const objectId = await createTestObject({ projectId: myStream.id })
const res = await getMyStreamObject(objectId)
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.project.object?.id).to.equal(objectId)
})
})
describe('when doing batch deletion', () => {
const createOtherGuyProjectBatch = async () => {
const projects: BasicTestStream[] = times(3, () => ({
id: '',
ownerId: otherUser.id,
name: `project ${Math.random()}`,
isPublic: false
}))
await createTestStreams(projects.map((p) => [p, me]))
return projects.map((p) => p.id)
}
const batchDeleteProjects = async (ids: string[], asAdmin?: boolean) =>
await apollo.execute(
BatchDeleteProjectsDocument,
{ ids },
{
context: asAdmin ? { role: Roles.Server.Admin } : undefined
}
)
it("it doesn't work if user is not an admin", async () => {
const projectIds = await createOtherGuyProjectBatch()
const res = await batchDeleteProjects(projectIds)
expect(res.data).to.not.be.ok
expect(res).to.haveGraphQLErrors('You do not have the required server role')
})
it('works if user is an admin, even for not owned projects', async () => {
const projectIds = await createOtherGuyProjectBatch()
const res = await batchDeleteProjects(projectIds, true)
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.projectMutations.batchDelete).to.be.true
})
})
})
})

Просмотреть файл

@ -0,0 +1,126 @@
import { ActionTypes } from '@/modules/activitystream/helpers/types'
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
CreateProjectVersionDocument,
CreateVersionInput,
MarkProjectVersionReceivedDocument,
MarkReceivedVersionInput
} from '@/test/graphql/generated/graphql'
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
import { beforeEachContext } from '@/test/hooks'
import { getStreamActivities } from '@/test/speckle-helpers/activityStreamHelper'
import {
BasicTestBranch,
createTestBranches
} from '@/test/speckle-helpers/branchHelper'
import { createTestObject } from '@/test/speckle-helpers/commitHelper'
import { BasicTestStream, createTestStreams } from '@/test/speckle-helpers/streamHelper'
import { expect } from 'chai'
import { omit } from 'lodash'
import { before, describe } from 'mocha'
describe('Versions', () => {
const me: BasicTestUser = {
name: 'hello itsa me',
email: '',
id: ''
}
const myPrivateStream: BasicTestStream = {
name: 'this is my private stream #1',
isPublic: false,
ownerId: '',
id: ''
}
const myBranch: BasicTestBranch = {
name: 'my branchy #1',
streamId: '',
id: '',
authorId: ''
}
before(async () => {
await beforeEachContext()
await createTestUsers([me])
await createTestStreams([[myPrivateStream, me]])
await createTestBranches([{ branch: myBranch, stream: myPrivateStream, owner: me }])
})
describe('in GraphQL API', () => {
let apollo: TestApolloServer
let objectId: string
const createVersion = async (input: CreateVersionInput) =>
await apollo.execute(CreateProjectVersionDocument, { input })
before(async () => {
apollo = await testApolloServer({
authUserId: me.id
})
objectId = await createTestObject({ projectId: myPrivateStream.id })
})
it('can be created', async () => {
const input: CreateVersionInput = {
projectId: myPrivateStream.id,
modelId: myBranch.id,
objectId,
message: 'Yoooo!',
sourceApplication: 'tests',
parents: []
}
const res = await createVersion(input)
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.versionMutations.create.id).to.be.ok
expect(res.data?.versionMutations.create.message).to.eq(input.message)
expect(res.data?.versionMutations.create.sourceApplication).to.eq(
input.sourceApplication
)
expect(res.data?.versionMutations.create.model.id).to.eq(myBranch.id)
expect(res.data?.versionMutations.create.referencedObject).to.eq(objectId)
})
describe('after creation', () => {
let firstVersion: CreateVersionInput & { id: string }
before(async () => {
firstVersion = {
projectId: myPrivateStream.id,
modelId: myBranch.id,
objectId,
message: 'welcome #1',
sourceApplication: 'testsz',
parents: [],
id: ''
}
const res = await createVersion(omit(firstVersion, ['id']))
firstVersion.id = res.data!.versionMutations.create.id
expect(firstVersion.id).to.be.ok
})
it('can be marked as received', async () => {
const input: MarkReceivedVersionInput = {
versionId: firstVersion.id,
projectId: myPrivateStream.id,
sourceApplication: 'booo',
message: 'hey hihihi'
}
const res = await apollo.execute(MarkProjectVersionReceivedDocument, {
input
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.versionMutations.markReceived).to.be.true
const activities = await getStreamActivities(myPrivateStream.id, {
actionType: ActionTypes.Commit.Receive,
userId: me.id
})
expect(activities).to.have.length(1)
expect(activities[0].info?.message).to.eq(input.message)
})
})
})
})

Просмотреть файл

@ -16,7 +16,6 @@ export type Scalars = {
BigInt: { input: bigint; output: bigint; }
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
DateTime: { input: string; output: string; }
EmailAddress: { input: any; output: any; }
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSONObject: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
@ -801,6 +800,16 @@ export type CreateModelInput = {
projectId: Scalars['ID']['input'];
};
export type CreateVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
modelId: Scalars['String']['input'];
objectId: Scalars['String']['input'];
parents?: InputMaybe<Array<Scalars['String']['input']>>;
projectId: Scalars['String']['input'];
sourceApplication?: InputMaybe<Scalars['String']['input']>;
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};
export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
@ -991,6 +1000,13 @@ export type LimitedUserTimelineArgs = {
limit?: Scalars['Int']['input'];
};
export type MarkReceivedVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
projectId: Scalars['String']['input'];
sourceApplication: Scalars['String']['input'];
versionId: Scalars['String']['input'];
};
export type Model = {
__typename?: 'Model';
author: LimitedUser;
@ -1160,11 +1176,11 @@ export type Mutation = {
* @deprecated Use commentMutations version
*/
commentView: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.create instead. */
commitCreate: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.delete instead. */
commitDelete: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.markReceived instead. */
commitReceive: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.update/moveToModel instead. */
commitUpdate: Scalars['Boolean']['output'];
@ -1190,7 +1206,7 @@ export type Mutation = {
inviteResend: Scalars['Boolean']['output'];
modelMutations: ModelMutations;
/** @deprecated Part of the old API surface and will be removed in the future. */
objectCreate: Array<Maybe<Scalars['String']['output']>>;
objectCreate: Array<Scalars['String']['output']>;
projectMutations: ProjectMutations;
/** (Re-)send the account verification e-mail */
requestVerification: Scalars['Boolean']['output'];
@ -1202,12 +1218,12 @@ export type Mutation = {
serverInviteCreate: Scalars['Boolean']['output'];
/**
* Request access to a specific stream
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.create instead.
*/
streamAccessRequestCreate: StreamAccessRequest;
/**
* Accept or decline a stream access request. Must be a stream owner to invoke this.
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.use instead.
*/
streamAccessRequestUse: Scalars['Boolean']['output'];
/**
@ -1264,7 +1280,7 @@ export type Mutation = {
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.updateRole instead.
*/
streamUpdatePermission?: Maybe<Scalars['Boolean']['output']>;
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.batchDelete instead. */
streamsDelete: Scalars['Boolean']['output'];
/**
* Used for broadcasting real time typing status in comment threads. Does not persist any info.
@ -1581,6 +1597,7 @@ export type MutationWebhookUpdateArgs = {
export type Object = {
__typename?: 'Object';
/** @deprecated Not implemented. */
applicationId?: Maybe<Scalars['String']['output']>;
/**
* Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
@ -1620,7 +1637,7 @@ export type ObjectChildrenArgs = {
export type ObjectCollection = {
__typename?: 'ObjectCollection';
cursor?: Maybe<Scalars['String']['output']>;
objects: Array<Maybe<Object>>;
objects: Array<Object>;
totalCount: Scalars['Int']['output'];
};
@ -1681,6 +1698,8 @@ export type Project = {
blob?: Maybe<BlobMetadata>;
/** Get the metadata collection of blobs stored for this stream. */
blobs?: Maybe<BlobMetadataCollection>;
/** Get specific project comment/thread by ID */
comment?: Maybe<Comment>;
/** All comment threads in this project */
commentThreads: ProjectCommentCollection;
createdAt: Scalars['DateTime']['output'];
@ -1690,6 +1709,8 @@ export type Project = {
invitedTeam?: Maybe<Array<PendingStreamCollaborator>>;
/** Returns a specific model by its ID */
model: Model;
/** Retrieve a specific project model by its ID */
modelByName: Model;
/** Return a model tree of children for the specified model name */
modelChildrenTree: Array<ModelsTreeItem>;
/** Returns a flat list of all models */
@ -1700,6 +1721,9 @@ export type Project = {
*/
modelsTree: ModelsTreeItemCollection;
name: Scalars['String']['output'];
object?: Maybe<Object>;
/** Pending project access requests */
pendingAccessRequests?: Maybe<Array<ProjectAccessRequest>>;
/** Returns a list models that are being created from a file import */
pendingImportedModels: Array<FileUpload>;
/** Active user's role for this project. `null` if request is not authenticated, or the project is not explicitly shared with you. */
@ -1743,6 +1767,11 @@ export type ProjectBlobsArgs = {
};
export type ProjectCommentArgs = {
id: Scalars['String']['input'];
};
export type ProjectCommentThreadsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<ProjectCommentsFilter>;
@ -1755,6 +1784,11 @@ export type ProjectModelArgs = {
};
export type ProjectModelByNameArgs = {
name: Scalars['String']['input'];
};
export type ProjectModelChildrenTreeArgs = {
fullName: Scalars['String']['input'];
};
@ -1774,6 +1808,11 @@ export type ProjectModelsTreeArgs = {
};
export type ProjectObjectArgs = {
id: Scalars['String']['input'];
};
export type ProjectPendingImportedModelsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>;
};
@ -1800,6 +1839,38 @@ export type ProjectWebhooksArgs = {
id?: InputMaybe<Scalars['String']['input']>;
};
/** Created when a user requests to become a contributor on a project */
export type ProjectAccessRequest = {
__typename?: 'ProjectAccessRequest';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
/** Can only be selected if authed user has proper access */
project: Project;
projectId: Scalars['String']['output'];
requester: LimitedUser;
requesterId: Scalars['String']['output'];
};
export type ProjectAccessRequestMutations = {
__typename?: 'ProjectAccessRequestMutations';
/** Request access to a specific project */
create: ProjectAccessRequest;
/** Accept or decline a project access request. Must be a project owner to invoke this. */
use: Project;
};
export type ProjectAccessRequestMutationsCreateArgs = {
projectId: Scalars['String']['input'];
};
export type ProjectAccessRequestMutationsUseArgs = {
accept: Scalars['Boolean']['input'];
requestId: Scalars['String']['input'];
role?: StreamRole;
};
export type ProjectAutomationCreateInput = {
enabled: Scalars['Boolean']['input'];
name: Scalars['String']['input'];
@ -2042,7 +2113,11 @@ export enum ProjectModelsUpdatedMessageType {
export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Access request related mutations */
accessRequestMutations: ProjectAccessRequestMutations;
automationMutations: ProjectAutomationMutations;
/** Batch delete projects */
batchDelete: Scalars['Boolean']['output'];
/** Create new project */
create: Project;
/**
@ -2068,6 +2143,11 @@ export type ProjectMutationsAutomationMutationsArgs = {
};
export type ProjectMutationsBatchDeleteArgs = {
ids: Array<Scalars['String']['input']>;
};
export type ProjectMutationsCreateArgs = {
input?: InputMaybe<ProjectCreateInput>;
};
@ -2231,7 +2311,7 @@ export type Query = {
automateFunctions: AutomateFunctionCollection;
/** Part of the automation/function creation handshake mechanism */
automateValidateAuthCode: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.comment instead. */
comment?: Maybe<Comment>;
/**
* This query can be used in the following ways:
@ -2270,7 +2350,7 @@ export type Query = {
stream?: Maybe<Stream>;
/**
* Get authed user's stream access request
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use User.projectAccessRequest instead.
*/
streamAccessRequest?: Maybe<StreamAccessRequest>;
/**
@ -2635,7 +2715,7 @@ export type Stream = {
* @deprecated Part of the old API surface and will be removed in the future. Use Project.blobs instead.
*/
blobs?: Maybe<BlobMetadataCollection>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model instead. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model or Project.modelByName instead. */
branch?: Maybe<Branch>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.models or Project.modelsTree instead. */
branches?: Maybe<BranchCollection>;
@ -2680,11 +2760,11 @@ export type Stream = {
/** Whether the stream can be viewed by non-contributors */
isPublic: Scalars['Boolean']['output'];
name: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.object instead. */
object?: Maybe<Object>;
/**
* Pending stream access requests
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use Project.pendingAccessRequests instead.
*/
pendingAccessRequests?: Maybe<Array<StreamAccessRequest>>;
/** Collaborators who have been invited, but not yet accepted. */
@ -3188,6 +3268,8 @@ export type User = {
name: Scalars['String']['output'];
notificationPreferences: Scalars['JSONObject']['output'];
profiles?: Maybe<Scalars['JSONObject']['output']>;
/** Get pending project access request, that the user made */
projectAccessRequest?: Maybe<ProjectAccessRequest>;
/** Get all invitations to projects that the active user has */
projectInvites: Array<PendingStreamCollaborator>;
/** Get projects that the user participates in */
@ -3253,6 +3335,15 @@ export type UserFavoriteStreamsArgs = {
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
*/
export type UserProjectAccessRequestArgs = {
projectId: Scalars['String']['input'];
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
@ -3397,18 +3488,30 @@ export type VersionCreatedTriggerDefinition = {
export type VersionMutations = {
__typename?: 'VersionMutations';
create: Version;
delete: Scalars['Boolean']['output'];
markReceived: Scalars['Boolean']['output'];
moveToModel: Model;
requestGendoAIRender: Scalars['Boolean']['output'];
update: Version;
};
export type VersionMutationsCreateArgs = {
input: CreateVersionInput;
};
export type VersionMutationsDeleteArgs = {
input: DeleteVersionsInput;
};
export type VersionMutationsMarkReceivedArgs = {
input: MarkReceivedVersionInput;
};
export type VersionMutationsMoveToModelArgs = {
input: MoveVersionsInput;
};

Просмотреть файл

@ -66,7 +66,7 @@ const getUserAclEntry = async ({ aclTableName, userId, resourceId }) => {
* @param {string | null | undefined} userId
* @param {string} resourceId
* @param {string} requiredRole
* @param {import('@/modules/serverinvites/services/operations').TokenResourceIdentifier[] | undefined | null} [userResourceAccessLimits]
* @param {import('@/modules/core/domain/tokens/types').TokenResourceIdentifier[] | undefined | null} [userResourceAccessLimits]
*/
async function authorizeResolver(
userId,

Просмотреть файл

@ -20,17 +20,49 @@ export type GetWorkspace = (args: GetWorkspaceArgs) => Promise<Workspace | null>
/** WorkspaceRole */
export type UpsertWorkspaceRole = (args: WorkspaceAcl) => Promise<void>
type GetWorkspaceRoleArgs = {
type DeleteWorkspaceRoleArgs = {
workspaceId: string
userId: string
}
export type GetWorkspaceRole = (
args: GetWorkspaceRoleArgs
export type DeleteWorkspaceRole = (
args: DeleteWorkspaceRoleArgs
) => Promise<WorkspaceAcl | null>
type GetWorkspaceRolesArgs = {
workspaceId: string
}
/** Get all roles in a given workspaces. */
export type GetWorkspaceRoles = (args: GetWorkspaceRolesArgs) => Promise<WorkspaceAcl[]>
type GetWorkspaceRoleForUserArgs = {
userId: string
workspaceId: string
}
/** Get role for given user in a specific workspace. */
export type GetWorkspaceRoleForUser = (
args: GetWorkspaceRoleForUserArgs
) => Promise<WorkspaceAcl | null>
type GetWorkspaceRolesForUserArgs = {
userId: string
}
type GetWorkspaceRolesForUserOptions = {
/** If provided, limit results to roles in given workspaces. */
workspaceIdFilter?: string[]
}
/** Get roles for given user across several (or all) workspaces. */
export type GetWorkspaceRolesForUser = (
args: GetWorkspaceRolesForUserArgs,
options?: GetWorkspaceRolesForUserOptions
) => Promise<WorkspaceAcl[]>
export type UpsertWorkspaceRole = (args: WorkspaceAcl) => Promise<void>
/** Blob */
export type StoreBlob = (args: string) => Promise<string>

Просмотреть файл

@ -0,0 +1,7 @@
import { BaseError } from '@/modules/shared/errors/base'
export class WorkspaceAdminRequiredError extends BaseError {
static defaultMessage = 'Cannot remove last admin from a workspace'
static code = 'WORKSPACE_ADMIN_REQUIRED_ERROR'
static statusCode = 400
}

Просмотреть файл

@ -1,7 +1,10 @@
import { Workspace, WorkspaceAcl } from '@/modules/workspaces/domain/types'
import {
DeleteWorkspaceRole,
GetWorkspace,
GetWorkspaceRole,
GetWorkspaceRoleForUser,
GetWorkspaceRoles,
GetWorkspaceRolesForUser,
UpsertWorkspace,
UpsertWorkspaceRole
} from '@/modules/workspaces/domain/operations'
@ -35,21 +38,59 @@ export const upsertWorkspaceFactory =
.merge(['description', 'logoUrl', 'name', 'updatedAt'])
}
export const getWorkspaceRoleFactory =
({ db }: { db: Knex }): GetWorkspaceRole =>
async ({ userId, workspaceId }) => {
const acl = await tables
.workspacesAcl(db)
.select('*')
.where({ userId, workspaceId })
.first()
export const getWorkspaceRolesFactory =
({ db }: { db: Knex }): GetWorkspaceRoles =>
async ({ workspaceId }) => {
return await tables.workspacesAcl(db).select('*').where({ workspaceId })
}
return acl || null
export const getWorkspaceRoleForUserFactory =
({ db }: { db: Knex }): GetWorkspaceRoleForUser =>
async ({ userId, workspaceId }) => {
return (
(await tables
.workspacesAcl(db)
.select('*')
.where({ userId, workspaceId })
.first()) ?? null
)
}
export const getWorkspaceRolesForUserFactory =
({ db }: { db: Knex }): GetWorkspaceRolesForUser =>
async ({ userId }, options) => {
const workspaceIdFilter = options?.workspaceIdFilter ?? []
const query = tables.workspacesAcl(db).select('*').where({ userId })
if (workspaceIdFilter.length > 0) {
query.whereIn('workspaceId', workspaceIdFilter)
}
return await query
}
export const deleteWorkspaceRoleFactory =
({ db }: { db: Knex }): DeleteWorkspaceRole =>
async ({ userId, workspaceId }) => {
const deletedRoles = await tables
.workspacesAcl(db)
.where({ workspaceId, userId })
.delete('*')
if (deletedRoles.length === 0) {
return null
}
// Given `workspaceId` and `userId` define a primary key for `workspace_acl` table,
// query returns either 0 or 1 row in all cases
return deletedRoles[0]
}
export const upsertWorkspaceRoleFactory =
({ db }: { db: Knex }): UpsertWorkspaceRole =>
async ({ userId, workspaceId, role }) => {
// Verify requested role is valid workspace role
const validRoles = Object.values(Roles.Workspace)
if (!validRoles.includes(role)) {
throw new Error(`Unexpected workspace role provided: ${role}`)

Просмотреть файл

@ -0,0 +1,89 @@
import {
DeleteWorkspaceRole,
EmitWorkspaceEvent,
GetWorkspaceRoleForUser,
GetWorkspaceRoles,
UpsertWorkspaceRole
} from '@/modules/workspaces/domain/operations'
import { WorkspaceAcl } from '@/modules/workspaces/domain/types'
import { WorkspaceAdminRequiredError } from '@/modules/workspaces/errors/workspace'
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/utils/isUserLastWorkspaceAdmin'
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
type WorkspaceRoleDeleteArgs = {
userId: string
workspaceId: string
}
export const deleteWorkspaceRoleFactory =
({
getWorkspaceRoles,
deleteWorkspaceRole,
emitWorkspaceEvent
}: {
getWorkspaceRoles: GetWorkspaceRoles
deleteWorkspaceRole: DeleteWorkspaceRole
emitWorkspaceEvent: EmitWorkspaceEvent
}) =>
async ({
userId,
workspaceId
}: WorkspaceRoleDeleteArgs): Promise<WorkspaceAcl | null> => {
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
if (isUserLastWorkspaceAdmin(workspaceRoles, userId)) {
throw new WorkspaceAdminRequiredError()
}
const deletedRole = await deleteWorkspaceRole({ userId, workspaceId })
if (!deletedRole) {
return null
}
emitWorkspaceEvent({ event: WorkspaceEvents.RoleDeleted, payload: deletedRole })
return deletedRole
}
type WorkspaceRoleGetArgs = {
userId: string
workspaceId: string
}
export const getWorkspaceRoleFactory =
({ getWorkspaceRoleForUser }: { getWorkspaceRoleForUser: GetWorkspaceRoleForUser }) =>
async ({
userId,
workspaceId
}: WorkspaceRoleGetArgs): Promise<WorkspaceAcl | null> => {
return await getWorkspaceRoleForUser({ userId, workspaceId })
}
export const setWorkspaceRoleFactory =
({
getWorkspaceRoles,
upsertWorkspaceRole,
emitWorkspaceEvent
}: {
getWorkspaceRoles: GetWorkspaceRoles
upsertWorkspaceRole: UpsertWorkspaceRole
emitWorkspaceEvent: EmitWorkspaceEvent
}) =>
async ({ userId, workspaceId, role }: WorkspaceAcl): Promise<void> => {
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
if (
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
role !== 'workspace:admin'
) {
throw new WorkspaceAdminRequiredError()
}
await upsertWorkspaceRole({ userId, workspaceId, role })
await emitWorkspaceEvent({
event: WorkspaceEvents.RoleUpdated,
payload: { userId, workspaceId, role }
})
}

Просмотреть файл

@ -1,18 +1,43 @@
import {
deleteWorkspaceRoleFactory,
getWorkspaceRoleForUserFactory,
getWorkspaceFactory,
upsertWorkspaceFactory,
upsertWorkspaceRoleFactory
upsertWorkspaceRoleFactory,
getWorkspaceRolesFactory,
getWorkspaceRolesForUserFactory
} from '@/modules/workspaces/repositories/workspaces'
import db from '@/db/knex'
import cryptoRandomString from 'crypto-random-string'
import { expect } from 'chai'
import { Workspace, WorkspaceAcl } from '@/modules/workspaces/domain/types'
import { expectToThrow } from '@/test/assertionHelper'
import { BasicTestUser, createTestUser } from '@/test/authHelper'
const getWorkspace = getWorkspaceFactory({ db })
const upsertWorkspace = upsertWorkspaceFactory({ db })
const deleteWorkspaceRole = deleteWorkspaceRoleFactory({ db })
const getWorkspaceRoles = getWorkspaceRolesFactory({ db })
const getWorkspaceRoleForUser = getWorkspaceRoleForUserFactory({ db })
const getWorkspaceRolesForUser = getWorkspaceRolesForUserFactory({ db })
const upsertWorkspaceRole = upsertWorkspaceRoleFactory({ db })
const createAndStoreTestUser = async (): Promise<BasicTestUser> => {
const testId = cryptoRandomString({ length: 6 })
const userRecord: BasicTestUser = {
name: `test-user-${testId}`,
email: `test-user-${testId}@example.org`,
password: '',
id: '',
role: 'server:user'
}
await createTestUser(userRecord)
return userRecord
}
const createAndStoreTestWorkspace = async (): Promise<Workspace> => {
const workspace: Workspace = {
id: cryptoRandomString({ length: 10 }),
@ -79,6 +104,140 @@ describe('Workspace repositories', () => {
})
})
describe('deleteWorkspaceRoleFactory creates a function, that', () => {
it('deletes specified workspace role', async () => {
const { id: userId } = await createAndStoreTestUser()
const { id: workspaceId } = await createAndStoreTestWorkspace()
await upsertWorkspaceRole({ userId, workspaceId, role: 'workspace:member' })
await deleteWorkspaceRole({ userId, workspaceId })
const role = await getWorkspaceRoleForUser({ userId, workspaceId })
expect(role).to.be.null
})
it('returns deleted workspace role', async () => {
const { id: userId } = await createAndStoreTestUser()
const { id: workspaceId } = await createAndStoreTestWorkspace()
const createdRole: WorkspaceAcl = {
userId,
workspaceId,
role: 'workspace:member'
}
await upsertWorkspaceRole(createdRole)
const deletedRole = await deleteWorkspaceRole({ userId, workspaceId })
expect(deletedRole).to.deep.equal(createdRole)
})
it('return null if role does not exist', async () => {
const deletedRole = await deleteWorkspaceRole({ userId: '', workspaceId: '' })
expect(deletedRole).to.be.null
})
})
describe('getWorkspaceRolesFactory creates a function, that', () => {
it('returns all roles in a given workspace', async () => {
const { id: workspaceId } = await createAndStoreTestWorkspace()
const { id: userIdA } = await createAndStoreTestUser()
const { id: userIdB } = await createAndStoreTestUser()
await upsertWorkspaceRole({
workspaceId,
userId: userIdA,
role: 'workspace:admin'
})
await upsertWorkspaceRole({
workspaceId,
userId: userIdB,
role: 'workspace:admin'
})
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
expect(workspaceRoles.length).to.equal(2)
expect(workspaceRoles.some(({ userId }) => userId === userIdA)).to.be.true
expect(workspaceRoles.some(({ userId }) => userId === userIdB)).to.be.true
})
})
describe('getWorkspaceRoleForUserFactory creates a function, that', () => {
it('returns the current role for a given user in a given workspace', async () => {
const { id: userId } = await createAndStoreTestUser()
const { id: workspaceId } = await createAndStoreTestWorkspace()
await upsertWorkspaceRole({ workspaceId, userId, role: 'workspace:admin' })
const workspaceRole = await getWorkspaceRoleForUser({ userId, workspaceId })
expect(workspaceRole).to.not.be.null
expect(workspaceRole?.userId).to.equal(userId)
})
it('returns `null` if the given user does not have a role in the given workspace', async () => {
const workspaceRole = await getWorkspaceRoleForUser({
userId: 'invalid-user-id',
workspaceId: 'invalid-workspace-id'
})
expect(workspaceRole).to.be.null
})
})
describe('getWorkspaceRolesForUserFactory creates a function, that', () => {
it('returns the current role for a given user across all workspaces', async () => {
const { id: userId } = await createAndStoreTestUser()
const { id: workspaceIdA } = await createAndStoreTestWorkspace()
const { id: workspaceIdB } = await createAndStoreTestWorkspace()
await upsertWorkspaceRole({
workspaceId: workspaceIdA,
userId,
role: 'workspace:admin'
})
await upsertWorkspaceRole({
workspaceId: workspaceIdB,
userId,
role: 'workspace:admin'
})
const workspaceRoles = await getWorkspaceRolesForUser({ userId })
expect(workspaceRoles.length).to.equal(2)
expect(workspaceRoles.some(({ workspaceId }) => workspaceId === workspaceIdA)).to
.be.true
expect(workspaceRoles.some(({ workspaceId }) => workspaceId === workspaceIdB)).to
.be.true
})
it('returns the current role for workspaces specified by the workspace id filter, if provided', async () => {
const { id: userId } = await createAndStoreTestUser()
const { id: workspaceIdA } = await createAndStoreTestWorkspace()
const { id: workspaceIdB } = await createAndStoreTestWorkspace()
await upsertWorkspaceRole({
workspaceId: workspaceIdA,
userId,
role: 'workspace:admin'
})
await upsertWorkspaceRole({
workspaceId: workspaceIdB,
userId,
role: 'workspace:admin'
})
const workspaceRoles = await getWorkspaceRolesForUser(
{ userId },
{ workspaceIdFilter: [workspaceIdA] }
)
expect(workspaceRoles.length).to.equal(1)
expect(workspaceRoles[0].workspaceId).to.equal(workspaceIdA)
})
})
describe('upsertWorkspaceRoleFactory creates a function, that', () => {
it('throws if an unknown role is provided', async () => {
const role: WorkspaceAcl = {

Просмотреть файл

@ -0,0 +1,172 @@
import { WorkspaceAcl } from '@/modules/workspaces/domain/types'
import {
deleteWorkspaceRoleFactory,
setWorkspaceRoleFactory
} from '@/modules/workspaces/services/workspaceRoleCreation'
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
import { expectToThrow } from '@/test/assertionHelper'
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
describe('Workspace role services', () => {
describe('deleteWorkspaceRoleFactory creates a function, that', () => {
it('deletes the workspace role', async () => {
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
let storedRoles: WorkspaceAcl[] = [role]
const deleteWorkspaceRole = deleteWorkspaceRoleFactory({
getWorkspaceRoles: async () => storedRoles,
deleteWorkspaceRole: async ({ userId, workspaceId }) => {
const role = storedRoles.find(
(r) => r.userId === userId && r.workspaceId === workspaceId
)
storedRoles = storedRoles.filter((r) => r.userId !== userId)
return role ?? null
},
emitWorkspaceEvent: async () => []
})
const deletedRole = await deleteWorkspaceRole({ userId, workspaceId })
expect(storedRoles.length).to.equal(0)
expect(deletedRole).to.deep.equal(role)
})
it('emits a role-deleted event', async () => {
const eventData = {
isCalled: false,
event: '',
payload: {}
}
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
const storedRoles: WorkspaceAcl[] = [role]
const deleteWorkspaceRole = deleteWorkspaceRoleFactory({
getWorkspaceRoles: async () => storedRoles,
deleteWorkspaceRole: async () => {
return storedRoles[0]
},
emitWorkspaceEvent: async ({ event, payload }) => {
eventData.isCalled = true
eventData.event = event
eventData.payload = payload
return []
}
})
await deleteWorkspaceRole({ userId, workspaceId })
expect(eventData.isCalled).to.be.true
expect(eventData.event).to.equal(WorkspaceEvents.RoleDeleted)
expect(eventData.payload).to.deep.equal(role)
})
it('throws if attempting to delete the last admin from a workspace', async () => {
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Admin }
let storedRoles: WorkspaceAcl[] = [role]
const deleteWorkspaceRole = deleteWorkspaceRoleFactory({
getWorkspaceRoles: async () => storedRoles,
deleteWorkspaceRole: async ({ userId, workspaceId }) => {
const role = storedRoles.find(
(r) => r.userId === userId && r.workspaceId === workspaceId
)
storedRoles = storedRoles.filter((r) => r.userId !== userId)
return role ?? null
},
emitWorkspaceEvent: async () => []
})
await expectToThrow(() => deleteWorkspaceRole({ userId, workspaceId }))
})
})
describe('setWorkspaceRoleFactory creates a function, that', () => {
it('sets the workspace role', async () => {
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
const storedRoles: WorkspaceAcl[] = []
const setWorkspaceRole = setWorkspaceRoleFactory({
getWorkspaceRoles: async () => storedRoles,
upsertWorkspaceRole: async (role) => {
storedRoles.push(role)
},
emitWorkspaceEvent: async () => []
})
await setWorkspaceRole(role)
expect(storedRoles.length).to.equal(1)
expect(storedRoles[0]).to.deep.equal(role)
})
it('emits a role-updated event', async () => {
const eventData = {
isCalled: false,
event: '',
payload: {}
}
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
const setWorkspaceRole = setWorkspaceRoleFactory({
getWorkspaceRoles: async () => [],
upsertWorkspaceRole: async () => {},
emitWorkspaceEvent: async ({ event, payload }) => {
eventData.isCalled = true
eventData.event = event
eventData.payload = payload
return []
}
})
await setWorkspaceRole(role)
expect(eventData.isCalled).to.be.true
expect(eventData.event).to.equal(WorkspaceEvents.RoleUpdated)
expect(eventData.payload).to.deep.equal(role)
})
it('throws if attempting to remove the last admin in a workspace', async () => {
const userId = cryptoRandomString({ length: 10 })
const workspaceId = cryptoRandomString({ length: 10 })
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Admin }
const storedRoles: WorkspaceAcl[] = [role]
const setWorkspaceRole = setWorkspaceRoleFactory({
getWorkspaceRoles: async () => storedRoles,
upsertWorkspaceRole: async () => {},
emitWorkspaceEvent: async () => []
})
await expectToThrow(() =>
setWorkspaceRole({ ...role, role: Roles.Workspace.Member })
)
})
})
})

Просмотреть файл

@ -0,0 +1,56 @@
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/utils/isUserLastWorkspaceAdmin'
import { WorkspaceAcl } from '@/modules/workspaces/domain/types'
import { expect } from 'chai'
import { Roles } from '@speckle/shared'
describe('given a workspace with several admins', () => {
const workspaceRoles: WorkspaceAcl[] = [
{ workspaceId: 'workspace-id', userId: 'non-admin', role: Roles.Workspace.Member },
{ workspaceId: 'workspace-id', userId: 'admin-a', role: Roles.Workspace.Admin },
{ workspaceId: 'workspace-id', userId: 'admin-b', role: Roles.Workspace.Admin }
]
describe('when testing a non-admin user', () => {
it('should return false', () => {
expect(isUserLastWorkspaceAdmin(workspaceRoles, 'non-admin')).to.be.false
})
})
describe('when testing an admin user', () => {
it('should return false', () => {
expect(isUserLastWorkspaceAdmin(workspaceRoles, 'admin-a')).to.be.false
})
})
})
describe('given a workspace with one admin', () => {
const workspaceRoles: WorkspaceAcl[] = [
{ workspaceId: 'workspace-id', userId: 'non-admin', role: Roles.Workspace.Member },
{ workspaceId: 'workspace-id', userId: 'admin', role: Roles.Workspace.Admin }
]
describe('when testing a non-admin user', () => {
it('should return false', () => {
expect(isUserLastWorkspaceAdmin(workspaceRoles, 'non-admin')).to.be.false
})
})
describe('when testing an admin user', () => {
it('should return true', () => {
expect(isUserLastWorkspaceAdmin(workspaceRoles, 'admin')).to.be.true
})
})
})
describe('given a workspace', () => {
const workspaceRoles: WorkspaceAcl[] = [
{ workspaceId: 'workspace-id', userId: 'non-admin', role: Roles.Workspace.Member },
{ workspaceId: 'workspace-id', userId: 'admin', role: Roles.Workspace.Admin }
]
describe('when testing a non-workspace user', () => {
it('should return false', () => {
expect(isUserLastWorkspaceAdmin(workspaceRoles, 'random-id')).to.be.false
})
})
})

Просмотреть файл

@ -0,0 +1,13 @@
import { WorkspaceAcl } from '@/modules/workspaces/domain/types'
export const isUserLastWorkspaceAdmin = (
workspaceRoles: WorkspaceAcl[],
userId: string
): boolean => {
const workspaceAdmins = workspaceRoles.filter(
({ role }) => role === 'workspace:admin'
)
const isUserAdmin = workspaceAdmins.some((role) => role.userId === userId)
return isUserAdmin && workspaceAdmins.length === 1
}

Просмотреть файл

@ -1,13 +1,19 @@
import { Workspace } from '@/modules/workspaces/domain/types'
import { Workspace, WorkspaceAcl } from '@/modules/workspaces/domain/types'
export const WorkspaceEvents = {
Created: 'created'
Created: 'created',
RoleDeleted: 'role-deleted',
RoleUpdated: 'role-updated'
} as const
export type WorkspaceEvents = (typeof WorkspaceEvents)[keyof typeof WorkspaceEvents]
type WorkspaceCreatedPayload = Workspace
type WorkspaceRoleDeletedPayload = WorkspaceAcl
type WorkspaceRoleUpdatedPayload = WorkspaceAcl
export type WorkspaceEventsPayloads = {
[WorkspaceEvents.Created]: WorkspaceCreatedPayload
[WorkspaceEvents.RoleDeleted]: WorkspaceRoleDeletedPayload
[WorkspaceEvents.RoleUpdated]: WorkspaceRoleUpdatedPayload
}

Просмотреть файл

@ -17,7 +17,6 @@ export type Scalars = {
BigInt: { input: bigint; output: bigint; }
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
DateTime: { input: string; output: string; }
EmailAddress: { input: any; output: any; }
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSONObject: { input: Record<string, unknown>; output: Record<string, unknown>; }
};
@ -802,6 +801,16 @@ export type CreateModelInput = {
projectId: Scalars['ID']['input'];
};
export type CreateVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
modelId: Scalars['String']['input'];
objectId: Scalars['String']['input'];
parents?: InputMaybe<Array<Scalars['String']['input']>>;
projectId: Scalars['String']['input'];
sourceApplication?: InputMaybe<Scalars['String']['input']>;
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};
export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
@ -992,6 +1001,13 @@ export type LimitedUserTimelineArgs = {
limit?: Scalars['Int']['input'];
};
export type MarkReceivedVersionInput = {
message?: InputMaybe<Scalars['String']['input']>;
projectId: Scalars['String']['input'];
sourceApplication: Scalars['String']['input'];
versionId: Scalars['String']['input'];
};
export type Model = {
__typename?: 'Model';
author: LimitedUser;
@ -1161,11 +1177,11 @@ export type Mutation = {
* @deprecated Use commentMutations version
*/
commentView: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.create instead. */
commitCreate: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.delete instead. */
commitDelete: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.markReceived instead. */
commitReceive: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.update/moveToModel instead. */
commitUpdate: Scalars['Boolean']['output'];
@ -1191,7 +1207,7 @@ export type Mutation = {
inviteResend: Scalars['Boolean']['output'];
modelMutations: ModelMutations;
/** @deprecated Part of the old API surface and will be removed in the future. */
objectCreate: Array<Maybe<Scalars['String']['output']>>;
objectCreate: Array<Scalars['String']['output']>;
projectMutations: ProjectMutations;
/** (Re-)send the account verification e-mail */
requestVerification: Scalars['Boolean']['output'];
@ -1203,12 +1219,12 @@ export type Mutation = {
serverInviteCreate: Scalars['Boolean']['output'];
/**
* Request access to a specific stream
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.create instead.
*/
streamAccessRequestCreate: StreamAccessRequest;
/**
* Accept or decline a stream access request. Must be a stream owner to invoke this.
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectAccessRequestMutations.use instead.
*/
streamAccessRequestUse: Scalars['Boolean']['output'];
/**
@ -1265,7 +1281,7 @@ export type Mutation = {
* @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.updateRole instead.
*/
streamUpdatePermission?: Maybe<Scalars['Boolean']['output']>;
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use ProjectMutations.batchDelete instead. */
streamsDelete: Scalars['Boolean']['output'];
/**
* Used for broadcasting real time typing status in comment threads. Does not persist any info.
@ -1582,6 +1598,7 @@ export type MutationWebhookUpdateArgs = {
export type Object = {
__typename?: 'Object';
/** @deprecated Not implemented. */
applicationId?: Maybe<Scalars['String']['output']>;
/**
* Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
@ -1621,7 +1638,7 @@ export type ObjectChildrenArgs = {
export type ObjectCollection = {
__typename?: 'ObjectCollection';
cursor?: Maybe<Scalars['String']['output']>;
objects: Array<Maybe<Object>>;
objects: Array<Object>;
totalCount: Scalars['Int']['output'];
};
@ -1682,6 +1699,8 @@ export type Project = {
blob?: Maybe<BlobMetadata>;
/** Get the metadata collection of blobs stored for this stream. */
blobs?: Maybe<BlobMetadataCollection>;
/** Get specific project comment/thread by ID */
comment?: Maybe<Comment>;
/** All comment threads in this project */
commentThreads: ProjectCommentCollection;
createdAt: Scalars['DateTime']['output'];
@ -1691,6 +1710,8 @@ export type Project = {
invitedTeam?: Maybe<Array<PendingStreamCollaborator>>;
/** Returns a specific model by its ID */
model: Model;
/** Retrieve a specific project model by its ID */
modelByName: Model;
/** Return a model tree of children for the specified model name */
modelChildrenTree: Array<ModelsTreeItem>;
/** Returns a flat list of all models */
@ -1701,6 +1722,9 @@ export type Project = {
*/
modelsTree: ModelsTreeItemCollection;
name: Scalars['String']['output'];
object?: Maybe<Object>;
/** Pending project access requests */
pendingAccessRequests?: Maybe<Array<ProjectAccessRequest>>;
/** Returns a list models that are being created from a file import */
pendingImportedModels: Array<FileUpload>;
/** Active user's role for this project. `null` if request is not authenticated, or the project is not explicitly shared with you. */
@ -1744,6 +1768,11 @@ export type ProjectBlobsArgs = {
};
export type ProjectCommentArgs = {
id: Scalars['String']['input'];
};
export type ProjectCommentThreadsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<ProjectCommentsFilter>;
@ -1756,6 +1785,11 @@ export type ProjectModelArgs = {
};
export type ProjectModelByNameArgs = {
name: Scalars['String']['input'];
};
export type ProjectModelChildrenTreeArgs = {
fullName: Scalars['String']['input'];
};
@ -1775,6 +1809,11 @@ export type ProjectModelsTreeArgs = {
};
export type ProjectObjectArgs = {
id: Scalars['String']['input'];
};
export type ProjectPendingImportedModelsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>;
};
@ -1801,6 +1840,38 @@ export type ProjectWebhooksArgs = {
id?: InputMaybe<Scalars['String']['input']>;
};
/** Created when a user requests to become a contributor on a project */
export type ProjectAccessRequest = {
__typename?: 'ProjectAccessRequest';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
/** Can only be selected if authed user has proper access */
project: Project;
projectId: Scalars['String']['output'];
requester: LimitedUser;
requesterId: Scalars['String']['output'];
};
export type ProjectAccessRequestMutations = {
__typename?: 'ProjectAccessRequestMutations';
/** Request access to a specific project */
create: ProjectAccessRequest;
/** Accept or decline a project access request. Must be a project owner to invoke this. */
use: Project;
};
export type ProjectAccessRequestMutationsCreateArgs = {
projectId: Scalars['String']['input'];
};
export type ProjectAccessRequestMutationsUseArgs = {
accept: Scalars['Boolean']['input'];
requestId: Scalars['String']['input'];
role?: StreamRole;
};
export type ProjectAutomationCreateInput = {
enabled: Scalars['Boolean']['input'];
name: Scalars['String']['input'];
@ -2043,7 +2114,11 @@ export enum ProjectModelsUpdatedMessageType {
export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Access request related mutations */
accessRequestMutations: ProjectAccessRequestMutations;
automationMutations: ProjectAutomationMutations;
/** Batch delete projects */
batchDelete: Scalars['Boolean']['output'];
/** Create new project */
create: Project;
/**
@ -2069,6 +2144,11 @@ export type ProjectMutationsAutomationMutationsArgs = {
};
export type ProjectMutationsBatchDeleteArgs = {
ids: Array<Scalars['String']['input']>;
};
export type ProjectMutationsCreateArgs = {
input?: InputMaybe<ProjectCreateInput>;
};
@ -2232,7 +2312,7 @@ export type Query = {
automateFunctions: AutomateFunctionCollection;
/** Part of the automation/function creation handshake mechanism */
automateValidateAuthCode: Scalars['Boolean']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.comment instead. */
comment?: Maybe<Comment>;
/**
* This query can be used in the following ways:
@ -2271,7 +2351,7 @@ export type Query = {
stream?: Maybe<Stream>;
/**
* Get authed user's stream access request
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use User.projectAccessRequest instead.
*/
streamAccessRequest?: Maybe<StreamAccessRequest>;
/**
@ -2636,7 +2716,7 @@ export type Stream = {
* @deprecated Part of the old API surface and will be removed in the future. Use Project.blobs instead.
*/
blobs?: Maybe<BlobMetadataCollection>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model instead. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.model or Project.modelByName instead. */
branch?: Maybe<Branch>;
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.models or Project.modelsTree instead. */
branches?: Maybe<BranchCollection>;
@ -2681,11 +2761,11 @@ export type Stream = {
/** Whether the stream can be viewed by non-contributors */
isPublic: Scalars['Boolean']['output'];
name: Scalars['String']['output'];
/** @deprecated Part of the old API surface and will be removed in the future. */
/** @deprecated Part of the old API surface and will be removed in the future. Use Project.object instead. */
object?: Maybe<Object>;
/**
* Pending stream access requests
* @deprecated Part of the old API surface and will be removed in the future.
* @deprecated Part of the old API surface and will be removed in the future. Use Project.pendingAccessRequests instead.
*/
pendingAccessRequests?: Maybe<Array<StreamAccessRequest>>;
/** Collaborators who have been invited, but not yet accepted. */
@ -3189,6 +3269,8 @@ export type User = {
name: Scalars['String']['output'];
notificationPreferences: Scalars['JSONObject']['output'];
profiles?: Maybe<Scalars['JSONObject']['output']>;
/** Get pending project access request, that the user made */
projectAccessRequest?: Maybe<ProjectAccessRequest>;
/** Get all invitations to projects that the active user has */
projectInvites: Array<PendingStreamCollaborator>;
/** Get projects that the user participates in */
@ -3254,6 +3336,15 @@ export type UserFavoriteStreamsArgs = {
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
*/
export type UserProjectAccessRequestArgs = {
projectId: Scalars['String']['input'];
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
@ -3398,18 +3489,30 @@ export type VersionCreatedTriggerDefinition = {
export type VersionMutations = {
__typename?: 'VersionMutations';
create: Version;
delete: Scalars['Boolean']['output'];
markReceived: Scalars['Boolean']['output'];
moveToModel: Model;
requestGendoAIRender: Scalars['Boolean']['output'];
update: Version;
};
export type VersionMutationsCreateArgs = {
input: CreateVersionInput;
};
export type VersionMutationsDeleteArgs = {
input: DeleteVersionsInput;
};
export type VersionMutationsMarkReceivedArgs = {
input: MarkReceivedVersionInput;
};
export type VersionMutationsMoveToModelArgs = {
input: MoveVersionsInput;
};
@ -3714,6 +3817,67 @@ export type DeleteCommitsMutationVariables = Exact<{
export type DeleteCommitsMutation = { __typename?: 'Mutation', commitsDelete: boolean };
export type CreateProjectModelMutationVariables = Exact<{
input: CreateModelInput;
}>;
export type CreateProjectModelMutation = { __typename?: 'Mutation', modelMutations: { __typename?: 'ModelMutations', create: { __typename?: 'Model', id: string, name: string, description?: string | null } } };
export type FindProjectModelByNameQueryVariables = Exact<{
projectId: Scalars['String']['input'];
name: Scalars['String']['input'];
}>;
export type FindProjectModelByNameQuery = { __typename?: 'Query', project: { __typename?: 'Project', modelByName: { __typename?: 'Model', id: string, name: string, description?: string | null } } };
export type BasicProjectAccessRequestFieldsFragment = { __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } };
export type CreateProjectAccessRequestMutationVariables = Exact<{
projectId: Scalars['String']['input'];
}>;
export type CreateProjectAccessRequestMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', accessRequestMutations: { __typename?: 'ProjectAccessRequestMutations', create: { __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } } } } };
export type GetActiveUserProjectAccessRequestQueryVariables = Exact<{
projectId: Scalars['String']['input'];
}>;
export type GetActiveUserProjectAccessRequestQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projectAccessRequest?: { __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } } | null } | null };
export type GetActiveUserFullProjectAccessRequestQueryVariables = Exact<{
projectId: Scalars['String']['input'];
}>;
export type GetActiveUserFullProjectAccessRequestQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projectAccessRequest?: { __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, project: { __typename?: 'Project', id: string, name: string }, requester: { __typename?: 'LimitedUser', id: string, name: string } } | null } | null };
export type GetPendingProjectAccessRequestsQueryVariables = Exact<{
projectId: Scalars['String']['input'];
}>;
export type GetPendingProjectAccessRequestsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, pendingAccessRequests?: Array<{ __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, project: { __typename?: 'Project', id: string, name: string }, requester: { __typename?: 'LimitedUser', id: string, name: string } }> | null } };
export type UseProjectAccessRequestMutationVariables = Exact<{
requestId: Scalars['String']['input'];
accept: Scalars['Boolean']['input'];
role?: StreamRole;
}>;
export type UseProjectAccessRequestMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', accessRequestMutations: { __typename?: 'ProjectAccessRequestMutations', use: { __typename?: 'Project', id: string } } } };
export type CreateProjectCommentMutationVariables = Exact<{
input: CreateCommentInput;
}>;
export type CreateProjectCommentMutation = { __typename?: 'Mutation', commentMutations: { __typename?: 'CommentMutations', create: { __typename?: 'Comment', id: string, rawText: string, authorId: string, text: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null } } } };
export type BasicProjectFieldsFragment = { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string };
export type AdminProjectListQueryVariables = Exact<{
@ -3727,6 +3891,28 @@ export type AdminProjectListQueryVariables = Exact<{
export type AdminProjectListQuery = { __typename?: 'Query', admin: { __typename?: 'AdminQueries', projectList: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string }> } } };
export type GetProjectObjectQueryVariables = Exact<{
projectId: Scalars['String']['input'];
objectId: Scalars['String']['input'];
}>;
export type GetProjectObjectQuery = { __typename?: 'Query', project: { __typename?: 'Project', object?: { __typename?: 'Object', id: string, createdAt?: string | null } | null } };
export type CreateProjectMutationVariables = Exact<{
input: ProjectCreateInput;
}>;
export type CreateProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', create: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } };
export type BatchDeleteProjectsMutationVariables = Exact<{
ids: Array<Scalars['String']['input']> | Scalars['String']['input'];
}>;
export type BatchDeleteProjectsMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', batchDelete: boolean } };
export type CreateServerInviteMutationVariables = Exact<{
input: ServerInviteCreateInput;
}>;
@ -3907,11 +4093,26 @@ export type RequestVerificationMutationVariables = Exact<{ [key: string]: never;
export type RequestVerificationMutation = { __typename?: 'Mutation', requestVerification: boolean };
export type CreateProjectVersionMutationVariables = Exact<{
input: CreateVersionInput;
}>;
export type CreateProjectVersionMutation = { __typename?: 'Mutation', versionMutations: { __typename?: 'VersionMutations', create: { __typename?: 'Version', id: string, message?: string | null, sourceApplication?: string | null, referencedObject: string, model: { __typename?: 'Model', id: string } } } };
export type MarkProjectVersionReceivedMutationVariables = Exact<{
input: MarkReceivedVersionInput;
}>;
export type MarkProjectVersionReceivedMutation = { __typename?: 'Mutation', versionMutations: { __typename?: 'VersionMutations', markReceived: boolean } };
export const BasicStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<BasicStreamAccessRequestFieldsFragment, unknown>;
export const TestAutomateFunctionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestAutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"5"}}],"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":"versionTag"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"automationCount"}},{"kind":"Field","name":{"kind":"Name","value":"supportedSourceApps"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}}]}}]} as unknown as DocumentNode<TestAutomateFunctionFragment, unknown>;
export const TestAutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestAutomation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"runs"},"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":"trigger"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"VersionCreatedTrigger"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"elapsed"}},{"kind":"Field","name":{"kind":"Name","value":"results"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"currentRevision"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"triggerDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"VersionCreatedTriggerDefinition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"functions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"parameters"}},{"kind":"Field","name":{"kind":"Name","value":"release"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versionTag"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TestAutomationFragment, unknown>;
export const CommentWithRepliesFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommentWithReplies"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"attachments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"replies"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"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":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"attachments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<CommentWithRepliesFragment, unknown>;
export const BaseCommitFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BaseCommitFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Commit"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}}]}}]} as unknown as DocumentNode<BaseCommitFieldsFragment, unknown>;
export const BasicProjectAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<BasicProjectAccessRequestFieldsFragment, unknown>;
export const BasicProjectFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<BasicProjectFieldsFragment, unknown>;
export const StreamInviteDataFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamInviteData"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingStreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"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"}}]}},{"kind":"Field","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":"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<StreamInviteDataFragment, unknown>;
export const BasicStreamFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Stream"}},"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":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"isDiscoverable"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<BasicStreamFieldsFragment, unknown>;
@ -3938,7 +4139,18 @@ export const ReadOtherUsersCommitsDocument = {"kind":"Document","definitions":[{
export const ReadStreamBranchCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ReadStreamBranchCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"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":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},"defaultValue":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"branch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}}}],"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":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BaseCommitFields"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BaseCommitFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Commit"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}}]}}]} as unknown as DocumentNode<ReadStreamBranchCommitsQuery, ReadStreamBranchCommitsQueryVariables>;
export const MoveCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CommitsMoveInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commitsMove"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<MoveCommitsMutation, MoveCommitsMutationVariables>;
export const DeleteCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CommitsDeleteInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commitsDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<DeleteCommitsMutation, DeleteCommitsMutationVariables>;
export const CreateProjectModelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectModel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateModelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelMutations"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]}}]} as unknown as DocumentNode<CreateProjectModelMutation, CreateProjectModelMutationVariables>;
export const FindProjectModelByNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindProjectModelByName"},"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":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"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"}}]}}]}}]}}]} as unknown as DocumentNode<FindProjectModelByNameQuery, FindProjectModelByNameQueryVariables>;
export const CreateProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessRequestMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<CreateProjectAccessRequestMutation, CreateProjectAccessRequestMutationVariables>;
export const GetActiveUserProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetActiveUserProjectAccessRequestQuery, GetActiveUserProjectAccessRequestQueryVariables>;
export const GetActiveUserFullProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserFullProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}},{"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"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetActiveUserFullProjectAccessRequestQuery, GetActiveUserFullProjectAccessRequestQueryVariables>;
export const GetPendingProjectAccessRequestsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPendingProjectAccessRequests"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pendingAccessRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}},{"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"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetPendingProjectAccessRequestsQuery, GetPendingProjectAccessRequestsQueryVariables>;
export const UseProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UseProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accept"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"role"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamRole"}}},"defaultValue":{"kind":"EnumValue","value":"STREAM_CONTRIBUTOR"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessRequestMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"use"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"requestId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"requestId"}}},{"kind":"Argument","name":{"kind":"Name","value":"accept"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accept"}}},{"kind":"Argument","name":{"kind":"Name","value":"role"},"value":{"kind":"Variable","name":{"kind":"Name","value":"role"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UseProjectAccessRequestMutation, UseProjectAccessRequestMutationVariables>;
export const CreateProjectCommentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectComment"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateCommentInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commentMutations"},"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":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}}]}}]}}]}}]} as unknown as DocumentNode<CreateProjectCommentMutation, CreateProjectCommentMutationVariables>;
export const AdminProjectListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminProjectList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"visibility"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},"defaultValue":{"kind":"IntValue","value":"25"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"admin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"visibility"},"value":{"kind":"Variable","name":{"kind":"Name","value":"visibility"}}},{"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":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<AdminProjectListQuery, AdminProjectListQueryVariables>;
export const GetProjectObjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectObject"},"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":"objectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"object"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<GetProjectObjectQuery, GetProjectObjectQueryVariables>;
export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"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":"BasicProjectFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<CreateProjectMutation, CreateProjectMutationVariables>;
export const BatchDeleteProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BatchDeleteProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ids"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"batchDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ids"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ids"}}}]}]}}]}}]} as unknown as DocumentNode<BatchDeleteProjectsMutation, BatchDeleteProjectsMutationVariables>;
export const CreateServerInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateServerInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInviteCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<CreateServerInviteMutation, CreateServerInviteMutationVariables>;
export const CreateStreamInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamInviteCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<CreateStreamInviteMutation, CreateStreamInviteMutationVariables>;
export const ResendInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inviteResend"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"inviteId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}}}]}]}}]} as unknown as DocumentNode<ResendInviteMutation, ResendInviteMutationVariables>;
@ -3962,4 +4174,6 @@ export const GetActiveUserDocument = {"kind":"Document","definitions":[{"kind":"
export const GetOtherUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOtherUser"},"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":"otherUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BaseLimitedUserFields"}}]}}]}},{"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<GetOtherUserQuery, GetOtherUserQueryVariables>;
export const GetAdminUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},"defaultValue":{"kind":"IntValue","value":"25"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},"defaultValue":{"kind":"IntValue","value":"0"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"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":"registeredUser"},"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":"invitedUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetAdminUsersQuery, GetAdminUsersQueryVariables>;
export const GetPendingEmailVerificationStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPendingEmailVerificationStatus"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"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":"hasPendingVerification"}}]}}]}}]} as unknown as DocumentNode<GetPendingEmailVerificationStatusQuery, GetPendingEmailVerificationStatusQueryVariables>;
export const RequestVerificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RequestVerification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestVerification"}}]}}]} as unknown as DocumentNode<RequestVerificationMutation, RequestVerificationMutationVariables>;
export const RequestVerificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RequestVerification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestVerification"}}]}}]} as unknown as DocumentNode<RequestVerificationMutation, RequestVerificationMutationVariables>;
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>;

Просмотреть файл

@ -0,0 +1,25 @@
import { gql } from 'apollo-server-express'
export const createProjectModelQuery = gql`
mutation CreateProjectModel($input: CreateModelInput!) {
modelMutations {
create(input: $input) {
id
name
description
}
}
}
`
export const findProjectModelByNameQuery = gql`
query FindProjectModelByName($projectId: String!, $name: String!) {
project(id: $projectId) {
modelByName(name: $name) {
id
name
description
}
}
}
`

Просмотреть файл

@ -0,0 +1,90 @@
import { gql } from 'apollo-server-express'
const basicProjectAccessRequestFragment = gql`
fragment BasicProjectAccessRequestFields on ProjectAccessRequest {
id
requester {
id
name
}
requesterId
projectId
createdAt
}
`
export const createProjectAccessRequestMutation = gql`
mutation CreateProjectAccessRequest($projectId: String!) {
projectMutations {
accessRequestMutations {
create(projectId: $projectId) {
...BasicProjectAccessRequestFields
}
}
}
}
${basicProjectAccessRequestFragment}
`
export const getActiveUserProjectAccessRequestQuery = gql`
query GetActiveUserProjectAccessRequest($projectId: String!) {
activeUser {
projectAccessRequest(projectId: $projectId) {
...BasicProjectAccessRequestFields
}
}
}
${basicProjectAccessRequestFragment}
`
export const getActiveUserFullProjectAccessRequestQuery = gql`
query GetActiveUserFullProjectAccessRequest($projectId: String!) {
activeUser {
projectAccessRequest(projectId: $projectId) {
...BasicProjectAccessRequestFields
project {
id
name
}
}
}
}
${basicProjectAccessRequestFragment}
`
export const getPendingProjectAccessRequestsQuery = gql`
query GetPendingProjectAccessRequests($projectId: String!) {
project(id: $projectId) {
id
name
pendingAccessRequests {
...BasicProjectAccessRequestFields
project {
id
name
}
}
}
}
${basicProjectAccessRequestFragment}
`
export const useProjectAccessRequestMutation = gql`
mutation UseProjectAccessRequest(
$requestId: String!
$accept: Boolean!
$role: StreamRole! = STREAM_CONTRIBUTOR
) {
projectMutations {
accessRequestMutations {
use(requestId: $requestId, accept: $accept, role: $role) {
id
}
}
}
}
`

Просмотреть файл

@ -0,0 +1,16 @@
import { gql } from 'apollo-server-express'
export const createProjectCommentMutation = gql`
mutation CreateProjectComment($input: CreateCommentInput!) {
commentMutations {
create(input: $input) {
id
rawText
text {
doc
}
authorId
}
}
}
`

Просмотреть файл

@ -13,15 +13,7 @@ export const basicProjectFieldsFragment = gql`
}
`
/**
* query: String
orderBy: String
visibility: String
limit: Int! = 25
cursor: String = null
*/
export const adminProjectList = gql`
export const adminProjectListQuery = gql`
query AdminProjectList(
$query: String
$orderBy: String
@ -48,3 +40,34 @@ export const adminProjectList = gql`
${basicProjectFieldsFragment}
`
export const getProjectObjectQuery = gql`
query GetProjectObject($projectId: String!, $objectId: String!) {
project(id: $projectId) {
object(id: $objectId) {
id
createdAt
}
}
}
`
export const createProjectMutation = gql`
mutation CreateProject($input: ProjectCreateInput!) {
projectMutations {
create(input: $input) {
...BasicProjectFields
}
}
}
${basicProjectFieldsFragment}
`
export const batchDeleteProjectsMutation = gql`
mutation BatchDeleteProjects($ids: [String!]!) {
projectMutations {
batchDelete(ids: $ids)
}
}
`

Просмотреть файл

@ -0,0 +1,25 @@
import { gql } from 'apollo-server-express'
export const createProjectVersionMutation = gql`
mutation CreateProjectVersion($input: CreateVersionInput!) {
versionMutations {
create(input: $input) {
id
message
sourceApplication
model {
id
}
referencedObject
}
}
}
`
export const markProjectVersionReceivedMutation = gql`
mutation MarkProjectVersionReceived($input: MarkReceivedVersionInput!) {
versionMutations {
markReceived(input: $input)
}
}
`

Просмотреть файл

@ -7,6 +7,8 @@ import { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { buildApolloServer } from '@/app'
import { addLoadersToCtx } from '@/modules/shared/middleware'
import { buildUnauthenticatedApolloServer } from '@/test/serverHelper'
import { Roles } from '@/modules/core/helpers/mainConstants'
import { AllScopes } from '@speckle/shared'
type TypedGraphqlResponse<R = Record<string, any>> = GraphQLResponse & {
data: Nullable<R>
@ -51,10 +53,26 @@ export const createTestContext = (
/**
* Utilities that make it easier to test against an Apollo Server instance
*/
export const testApolloServer = async (params?: { context?: GraphQLContext }) => {
const instance = params?.context
export const testApolloServer = async (params?: {
context?: GraphQLContext
/**
* If set, will create an authed context w/ all scopes and Server.User role for thies user id
*/
authUserId?: string
}) => {
const initialCtx: GraphQLContext | undefined = params?.authUserId
? createTestContext({
auth: true,
userId: params.authUserId,
role: Roles.Server.User,
token: 'asd',
scopes: AllScopes
})
: params?.context
const instance = initialCtx
? await buildApolloServer({
context: params.context
context: initialCtx
})
: await buildUnauthenticatedApolloServer()
@ -77,7 +95,7 @@ export const testApolloServer = async (params?: { context?: GraphQLContext }) =>
const realInstance = options?.context
? await buildApolloServer({
context: createTestContext({
...(params?.context || {}),
...(initialCtx || {}),
...options.context
})
})

Просмотреть файл

@ -0,0 +1,44 @@
import { createBranch } from '@/modules/core/services/branches'
import { BasicTestUser } from '@/test/authHelper'
import { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
import { omit } from 'lodash'
export type BasicTestBranch = {
name: string
description?: string
/**
* The ID of the stream. Will be filled in by createTestBranch().
*/
streamId: string
/**
* The ID of the owner. Will be filled in by createTestBranch().
*/
authorId: string
/**
* The ID of the branch. Will be filled in by createTestBranch().
*/
id: string
}
export async function createTestBranch(params: {
branch: BasicTestBranch
stream: BasicTestStream
owner: BasicTestUser
}) {
const { branch, stream, owner } = params
branch.streamId = stream.id
branch.authorId = owner.id
const id = await createBranch({
...omit(branch, ['id']),
description: branch.description || null
})
branch.id = id
}
export async function createTestBranches(
branches: Array<Parameters<typeof createTestBranch>[0]>
) {
await Promise.all(branches.map((p) => createTestBranch(p)))
}

Просмотреть файл

@ -1,5 +1,7 @@
import { createCommitByBranchName } from '@/modules/core/services/commits'
import { createObject } from '@/modules/core/services/objects'
import { BasicTestUser } from '@/test/authHelper'
import { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
export type BasicTestCommit = {
/**
@ -10,7 +12,13 @@ export type BasicTestCommit = {
* Can be left empty, will be filled on creation
*/
objectId: string
/**
* Can be left empty, will be filled on creation if stream passed in
*/
streamId: string
/**
* Can be left empty, will be filled on creation if owner passed in
*/
authorId: string
/**
* Defaults to 'main'
@ -27,6 +35,10 @@ export type BasicTestCommit = {
parents?: string[]
}
export async function createTestObject(params: { projectId: string }) {
return await createObject(params.projectId, { foo: 'bar' })
}
/**
* Ensure all commits have objectId set
*/
@ -42,13 +54,23 @@ async function ensureObjects(commits: BasicTestCommit[]) {
/**
* Create test commits
*/
export async function createTestCommits(commits: BasicTestCommit[]) {
export async function createTestCommits(
commits: BasicTestCommit[],
options?: Partial<{ owner: BasicTestUser; stream: BasicTestStream }>
) {
const { owner, stream } = options || {}
commits.forEach((c) => {
if (owner) c.authorId = owner.id
if (stream) c.streamId = stream.id
})
await ensureObjects(commits)
await Promise.all(
commits.map((c) =>
createCommitByBranchName({
streamId: c.streamId,
branchName: 'main',
branchName: c.branchName || 'main',
message: c.message || 'this message is auto generated',
sourceApplication: 'tests',
objectId: c.objectId,
@ -60,6 +82,9 @@ export async function createTestCommits(commits: BasicTestCommit[]) {
)
}
export async function createTestCommit(commit: BasicTestCommit) {
await createTestCommits([commit])
export async function createTestCommit(
commit: BasicTestCommit,
options?: Partial<{ owner: BasicTestUser; stream: BasicTestStream }>
) {
await createTestCommits([commit], options)
}

Просмотреть файл

@ -9,7 +9,7 @@ export type BasicTestStream = {
name: string
isPublic: boolean
/**
* The ID of the owner user
* The ID of the owner user. Will be filled in by createTestStream().
*/
ownerId: string
/**

Просмотреть файл

@ -1,4 +1,4 @@
import { onKeyDown } from '@vueuse/core'
import { useMagicKeys, whenever } from '@vueuse/core'
import { OperatingSystem } from '@speckle/shared'
import { clientOs, ModifierKeys } from '~~/src/helpers/form/input'
import { computed, ref } from 'vue'
@ -9,36 +9,27 @@ import type { Ref } from 'vue'
*/
export function onKeyboardShortcut(
modifiers: ModifierKeys[],
...args: Parameters<typeof onKeyDown>
key: string,
callback: () => void
) {
onKeyDown(
args[0],
(e) => {
const isAltOrOpt = e.getModifierState('Alt')
const isCtrlOrCmd =
clientOs === OperatingSystem.Mac
? e.getModifierState('Meta')
: e.getModifierState('Control')
const isShift = e.getModifierState('Shift')
const keys = useMagicKeys()
for (const modifier of modifiers) {
switch (modifier) {
case ModifierKeys.CtrlOrCmd:
if (!isCtrlOrCmd) return
break
case ModifierKeys.AltOrOpt:
if (!isAltOrOpt) return
break
case ModifierKeys.Shift:
if (!isShift) return
break
}
}
const modifierKeys = modifiers.map((modifier) => {
switch (modifier) {
case ModifierKeys.CtrlOrCmd:
return clientOs === OperatingSystem.Mac ? 'Meta' : 'Control'
case ModifierKeys.AltOrOpt:
return 'Alt'
case ModifierKeys.Shift:
return 'Shift'
default:
return ''
}
})
args[1](e)
},
args[2]
)
const keyCombination = `${modifierKeys.join('+')}+${key}`
whenever(keys[keyCombination], callback)
}
/**