Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-1172-change-the-users-repository-to-abstract-the-email-field-in
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable no-restricted-imports */
|
||||
/* istanbul ignore file */
|
||||
import './bootstrap'
|
||||
import http from 'http'
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
type Workspace {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
"""
|
||||
Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you.
|
||||
"""
|
||||
role: String
|
||||
team: [WorkspaceCollaborator!]!
|
||||
invitedTeam: [PendingWorkspaceCollaborator!]
|
||||
projects(
|
||||
limit: Int! = 25
|
||||
cursor: String
|
||||
filter: UserProjectsFilter
|
||||
): ProjectCollection!
|
||||
}
|
||||
|
||||
type WorkspaceCollaborator {
|
||||
id: ID!
|
||||
role: String!
|
||||
user: LimitedUser!
|
||||
}
|
||||
|
||||
type PendingWorkspaceCollaborator {
|
||||
id: ID!
|
||||
inviteId: String!
|
||||
workspaceId: String!
|
||||
workspaceName: String!
|
||||
"""
|
||||
E-mail address or name of the invited user
|
||||
"""
|
||||
title: String!
|
||||
role: String!
|
||||
invitedBy: LimitedUser!
|
||||
"""
|
||||
Set only if user is registered
|
||||
"""
|
||||
user: LimitedUser
|
||||
"""
|
||||
Only available if the active user is the pending workspace collaborator
|
||||
"""
|
||||
token: String
|
||||
}
|
||||
|
||||
type WorkspaceCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [Workspace!]!
|
||||
}
|
||||
|
||||
extend type User {
|
||||
"""
|
||||
Get the workspaces for the user
|
||||
"""
|
||||
workspaces(
|
||||
limit: Int! = 25
|
||||
cursor: String = null
|
||||
filter: UserWorkspacesFilter
|
||||
): WorkspaceCollection! @isOwner
|
||||
}
|
||||
|
||||
extend type Project {
|
||||
workspace: Workspace
|
||||
}
|
||||
|
||||
type ServerWorkspacesInfo {
|
||||
"""
|
||||
This is a backend control variable for the workspaces feature set.
|
||||
Since workspaces need a backend logic to be enabled, this is not enough as a feature flag.
|
||||
"""
|
||||
workspacesEnabled: Boolean!
|
||||
}
|
||||
|
||||
extend type ServerInfo {
|
||||
workspaces: ServerWorkspacesInfo!
|
||||
}
|
||||
|
||||
extend type AdminQueries {
|
||||
workspaceList(
|
||||
query: String
|
||||
limit: Int! = 25
|
||||
cursor: String = null
|
||||
): WorkspaceCollection!
|
||||
}
|
||||
|
||||
input UserWorkspacesFilter {
|
||||
search: String
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
extend type Query {
|
||||
workspace(id: String!): Workspace! @hasScope(scope: "workspace:read")
|
||||
}
|
||||
|
||||
input WorkspaceCreateInput {
|
||||
name: String!
|
||||
description: String
|
||||
logoUrl: String
|
||||
}
|
||||
|
||||
input WorkspaceUpdateInput {
|
||||
id: String!
|
||||
name: String
|
||||
description: String
|
||||
logoUrl: String
|
||||
}
|
||||
|
||||
input WorkspaceRoleUpdateInput {
|
||||
userId: String!
|
||||
workspaceId: String!
|
||||
role: WorkspaceRole!
|
||||
}
|
||||
|
||||
input WorkspaceRoleDeleteInput {
|
||||
userId: String!
|
||||
workspaceId: String!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
workspaceMutations: WorkspaceMutations! @hasServerRole(role: SERVER_USER)
|
||||
}
|
||||
|
||||
type WorkspaceMutations {
|
||||
create(input: WorkspaceCreateInput!): Workspace! @hasScope(scope: "workspace:create")
|
||||
delete(workspaceId: String!): Workspace! @hasScope(scope: "workspace:delete")
|
||||
update(input: WorkspaceUpdateInput!): Workspace! @hasScope(scope: "workspace:update")
|
||||
"""
|
||||
TODO: `@hasWorkspaceRole(role: WORKSPACE_ADMIN)` for role changes
|
||||
"""
|
||||
updateRole(input: WorkspaceRoleUpdateInput!): Boolean!
|
||||
@hasScope(scope: "workspace:update")
|
||||
deleteRole(input: WorkspaceRoleDeleteInput!): Boolean!
|
||||
@hasScope(scope: "workspace:update")
|
||||
invites: WorkspaceInviteMutations!
|
||||
}
|
||||
|
||||
input WorkspaceInviteCreateInput {
|
||||
"""
|
||||
Either this or userId must be filled
|
||||
"""
|
||||
email: String
|
||||
"""
|
||||
Either this or email must be filled
|
||||
"""
|
||||
userId: String
|
||||
"""
|
||||
Defaults to the member role, if not specified
|
||||
"""
|
||||
role: WorkspaceRole
|
||||
}
|
||||
|
||||
input WorkspaceInviteUseInput {
|
||||
workspaceId: String!
|
||||
token: String!
|
||||
accept: Boolean!
|
||||
}
|
||||
|
||||
type WorkspaceInviteMutations {
|
||||
create(workspaceId: String!, input: WorkspaceInviteCreateInput!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
batchCreate(workspaceId: String!, input: [WorkspaceInviteCreateInput!]!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
use(input: WorkspaceInviteUseInput!): Boolean!
|
||||
cancel(workspaceId: String!, inviteId: String!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
}
|
||||
|
||||
type Workspace {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
"""
|
||||
Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you.
|
||||
"""
|
||||
role: String
|
||||
team: [WorkspaceCollaborator!]!
|
||||
invitedTeam: [PendingWorkspaceCollaborator!]
|
||||
projects(
|
||||
limit: Int! = 25
|
||||
cursor: String
|
||||
filter: UserProjectsFilter
|
||||
): ProjectCollection!
|
||||
}
|
||||
|
||||
type WorkspaceCollaborator {
|
||||
id: ID!
|
||||
role: String!
|
||||
user: LimitedUser!
|
||||
}
|
||||
|
||||
type PendingWorkspaceCollaborator {
|
||||
id: ID!
|
||||
inviteId: String!
|
||||
workspaceId: String!
|
||||
workspaceName: String!
|
||||
"""
|
||||
E-mail address or name of the invited user
|
||||
"""
|
||||
title: String!
|
||||
role: String!
|
||||
invitedBy: LimitedUser!
|
||||
"""
|
||||
Set only if user is registered
|
||||
"""
|
||||
user: LimitedUser
|
||||
"""
|
||||
Only available if the active user is the pending workspace collaborator
|
||||
"""
|
||||
token: String
|
||||
}
|
||||
|
||||
type WorkspaceCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [Workspace!]!
|
||||
}
|
||||
|
||||
extend type User {
|
||||
"""
|
||||
Get the workspaces for the user
|
||||
"""
|
||||
workspaces(
|
||||
limit: Int! = 25
|
||||
cursor: String = null
|
||||
filter: UserWorkspacesFilter
|
||||
): WorkspaceCollection! @isOwner
|
||||
}
|
||||
|
||||
extend type Project {
|
||||
workspace: Workspace
|
||||
}
|
||||
|
||||
type ServerWorkspacesInfo {
|
||||
"""
|
||||
This is a backend control variable for the workspaces feature set.
|
||||
Since workspaces need a backend logic to be enabled, this is not enough as a feature flag.
|
||||
"""
|
||||
workspacesEnabled: Boolean!
|
||||
}
|
||||
|
||||
extend type ServerInfo {
|
||||
workspaces: ServerWorkspacesInfo!
|
||||
}
|
||||
|
||||
extend type AdminQueries {
|
||||
workspaceList(
|
||||
query: String
|
||||
limit: Int! = 25
|
||||
cursor: String = null
|
||||
): WorkspaceCollection!
|
||||
}
|
||||
|
||||
input UserWorkspacesFilter {
|
||||
search: String
|
||||
}
|
||||
|
||||
enum WorkspaceRole {
|
||||
ADMIN
|
||||
MEMBER
|
||||
GUEST
|
||||
}
|
||||
@@ -38,6 +38,12 @@ const configs = [
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: ['.*']
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-base-to-string': 'off',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logger } from './logging'
|
||||
import { logger } from '@/logging/logging'
|
||||
import { randomUUID } from 'crypto'
|
||||
import HttpLogger from 'pino-http'
|
||||
import { IncomingMessage } from 'http'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import { getNameFromUserInfo } from '../../domain/logic'
|
||||
import { getNameFromUserInfo } from '@/modules/auth/domain/logic'
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
describe('getNameFromUserInfo', () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { getCommit } from '@/modules/core/repositories/commits'
|
||||
import { getUserById } from '@/modules/core/services/users'
|
||||
import { mixpanel } from '@/modules/shared/utils/mixpanel'
|
||||
import { throwUncoveredError } from '@speckle/shared'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const isFinished = (runStatus: AutomationRunStatus) => {
|
||||
const finishedStatuses: AutomationRunStatus[] = [
|
||||
@@ -80,10 +79,8 @@ const onAutomationRunStatusUpdated =
|
||||
runId: run.id,
|
||||
functionRunId: functionRun.id,
|
||||
status: functionRun.status,
|
||||
durationInSeconds: dayjs(functionRun.updatedAt).diff(
|
||||
functionRun.createdAt,
|
||||
'second'
|
||||
)
|
||||
durationInSeconds: functionRun.elapsed / 1000,
|
||||
durationInMilliseconds: functionRun.elapsed
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ export type AdminQueries = {
|
||||
projectList: ProjectCollection;
|
||||
serverStatistics: ServerStatistics;
|
||||
userList: AdminUserList;
|
||||
workspaceList: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +103,13 @@ export type AdminQueriesUserListArgs = {
|
||||
role?: InputMaybe<ServerRole>;
|
||||
};
|
||||
|
||||
|
||||
export type AdminQueriesWorkspaceListArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUserList = {
|
||||
__typename?: 'AdminUserList';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1318,6 +1326,7 @@ export type Mutation = {
|
||||
webhookDelete: Scalars['String']['output'];
|
||||
/** Updates an existing webhook */
|
||||
webhookUpdate: Scalars['String']['output'];
|
||||
workspaceMutations: WorkspaceMutations;
|
||||
};
|
||||
|
||||
|
||||
@@ -1699,6 +1708,22 @@ export type PendingStreamCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaborator = {
|
||||
__typename?: 'PendingWorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
inviteId: Scalars['String']['output'];
|
||||
invitedBy: LimitedUser;
|
||||
role: Scalars['String']['output'];
|
||||
/** E-mail address or name of the invited user */
|
||||
title: Scalars['String']['output'];
|
||||
/** Only available if the active user is the pending workspace collaborator */
|
||||
token?: Maybe<Scalars['String']['output']>;
|
||||
/** Set only if user is registered */
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
__typename?: 'Project';
|
||||
allowPublicComments: Scalars['Boolean']['output'];
|
||||
@@ -1750,6 +1775,7 @@ export type Project = {
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2396,6 +2422,7 @@ export type Query = {
|
||||
* The query looks for matches in name & email
|
||||
*/
|
||||
userSearch: UserSearchResultCollection;
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
|
||||
@@ -2521,6 +2548,11 @@ export type QueryUserSearchArgs = {
|
||||
query: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryWorkspaceArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
/** Deprecated: Used by old stream-based mutations */
|
||||
export type ReplyCreateInput = {
|
||||
/** IDs of uploaded blobs that should be attached to this reply */
|
||||
@@ -2623,6 +2655,7 @@ export type ServerInfo = {
|
||||
serverRoles: Array<ServerRoleItem>;
|
||||
termsOfService?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
workspaces: ServerWorkspacesInfo;
|
||||
};
|
||||
|
||||
export type ServerInfoUpdateInput = {
|
||||
@@ -2691,6 +2724,15 @@ export type ServerStats = {
|
||||
userHistory?: Maybe<Array<Maybe<Scalars['JSONObject']['output']>>>;
|
||||
};
|
||||
|
||||
export type ServerWorkspacesInfo = {
|
||||
__typename?: 'ServerWorkspacesInfo';
|
||||
/**
|
||||
* This is a backend control variable for the workspaces feature set.
|
||||
* Since workspaces need a backend logic to be enabled, this is not enough as a feature flag.
|
||||
*/
|
||||
workspacesEnabled: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -3311,6 +3353,8 @@ export type User = {
|
||||
* Note: Only count resolution is currently implemented
|
||||
*/
|
||||
versions: CountOnlyCollection;
|
||||
/** Get the workspaces for the user */
|
||||
workspaces: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -3398,6 +3442,17 @@ export type UserVersionsArgs = {
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 UserWorkspacesArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserWorkspacesFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type UserAutomateInfo = {
|
||||
__typename?: 'UserAutomateInfo';
|
||||
availableGithubOrgs: Array<Scalars['String']['output']>;
|
||||
@@ -3448,6 +3503,10 @@ export type UserUpdateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UserWorkspacesFilter = {
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Version = {
|
||||
__typename?: 'Version';
|
||||
authorUser?: Maybe<LimitedUser>;
|
||||
@@ -3660,6 +3719,152 @@ export type WebhookUpdateInput = {
|
||||
url?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
description?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
|
||||
name: Scalars['String']['output'];
|
||||
projects: ProjectCollection;
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
team: Array<WorkspaceCollaborator>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserProjectsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceCollaborator = {
|
||||
__typename?: 'WorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
export type WorkspaceCollection = {
|
||||
__typename?: 'WorkspaceCollection';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
items: Array<Workspace>;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceInviteCreateInput = {
|
||||
/** Either this or userId must be filled */
|
||||
email?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Defaults to the member role, if not specified */
|
||||
role?: InputMaybe<WorkspaceRole>;
|
||||
/** Either this or email must be filled */
|
||||
userId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteMutations = {
|
||||
__typename?: 'WorkspaceInviteMutations';
|
||||
batchCreate: Workspace;
|
||||
cancel: Workspace;
|
||||
create: Workspace;
|
||||
use: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsBatchCreateArgs = {
|
||||
input: Array<WorkspaceInviteCreateInput>;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCancelArgs = {
|
||||
inviteId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCreateArgs = {
|
||||
input: WorkspaceInviteCreateInput;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsUseArgs = {
|
||||
input: WorkspaceInviteUseInput;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteUseInput = {
|
||||
accept: Scalars['Boolean']['input'];
|
||||
token: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceMutations = {
|
||||
__typename?: 'WorkspaceMutations';
|
||||
create: Workspace;
|
||||
delete: Workspace;
|
||||
deleteRole: Scalars['Boolean']['output'];
|
||||
invites: WorkspaceInviteMutations;
|
||||
update: Workspace;
|
||||
updateRole: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsCreateArgs = {
|
||||
input: WorkspaceCreateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteRoleArgs = {
|
||||
input: WorkspaceRoleDeleteInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateArgs = {
|
||||
input: WorkspaceUpdateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateRoleArgs = {
|
||||
input: WorkspaceRoleUpdateInput;
|
||||
};
|
||||
|
||||
export enum WorkspaceRole {
|
||||
Admin = 'ADMIN',
|
||||
Guest = 'GUEST',
|
||||
Member = 'MEMBER'
|
||||
}
|
||||
|
||||
export type WorkspaceRoleDeleteInput = {
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceRoleUpdateInput = {
|
||||
role: WorkspaceRole;
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceUpdateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
id: Scalars['String']['input'];
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export type ResolverTypeWrapper<T> = Promise<T> | T;
|
||||
@@ -3837,6 +4042,7 @@ export type ResolversTypes = {
|
||||
PasswordStrengthCheckFeedback: ResolverTypeWrapper<PasswordStrengthCheckFeedback>;
|
||||
PasswordStrengthCheckResults: ResolverTypeWrapper<PasswordStrengthCheckResults>;
|
||||
PendingStreamCollaborator: ResolverTypeWrapper<PendingStreamCollaboratorGraphQLReturn>;
|
||||
PendingWorkspaceCollaborator: ResolverTypeWrapper<Omit<PendingWorkspaceCollaborator, 'invitedBy' | 'user'> & { invitedBy: ResolversTypes['LimitedUser'], user?: Maybe<ResolversTypes['LimitedUser']> }>;
|
||||
Project: ResolverTypeWrapper<ProjectGraphQLReturn>;
|
||||
ProjectAccessRequest: ResolverTypeWrapper<ProjectAccessRequestGraphQLReturn>;
|
||||
ProjectAccessRequestMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
|
||||
@@ -3897,6 +4103,7 @@ export type ResolversTypes = {
|
||||
ServerRoleItem: ResolverTypeWrapper<ServerRoleItem>;
|
||||
ServerStatistics: ResolverTypeWrapper<GraphQLEmptyReturn>;
|
||||
ServerStats: ResolverTypeWrapper<ServerStats>;
|
||||
ServerWorkspacesInfo: ResolverTypeWrapper<ServerWorkspacesInfo>;
|
||||
SmartTextEditorValue: ResolverTypeWrapper<SmartTextEditorValue>;
|
||||
SortDirection: SortDirection;
|
||||
Stream: ResolverTypeWrapper<StreamGraphQLReturn>;
|
||||
@@ -3921,7 +4128,7 @@ export type ResolversTypes = {
|
||||
UpdateAutomateFunctionInput: UpdateAutomateFunctionInput;
|
||||
UpdateModelInput: UpdateModelInput;
|
||||
UpdateVersionInput: UpdateVersionInput;
|
||||
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'] }>;
|
||||
User: ResolverTypeWrapper<Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectAccessRequest' | 'projectInvites' | 'projects' | 'streams' | 'workspaces'> & { automateInfo: ResolversTypes['UserAutomateInfo'], commits?: Maybe<ResolversTypes['CommitCollection']>, favoriteStreams: ResolversTypes['StreamCollection'], projectAccessRequest?: Maybe<ResolversTypes['ProjectAccessRequest']>, projectInvites: Array<ResolversTypes['PendingStreamCollaborator']>, projects: ResolversTypes['ProjectCollection'], streams: ResolversTypes['StreamCollection'], workspaces: ResolversTypes['WorkspaceCollection'] }>;
|
||||
UserAutomateInfo: ResolverTypeWrapper<UserAutomateInfoGraphQLReturn>;
|
||||
UserDeleteInput: UserDeleteInput;
|
||||
UserProjectsFilter: UserProjectsFilter;
|
||||
@@ -3930,6 +4137,7 @@ export type ResolversTypes = {
|
||||
UserRoleInput: UserRoleInput;
|
||||
UserSearchResultCollection: ResolverTypeWrapper<Omit<UserSearchResultCollection, 'items'> & { items: Array<ResolversTypes['LimitedUser']> }>;
|
||||
UserUpdateInput: UserUpdateInput;
|
||||
UserWorkspacesFilter: UserWorkspacesFilter;
|
||||
Version: ResolverTypeWrapper<VersionGraphQLReturn>;
|
||||
VersionCollection: ResolverTypeWrapper<Omit<VersionCollection, 'items'> & { items: Array<ResolversTypes['Version']> }>;
|
||||
VersionCreatedTrigger: ResolverTypeWrapper<AutomationRunTriggerGraphQLReturn>;
|
||||
@@ -3948,6 +4156,18 @@ export type ResolversTypes = {
|
||||
WebhookEvent: ResolverTypeWrapper<WebhookEvent>;
|
||||
WebhookEventCollection: ResolverTypeWrapper<WebhookEventCollection>;
|
||||
WebhookUpdateInput: WebhookUpdateInput;
|
||||
Workspace: ResolverTypeWrapper<Omit<Workspace, 'invitedTeam' | 'projects' | 'team'> & { invitedTeam?: Maybe<Array<ResolversTypes['PendingWorkspaceCollaborator']>>, projects: ResolversTypes['ProjectCollection'], team: Array<ResolversTypes['WorkspaceCollaborator']> }>;
|
||||
WorkspaceCollaborator: ResolverTypeWrapper<Omit<WorkspaceCollaborator, 'user'> & { user: ResolversTypes['LimitedUser'] }>;
|
||||
WorkspaceCollection: ResolverTypeWrapper<Omit<WorkspaceCollection, 'items'> & { items: Array<ResolversTypes['Workspace']> }>;
|
||||
WorkspaceCreateInput: WorkspaceCreateInput;
|
||||
WorkspaceInviteCreateInput: WorkspaceInviteCreateInput;
|
||||
WorkspaceInviteMutations: ResolverTypeWrapper<Omit<WorkspaceInviteMutations, 'batchCreate' | 'cancel' | 'create'> & { batchCreate: ResolversTypes['Workspace'], cancel: ResolversTypes['Workspace'], create: ResolversTypes['Workspace'] }>;
|
||||
WorkspaceInviteUseInput: WorkspaceInviteUseInput;
|
||||
WorkspaceMutations: ResolverTypeWrapper<Omit<WorkspaceMutations, 'create' | 'delete' | 'invites' | 'update'> & { create: ResolversTypes['Workspace'], delete: ResolversTypes['Workspace'], invites: ResolversTypes['WorkspaceInviteMutations'], update: ResolversTypes['Workspace'] }>;
|
||||
WorkspaceRole: WorkspaceRole;
|
||||
WorkspaceRoleDeleteInput: WorkspaceRoleDeleteInput;
|
||||
WorkspaceRoleUpdateInput: WorkspaceRoleUpdateInput;
|
||||
WorkspaceUpdateInput: WorkspaceUpdateInput;
|
||||
};
|
||||
|
||||
/** Mapping between all available schema types and the resolvers parents */
|
||||
@@ -4054,6 +4274,7 @@ export type ResolversParentTypes = {
|
||||
PasswordStrengthCheckFeedback: PasswordStrengthCheckFeedback;
|
||||
PasswordStrengthCheckResults: PasswordStrengthCheckResults;
|
||||
PendingStreamCollaborator: PendingStreamCollaboratorGraphQLReturn;
|
||||
PendingWorkspaceCollaborator: Omit<PendingWorkspaceCollaborator, 'invitedBy' | 'user'> & { invitedBy: ResolversParentTypes['LimitedUser'], user?: Maybe<ResolversParentTypes['LimitedUser']> };
|
||||
Project: ProjectGraphQLReturn;
|
||||
ProjectAccessRequest: ProjectAccessRequestGraphQLReturn;
|
||||
ProjectAccessRequestMutations: MutationsObjectGraphQLReturn;
|
||||
@@ -4102,6 +4323,7 @@ export type ResolversParentTypes = {
|
||||
ServerRoleItem: ServerRoleItem;
|
||||
ServerStatistics: GraphQLEmptyReturn;
|
||||
ServerStats: ServerStats;
|
||||
ServerWorkspacesInfo: ServerWorkspacesInfo;
|
||||
SmartTextEditorValue: SmartTextEditorValue;
|
||||
Stream: StreamGraphQLReturn;
|
||||
StreamAccessRequest: StreamAccessRequestGraphQLReturn;
|
||||
@@ -4123,7 +4345,7 @@ export type ResolversParentTypes = {
|
||||
UpdateAutomateFunctionInput: UpdateAutomateFunctionInput;
|
||||
UpdateModelInput: UpdateModelInput;
|
||||
UpdateVersionInput: UpdateVersionInput;
|
||||
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'] };
|
||||
User: Omit<User, 'automateInfo' | 'commits' | 'favoriteStreams' | 'projectAccessRequest' | 'projectInvites' | 'projects' | 'streams' | 'workspaces'> & { automateInfo: ResolversParentTypes['UserAutomateInfo'], commits?: Maybe<ResolversParentTypes['CommitCollection']>, favoriteStreams: ResolversParentTypes['StreamCollection'], projectAccessRequest?: Maybe<ResolversParentTypes['ProjectAccessRequest']>, projectInvites: Array<ResolversParentTypes['PendingStreamCollaborator']>, projects: ResolversParentTypes['ProjectCollection'], streams: ResolversParentTypes['StreamCollection'], workspaces: ResolversParentTypes['WorkspaceCollection'] };
|
||||
UserAutomateInfo: UserAutomateInfoGraphQLReturn;
|
||||
UserDeleteInput: UserDeleteInput;
|
||||
UserProjectsFilter: UserProjectsFilter;
|
||||
@@ -4131,6 +4353,7 @@ export type ResolversParentTypes = {
|
||||
UserRoleInput: UserRoleInput;
|
||||
UserSearchResultCollection: Omit<UserSearchResultCollection, 'items'> & { items: Array<ResolversParentTypes['LimitedUser']> };
|
||||
UserUpdateInput: UserUpdateInput;
|
||||
UserWorkspacesFilter: UserWorkspacesFilter;
|
||||
Version: VersionGraphQLReturn;
|
||||
VersionCollection: Omit<VersionCollection, 'items'> & { items: Array<ResolversParentTypes['Version']> };
|
||||
VersionCreatedTrigger: AutomationRunTriggerGraphQLReturn;
|
||||
@@ -4148,6 +4371,17 @@ export type ResolversParentTypes = {
|
||||
WebhookEvent: WebhookEvent;
|
||||
WebhookEventCollection: WebhookEventCollection;
|
||||
WebhookUpdateInput: WebhookUpdateInput;
|
||||
Workspace: Omit<Workspace, 'invitedTeam' | 'projects' | 'team'> & { invitedTeam?: Maybe<Array<ResolversParentTypes['PendingWorkspaceCollaborator']>>, projects: ResolversParentTypes['ProjectCollection'], team: Array<ResolversParentTypes['WorkspaceCollaborator']> };
|
||||
WorkspaceCollaborator: Omit<WorkspaceCollaborator, 'user'> & { user: ResolversParentTypes['LimitedUser'] };
|
||||
WorkspaceCollection: Omit<WorkspaceCollection, 'items'> & { items: Array<ResolversParentTypes['Workspace']> };
|
||||
WorkspaceCreateInput: WorkspaceCreateInput;
|
||||
WorkspaceInviteCreateInput: WorkspaceInviteCreateInput;
|
||||
WorkspaceInviteMutations: Omit<WorkspaceInviteMutations, 'batchCreate' | 'cancel' | 'create'> & { batchCreate: ResolversParentTypes['Workspace'], cancel: ResolversParentTypes['Workspace'], create: ResolversParentTypes['Workspace'] };
|
||||
WorkspaceInviteUseInput: WorkspaceInviteUseInput;
|
||||
WorkspaceMutations: Omit<WorkspaceMutations, 'create' | 'delete' | 'invites' | 'update'> & { create: ResolversParentTypes['Workspace'], delete: ResolversParentTypes['Workspace'], invites: ResolversParentTypes['WorkspaceInviteMutations'], update: ResolversParentTypes['Workspace'] };
|
||||
WorkspaceRoleDeleteInput: WorkspaceRoleDeleteInput;
|
||||
WorkspaceRoleUpdateInput: WorkspaceRoleUpdateInput;
|
||||
WorkspaceUpdateInput: WorkspaceUpdateInput;
|
||||
};
|
||||
|
||||
export type HasScopeDirectiveArgs = {
|
||||
@@ -4216,6 +4450,7 @@ export type AdminQueriesResolvers<ContextType = GraphQLContext, ParentType exten
|
||||
projectList?: Resolver<ResolversTypes['ProjectCollection'], ParentType, ContextType, RequireFields<AdminQueriesProjectListArgs, 'cursor' | 'limit'>>;
|
||||
serverStatistics?: Resolver<ResolversTypes['ServerStatistics'], ParentType, ContextType>;
|
||||
userList?: Resolver<ResolversTypes['AdminUserList'], ParentType, ContextType, RequireFields<AdminQueriesUserListArgs, 'cursor' | 'limit' | 'query' | 'role'>>;
|
||||
workspaceList?: Resolver<ResolversTypes['WorkspaceCollection'], ParentType, ContextType, RequireFields<AdminQueriesWorkspaceListArgs, 'cursor' | 'limit'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
@@ -4756,6 +4991,7 @@ export type MutationResolvers<ContextType = GraphQLContext, ParentType extends R
|
||||
webhookCreate?: Resolver<ResolversTypes['String'], ParentType, ContextType, RequireFields<MutationWebhookCreateArgs, 'webhook'>>;
|
||||
webhookDelete?: Resolver<ResolversTypes['String'], ParentType, ContextType, RequireFields<MutationWebhookDeleteArgs, 'webhook'>>;
|
||||
webhookUpdate?: Resolver<ResolversTypes['String'], ParentType, ContextType, RequireFields<MutationWebhookUpdateArgs, 'webhook'>>;
|
||||
workspaceMutations?: Resolver<ResolversTypes['WorkspaceMutations'], ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ObjectResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Object'] = ResolversParentTypes['Object']> = {
|
||||
@@ -4804,6 +5040,19 @@ export type PendingStreamCollaboratorResolvers<ContextType = GraphQLContext, Par
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaboratorResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['PendingWorkspaceCollaborator'] = ResolversParentTypes['PendingWorkspaceCollaborator']> = {
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
inviteId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
invitedBy?: Resolver<ResolversTypes['LimitedUser'], ParentType, ContextType>;
|
||||
role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
token?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
user?: Resolver<Maybe<ResolversTypes['LimitedUser']>, ParentType, ContextType>;
|
||||
workspaceId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
workspaceName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Project'] = ResolversParentTypes['Project']> = {
|
||||
allowPublicComments?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
automation?: Resolver<ResolversTypes['Automation'], ParentType, ContextType, RequireFields<ProjectAutomationArgs, 'id'>>;
|
||||
@@ -4834,6 +5083,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
viewerResources?: Resolver<Array<ResolversTypes['ViewerResourceGroup']>, ParentType, ContextType, RequireFields<ProjectViewerResourcesArgs, 'loadedVersionsOnly' | 'resourceIdString'>>;
|
||||
visibility?: Resolver<ResolversTypes['ProjectVisibility'], ParentType, ContextType>;
|
||||
webhooks?: Resolver<ResolversTypes['WebhookCollection'], ParentType, ContextType, Partial<ProjectWebhooksArgs>>;
|
||||
workspace?: Resolver<Maybe<ResolversTypes['Workspace']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
@@ -5010,6 +5260,7 @@ export type QueryResolvers<ContextType = GraphQLContext, ParentType extends Reso
|
||||
user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, Partial<QueryUserArgs>>;
|
||||
userPwdStrength?: Resolver<ResolversTypes['PasswordStrengthCheckResults'], ParentType, ContextType, RequireFields<QueryUserPwdStrengthArgs, 'pwd'>>;
|
||||
userSearch?: Resolver<ResolversTypes['UserSearchResultCollection'], ParentType, ContextType, RequireFields<QueryUserSearchArgs, 'archived' | 'emailOnly' | 'limit' | 'query'>>;
|
||||
workspace?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<QueryWorkspaceArgs, 'id'>>;
|
||||
};
|
||||
|
||||
export type ResourceIdentifierResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ResourceIdentifier'] = ResolversParentTypes['ResourceIdentifier']> = {
|
||||
@@ -5083,6 +5334,7 @@ export type ServerInfoResolvers<ContextType = GraphQLContext, ParentType extends
|
||||
serverRoles?: Resolver<Array<ResolversTypes['ServerRoleItem']>, ParentType, ContextType>;
|
||||
termsOfService?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
version?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
workspaces?: Resolver<ResolversTypes['ServerWorkspacesInfo'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
@@ -5124,6 +5376,11 @@ export type ServerStatsResolvers<ContextType = GraphQLContext, ParentType extend
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ServerWorkspacesInfoResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ServerWorkspacesInfo'] = ResolversParentTypes['ServerWorkspacesInfo']> = {
|
||||
workspacesEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SmartTextEditorValueResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SmartTextEditorValue'] = ResolversParentTypes['SmartTextEditorValue']> = {
|
||||
attachments?: Resolver<Maybe<Array<ResolversTypes['BlobMetadata']>>, ParentType, ContextType>;
|
||||
doc?: Resolver<Maybe<ResolversTypes['JSONObject']>, ParentType, ContextType>;
|
||||
@@ -5282,6 +5539,7 @@ export type UserResolvers<ContextType = GraphQLContext, ParentType extends Resol
|
||||
totalOwnedStreamsFavorites?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
verified?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
versions?: Resolver<ResolversTypes['CountOnlyCollection'], ParentType, ContextType, RequireFields<UserVersionsArgs, 'authoredOnly' | 'limit'>>;
|
||||
workspaces?: Resolver<ResolversTypes['WorkspaceCollection'], ParentType, ContextType, RequireFields<UserWorkspacesArgs, 'cursor' | 'limit'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
@@ -5411,6 +5669,51 @@ export type WebhookEventCollectionResolvers<ContextType = GraphQLContext, Parent
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Workspace'] = ResolversParentTypes['Workspace']> = {
|
||||
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
invitedTeam?: Resolver<Maybe<Array<ResolversTypes['PendingWorkspaceCollaborator']>>, ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
projects?: Resolver<ResolversTypes['ProjectCollection'], ParentType, ContextType, RequireFields<WorkspaceProjectsArgs, 'limit'>>;
|
||||
role?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
team?: Resolver<Array<ResolversTypes['WorkspaceCollaborator']>, ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type WorkspaceCollaboratorResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceCollaborator'] = ResolversParentTypes['WorkspaceCollaborator']> = {
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
user?: Resolver<ResolversTypes['LimitedUser'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type WorkspaceCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceCollection'] = ResolversParentTypes['WorkspaceCollection']> = {
|
||||
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
items?: Resolver<Array<ResolversTypes['Workspace']>, ParentType, ContextType>;
|
||||
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceInviteMutations'] = ResolversParentTypes['WorkspaceInviteMutations']> = {
|
||||
batchCreate?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceInviteMutationsBatchCreateArgs, 'input' | 'workspaceId'>>;
|
||||
cancel?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceInviteMutationsCancelArgs, 'inviteId' | 'workspaceId'>>;
|
||||
create?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceInviteMutationsCreateArgs, 'input' | 'workspaceId'>>;
|
||||
use?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<WorkspaceInviteMutationsUseArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type WorkspaceMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceMutations'] = ResolversParentTypes['WorkspaceMutations']> = {
|
||||
create?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceMutationsCreateArgs, 'input'>>;
|
||||
delete?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceMutationsDeleteArgs, 'workspaceId'>>;
|
||||
deleteRole?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<WorkspaceMutationsDeleteRoleArgs, 'input'>>;
|
||||
invites?: Resolver<ResolversTypes['WorkspaceInviteMutations'], ParentType, ContextType>;
|
||||
update?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<WorkspaceMutationsUpdateArgs, 'input'>>;
|
||||
updateRole?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<WorkspaceMutationsUpdateRoleArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type Resolvers<ContextType = GraphQLContext> = {
|
||||
ActiveUserMutations?: ActiveUserMutationsResolvers<ContextType>;
|
||||
Activity?: ActivityResolvers<ContextType>;
|
||||
@@ -5474,6 +5777,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
PasswordStrengthCheckFeedback?: PasswordStrengthCheckFeedbackResolvers<ContextType>;
|
||||
PasswordStrengthCheckResults?: PasswordStrengthCheckResultsResolvers<ContextType>;
|
||||
PendingStreamCollaborator?: PendingStreamCollaboratorResolvers<ContextType>;
|
||||
PendingWorkspaceCollaborator?: PendingWorkspaceCollaboratorResolvers<ContextType>;
|
||||
Project?: ProjectResolvers<ContextType>;
|
||||
ProjectAccessRequest?: ProjectAccessRequestResolvers<ContextType>;
|
||||
ProjectAccessRequestMutations?: ProjectAccessRequestMutationsResolvers<ContextType>;
|
||||
@@ -5506,6 +5810,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
ServerRoleItem?: ServerRoleItemResolvers<ContextType>;
|
||||
ServerStatistics?: ServerStatisticsResolvers<ContextType>;
|
||||
ServerStats?: ServerStatsResolvers<ContextType>;
|
||||
ServerWorkspacesInfo?: ServerWorkspacesInfoResolvers<ContextType>;
|
||||
SmartTextEditorValue?: SmartTextEditorValueResolvers<ContextType>;
|
||||
Stream?: StreamResolvers<ContextType>;
|
||||
StreamAccessRequest?: StreamAccessRequestResolvers<ContextType>;
|
||||
@@ -5533,6 +5838,11 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
WebhookCollection?: WebhookCollectionResolvers<ContextType>;
|
||||
WebhookEvent?: WebhookEventResolvers<ContextType>;
|
||||
WebhookEventCollection?: WebhookEventCollectionResolvers<ContextType>;
|
||||
Workspace?: WorkspaceResolvers<ContextType>;
|
||||
WorkspaceCollaborator?: WorkspaceCollaboratorResolvers<ContextType>;
|
||||
WorkspaceCollection?: WorkspaceCollectionResolvers<ContextType>;
|
||||
WorkspaceInviteMutations?: WorkspaceInviteMutationsResolvers<ContextType>;
|
||||
WorkspaceMutations?: WorkspaceMutationsResolvers<ContextType>;
|
||||
};
|
||||
|
||||
export type DirectiveResolvers<ContextType = GraphQLContext> = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
createObjects,
|
||||
getObjectChildren,
|
||||
getObjectChildrenQuery
|
||||
} from '../../services/objects'
|
||||
} from '@/modules/core/services/objects'
|
||||
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
|
||||
@@ -49,6 +49,7 @@ export type StreamRecord = {
|
||||
updatedAt: Date
|
||||
allowPublicComments: boolean
|
||||
isDiscoverable: boolean
|
||||
workspaceId: Nullable<string>
|
||||
}
|
||||
|
||||
export type StreamAclRecord = {
|
||||
|
||||
@@ -106,7 +106,8 @@ export const adminProjectList = async (
|
||||
...args,
|
||||
searchQuery: args.query,
|
||||
cursor: parsedCursor,
|
||||
streamIdWhitelist: args.streamIdWhitelist
|
||||
streamIdWhitelist: args.streamIdWhitelist,
|
||||
workspaceIdWhitelist: null
|
||||
})
|
||||
const cursor = cursorDate ? convertDateToCursor(cursorDate) : null
|
||||
return {
|
||||
|
||||
@@ -83,7 +83,8 @@ module.exports = {
|
||||
orderBy,
|
||||
visibility,
|
||||
searchQuery,
|
||||
streamIdWhitelist
|
||||
streamIdWhitelist,
|
||||
workspaceIdWhitelist
|
||||
}) {
|
||||
const query = knex.select().from('streams')
|
||||
|
||||
@@ -116,6 +117,11 @@ module.exports = {
|
||||
countQuery.whereIn('id', streamIdWhitelist)
|
||||
}
|
||||
|
||||
if (workspaceIdWhitelist?.length) {
|
||||
query.whereIn('workspaceId', workspaceIdWhitelist)
|
||||
countQuery.whereIn('workspaceId', workspaceIdWhitelist)
|
||||
}
|
||||
|
||||
const [res] = await countQuery.count()
|
||||
const count = parseInt(res.count)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { logger } from '@/logging/logging'
|
||||
import { createStreamReturnRecord } from '@/modules/core/services/streams/management'
|
||||
import { getOnboardingBaseProject } from '@/modules/cross-server-sync/services/onboardingProject'
|
||||
import { updateStream } from '../../repositories/streams'
|
||||
import { getUser } from '../users'
|
||||
import { updateStream } from '@/modules/core/repositories/streams'
|
||||
import { getUser } from '@/modules/core/services/users'
|
||||
import {
|
||||
ContextResourceAccessRules,
|
||||
isNewResourceAllowed
|
||||
|
||||
@@ -6,15 +6,15 @@ import {
|
||||
deleteStream,
|
||||
getStreamUsers,
|
||||
grantPermissionsStream
|
||||
} from '../services/streams'
|
||||
} from '@/modules/core/services/streams'
|
||||
|
||||
import {
|
||||
createBranch,
|
||||
getBranchByNameAndStreamId,
|
||||
deleteBranchById
|
||||
} from '../services/branches'
|
||||
import { createObject } from '../services/objects'
|
||||
import { createCommitByBranchName } from '../services/commits'
|
||||
} from '@/modules/core/services/branches'
|
||||
import { createObject } from '@/modules/core/services/objects'
|
||||
import { createCommitByBranchName } from '@/modules/core/services/commits'
|
||||
|
||||
import { beforeEachContext, truncateTables } from '@/test/hooks'
|
||||
import {
|
||||
|
||||
@@ -66,6 +66,7 @@ export type AdminQueries = {
|
||||
projectList: ProjectCollection;
|
||||
serverStatistics: ServerStatistics;
|
||||
userList: AdminUserList;
|
||||
workspaceList: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -92,6 +93,13 @@ export type AdminQueriesUserListArgs = {
|
||||
role?: InputMaybe<ServerRole>;
|
||||
};
|
||||
|
||||
|
||||
export type AdminQueriesWorkspaceListArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUserList = {
|
||||
__typename?: 'AdminUserList';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1308,6 +1316,7 @@ export type Mutation = {
|
||||
webhookDelete: Scalars['String']['output'];
|
||||
/** Updates an existing webhook */
|
||||
webhookUpdate: Scalars['String']['output'];
|
||||
workspaceMutations: WorkspaceMutations;
|
||||
};
|
||||
|
||||
|
||||
@@ -1689,6 +1698,22 @@ export type PendingStreamCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaborator = {
|
||||
__typename?: 'PendingWorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
inviteId: Scalars['String']['output'];
|
||||
invitedBy: LimitedUser;
|
||||
role: Scalars['String']['output'];
|
||||
/** E-mail address or name of the invited user */
|
||||
title: Scalars['String']['output'];
|
||||
/** Only available if the active user is the pending workspace collaborator */
|
||||
token?: Maybe<Scalars['String']['output']>;
|
||||
/** Set only if user is registered */
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
__typename?: 'Project';
|
||||
allowPublicComments: Scalars['Boolean']['output'];
|
||||
@@ -1740,6 +1765,7 @@ export type Project = {
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2386,6 +2412,7 @@ export type Query = {
|
||||
* The query looks for matches in name & email
|
||||
*/
|
||||
userSearch: UserSearchResultCollection;
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
|
||||
@@ -2511,6 +2538,11 @@ export type QueryUserSearchArgs = {
|
||||
query: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryWorkspaceArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
/** Deprecated: Used by old stream-based mutations */
|
||||
export type ReplyCreateInput = {
|
||||
/** IDs of uploaded blobs that should be attached to this reply */
|
||||
@@ -2613,6 +2645,7 @@ export type ServerInfo = {
|
||||
serverRoles: Array<ServerRoleItem>;
|
||||
termsOfService?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
workspaces: ServerWorkspacesInfo;
|
||||
};
|
||||
|
||||
export type ServerInfoUpdateInput = {
|
||||
@@ -2681,6 +2714,15 @@ export type ServerStats = {
|
||||
userHistory?: Maybe<Array<Maybe<Scalars['JSONObject']['output']>>>;
|
||||
};
|
||||
|
||||
export type ServerWorkspacesInfo = {
|
||||
__typename?: 'ServerWorkspacesInfo';
|
||||
/**
|
||||
* This is a backend control variable for the workspaces feature set.
|
||||
* Since workspaces need a backend logic to be enabled, this is not enough as a feature flag.
|
||||
*/
|
||||
workspacesEnabled: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -3301,6 +3343,8 @@ export type User = {
|
||||
* Note: Only count resolution is currently implemented
|
||||
*/
|
||||
versions: CountOnlyCollection;
|
||||
/** Get the workspaces for the user */
|
||||
workspaces: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -3388,6 +3432,17 @@ export type UserVersionsArgs = {
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 UserWorkspacesArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserWorkspacesFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type UserAutomateInfo = {
|
||||
__typename?: 'UserAutomateInfo';
|
||||
availableGithubOrgs: Array<Scalars['String']['output']>;
|
||||
@@ -3438,6 +3493,10 @@ export type UserUpdateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UserWorkspacesFilter = {
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Version = {
|
||||
__typename?: 'Version';
|
||||
authorUser?: Maybe<LimitedUser>;
|
||||
@@ -3650,6 +3709,152 @@ export type WebhookUpdateInput = {
|
||||
url?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
description?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
|
||||
name: Scalars['String']['output'];
|
||||
projects: ProjectCollection;
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
team: Array<WorkspaceCollaborator>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserProjectsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceCollaborator = {
|
||||
__typename?: 'WorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
export type WorkspaceCollection = {
|
||||
__typename?: 'WorkspaceCollection';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
items: Array<Workspace>;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceInviteCreateInput = {
|
||||
/** Either this or userId must be filled */
|
||||
email?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Defaults to the member role, if not specified */
|
||||
role?: InputMaybe<WorkspaceRole>;
|
||||
/** Either this or email must be filled */
|
||||
userId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteMutations = {
|
||||
__typename?: 'WorkspaceInviteMutations';
|
||||
batchCreate: Workspace;
|
||||
cancel: Workspace;
|
||||
create: Workspace;
|
||||
use: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsBatchCreateArgs = {
|
||||
input: Array<WorkspaceInviteCreateInput>;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCancelArgs = {
|
||||
inviteId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCreateArgs = {
|
||||
input: WorkspaceInviteCreateInput;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsUseArgs = {
|
||||
input: WorkspaceInviteUseInput;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteUseInput = {
|
||||
accept: Scalars['Boolean']['input'];
|
||||
token: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceMutations = {
|
||||
__typename?: 'WorkspaceMutations';
|
||||
create: Workspace;
|
||||
delete: Workspace;
|
||||
deleteRole: Scalars['Boolean']['output'];
|
||||
invites: WorkspaceInviteMutations;
|
||||
update: Workspace;
|
||||
updateRole: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsCreateArgs = {
|
||||
input: WorkspaceCreateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteRoleArgs = {
|
||||
input: WorkspaceRoleDeleteInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateArgs = {
|
||||
input: WorkspaceUpdateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateRoleArgs = {
|
||||
input: WorkspaceRoleUpdateInput;
|
||||
};
|
||||
|
||||
export enum WorkspaceRole {
|
||||
Admin = 'ADMIN',
|
||||
Guest = 'GUEST',
|
||||
Member = 'MEMBER'
|
||||
}
|
||||
|
||||
export type WorkspaceRoleDeleteInput = {
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceRoleUpdateInput = {
|
||||
role: WorkspaceRole;
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceUpdateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
id: Scalars['String']['input'];
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type CrossSyncCommitBranchMetadataQueryVariables = Exact<{
|
||||
streamId: Scalars['String']['input'];
|
||||
commitId: Scalars['String']['input'];
|
||||
|
||||
@@ -111,7 +111,8 @@ describe('Activity digest notifications @notifications', () => {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
allowPublicComments: true,
|
||||
isDiscoverable: true
|
||||
isDiscoverable: true,
|
||||
workspaceId: null
|
||||
},
|
||||
activity: activities ?? [createActivity()]
|
||||
})
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { UserRecord } from '@/modules/core/helpers/types'
|
||||
import { CreateInviteParams } from '@/modules/serverinvites/domain/operations'
|
||||
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
|
||||
import { ResourceTargets, isServerInvite, resolveTarget } from '../helpers/inviteHelper'
|
||||
import {
|
||||
ResourceTargets,
|
||||
isServerInvite,
|
||||
resolveTarget
|
||||
} from '@/modules/serverinvites/helpers/inviteHelper'
|
||||
import { UserWithOptionalRole } from '@/modules/core/repositories/users'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { Roles } from '@speckle/shared'
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
WorkspaceEvents,
|
||||
WorkspaceEventsPayloads
|
||||
} from '@/modules/workspacesCore/domain/events'
|
||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
|
||||
/** Workspace */
|
||||
@@ -18,7 +19,7 @@ type GetWorkspaceArgs = {
|
||||
|
||||
export type GetWorkspace = (args: GetWorkspaceArgs) => Promise<Workspace | null>
|
||||
|
||||
/** WorkspaceRole */
|
||||
/** Workspace Roles */
|
||||
|
||||
type DeleteWorkspaceRoleArgs = {
|
||||
workspaceId: string
|
||||
@@ -63,6 +64,28 @@ export type GetWorkspaceRolesForUser = (
|
||||
|
||||
export type UpsertWorkspaceRole = (args: WorkspaceAcl) => Promise<void>
|
||||
|
||||
/** Workspace Projects */
|
||||
|
||||
type GetAllWorkspaceProjectsForUserArgs = {
|
||||
userId: string
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export type GetAllWorkspaceProjectsForUser = (
|
||||
args: GetAllWorkspaceProjectsForUserArgs
|
||||
) => Promise<StreamRecord[]>
|
||||
|
||||
/** Workspace Project Roles */
|
||||
|
||||
type GrantWorkspaceProjectRolesArgs = {
|
||||
projectId: string
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export type GrantWorkspaceProjectRoles = (
|
||||
args: GrantWorkspaceProjectRolesArgs
|
||||
) => Promise<void>
|
||||
|
||||
/** Blob */
|
||||
|
||||
export type StoreBlob = (args: string) => Promise<string>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Roles, StreamRoles, WorkspaceRoles } from '@speckle/shared'
|
||||
|
||||
/**
|
||||
* Given a user's workspace role, return the role they should have for workspace projects.
|
||||
*/
|
||||
export const mapWorkspaceRoleToProjectRole = (
|
||||
workspaceRole: WorkspaceRoles
|
||||
): StreamRoles => {
|
||||
switch (workspaceRole) {
|
||||
case Roles.Workspace.Guest:
|
||||
case Roles.Workspace.Member:
|
||||
return Roles.Stream.Reviewer
|
||||
case Roles.Workspace.Admin:
|
||||
return Roles.Stream.Owner
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,18 @@ export class WorkspaceAdminRequiredError extends BaseError {
|
||||
static code = 'WORKSPACE_ADMIN_REQUIRED_ERROR'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceInvalidRoleError extends BaseError {
|
||||
static defaultMessage = 'Invalid workspace role provided'
|
||||
static code = 'WORKSPACE_INVALID_ROLE_ERROR'
|
||||
}
|
||||
|
||||
export class WorkspaceQueryError extends BaseError {
|
||||
static defaultMessage = 'Unexpected error during query operation'
|
||||
static code = 'WORKSPACE_QUERY_ERROR'
|
||||
}
|
||||
|
||||
export class WorkspacesNotYetImplementedError extends BaseError {
|
||||
static defaultMessage = 'Not yet implemented'
|
||||
static code = 'WORKSPACES_NOT_YET_IMPLEMENTED_ERROR'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
ProjectsEmitter,
|
||||
ProjectEvents,
|
||||
ProjectEventsPayloads
|
||||
} from '@/modules/core/events/projectsEmitter'
|
||||
import { getWorkspaceRolesFactory } from '@/modules/workspaces/repositories/workspaces'
|
||||
import { grantStreamPermissions as repoGrantStreamPermissions } from '@/modules/core/repositories/streams'
|
||||
import { Knex } from 'knex'
|
||||
import { GetWorkspaceRoles } from '@/modules/workspaces/domain/operations'
|
||||
import { mapWorkspaceRoleToProjectRole } from '@/modules/workspaces/domain/roles'
|
||||
|
||||
export const onProjectCreatedFactory =
|
||||
({
|
||||
getWorkspaceRoles,
|
||||
grantStreamPermissions
|
||||
}: {
|
||||
getWorkspaceRoles: GetWorkspaceRoles
|
||||
grantStreamPermissions: typeof repoGrantStreamPermissions
|
||||
}) =>
|
||||
async (payload: ProjectEventsPayloads[typeof ProjectEvents.Created]) => {
|
||||
const { id: projectId, workspaceId } = payload.project
|
||||
|
||||
if (!workspaceId) {
|
||||
return
|
||||
}
|
||||
|
||||
const workspaceMembers = await getWorkspaceRoles({ workspaceId })
|
||||
|
||||
await Promise.all(
|
||||
workspaceMembers.map(({ userId, role: workspaceRole }) =>
|
||||
grantStreamPermissions({
|
||||
streamId: projectId,
|
||||
userId,
|
||||
role: mapWorkspaceRoleToProjectRole(workspaceRole)
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const initializeEventListenersFactory =
|
||||
({ db }: { db: Knex }) =>
|
||||
() => {
|
||||
const onProjectCreated = onProjectCreatedFactory({
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
|
||||
// TODO: Instantiate via factory function
|
||||
grantStreamPermissions: repoGrantStreamPermissions
|
||||
})
|
||||
|
||||
const quitCbs = [ProjectsEmitter.listen(ProjectEvents.Created, onProjectCreated)]
|
||||
|
||||
return () => quitCbs.forEach((quit) => quit())
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { WorkspacesNotYetImplementedError } from '@/modules/workspaces/errors/workspace'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
export = FF_WORKSPACES_MODULE_ENABLED
|
||||
? ({
|
||||
Query: {
|
||||
workspace: async () => {
|
||||
// Get workspace by id
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
create: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
delete: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
update: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
updateRole: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
deleteRole: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
WorkspaceInviteMutations: {
|
||||
create: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
batchCreate: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
use: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
cancel: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
Workspace: {
|
||||
role: async () => {
|
||||
// Get user id from parent, get role and return
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
team: async () => {
|
||||
// Get roles for workspace
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
invitedTeam: async () => {
|
||||
// Get invites
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
},
|
||||
projects: async () => {
|
||||
// Get projects in workspace
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
User: {
|
||||
workspaces: async () => {
|
||||
// Get roles for user, get workspaces
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
Project: {
|
||||
workspace: async () => {
|
||||
// Get workspaceId from project, get and return workspace data
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
},
|
||||
AdminQueries: {
|
||||
workspaceList: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
}
|
||||
} as Resolvers)
|
||||
: {}
|
||||
@@ -6,6 +6,7 @@ import { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
|
||||
import { workspaceRoles } from '@/modules/workspaces/roles'
|
||||
import { workspaceScopes } from '@/modules/workspaces/scopes'
|
||||
import { registerOrUpdateRole } from '@/modules/shared/repositories/roles'
|
||||
import { initializeEventListenersFactory } from '@/modules/workspaces/events/eventListener'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -20,9 +21,12 @@ const initRoles = () => {
|
||||
}
|
||||
|
||||
const workspacesModule: SpeckleModule = {
|
||||
async init() {
|
||||
async init(_, isInitial) {
|
||||
if (!FF_WORKSPACES_MODULE_ENABLED) return
|
||||
moduleLogger.info('⚒️ Init workspaces module')
|
||||
if (isInitial) {
|
||||
initializeEventListenersFactory({ db })()
|
||||
}
|
||||
await Promise.all([initScopes(), initRoles()])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ import {
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { Knex } from 'knex'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { WorkspaceInvalidRoleError } from '@/modules/workspaces/errors/workspace'
|
||||
|
||||
const tables = {
|
||||
streams: (db: Knex) => db<StreamRecord>('streams'),
|
||||
workspaces: (db: Knex) => db<Workspace>('workspaces'),
|
||||
workspacesAcl: (db: Knex) => db<WorkspaceAcl>('workspace_acl')
|
||||
}
|
||||
@@ -93,7 +96,7 @@ export const upsertWorkspaceRoleFactory =
|
||||
// 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}`)
|
||||
throw new WorkspaceInvalidRoleError()
|
||||
}
|
||||
|
||||
await tables
|
||||
|
||||
@@ -12,6 +12,11 @@ export const workspaceScopes: TokenScopeData[] = [
|
||||
description: 'Required for editing workspace information',
|
||||
public: true
|
||||
},
|
||||
{
|
||||
name: Scopes.Workspaces.Read,
|
||||
description: 'Required for reading workspace data',
|
||||
public: true
|
||||
},
|
||||
{
|
||||
name: Scopes.Workspaces.Delete,
|
||||
description: 'Required for deleting workspaces',
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import {
|
||||
EmitWorkspaceEvent,
|
||||
StoreBlob,
|
||||
UpsertWorkspace,
|
||||
UpsertWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import {
|
||||
grantStreamPermissions as repoGrantStreamPermissions,
|
||||
revokeStreamPermissions as repoRevokeStreamPermissions
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import { getStreams as repoGetStreams } from '@/modules/core/services/streams'
|
||||
import {
|
||||
DeleteWorkspaceRole,
|
||||
GetWorkspaceRoleForUser,
|
||||
GetWorkspaceRoles
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceAdminRequiredError } from '@/modules/workspaces/errors/workspace'
|
||||
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/utils/roles'
|
||||
import { mapWorkspaceRoleToProjectRole } from '@/modules/workspaces/domain/roles'
|
||||
import { queryAllWorkspaceProjectsFactory } from '@/modules/workspaces/services/projects'
|
||||
|
||||
type WorkspaceCreateArgs = {
|
||||
workspaceInput: { name: string; description: string | null; logo: string | null }
|
||||
userId: string
|
||||
}
|
||||
|
||||
export const createWorkspaceFactory =
|
||||
({
|
||||
upsertWorkspace,
|
||||
upsertWorkspaceRole,
|
||||
emitWorkspaceEvent,
|
||||
storeBlob
|
||||
}: {
|
||||
upsertWorkspace: UpsertWorkspace
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
storeBlob: StoreBlob
|
||||
emitWorkspaceEvent: EmitWorkspaceEvent
|
||||
}) =>
|
||||
async ({ userId, workspaceInput }: WorkspaceCreateArgs): Promise<Workspace> => {
|
||||
let logoUrl: string | null = null
|
||||
if (workspaceInput.logo) {
|
||||
logoUrl = await storeBlob(workspaceInput.logo)
|
||||
}
|
||||
|
||||
const workspace = {
|
||||
...workspaceInput,
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
logoUrl
|
||||
}
|
||||
await upsertWorkspace({ workspace })
|
||||
// assign the creator as workspace administrator
|
||||
await upsertWorkspaceRole({
|
||||
userId,
|
||||
role: Roles.Workspace.Admin,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
// emit a workspace created event
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.Created,
|
||||
payload: { ...workspace, createdByUserId: userId }
|
||||
})
|
||||
|
||||
return workspace
|
||||
}
|
||||
|
||||
type WorkspaceRoleDeleteArgs = {
|
||||
userId: string
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export const deleteWorkspaceRoleFactory =
|
||||
({
|
||||
getWorkspaceRoles,
|
||||
deleteWorkspaceRole,
|
||||
emitWorkspaceEvent,
|
||||
getStreams,
|
||||
revokeStreamPermissions
|
||||
}: {
|
||||
getWorkspaceRoles: GetWorkspaceRoles
|
||||
deleteWorkspaceRole: DeleteWorkspaceRole
|
||||
emitWorkspaceEvent: EmitWorkspaceEvent
|
||||
getStreams: typeof repoGetStreams
|
||||
revokeStreamPermissions: typeof repoRevokeStreamPermissions
|
||||
}) =>
|
||||
async ({
|
||||
userId,
|
||||
workspaceId
|
||||
}: WorkspaceRoleDeleteArgs): Promise<WorkspaceAcl | null> => {
|
||||
// Protect against removing last admin
|
||||
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
|
||||
if (isUserLastWorkspaceAdmin(workspaceRoles, userId)) {
|
||||
throw new WorkspaceAdminRequiredError()
|
||||
}
|
||||
|
||||
// Perform delete
|
||||
const deletedRole = await deleteWorkspaceRole({ userId, workspaceId })
|
||||
if (!deletedRole) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Delete workspace project roles
|
||||
const queryAllWorkspaceProjectsGenerator = queryAllWorkspaceProjectsFactory({
|
||||
getStreams
|
||||
})
|
||||
for await (const projectsPage of queryAllWorkspaceProjectsGenerator(workspaceId)) {
|
||||
await Promise.all(
|
||||
projectsPage.map(({ id: streamId }) =>
|
||||
revokeStreamPermissions({ streamId, userId })
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Emit deleted role
|
||||
await emitWorkspaceEvent({
|
||||
eventName: 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,
|
||||
getStreams,
|
||||
grantStreamPermissions
|
||||
}: {
|
||||
getWorkspaceRoles: GetWorkspaceRoles
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
emitWorkspaceEvent: EmitWorkspaceEvent
|
||||
// TODO: Create `core` domain and import type from there
|
||||
getStreams: typeof repoGetStreams
|
||||
grantStreamPermissions: typeof repoGrantStreamPermissions
|
||||
}) =>
|
||||
async ({ userId, workspaceId, role }: WorkspaceAcl): Promise<void> => {
|
||||
// Protect against removing last admin
|
||||
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
|
||||
if (
|
||||
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
|
||||
role !== 'workspace:admin'
|
||||
) {
|
||||
throw new WorkspaceAdminRequiredError()
|
||||
}
|
||||
|
||||
// Perform upsert
|
||||
await upsertWorkspaceRole({ userId, workspaceId, role })
|
||||
|
||||
// Update user role in all workspace projects
|
||||
// TODO: Should these be in a transaction with the workspace role change?
|
||||
const queryAllWorkspaceProjectsGenerator = queryAllWorkspaceProjectsFactory({
|
||||
getStreams
|
||||
})
|
||||
const projectRole = mapWorkspaceRoleToProjectRole(role)
|
||||
for await (const projectsPage of queryAllWorkspaceProjectsGenerator(workspaceId)) {
|
||||
await Promise.all(
|
||||
projectsPage.map(({ id: streamId }) =>
|
||||
grantStreamPermissions({ streamId, userId, role: projectRole })
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Emit new role
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.RoleUpdated,
|
||||
payload: { userId, workspaceId, role }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { getStreams as repoGetStreams } from '@/modules/core/services/streams'
|
||||
import { WorkspaceQueryError } from '@/modules/workspaces/errors/workspace'
|
||||
|
||||
export const queryAllWorkspaceProjectsFactory = ({
|
||||
getStreams
|
||||
}: {
|
||||
// TODO: Core service factory functions
|
||||
getStreams: typeof repoGetStreams
|
||||
}) =>
|
||||
async function* queryAllWorkspaceProjects(
|
||||
workspaceId: string
|
||||
): AsyncGenerator<StreamRecord[], void, unknown> {
|
||||
let cursor: Date | null = null
|
||||
let iterationCount = 0
|
||||
|
||||
do {
|
||||
if (iterationCount > 500) throw new WorkspaceQueryError()
|
||||
|
||||
const { streams, cursorDate } = await getStreams({
|
||||
cursor,
|
||||
orderBy: null,
|
||||
limit: 1000,
|
||||
visibility: null,
|
||||
searchQuery: null,
|
||||
streamIdWhitelist: null,
|
||||
workspaceIdWhitelist: [workspaceId]
|
||||
})
|
||||
|
||||
yield streams
|
||||
|
||||
cursor = cursorDate
|
||||
iterationCount++
|
||||
} while (!!cursor)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import {
|
||||
EmitWorkspaceEvent,
|
||||
StoreBlob,
|
||||
UpsertWorkspace,
|
||||
UpsertWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { Workspace } from '@/modules/workspacesCore/domain/types'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
type WorkspaceCreateArgs = {
|
||||
workspaceInput: { name: string; description: string | null; logo: string | null }
|
||||
userId: string
|
||||
}
|
||||
|
||||
export const createWorkspaceFactory =
|
||||
({
|
||||
upsertWorkspace,
|
||||
upsertWorkspaceRole,
|
||||
emitWorkspaceEvent,
|
||||
storeBlob
|
||||
}: {
|
||||
upsertWorkspace: UpsertWorkspace
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
storeBlob: StoreBlob
|
||||
emitWorkspaceEvent: EmitWorkspaceEvent
|
||||
}) =>
|
||||
async ({ userId, workspaceInput }: WorkspaceCreateArgs): Promise<Workspace> => {
|
||||
let logoUrl: string | null = null
|
||||
if (workspaceInput.logo) {
|
||||
logoUrl = await storeBlob(workspaceInput.logo)
|
||||
}
|
||||
|
||||
const workspace = {
|
||||
...workspaceInput,
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
logoUrl
|
||||
}
|
||||
await upsertWorkspace({ workspace })
|
||||
// assign the creator as workspace administrator
|
||||
await upsertWorkspaceRole({
|
||||
userId,
|
||||
role: Roles.Workspace.Admin,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.Created,
|
||||
payload: { ...workspace, createdByUserId: userId }
|
||||
})
|
||||
// emit a workspace created event
|
||||
|
||||
return workspace
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import {
|
||||
DeleteWorkspaceRole,
|
||||
EmitWorkspaceEvent,
|
||||
GetWorkspaceRoleForUser,
|
||||
GetWorkspaceRoles,
|
||||
UpsertWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/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({ eventName: 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({
|
||||
eventName: WorkspaceEvents.RoleUpdated,
|
||||
payload: { userId, workspaceId, role }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { onProjectCreatedFactory } from '@/modules/workspaces/events/eventListener'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('Event handlers', () => {
|
||||
describe('onProjectCreatedFactory creates a function, that', () => {
|
||||
it('grants project roles for all workspace members', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspaceRoles: WorkspaceAcl[] = [
|
||||
{
|
||||
workspaceId,
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
role: Roles.Workspace.Admin
|
||||
},
|
||||
{
|
||||
workspaceId,
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
role: Roles.Workspace.Member
|
||||
},
|
||||
{
|
||||
workspaceId,
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
role: Roles.Workspace.Guest
|
||||
}
|
||||
]
|
||||
|
||||
const projectRoles: StreamAclRecord[] = []
|
||||
|
||||
const onProjectCreated = onProjectCreatedFactory({
|
||||
getWorkspaceRoles: async () => workspaceRoles,
|
||||
grantStreamPermissions: async ({ streamId, userId, role }) => {
|
||||
projectRoles.push({
|
||||
resourceId: streamId,
|
||||
userId,
|
||||
role
|
||||
})
|
||||
|
||||
return {} as StreamRecord
|
||||
}
|
||||
})
|
||||
|
||||
await onProjectCreated({
|
||||
project: { workspaceId, id: projectId } as StreamRecord
|
||||
})
|
||||
|
||||
expect(projectRoles.length).to.equal(3)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,384 @@
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import {
|
||||
createWorkspaceFactory,
|
||||
deleteWorkspaceRoleFactory,
|
||||
setWorkspaceRoleFactory
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
|
||||
type WorkspaceTestContext = {
|
||||
storedWorkspaces: Workspace[]
|
||||
storedRoles: WorkspaceAcl[]
|
||||
eventData: {
|
||||
isCalled: boolean
|
||||
eventName: string
|
||||
payload: unknown
|
||||
}
|
||||
}
|
||||
|
||||
const buildCreateWorkspaceWithTestContext = (
|
||||
dependecyOverrides: Partial<Parameters<typeof createWorkspaceFactory>[0]> = {}
|
||||
) => {
|
||||
const context: WorkspaceTestContext = {
|
||||
storedWorkspaces: [],
|
||||
storedRoles: [],
|
||||
eventData: {
|
||||
isCalled: false,
|
||||
eventName: '',
|
||||
payload: {}
|
||||
}
|
||||
}
|
||||
|
||||
const deps: Parameters<typeof createWorkspaceFactory>[0] = {
|
||||
upsertWorkspace: async ({ workspace }: { workspace: Workspace }) => {
|
||||
context.storedWorkspaces.push(workspace)
|
||||
},
|
||||
upsertWorkspaceRole: async (workspaceAcl: WorkspaceAcl) => {
|
||||
context.storedRoles.push(workspaceAcl)
|
||||
},
|
||||
emitWorkspaceEvent: async ({ eventName, payload }) => {
|
||||
context.eventData.isCalled = true
|
||||
context.eventData.eventName = eventName
|
||||
context.eventData.payload = payload
|
||||
return []
|
||||
},
|
||||
storeBlob: async () => cryptoRandomString({ length: 10 }),
|
||||
...dependecyOverrides
|
||||
}
|
||||
|
||||
const createWorkspace = createWorkspaceFactory(deps)
|
||||
|
||||
return { context, createWorkspace }
|
||||
}
|
||||
|
||||
const getCreateWorkspaceInput = () => {
|
||||
return {
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceInput: {
|
||||
description: 'foobar',
|
||||
logo: null,
|
||||
name: cryptoRandomString({ length: 6 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('Workspace services', () => {
|
||||
describe('createWorkspaceFactory creates a function, that', () => {
|
||||
it('stores the workspace', async () => {
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext()
|
||||
|
||||
const { userId, workspaceInput } = getCreateWorkspaceInput()
|
||||
const workspace = await createWorkspace({ userId, workspaceInput })
|
||||
|
||||
expect(context.storedWorkspaces.length).to.equal(1)
|
||||
expect(context.storedWorkspaces[0]).to.deep.equal(workspace)
|
||||
})
|
||||
it('makes the workspace creator becomes a workspace:admin', async () => {
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext()
|
||||
|
||||
const { userId, workspaceInput } = getCreateWorkspaceInput()
|
||||
const workspace = await createWorkspace({ userId, workspaceInput })
|
||||
|
||||
expect(context.storedRoles.length).to.equal(1)
|
||||
expect(context.storedRoles[0]).to.deep.equal({
|
||||
userId,
|
||||
workspaceId: workspace.id,
|
||||
role: Roles.Workspace.Admin
|
||||
})
|
||||
})
|
||||
it('emits a workspace created event', async () => {
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext()
|
||||
|
||||
const { userId, workspaceInput } = getCreateWorkspaceInput()
|
||||
const workspace = await createWorkspace({ userId, workspaceInput })
|
||||
|
||||
expect(context.eventData.isCalled).to.equal(true)
|
||||
expect(context.eventData.eventName).to.equal(WorkspaceEvents.Created)
|
||||
expect(context.eventData.payload).to.deep.equal({
|
||||
...workspace,
|
||||
createdByUserId: userId
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type WorkspaceRoleTestContext = {
|
||||
workspaceId: string
|
||||
workspaceRoles: WorkspaceAcl[]
|
||||
workspaceProjects: StreamRecord[]
|
||||
workspaceProjectRoles: StreamAclRecord[]
|
||||
eventData: {
|
||||
isCalled: boolean
|
||||
eventName: string
|
||||
payload: unknown
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultWorkspaceRoleTestContext = (): WorkspaceRoleTestContext => {
|
||||
return {
|
||||
workspaceId: cryptoRandomString({ length: 10 }),
|
||||
workspaceRoles: [],
|
||||
workspaceProjects: [],
|
||||
workspaceProjectRoles: [],
|
||||
eventData: {
|
||||
isCalled: false,
|
||||
eventName: '',
|
||||
payload: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildDeleteWorkspaceRoleAndTestContext = (
|
||||
contextOverrides: Partial<WorkspaceRoleTestContext> = {},
|
||||
dependencyOverrides: Partial<Parameters<typeof deleteWorkspaceRoleFactory>[0]> = {}
|
||||
) => {
|
||||
const context: WorkspaceRoleTestContext = {
|
||||
...getDefaultWorkspaceRoleTestContext(),
|
||||
...contextOverrides
|
||||
}
|
||||
|
||||
const deps: Parameters<typeof deleteWorkspaceRoleFactory>[0] = {
|
||||
getWorkspaceRoles: async () => context.workspaceRoles,
|
||||
deleteWorkspaceRole: async (role) => {
|
||||
const isMatch = (acl: WorkspaceAcl): boolean => {
|
||||
return acl.workspaceId === role.workspaceId && acl.userId === role.userId
|
||||
}
|
||||
|
||||
const deletedRoleIndex = context.workspaceRoles.findIndex(isMatch)
|
||||
|
||||
if (deletedRoleIndex < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const deletedRole = structuredClone(context.workspaceRoles[deletedRoleIndex])
|
||||
|
||||
context.workspaceRoles = context.workspaceRoles.filter((acl) => !isMatch(acl))
|
||||
|
||||
return deletedRole
|
||||
},
|
||||
emitWorkspaceEvent: async ({ eventName, payload }) => {
|
||||
context.eventData.isCalled = true
|
||||
context.eventData.eventName = eventName
|
||||
context.eventData.payload = payload
|
||||
|
||||
return []
|
||||
},
|
||||
getStreams: async () => ({
|
||||
streams: context.workspaceProjects,
|
||||
totalCount: context.workspaceProjects.length,
|
||||
cursorDate: null
|
||||
}),
|
||||
revokeStreamPermissions: async ({ streamId, userId }) => {
|
||||
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
|
||||
(role) => role.resourceId !== streamId && role.userId !== userId
|
||||
)
|
||||
return {} as StreamRecord
|
||||
},
|
||||
...dependencyOverrides
|
||||
}
|
||||
|
||||
const deleteWorkspaceRole = deleteWorkspaceRoleFactory(deps)
|
||||
|
||||
return { deleteWorkspaceRole, context }
|
||||
}
|
||||
|
||||
const buildSetWorkspaceRoleAndTestContext = (
|
||||
contextOverrides: Partial<WorkspaceRoleTestContext> = {},
|
||||
dependencyOverrides: Partial<Parameters<typeof setWorkspaceRoleFactory>[0]> = {}
|
||||
) => {
|
||||
const context = {
|
||||
...getDefaultWorkspaceRoleTestContext(),
|
||||
...contextOverrides
|
||||
}
|
||||
|
||||
const deps: Parameters<typeof setWorkspaceRoleFactory>[0] = {
|
||||
getWorkspaceRoles: async () => context.workspaceRoles,
|
||||
upsertWorkspaceRole: async (role) => {
|
||||
const currentRoleIndex = context.workspaceRoles.findIndex(
|
||||
(acl) => acl.userId === role.userId && acl.workspaceId === role.workspaceId
|
||||
)
|
||||
|
||||
if (currentRoleIndex >= 0) {
|
||||
context.workspaceRoles[currentRoleIndex] = role
|
||||
} else {
|
||||
context.workspaceRoles.push(role)
|
||||
}
|
||||
},
|
||||
emitWorkspaceEvent: async ({ eventName, payload }) => {
|
||||
context.eventData.isCalled = true
|
||||
context.eventData.eventName = eventName
|
||||
context.eventData.payload = payload
|
||||
|
||||
return []
|
||||
},
|
||||
getStreams: async () => ({
|
||||
streams: context.workspaceProjects,
|
||||
totalCount: context.workspaceProjects.length,
|
||||
cursorDate: null
|
||||
}),
|
||||
grantStreamPermissions: async (role) => {
|
||||
const currentRoleIndex = context.workspaceProjectRoles.findIndex(
|
||||
(acl) => acl.userId === role.userId && acl.resourceId === role.streamId
|
||||
)
|
||||
|
||||
const streamAcl: StreamAclRecord = {
|
||||
userId: role.userId,
|
||||
role: role.role,
|
||||
resourceId: role.streamId
|
||||
}
|
||||
|
||||
if (currentRoleIndex > 0) {
|
||||
context.workspaceProjectRoles[currentRoleIndex] = streamAcl
|
||||
} else {
|
||||
context.workspaceProjectRoles.push(streamAcl)
|
||||
}
|
||||
|
||||
return {} as StreamRecord
|
||||
},
|
||||
...dependencyOverrides
|
||||
}
|
||||
|
||||
const setWorkspaceRole = setWorkspaceRoleFactory(deps)
|
||||
|
||||
return { setWorkspaceRole, context }
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
const { deleteWorkspaceRole, context } = buildDeleteWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceRoles: [role]
|
||||
})
|
||||
|
||||
const deletedRole = await deleteWorkspaceRole({ userId, workspaceId })
|
||||
|
||||
expect(context.workspaceRoles.length).to.equal(0)
|
||||
expect(deletedRole).to.deep.equal(role)
|
||||
})
|
||||
it('emits a role-deleted event', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
|
||||
|
||||
const { deleteWorkspaceRole, context } = buildDeleteWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceRoles: [role]
|
||||
})
|
||||
|
||||
await deleteWorkspaceRole({ userId, workspaceId })
|
||||
|
||||
expect(context.eventData.isCalled).to.be.true
|
||||
expect(context.eventData.eventName).to.equal(WorkspaceEvents.RoleDeleted)
|
||||
expect(context.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 }
|
||||
|
||||
const { deleteWorkspaceRole } = buildDeleteWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceRoles: [role]
|
||||
})
|
||||
|
||||
await expectToThrow(() => deleteWorkspaceRole({ userId, workspaceId }))
|
||||
})
|
||||
it('deletes workspace project roles', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const { deleteWorkspaceRole, context } = buildDeleteWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceRoles: [{ userId, workspaceId, role: Roles.Workspace.Member }],
|
||||
workspaceProjects: [{ id: projectId } as StreamRecord],
|
||||
workspaceProjectRoles: [
|
||||
{ userId, role: Roles.Stream.Contributor, resourceId: projectId }
|
||||
]
|
||||
})
|
||||
|
||||
await deleteWorkspaceRole({ userId, workspaceId })
|
||||
|
||||
expect(context.workspaceProjectRoles.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
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 { setWorkspaceRole, context } = buildSetWorkspaceRoleAndTestContext({
|
||||
workspaceId
|
||||
})
|
||||
|
||||
await setWorkspaceRole(role)
|
||||
|
||||
expect(context.workspaceRoles.length).to.equal(1)
|
||||
expect(context.workspaceRoles[0]).to.deep.equal(role)
|
||||
})
|
||||
it('emits a role-updated event', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const role: WorkspaceAcl = { userId, workspaceId, role: Roles.Workspace.Member }
|
||||
|
||||
const { setWorkspaceRole, context } = buildSetWorkspaceRoleAndTestContext({
|
||||
workspaceId
|
||||
})
|
||||
|
||||
await setWorkspaceRole(role)
|
||||
|
||||
expect(context.eventData.isCalled).to.be.true
|
||||
expect(context.eventData.eventName).to.equal(WorkspaceEvents.RoleUpdated)
|
||||
expect(context.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 { setWorkspaceRole } = buildSetWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceRoles: [role]
|
||||
})
|
||||
|
||||
await expectToThrow(() =>
|
||||
setWorkspaceRole({ ...role, role: Roles.Workspace.Member })
|
||||
)
|
||||
})
|
||||
it('sets roles on workspace projects', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspaceRole: WorkspaceAcl = {
|
||||
userId,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Admin
|
||||
}
|
||||
|
||||
const { setWorkspaceRole, context } = buildSetWorkspaceRoleAndTestContext({
|
||||
workspaceId,
|
||||
workspaceProjects: [{ id: projectId } as StreamRecord]
|
||||
})
|
||||
|
||||
await setWorkspaceRole(workspaceRole)
|
||||
|
||||
expect(context.workspaceProjectRoles.length).to.equal(1)
|
||||
expect(context.workspaceProjectRoles[0].userId).to.equal(userId)
|
||||
expect(context.workspaceProjectRoles[0].resourceId).to.equal(projectId)
|
||||
expect(context.workspaceProjectRoles[0].role).to.equal(Roles.Stream.Owner)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { queryAllWorkspaceProjectsFactory } from '@/modules/workspaces/services/projects'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
describe('Project retrieval services', () => {
|
||||
describe('queryAllWorkspaceProjectFactory returns a generator, that', () => {
|
||||
it('returns all streams for a workspace', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const foundProjects: StreamRecord[] = []
|
||||
const storedProjects: StreamRecord[] = [{ workspaceId } as StreamRecord]
|
||||
|
||||
const queryAllWorkspaceProjectsGenerator = queryAllWorkspaceProjectsFactory({
|
||||
getStreams: async () => {
|
||||
return {
|
||||
streams: storedProjects,
|
||||
totalCount: storedProjects.length,
|
||||
cursorDate: null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for await (const projectsPage of queryAllWorkspaceProjectsGenerator(
|
||||
workspaceId
|
||||
)) {
|
||||
foundProjects.push(...projectsPage)
|
||||
}
|
||||
|
||||
expect(foundProjects.length).to.equal(1)
|
||||
})
|
||||
it('returns all streams for a workspace if the query requires multiple pages of results', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const foundProjects: StreamRecord[] = []
|
||||
const storedProjects: StreamRecord[] = [
|
||||
{ workspaceId } as StreamRecord,
|
||||
{ workspaceId } as StreamRecord
|
||||
]
|
||||
|
||||
const queryAllWorkspaceProjectsGenerator = queryAllWorkspaceProjectsFactory({
|
||||
getStreams: async ({ cursor }) => {
|
||||
return cursor
|
||||
? { streams: [storedProjects[1]], totalCount: 1, cursorDate: null }
|
||||
: { streams: [storedProjects[0]], totalCount: 1, cursorDate: new Date() }
|
||||
}
|
||||
})
|
||||
|
||||
for await (const projectsPage of queryAllWorkspaceProjectsGenerator(
|
||||
workspaceId
|
||||
)) {
|
||||
foundProjects.push(...projectsPage)
|
||||
}
|
||||
|
||||
expect(foundProjects.length).to.equal(2)
|
||||
})
|
||||
it('exits if no results are found', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const foundProjects: StreamRecord[] = []
|
||||
|
||||
const queryAllWorkspaceProjectsGenerator = queryAllWorkspaceProjectsFactory({
|
||||
getStreams: async () => {
|
||||
return { streams: [], totalCount: 0, cursorDate: null }
|
||||
}
|
||||
})
|
||||
|
||||
for await (const projectsPage of queryAllWorkspaceProjectsGenerator(
|
||||
workspaceId
|
||||
)) {
|
||||
foundProjects.push(...projectsPage)
|
||||
}
|
||||
|
||||
expect(foundProjects.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,96 +0,0 @@
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { createWorkspaceFactory } from '@/modules/workspaces/services/workspaceCreation'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
|
||||
describe('Workspace services', () => {
|
||||
describe('createWorkspaceFactory creates a function, that', () => {
|
||||
it('stores the workspace', async () => {
|
||||
const storedWorkspaces: Workspace[] = []
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
upsertWorkspace: async ({ workspace }: { workspace: Workspace }) => {
|
||||
storedWorkspaces.push(workspace)
|
||||
},
|
||||
upsertWorkspaceRole: async () => {},
|
||||
emitWorkspaceEvent: async () => [],
|
||||
storeBlob: async () => cryptoRandomString({ length: 10 })
|
||||
})
|
||||
|
||||
const workspaceInput = {
|
||||
description: 'foobar',
|
||||
logo: null,
|
||||
name: cryptoRandomString({ length: 6 })
|
||||
}
|
||||
const workspace = await createWorkspace({
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceInput
|
||||
})
|
||||
expect(storedWorkspaces.length).to.equal(1)
|
||||
expect(storedWorkspaces[0]).to.deep.equal(workspace)
|
||||
})
|
||||
it('makes the workspace creator becomes a workspace:admin', async () => {
|
||||
const storedRole: WorkspaceAcl[] = []
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
upsertWorkspace: async () => {},
|
||||
upsertWorkspaceRole: async (workspaceAcl: WorkspaceAcl) => {
|
||||
storedRole.push(workspaceAcl)
|
||||
},
|
||||
emitWorkspaceEvent: async () => [],
|
||||
storeBlob: async () => cryptoRandomString({ length: 10 })
|
||||
})
|
||||
|
||||
const workspaceInput = {
|
||||
description: 'foobar',
|
||||
logo: null,
|
||||
name: cryptoRandomString({ length: 6 })
|
||||
}
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspace = await createWorkspace({
|
||||
userId,
|
||||
workspaceInput
|
||||
})
|
||||
expect(storedRole.length).to.equal(1)
|
||||
expect(storedRole[0]).to.deep.equal({
|
||||
userId,
|
||||
workspaceId: workspace.id,
|
||||
role: Roles.Workspace.Admin
|
||||
})
|
||||
})
|
||||
it('emits a workspace created event', async () => {
|
||||
const eventData = {
|
||||
isCalled: false,
|
||||
eventName: '',
|
||||
payload: {}
|
||||
}
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
upsertWorkspace: async () => {},
|
||||
upsertWorkspaceRole: async () => {},
|
||||
emitWorkspaceEvent: async ({ eventName, payload }) => {
|
||||
eventData.isCalled = true
|
||||
eventData.eventName = eventName
|
||||
eventData.payload = payload
|
||||
return []
|
||||
},
|
||||
storeBlob: async () => cryptoRandomString({ length: 10 })
|
||||
})
|
||||
|
||||
const workspaceInput = {
|
||||
description: 'foobar',
|
||||
logo: null,
|
||||
name: cryptoRandomString({ length: 6 })
|
||||
}
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspace = await createWorkspace({
|
||||
userId,
|
||||
workspaceInput
|
||||
})
|
||||
|
||||
expect(eventData.isCalled).to.equal(true)
|
||||
expect(eventData.eventName).to.equal(WorkspaceEvents.Created)
|
||||
expect(eventData.payload).to.deep.equal({ ...workspace, createdByUserId: userId })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,172 +0,0 @@
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/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,
|
||||
eventName: '',
|
||||
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 ({ eventName, payload }) => {
|
||||
eventData.isCalled = true
|
||||
eventData.eventName = eventName
|
||||
eventData.payload = payload
|
||||
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
await deleteWorkspaceRole({ userId, workspaceId })
|
||||
|
||||
expect(eventData.isCalled).to.be.true
|
||||
expect(eventData.eventName).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,
|
||||
eventName: '',
|
||||
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 ({ eventName, payload }) => {
|
||||
eventData.isCalled = true
|
||||
eventData.eventName = eventName
|
||||
eventData.payload = payload
|
||||
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
await setWorkspaceRole(role)
|
||||
|
||||
expect(eventData.isCalled).to.be.true
|
||||
expect(eventData.eventName).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 })
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/utils/isUserLastWorkspaceAdmin'
|
||||
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/utils/roles'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { expect } from 'chai'
|
||||
import { Roles } from '@speckle/shared'
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
|
||||
export const isUserLastWorkspaceAdmin = (
|
||||
@@ -5,7 +6,7 @@ export const isUserLastWorkspaceAdmin = (
|
||||
userId: string
|
||||
): boolean => {
|
||||
const workspaceAdmins = workspaceRoles.filter(
|
||||
({ role }) => role === 'workspace:admin'
|
||||
({ role }) => role === Roles.Workspace.Admin
|
||||
)
|
||||
const isUserAdmin = workspaceAdmins.some((role) => role.userId === userId)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { WorkspacesModuleDisabledError } from '@/modules/core/errors/workspaces'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
export = !FF_WORKSPACES_MODULE_ENABLED
|
||||
? ({
|
||||
Query: {
|
||||
workspace: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
create: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
delete: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
update: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
updateRole: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
deleteRole: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
WorkspaceInviteMutations: {
|
||||
create: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
batchCreate: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
use: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
cancel: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
Workspace: {
|
||||
role: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
team: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
invitedTeam: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
},
|
||||
projects: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
User: {
|
||||
workspaces: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
Project: {
|
||||
workspace: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
},
|
||||
AdminQueries: {
|
||||
workspaceList: async () => {
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
}
|
||||
} as Resolvers)
|
||||
: {}
|
||||
@@ -67,6 +67,7 @@ export type AdminQueries = {
|
||||
projectList: ProjectCollection;
|
||||
serverStatistics: ServerStatistics;
|
||||
userList: AdminUserList;
|
||||
workspaceList: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -93,6 +94,13 @@ export type AdminQueriesUserListArgs = {
|
||||
role?: InputMaybe<ServerRole>;
|
||||
};
|
||||
|
||||
|
||||
export type AdminQueriesWorkspaceListArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUserList = {
|
||||
__typename?: 'AdminUserList';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1309,6 +1317,7 @@ export type Mutation = {
|
||||
webhookDelete: Scalars['String']['output'];
|
||||
/** Updates an existing webhook */
|
||||
webhookUpdate: Scalars['String']['output'];
|
||||
workspaceMutations: WorkspaceMutations;
|
||||
};
|
||||
|
||||
|
||||
@@ -1690,6 +1699,22 @@ export type PendingStreamCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaborator = {
|
||||
__typename?: 'PendingWorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
inviteId: Scalars['String']['output'];
|
||||
invitedBy: LimitedUser;
|
||||
role: Scalars['String']['output'];
|
||||
/** E-mail address or name of the invited user */
|
||||
title: Scalars['String']['output'];
|
||||
/** Only available if the active user is the pending workspace collaborator */
|
||||
token?: Maybe<Scalars['String']['output']>;
|
||||
/** Set only if user is registered */
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
__typename?: 'Project';
|
||||
allowPublicComments: Scalars['Boolean']['output'];
|
||||
@@ -1741,6 +1766,7 @@ export type Project = {
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2387,6 +2413,7 @@ export type Query = {
|
||||
* The query looks for matches in name & email
|
||||
*/
|
||||
userSearch: UserSearchResultCollection;
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
|
||||
@@ -2512,6 +2539,11 @@ export type QueryUserSearchArgs = {
|
||||
query: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryWorkspaceArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
/** Deprecated: Used by old stream-based mutations */
|
||||
export type ReplyCreateInput = {
|
||||
/** IDs of uploaded blobs that should be attached to this reply */
|
||||
@@ -2614,6 +2646,7 @@ export type ServerInfo = {
|
||||
serverRoles: Array<ServerRoleItem>;
|
||||
termsOfService?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
workspaces: ServerWorkspacesInfo;
|
||||
};
|
||||
|
||||
export type ServerInfoUpdateInput = {
|
||||
@@ -2682,6 +2715,15 @@ export type ServerStats = {
|
||||
userHistory?: Maybe<Array<Maybe<Scalars['JSONObject']['output']>>>;
|
||||
};
|
||||
|
||||
export type ServerWorkspacesInfo = {
|
||||
__typename?: 'ServerWorkspacesInfo';
|
||||
/**
|
||||
* This is a backend control variable for the workspaces feature set.
|
||||
* Since workspaces need a backend logic to be enabled, this is not enough as a feature flag.
|
||||
*/
|
||||
workspacesEnabled: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -3302,6 +3344,8 @@ export type User = {
|
||||
* Note: Only count resolution is currently implemented
|
||||
*/
|
||||
versions: CountOnlyCollection;
|
||||
/** Get the workspaces for the user */
|
||||
workspaces: WorkspaceCollection;
|
||||
};
|
||||
|
||||
|
||||
@@ -3389,6 +3433,17 @@ export type UserVersionsArgs = {
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 UserWorkspacesArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserWorkspacesFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type UserAutomateInfo = {
|
||||
__typename?: 'UserAutomateInfo';
|
||||
availableGithubOrgs: Array<Scalars['String']['output']>;
|
||||
@@ -3439,6 +3494,10 @@ export type UserUpdateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UserWorkspacesFilter = {
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Version = {
|
||||
__typename?: 'Version';
|
||||
authorUser?: Maybe<LimitedUser>;
|
||||
@@ -3651,6 +3710,152 @@ export type WebhookUpdateInput = {
|
||||
url?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
description?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
|
||||
name: Scalars['String']['output'];
|
||||
projects: ProjectCollection;
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
team: Array<WorkspaceCollaborator>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UserProjectsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceCollaborator = {
|
||||
__typename?: 'WorkspaceCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
export type WorkspaceCollection = {
|
||||
__typename?: 'WorkspaceCollection';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
items: Array<Workspace>;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceInviteCreateInput = {
|
||||
/** Either this or userId must be filled */
|
||||
email?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Defaults to the member role, if not specified */
|
||||
role?: InputMaybe<WorkspaceRole>;
|
||||
/** Either this or email must be filled */
|
||||
userId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteMutations = {
|
||||
__typename?: 'WorkspaceInviteMutations';
|
||||
batchCreate: Workspace;
|
||||
cancel: Workspace;
|
||||
create: Workspace;
|
||||
use: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsBatchCreateArgs = {
|
||||
input: Array<WorkspaceInviteCreateInput>;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCancelArgs = {
|
||||
inviteId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsCreateArgs = {
|
||||
input: WorkspaceInviteCreateInput;
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceInviteMutationsUseArgs = {
|
||||
input: WorkspaceInviteUseInput;
|
||||
};
|
||||
|
||||
export type WorkspaceInviteUseInput = {
|
||||
accept: Scalars['Boolean']['input'];
|
||||
token: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceMutations = {
|
||||
__typename?: 'WorkspaceMutations';
|
||||
create: Workspace;
|
||||
delete: Workspace;
|
||||
deleteRole: Scalars['Boolean']['output'];
|
||||
invites: WorkspaceInviteMutations;
|
||||
update: Workspace;
|
||||
updateRole: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsCreateArgs = {
|
||||
input: WorkspaceCreateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsDeleteRoleArgs = {
|
||||
input: WorkspaceRoleDeleteInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateArgs = {
|
||||
input: WorkspaceUpdateInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceMutationsUpdateRoleArgs = {
|
||||
input: WorkspaceRoleUpdateInput;
|
||||
};
|
||||
|
||||
export enum WorkspaceRole {
|
||||
Admin = 'ADMIN',
|
||||
Guest = 'GUEST',
|
||||
Member = 'MEMBER'
|
||||
}
|
||||
|
||||
export type WorkspaceRoleDeleteInput = {
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceRoleUpdateInput = {
|
||||
role: WorkspaceRole;
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceUpdateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
id: Scalars['String']['input'];
|
||||
logoUrl?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type BasicStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, requesterId: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } };
|
||||
|
||||
export type CreateStreamAccessRequestMutationVariables = Exact<{
|
||||
|
||||
@@ -113,6 +113,7 @@ export const Scopes = Object.freeze(<const>{
|
||||
},
|
||||
Workspaces: {
|
||||
Create: 'workspace:create',
|
||||
Read: 'workspace:read',
|
||||
Update: 'workspace:update',
|
||||
Delete: 'workspace:delete'
|
||||
}
|
||||
@@ -140,7 +141,7 @@ export type AvailableScopes =
|
||||
| AppScopes
|
||||
| AutomateScopes
|
||||
| AutomateFunctionScopes
|
||||
| WorkspaceRoles
|
||||
| WorkspaceScopes
|
||||
|
||||
/**
|
||||
* All scopes
|
||||
|
||||
Reference in New Issue
Block a user