diff --git a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql index 97d360cb60..ab9797c64d 100644 --- a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql +++ b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql @@ -311,6 +311,17 @@ type Workspace { Info about the workspace creation state """ creationState: WorkspaceCreationState + """ + Get all join requests for all the workspaces the user is an admin of + """ + adminWorkspacesJoinRequests( + filter: AdminWorkspaceJoinRequestFilter + cursor: String + limit: Int! = 25 + ): WorkspaceJoinRequestCollection! + @hasServerRole(role: SERVER_USER) + @hasScope(scope: "workspace:read") + @hasWorkspaceRole(role: ADMIN) } type WorkspaceCreationState { @@ -450,6 +461,29 @@ type WorkspaceCollection { items: [Workspace!]! } +type WorkspaceJoinRequestCollection { + totalCount: Int! + cursor: String + items: [WorkspaceJoinRequest!]! +} + +type WorkspaceJoinRequest { + workspace: Workspace! + user: LimitedUser! + status: WorkspaceJoinRequestStatus! + createdAt: DateTime! +} + +enum WorkspaceJoinRequestStatus { + pending + accepted + denied +} + +input AdminWorkspaceJoinRequestFilter { + status: WorkspaceJoinRequestStatus +} + extend type User { """ Get discoverable workspaces with verified domains that match the active user's diff --git a/packages/server/codegen.yml b/packages/server/codegen.yml index 24a7a59011..3a977571d5 100644 --- a/packages/server/codegen.yml +++ b/packages/server/codegen.yml @@ -64,6 +64,7 @@ generates: WorkspaceBillingMutations: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceBillingMutationsGraphQLReturn' PendingWorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#PendingWorkspaceCollaboratorGraphQLReturn' WorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceCollaboratorGraphQLReturn' + WorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn' Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn' SmartTextEditorValue: '@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn' BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem' diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index fb5c4c9079..40dc3530db 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -5,7 +5,7 @@ import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes'; import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types'; import { AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes'; -import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes'; +import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes'; import { WorkspaceBillingMutationsGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes'; import { WebhookGraphQLReturn } from '@/modules/webhooks/helpers/graphTypes'; import { SmartTextEditorValueGraphQLReturn } from '@/modules/core/services/richTextEditorService'; @@ -176,6 +176,10 @@ export type AdminUsersListItem = { registeredUser?: Maybe; }; +export type AdminWorkspaceJoinRequestFilter = { + status?: InputMaybe; +}; + export type ApiToken = { __typename?: 'ApiToken'; createdAt: Scalars['DateTime']['output']; @@ -4168,6 +4172,8 @@ export type WebhookUpdateInput = { export type Workspace = { __typename?: 'Workspace'; + /** Get all join requests for all the workspaces the user is an admin of */ + adminWorkspacesJoinRequests: WorkspaceJoinRequestCollection; automateFunctions: AutomateFunctionCollection; createdAt: Scalars['DateTime']['output']; /** Info about the workspace creation state */ @@ -4211,6 +4217,13 @@ export type Workspace = { }; +export type WorkspaceAdminWorkspacesJoinRequestsArgs = { + cursor?: InputMaybe; + filter?: InputMaybe; + limit?: Scalars['Int']['input']; +}; + + export type WorkspaceAutomateFunctionsArgs = { cursor?: InputMaybe; filter?: InputMaybe; @@ -4396,6 +4409,27 @@ export type WorkspaceInviteUseInput = { token: Scalars['String']['input']; }; +export type WorkspaceJoinRequest = { + __typename?: 'WorkspaceJoinRequest'; + createdAt: Scalars['DateTime']['output']; + status: WorkspaceJoinRequestStatus; + user: LimitedUser; + workspace: Workspace; +}; + +export type WorkspaceJoinRequestCollection = { + __typename?: 'WorkspaceJoinRequestCollection'; + cursor?: Maybe; + items: Array; + totalCount: Scalars['Int']['output']; +}; + +export enum WorkspaceJoinRequestStatus { + Accepted = 'accepted', + Denied = 'denied', + Pending = 'pending' +} + export type WorkspaceMutations = { __typename?: 'WorkspaceMutations'; addDomain: Workspace; @@ -4742,6 +4776,7 @@ export type ResolversTypes = { AdminUserListItem: ResolverTypeWrapper; AdminUsersListCollection: ResolverTypeWrapper & { items: Array }>; AdminUsersListItem: ResolverTypeWrapper & { invitedUser?: Maybe, registeredUser?: Maybe }>; + AdminWorkspaceJoinRequestFilter: AdminWorkspaceJoinRequestFilter; ApiToken: ResolverTypeWrapper; ApiTokenCreateInput: ApiTokenCreateInput; AppAuthor: ResolverTypeWrapper; @@ -5002,6 +5037,9 @@ export type ResolversTypes = { WorkspaceInviteMutations: ResolverTypeWrapper; WorkspaceInviteResendInput: WorkspaceInviteResendInput; WorkspaceInviteUseInput: WorkspaceInviteUseInput; + WorkspaceJoinRequest: ResolverTypeWrapper; + WorkspaceJoinRequestCollection: ResolverTypeWrapper & { items: Array }>; + WorkspaceJoinRequestStatus: WorkspaceJoinRequestStatus; WorkspaceMutations: ResolverTypeWrapper; WorkspacePlan: ResolverTypeWrapper; WorkspacePlanStatuses: WorkspacePlanStatuses; @@ -5040,6 +5078,7 @@ export type ResolversParentTypes = { AdminUserListItem: AdminUserListItem; AdminUsersListCollection: Omit & { items: Array }; AdminUsersListItem: Omit & { invitedUser?: Maybe, registeredUser?: Maybe }; + AdminWorkspaceJoinRequestFilter: AdminWorkspaceJoinRequestFilter; ApiToken: ApiToken; ApiTokenCreateInput: ApiTokenCreateInput; AppAuthor: AppAuthor; @@ -5275,6 +5314,8 @@ export type ResolversParentTypes = { WorkspaceInviteMutations: WorkspaceInviteMutationsGraphQLReturn; WorkspaceInviteResendInput: WorkspaceInviteResendInput; WorkspaceInviteUseInput: WorkspaceInviteUseInput; + WorkspaceJoinRequest: WorkspaceJoinRequestGraphQLReturn; + WorkspaceJoinRequestCollection: Omit & { items: Array }; WorkspaceMutations: WorkspaceMutationsGraphQLReturn; WorkspacePlan: WorkspacePlan; WorkspaceProjectCreateInput: WorkspaceProjectCreateInput; @@ -6719,6 +6760,7 @@ export type WebhookEventCollectionResolvers = { + adminWorkspacesJoinRequests?: Resolver>; automateFunctions?: Resolver>; createdAt?: Resolver; creationState?: Resolver, ParentType, ContextType>; @@ -6797,6 +6839,21 @@ export type WorkspaceInviteMutationsResolvers; }; +export type WorkspaceJoinRequestResolvers = { + createdAt?: Resolver; + status?: Resolver; + user?: Resolver; + workspace?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type WorkspaceJoinRequestCollectionResolvers = { + cursor?: Resolver, ParentType, ContextType>; + items?: Resolver, ParentType, ContextType>; + totalCount?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type WorkspaceMutationsResolvers = { addDomain?: Resolver>; billing?: Resolver; @@ -7026,6 +7083,8 @@ export type Resolvers = { WorkspaceCreationState?: WorkspaceCreationStateResolvers; WorkspaceDomain?: WorkspaceDomainResolvers; WorkspaceInviteMutations?: WorkspaceInviteMutationsResolvers; + WorkspaceJoinRequest?: WorkspaceJoinRequestResolvers; + WorkspaceJoinRequestCollection?: WorkspaceJoinRequestCollectionResolvers; WorkspaceMutations?: WorkspaceMutationsResolvers; WorkspacePlan?: WorkspacePlanResolvers; WorkspaceProjectMutations?: WorkspaceProjectMutationsResolvers; diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 804fe3d907..b907c0cc74 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -157,6 +157,10 @@ export type AdminUsersListItem = { registeredUser?: Maybe; }; +export type AdminWorkspaceJoinRequestFilter = { + status?: InputMaybe; +}; + export type ApiToken = { __typename?: 'ApiToken'; createdAt: Scalars['DateTime']['output']; @@ -4149,6 +4153,8 @@ export type WebhookUpdateInput = { export type Workspace = { __typename?: 'Workspace'; + /** Get all join requests for all the workspaces the user is an admin of */ + adminWorkspacesJoinRequests: WorkspaceJoinRequestCollection; automateFunctions: AutomateFunctionCollection; createdAt: Scalars['DateTime']['output']; /** Info about the workspace creation state */ @@ -4192,6 +4198,13 @@ export type Workspace = { }; +export type WorkspaceAdminWorkspacesJoinRequestsArgs = { + cursor?: InputMaybe; + filter?: InputMaybe; + limit?: Scalars['Int']['input']; +}; + + export type WorkspaceAutomateFunctionsArgs = { cursor?: InputMaybe; filter?: InputMaybe; @@ -4377,6 +4390,27 @@ export type WorkspaceInviteUseInput = { token: Scalars['String']['input']; }; +export type WorkspaceJoinRequest = { + __typename?: 'WorkspaceJoinRequest'; + createdAt: Scalars['DateTime']['output']; + status: WorkspaceJoinRequestStatus; + user: LimitedUser; + workspace: Workspace; +}; + +export type WorkspaceJoinRequestCollection = { + __typename?: 'WorkspaceJoinRequestCollection'; + cursor?: Maybe; + items: Array; + totalCount: Scalars['Int']['output']; +}; + +export enum WorkspaceJoinRequestStatus { + Accepted = 'accepted', + Denied = 'denied', + Pending = 'pending' +} + export type WorkspaceMutations = { __typename?: 'WorkspaceMutations'; addDomain: Workspace; diff --git a/packages/server/modules/shared/services/paginatedItems.ts b/packages/server/modules/shared/services/paginatedItems.ts index 7a0f902758..4960b46d53 100644 --- a/packages/server/modules/shared/services/paginatedItems.ts +++ b/packages/server/modules/shared/services/paginatedItems.ts @@ -20,7 +20,7 @@ export const getPaginatedItemsFactory = getTotalCount }: { getItems: (args: TArgs) => Promise - getTotalCount: (args: TArgs) => Promise + getTotalCount: (args: Omit) => Promise }) => async (args: TArgs): Promise> => { const maybeDecodedCursor = args.cursor ? decodeIsoDateCursor(args.cursor) : null diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaceJoinRequests.ts b/packages/server/modules/workspaces/graph/resolvers/workspaceJoinRequests.ts new file mode 100644 index 0000000000..e7c66549b3 --- /dev/null +++ b/packages/server/modules/workspaces/graph/resolvers/workspaceJoinRequests.ts @@ -0,0 +1,49 @@ +import { db } from '@/db/knex' +import { Resolvers } from '@/modules/core/graph/generated/graphql' +import { getPaginatedItemsFactory } from '@/modules/shared/services/paginatedItems' +import { + countAdminWorkspaceJoinRequestsFactory, + getAdminWorkspaceJoinRequestsFactory +} from '@/modules/workspaces/repositories/workspaceJoinRequests' +import { WorkspaceJoinRequestStatus } from '@/modules/workspacesCore/domain/types' +import { WorkspaceJoinRequestGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes' + +export default { + Workspace: { + adminWorkspacesJoinRequests: async (parent, args, ctx) => { + const { filter, cursor, limit } = args + + return await getPaginatedItemsFactory< + { + limit: number + cursor?: string + filter: { + workspaceId: string + userId: string + status?: WorkspaceJoinRequestStatus | null + } + }, + WorkspaceJoinRequestGraphQLReturn + >({ + getItems: getAdminWorkspaceJoinRequestsFactory({ db }), + getTotalCount: countAdminWorkspaceJoinRequestsFactory({ db }) + })({ + filter: { + workspaceId: parent.id, + status: filter?.status ?? undefined, + userId: ctx.userId! // This is the worskpace admin, not the request userId + }, + cursor: cursor ?? undefined, + limit + }) + } + }, + WorkspaceJoinRequest: { + user: async (parent, _args, ctx) => { + return await ctx.loaders.users.getUser.load(parent.userId) + }, + workspace: async (parent, _args, ctx) => { + return await ctx.loaders.workspaces!.getWorkspace.load(parent.workspaceId) + } + } +} as Resolvers diff --git a/packages/server/modules/workspaces/repositories/workspaceJoinRequests.ts b/packages/server/modules/workspaces/repositories/workspaceJoinRequests.ts index 8c3ff44a60..13a77feed3 100644 --- a/packages/server/modules/workspaces/repositories/workspaceJoinRequests.ts +++ b/packages/server/modules/workspaces/repositories/workspaceJoinRequests.ts @@ -2,8 +2,15 @@ import { CreateWorkspaceJoinRequest, UpdateWorkspaceJoinRequestStatus } from '@/modules/workspaces/domain/operations' -import { WorkspaceJoinRequest } from '@/modules/workspacesCore/domain/types' -import { WorkspaceJoinRequests } from '@/modules/workspacesCore/helpers/db' +import { + WorkspaceJoinRequest, + WorkspaceJoinRequestStatus +} from '@/modules/workspacesCore/domain/types' +import { + WorkspaceAcl, + WorkspaceJoinRequests +} from '@/modules/workspacesCore/helpers/db' +import { Roles } from '@speckle/shared' import { Knex } from 'knex' const tables = { @@ -27,3 +34,56 @@ export const updateWorkspaceJoinRequestStatusFactory = .onConflict(['workspaceId', 'userId']) .merge(['status']) } + +type WorkspaceJoinRequestFilter = { + workspaceId: string + status?: WorkspaceJoinRequestStatus | null + userId: string +} + +const adminWorkspaceJoinRequestsBaseQueryFactory = + (db: Knex) => (filter: WorkspaceJoinRequestFilter) => { + const query = tables + .workspaceJoinRequests(db) + .innerJoin( + WorkspaceAcl.name, + WorkspaceAcl.col.workspaceId, + WorkspaceJoinRequests.col.workspaceId + ) + .where(WorkspaceAcl.col.role, Roles.Workspace.Admin) + .where(WorkspaceAcl.col.userId, filter.userId) + .where(WorkspaceJoinRequests.col.workspaceId, filter.workspaceId) + if (filter.status) query.andWhere(WorkspaceJoinRequests.col.status, filter.status) + return query + } + +export const getAdminWorkspaceJoinRequestsFactory = + ({ db }: { db: Knex }) => + async ({ + filter, + cursor, + limit + }: { + filter: WorkspaceJoinRequestFilter + cursor?: string + limit: number + }) => { + const query = adminWorkspaceJoinRequestsBaseQueryFactory(db)(filter) + + if (cursor) { + query.andWhere(WorkspaceJoinRequests.col.createdAt, '<', cursor) + } + return await query + .select(WorkspaceJoinRequests.cols) + .orderBy(WorkspaceJoinRequests.col.createdAt, 'desc') + .limit(limit) + } + +export const countAdminWorkspaceJoinRequestsFactory = + ({ db }: { db: Knex }) => + async ({ filter }: { filter: WorkspaceJoinRequestFilter }) => { + const query = adminWorkspaceJoinRequestsBaseQueryFactory(db)(filter) + + const [res] = await query.count() + return parseInt(res.count.toString()) + } diff --git a/packages/server/modules/workspaces/tests/helpers/graphql.ts b/packages/server/modules/workspaces/tests/helpers/graphql.ts index f6a6266775..175ce9b600 100644 --- a/packages/server/modules/workspaces/tests/helpers/graphql.ts +++ b/packages/server/modules/workspaces/tests/helpers/graphql.ts @@ -323,3 +323,41 @@ export const dismissWorkspaceMutation = gql` } } ` + +export const requestToJoinWorkspaceMutation = gql` + mutation requestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) { + workspaceMutations { + requestToJoin(input: $input) + } + } +` + +export const getWorkspaceWithJoinRequestsQuery = gql` + query GetWorkspaceWithJoinRequests( + $workspaceId: String! + $filter: AdminWorkspaceJoinRequestFilter + $cursor: String + $limit: Int + ) { + workspace(id: $workspaceId) { + ...BasicWorkspace + adminWorkspacesJoinRequests(filter: $filter, cursor: $cursor, limit: $limit) { + items { + status + user { + id + name + } + workspace { + id + name + } + createdAt + } + cursor + totalCount + } + } + } + ${basicWorkspaceFragment} +` diff --git a/packages/server/modules/workspaces/tests/integration/workspaceJoinRequests.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/workspaceJoinRequests.graph.spec.ts new file mode 100644 index 0000000000..ec10b513b8 --- /dev/null +++ b/packages/server/modules/workspaces/tests/integration/workspaceJoinRequests.graph.spec.ts @@ -0,0 +1,141 @@ +import { db } from '@/db/knex' +import { createRandomString } from '@/modules/core/helpers/testHelpers' +import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation' +import { + BasicTestUser, + createAuthTokenForUser, + createTestUser +} from '@/test/authHelper' +import { + GetWorkspaceWithJoinRequestsDocument, + RequestToJoinWorkspaceDocument +} from '@/test/graphql/generated/graphql' +import { createTestContext, testApolloServer } from '@/test/graphqlHelper' +import { beforeEachContext } from '@/test/hooks' +import { AllScopes, Roles } from '@speckle/shared' +import { expect } from 'chai' +import { upsertWorkspaceRoleFactory } from '@/modules/workspaces/repositories/workspaces' + +async function login(user: BasicTestUser) { + const token = await createAuthTokenForUser(user.id, AllScopes) + return await testApolloServer({ + context: await createTestContext({ + auth: true, + userId: user.id, + token, + role: user.role, + scopes: AllScopes + }) + }) +} + +before(async () => { + await beforeEachContext() +}) + +describe('WorkspaceJoinRequests GQL', () => { + describe('Workspace.adminWorkspacesJoinRequests', () => { + it('should return the workspace join requests for the admin', async () => { + const admin = await createTestUser({ + name: 'admin user', + role: Roles.Server.User + }) + + const user1 = await createTestUser({ name: 'user 1', role: Roles.Server.User }) + const user2 = await createTestUser({ name: 'user 2', role: Roles.Server.User }) + + const workspace1 = { + id: createRandomString(), + name: 'Workspace 1', + ownerId: admin.id, + description: '' + } + await createTestWorkspace(workspace1, admin) + + const workspace2 = { + id: createRandomString(), + name: 'Workspace 2', + ownerId: admin.id, + description: '' + } + await createTestWorkspace(workspace2, admin) + + const nobodyWorkspace = { + id: createRandomString(), + name: 'nobody', + ownerId: admin.id, + description: '' + } + await createTestWorkspace(nobodyWorkspace, admin) + + const nonAdminWorkspace = { + id: createRandomString(), + name: 'nonadmin', + ownerId: admin.id, + description: '' + } + await createTestWorkspace(nonAdminWorkspace, admin) + await upsertWorkspaceRoleFactory({ db })({ + userId: admin.id, + workspaceId: nonAdminWorkspace.id, + role: Roles.Workspace.Member, + createdAt: new Date() + }) + + // User1 requests to join workspace1 + const sessionUser1 = await login(user1) + const joinReq1 = await sessionUser1.execute(RequestToJoinWorkspaceDocument, { + input: { + workspaceId: workspace1.id + } + }) + expect(joinReq1).to.not.haveGraphQLErrors() + + // User2 requests to join workspace2 + const sessionUser2 = await login(user2) + const joinReq2 = await sessionUser2.execute(RequestToJoinWorkspaceDocument, { + input: { + workspaceId: workspace2.id + } + }) + expect(joinReq2).to.not.haveGraphQLErrors() + + const sessionAdmin = await login(admin) + const workspace1Res = await sessionAdmin.execute( + GetWorkspaceWithJoinRequestsDocument, + { + workspaceId: workspace1.id + } + ) + expect(workspace1Res).to.not.haveGraphQLErrors() + + const { items: items1, totalCount: totalCount1 } = + workspace1Res.data!.workspace!.adminWorkspacesJoinRequests! + + expect(totalCount1).to.equal(1) + + expect(items1).to.have.length(1) + expect(items1[0].status).to.equal('pending') + expect(items1[0].workspace.id).to.equal(workspace1.id) + expect(items1[0].user.id).to.equal(user1.id) + + const workspace2Res = await sessionAdmin.execute( + GetWorkspaceWithJoinRequestsDocument, + { + workspaceId: workspace2.id + } + ) + expect(workspace2Res).to.not.haveGraphQLErrors() + + const { items: items2, totalCount: totalCount2 } = + workspace2Res.data!.workspace!.adminWorkspacesJoinRequests! + + expect(totalCount2).to.equal(1) + + expect(items2).to.have.length(1) + expect(items2[0].status).to.equal('pending') + expect(items2[0].workspace.id).to.equal(workspace2.id) + expect(items2[0].user.id).to.equal(user2.id) + }) + }) +}) diff --git a/packages/server/modules/workspacesCore/domain/types.ts b/packages/server/modules/workspacesCore/domain/types.ts index 65d4ff5f91..182d60cfa7 100644 --- a/packages/server/modules/workspacesCore/domain/types.ts +++ b/packages/server/modules/workspacesCore/domain/types.ts @@ -67,11 +67,7 @@ export type WorkspaceRegionAssignment = { createdAt: Date } -export type WorkspaceJoinRequestStatus = - | 'pending' - | 'accepted' - | 'rejected' - | 'dismissed' +export type WorkspaceJoinRequestStatus = 'pending' | 'accepted' | 'denied' | 'dismissed' export type WorkspaceJoinRequest = { workspaceId: string diff --git a/packages/server/modules/workspacesCore/helpers/graphTypes.ts b/packages/server/modules/workspacesCore/helpers/graphTypes.ts index 34cc0f81d3..427a315915 100644 --- a/packages/server/modules/workspacesCore/helpers/graphTypes.ts +++ b/packages/server/modules/workspacesCore/helpers/graphTypes.ts @@ -2,10 +2,11 @@ import { MutationsObjectGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { LimitedUserRecord } from '@/modules/core/helpers/types' import { WorkspaceSsoProviderRecord } from '@/modules/workspaces/domain/sso/types' import { WorkspaceTeamMember } from '@/modules/workspaces/domain/types' -import { Workspace } from '@/modules/workspacesCore/domain/types' +import { Workspace, WorkspaceJoinRequest } from '@/modules/workspacesCore/domain/types' import { WorkspaceRoles } from '@speckle/shared' export type WorkspaceGraphQLReturn = Workspace +export type WorkspaceJoinRequestGraphQLReturn = WorkspaceJoinRequest export type WorkspaceBillingGraphQLReturn = { parent: Workspace } export type WorkspaceSsoGraphQLReturn = WorkspaceSsoProviderRecord export type WorkspaceMutationsGraphQLReturn = MutationsObjectGraphQLReturn diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index a78e5a42a5..8212f5b51c 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -158,6 +158,10 @@ export type AdminUsersListItem = { registeredUser?: Maybe; }; +export type AdminWorkspaceJoinRequestFilter = { + status?: InputMaybe; +}; + export type ApiToken = { __typename?: 'ApiToken'; createdAt: Scalars['DateTime']['output']; @@ -4150,6 +4154,8 @@ export type WebhookUpdateInput = { export type Workspace = { __typename?: 'Workspace'; + /** Get all join requests for all the workspaces the user is an admin of */ + adminWorkspacesJoinRequests: WorkspaceJoinRequestCollection; automateFunctions: AutomateFunctionCollection; createdAt: Scalars['DateTime']['output']; /** Info about the workspace creation state */ @@ -4193,6 +4199,13 @@ export type Workspace = { }; +export type WorkspaceAdminWorkspacesJoinRequestsArgs = { + cursor?: InputMaybe; + filter?: InputMaybe; + limit?: Scalars['Int']['input']; +}; + + export type WorkspaceAutomateFunctionsArgs = { cursor?: InputMaybe; filter?: InputMaybe; @@ -4378,6 +4391,27 @@ export type WorkspaceInviteUseInput = { token: Scalars['String']['input']; }; +export type WorkspaceJoinRequest = { + __typename?: 'WorkspaceJoinRequest'; + createdAt: Scalars['DateTime']['output']; + status: WorkspaceJoinRequestStatus; + user: LimitedUser; + workspace: Workspace; +}; + +export type WorkspaceJoinRequestCollection = { + __typename?: 'WorkspaceJoinRequestCollection'; + cursor?: Maybe; + items: Array; + totalCount: Scalars['Int']['output']; +}; + +export enum WorkspaceJoinRequestStatus { + Accepted = 'accepted', + Denied = 'denied', + Pending = 'pending' +} + export type WorkspaceMutations = { __typename?: 'WorkspaceMutations'; addDomain: Workspace; @@ -4899,6 +4933,23 @@ export type DismissWorkspaceMutationVariables = Exact<{ export type DismissWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', dismiss: boolean } }; +export type RequestToJoinWorkspaceMutationVariables = Exact<{ + input: WorkspaceRequestToJoinInput; +}>; + + +export type RequestToJoinWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', requestToJoin: boolean } }; + +export type GetWorkspaceWithJoinRequestsQueryVariables = Exact<{ + workspaceId: Scalars['String']['input']; + filter?: InputMaybe; + cursor?: InputMaybe; + limit?: InputMaybe; +}>; + + +export type GetWorkspaceWithJoinRequestsQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, updatedAt: string, createdAt: string, role?: string | null, readOnly: boolean, adminWorkspacesJoinRequests: { __typename?: 'WorkspaceJoinRequestCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'WorkspaceJoinRequest', status: WorkspaceJoinRequestStatus, createdAt: string, user: { __typename?: 'LimitedUser', id: string, name: string }, workspace: { __typename?: 'Workspace', id: string, name: string } }> } } }; + export type BasicStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, requesterId: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } }; export type CreateStreamAccessRequestMutationVariables = Exact<{ @@ -5620,6 +5671,8 @@ export const SetWorkspaceDefaultRegionDocument = {"kind":"Document","definitions export const OnWorkspaceProjectsUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OnWorkspaceProjectsUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceProjectsUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceSlug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const OnWorkspaceUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OnWorkspaceUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceSlug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode; export const DismissWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"dismissWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceDismissInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dismiss"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode; +export const RequestToJoinWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"requestToJoinWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceRequestToJoinInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestToJoin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode; +export const GetWorkspaceWithJoinRequestsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithJoinRequests"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"AdminWorkspaceJoinRequestFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode; export const CreateStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequestCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const GetStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const GetFullStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFullStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}},{"kind":"Field","name":{"kind":"Name","value":"stream"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode;