Skip to content

Commit

Permalink
feat(workspaces): list workspace join requests for admin
Browse files Browse the repository at this point in the history
  • Loading branch information
alemagio committed Jan 14, 2025
1 parent fd7a8d4 commit 17af665
Show file tree
Hide file tree
Showing 11 changed files with 609 additions and 158 deletions.
34 changes: 34 additions & 0 deletions packages/server/assets/workspacesCore/typedefs/workspaces.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ extend type Query {
Validates the slug, to make sure it contains only valid characters and its not taken.
"""
validateWorkspaceSlug(slug: String!): Boolean!

"""
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")
}

input WorkspaceInviteLookupOptions {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions packages/server/codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ generates:
- 'typescript-resolvers'
config:
contextType: '@/modules/shared/helpers/typeHelper#GraphQLContext'
allowParentTypeOverride: true
mappers:
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn'
Commit: '@/modules/core/helpers/graphTypes#CommitGraphQLReturn'
Expand Down Expand Up @@ -64,6 +65,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'
Expand Down Expand Up @@ -105,6 +107,8 @@ config:
scalars:
JSONObject: Record<string, unknown>
DateTime: Date
enumValues:
WorkspaceJoinRequestStatus: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestStatusGraphQLReturn'
require:
- ts-node/register
- tsconfig-paths/register
366 changes: 213 additions & 153 deletions packages/server/modules/core/graph/generated/graphql.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WorkspaceJoinRequestStatusGraphQLReturn as WorkspaceJoinRequestStatus } from '@/modules/workspacesCore/helpers/graphTypes';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
Expand Down Expand Up @@ -157,6 +158,10 @@ export type AdminUsersListItem = {
registeredUser?: Maybe<User>;
};

export type AdminWorkspaceJoinRequestFilter = {
status?: InputMaybe<WorkspaceJoinRequestStatus>;
};

export type ApiToken = {
__typename?: 'ApiToken';
createdAt: Scalars['DateTime']['output'];
Expand Down Expand Up @@ -2506,6 +2511,8 @@ export type Query = {
* @deprecated use admin.UserList instead
*/
adminUsers?: Maybe<AdminUsersListCollection>;
/** Get all join requests for all the workspaces the user is an admin of */
adminWorkspacesJoinRequests: WorkspaceJoinRequestCollection;
/** Gets a specific app from the server. */
app?: Maybe<ServerApp>;
/**
Expand Down Expand Up @@ -2633,6 +2640,13 @@ export type QueryAdminUsersArgs = {
};


export type QueryAdminWorkspacesJoinRequestsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<AdminWorkspaceJoinRequestFilter>;
limit?: Scalars['Int']['input'];
};


export type QueryAppArgs = {
id: Scalars['String']['input'];
};
Expand Down Expand Up @@ -4377,6 +4391,23 @@ 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<Scalars['String']['output']>;
items: Array<WorkspaceJoinRequest>;
totalCount: Scalars['Int']['output'];
};

export { WorkspaceJoinRequestStatus };

export type WorkspaceMutations = {
__typename?: 'WorkspaceMutations';
addDomain: Workspace;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/modules/shared/services/paginatedItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const getPaginatedItemsFactory =
getTotalCount
}: {
getItems: (args: TArgs) => Promise<T[]>
getTotalCount: (args: TArgs) => Promise<number>
getTotalCount: (args: Omit<TArgs, 'cursor' | 'limit'>) => Promise<number>
}) =>
async (args: TArgs): Promise<Collection<T>> => {
const maybeDecodedCursor = args.cursor ? decodeIsoDateCursor(args.cursor) : null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 {
Query: {
adminWorkspacesJoinRequests: async (_parent, args, ctx) => {
const { filter, cursor, limit } = args

return await getPaginatedItemsFactory<
{
limit: number
cursor?: string
filter: {
userId: string
status?: WorkspaceJoinRequestStatus | null
}
},
WorkspaceJoinRequestGraphQLReturn
>({
getItems: getAdminWorkspaceJoinRequestsFactory({ db }),
getTotalCount: countAdminWorkspaceJoinRequestsFactory({ db })
})({
filter: {
status: filter?.status ?? undefined,
userId: ctx.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
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -31,3 +38,54 @@ export const updateWorkspaceJoinRequestStatusFactory =
.returning('*')
return request
}

type WorkspaceJoinRequestFilter = {
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)
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<WorkspaceJoinRequest>(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())
}
33 changes: 33 additions & 0 deletions packages/server/modules/workspaces/tests/helpers/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,36 @@ export const dismissWorkspaceMutation = gql`
}
}
`

export const requestToJoinWorkspaceMutation = gql`
mutation requestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {
workspaceMutations {
requestToJoin(input: $input)
}
}
`

export const adminWorkspaceJoinRequestsQuery = gql`
query adminWorkspaceJoinRequests(
$filter: AdminWorkspaceJoinRequestFilter
$cursor: String
$limit: Int
) {
adminWorkspacesJoinRequests(filter: $filter, cursor: $cursor, limit: $limit) {
items {
status
user {
id
name
}
workspace {
id
name
}
createdAt
}
cursor
totalCount
}
}
`
Loading

0 comments on commit 17af665

Please sign in to comment.