From 85434fe309b8a39c87f289e3af2b27716fd8f31f Mon Sep 17 00:00:00 2001 From: forehalo Date: Thu, 23 Jan 2025 08:09:16 +0000 Subject: [PATCH] feat(server): search user in workspace (#9870) --- packages/backend/server/src/base/error/def.ts | 7 +++ .../server/src/base/error/errors.gen.ts | 13 +++++- .../core/workspaces/resolvers/workspace.ts | 44 +++++++++++++------ packages/backend/server/src/schema.gql | 9 +++- 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/packages/backend/server/src/base/error/def.ts b/packages/backend/server/src/base/error/def.ts index 3efa8d6bb8..4ad588e6d0 100644 --- a/packages/backend/server/src/base/error/def.ts +++ b/packages/backend/server/src/base/error/def.ts @@ -250,6 +250,13 @@ export const USER_FRIENDLY_ERRORS = { message: 'Resource not found.', }, + // Input errors + query_too_long: { + type: 'invalid_input', + args: { max: 'number' }, + message: ({ max }) => `Query is too long, max length is ${max}.`, + }, + // User Errors user_not_found: { type: 'resource_not_found', diff --git a/packages/backend/server/src/base/error/errors.gen.ts b/packages/backend/server/src/base/error/errors.gen.ts index 99fb8a2f1a..2d141f77ab 100644 --- a/packages/backend/server/src/base/error/errors.gen.ts +++ b/packages/backend/server/src/base/error/errors.gen.ts @@ -21,6 +21,16 @@ export class NotFound extends UserFriendlyError { super('resource_not_found', 'not_found', message); } } +@ObjectType() +class QueryTooLongDataType { + @Field() max!: number +} + +export class QueryTooLong extends UserFriendlyError { + constructor(args: QueryTooLongDataType, message?: string | ((args: QueryTooLongDataType) => string)) { + super('invalid_input', 'query_too_long', message, args); + } +} export class UserNotFound extends UserFriendlyError { constructor(message?: string) { @@ -645,6 +655,7 @@ export enum ErrorNames { INTERNAL_SERVER_ERROR, TOO_MANY_REQUEST, NOT_FOUND, + QUERY_TOO_LONG, USER_NOT_FOUND, USER_AVATAR_NOT_FOUND, EMAIL_ALREADY_USED, @@ -735,5 +746,5 @@ registerEnumType(ErrorNames, { export const ErrorDataUnionType = createUnionType({ name: 'ErrorDataUnion', types: () => - [WrongSignInCredentialsDataType, UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType] as const, + [QueryTooLongDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, DocNotFoundDataType, DocAccessDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType] as const, }); diff --git a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts index 2e7bad2a27..a73bc619ab 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts @@ -10,7 +10,7 @@ import { ResolveField, Resolver, } from '@nestjs/graphql'; -import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client'; +import { Prisma, PrismaClient, WorkspaceMemberStatus } from '@prisma/client'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import type { FileUpload } from '../../../base'; @@ -21,6 +21,7 @@ import { EventEmitter, InternalServerError, MemberQuotaExceeded, + QueryTooLong, RequestMutex, SpaceAccessDenied, SpaceNotFound, @@ -148,25 +149,42 @@ export class WorkspaceResolver { async members( @Parent() workspace: WorkspaceType, @Args('skip', { type: () => Int, nullable: true }) skip?: number, - @Args('take', { type: () => Int, nullable: true }) take?: number + @Args('take', { type: () => Int, nullable: true }) take?: number, + @Args('query', { type: () => String, nullable: true }) query?: string ) { - const data = await this.prisma.workspaceUserPermission.findMany({ + const args: Prisma.WorkspaceUserPermissionFindManyArgs = { where: { workspaceId: workspace.id }, skip, take: take || 8, orderBy: [{ createdAt: 'asc' }, { type: 'desc' }], - include: { user: true }, + }; + + if (query) { + if (query.length > 255) { + throw new QueryTooLong({ max: 255 }); + } + + // @ts-expect-error not null + args.where.user = { + // TODO(@forehalo): case-insensitive search later + OR: [{ name: { contains: query } }, { email: { contains: query } }], + }; + } + + const data = await this.prisma.workspaceUserPermission.findMany({ + ...args, + include: { + user: true, + }, }); - return data - .filter(({ user }) => !!user) - .map(({ id, accepted, status, type, user }) => ({ - ...user, - permission: type, - inviteId: id, - accepted, - status, - })); + return data.map(({ id, accepted, status, type, user }) => ({ + ...user, + permission: type, + inviteId: id, + accepted, + status, + })); } @ResolveField(() => WorkspacePageMeta, { diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index ff9f512984..69c0e60d4c 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -209,7 +209,7 @@ type EditorType { name: String! } -union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WrongSignInCredentialsDataType +union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WrongSignInCredentialsDataType enum ErrorNames { ACCESS_DENIED @@ -271,6 +271,7 @@ enum ErrorNames { OAUTH_STATE_EXPIRED PAGE_IS_NOT_PUBLIC PASSWORD_REQUIRED + QUERY_TOO_LONG RUNTIME_CONFIG_NOT_FOUND SAME_EMAIL_PROVIDED SAME_SUBSCRIPTION_RECURRING @@ -692,6 +693,10 @@ input QueryChatHistoriesInput { skip: Int } +type QueryTooLongDataType { + max: Int! +} + type QuotaQueryType { blobLimit: SafeInt! copilotActionLimit: SafeInt @@ -1059,7 +1064,7 @@ type WorkspaceType { memberCount: Int! """Members of workspace""" - members(skip: Int, take: Int): [InviteUserType!]! + members(query: String, skip: Int, take: Int): [InviteUserType!]! """Owner of workspace""" owner: UserType!