diff --git a/graphql/index.tsx b/graphql/index.tsx index 56dfa13d4..9dec5afe1 100644 --- a/graphql/index.tsx +++ b/graphql/index.tsx @@ -160,6 +160,7 @@ export type Mutation = { deleteExercise: Exercise deleteModule: Module editComment?: Maybe + editExerciseComment: ExerciseComment flagExercise?: Maybe login?: Maybe logout?: Maybe @@ -278,6 +279,11 @@ export type MutationEditCommentArgs = { id: Scalars['Int'] } +export type MutationEditExerciseCommentArgs = { + content: Scalars['String'] + id: Scalars['Int'] +} + export type MutationFlagExerciseArgs = { flagReason: Scalars['String'] id: Scalars['Int'] @@ -504,6 +510,57 @@ export type UserLesson = { userId?: Maybe } +export type LessonAndChallengeInfoFragment = { + __typename?: 'Lesson' + id: number + docUrl?: string | null + githubUrl?: string | null + videoUrl?: string | null + chatUrl?: string | null + order: number + description: string + title: string + challenges: Array<{ + __typename?: 'Challenge' + id: number + description: string + lessonId: number + title: string + order: number + }> +} + +export type SubmissionsInfoFragment = { + __typename?: 'Submission' + id: number + status: SubmissionStatus + diff?: string | null + comment?: string | null + challengeId: number + lessonId: number + createdAt?: string | null + updatedAt: string + challenge: { __typename?: 'Challenge'; title: string; description: string } + user: { __typename?: 'User'; id: number; username: string } + reviewer?: { + __typename?: 'User' + id: number + username: string + name: string + } | null + comments?: Array<{ + __typename?: 'Comment' + id: number + content: string + submissionId: number + createdAt: string + authorId: number + line?: number | null + fileName?: string | null + author?: { __typename?: 'User'; username: string; name: string } | null + }> | null +} + export type AcceptSubmissionMutationVariables = Exact<{ submissionId: Scalars['Int'] comment: Scalars['String'] @@ -805,57 +862,6 @@ export type FlagExerciseMutation = { flagExercise?: { __typename?: 'Exercise'; id: number } | null } -export type LessonAndChallengeInfoFragment = { - __typename?: 'Lesson' - id: number - docUrl?: string | null - githubUrl?: string | null - videoUrl?: string | null - chatUrl?: string | null - order: number - description: string - title: string - challenges: Array<{ - __typename?: 'Challenge' - id: number - description: string - lessonId: number - title: string - order: number - }> -} - -export type SubmissionsInfoFragment = { - __typename?: 'Submission' - id: number - status: SubmissionStatus - diff?: string | null - comment?: string | null - challengeId: number - lessonId: number - createdAt?: string | null - updatedAt: string - challenge: { __typename?: 'Challenge'; title: string; description: string } - user: { __typename?: 'User'; id: number; username: string } - reviewer?: { - __typename?: 'User' - id: number - username: string - name: string - } | null - comments?: Array<{ - __typename?: 'Comment' - id: number - content: string - submissionId: number - createdAt: string - authorId: number - line?: number | null - fileName?: string | null - author?: { __typename?: 'User'; username: string; name: string } | null - }> | null -} - export type GetAppQueryVariables = Exact<{ [key: string]: never }> export type GetAppQuery = { @@ -1543,6 +1549,20 @@ export type UserInfoQuery = { } | null } +export type EditExerciseCommentMutationVariables = Exact<{ + id: Scalars['Int'] + content: Scalars['String'] +}> + +export type EditExerciseCommentMutation = { + __typename?: 'Mutation' + editExerciseComment: { + __typename?: 'ExerciseComment' + id: number + content: string + } +} + export type WithIndex = TObject & Record export type ResolversObject = WithIndex @@ -1991,6 +2011,12 @@ export type MutationResolvers< ContextType, RequireFields > + editExerciseComment?: Resolver< + ResolversTypes['ExerciseComment'], + ParentType, + ContextType, + RequireFields + > flagExercise?: Resolver< Maybe, ParentType, @@ -6691,6 +6717,89 @@ export type UserInfoQueryResult = Apollo.QueryResult< UserInfoQuery, UserInfoQueryVariables > +export const EditExerciseCommentDocument = gql` + mutation editExerciseComment($id: Int!, $content: String!) { + editExerciseComment(id: $id, content: $content) { + id + content + } + } +` +export type EditExerciseCommentMutationFn = Apollo.MutationFunction< + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables +> +export type EditExerciseCommentProps< + TChildProps = {}, + TDataName extends string = 'mutate' +> = { + [key in TDataName]: Apollo.MutationFunction< + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables + > +} & TChildProps +export function withEditExerciseComment< + TProps, + TChildProps = {}, + TDataName extends string = 'mutate' +>( + operationOptions?: ApolloReactHoc.OperationOption< + TProps, + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables, + EditExerciseCommentProps + > +) { + return ApolloReactHoc.withMutation< + TProps, + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables, + EditExerciseCommentProps + >(EditExerciseCommentDocument, { + alias: 'editExerciseComment', + ...operationOptions + }) +} + +/** + * __useEditExerciseCommentMutation__ + * + * To run a mutation, you first call `useEditExerciseCommentMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useEditExerciseCommentMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [editExerciseCommentMutation, { data, loading, error }] = useEditExerciseCommentMutation({ + * variables: { + * id: // value for 'id' + * content: // value for 'content' + * }, + * }); + */ +export function useEditExerciseCommentMutation( + baseOptions?: Apollo.MutationHookOptions< + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables + > +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useMutation< + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables + >(EditExerciseCommentDocument, options) +} +export type EditExerciseCommentMutationHookResult = ReturnType< + typeof useEditExerciseCommentMutation +> +export type EditExerciseCommentMutationResult = + Apollo.MutationResult +export type EditExerciseCommentMutationOptions = Apollo.BaseMutationOptions< + EditExerciseCommentMutation, + EditExerciseCommentMutationVariables +> export type AlertKeySpecifier = ( | 'id' | 'text' @@ -6897,6 +7006,7 @@ export type MutationKeySpecifier = ( | 'deleteExercise' | 'deleteModule' | 'editComment' + | 'editExerciseComment' | 'flagExercise' | 'login' | 'logout' @@ -6933,6 +7043,7 @@ export type MutationFieldPolicy = { deleteExercise?: FieldPolicy | FieldReadFunction deleteModule?: FieldPolicy | FieldReadFunction editComment?: FieldPolicy | FieldReadFunction + editExerciseComment?: FieldPolicy | FieldReadFunction flagExercise?: FieldPolicy | FieldReadFunction login?: FieldPolicy | FieldReadFunction logout?: FieldPolicy | FieldReadFunction diff --git a/graphql/queries/editExerciseComment.ts b/graphql/queries/editExerciseComment.ts new file mode 100644 index 000000000..209f6a975 --- /dev/null +++ b/graphql/queries/editExerciseComment.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client' + +const EDIT_EXERCISE_COMMENT = gql` + mutation editExerciseComment($id: Int!, $content: String!) { + editExerciseComment(id: $id, content: $content) { + id + content + } + } +` +export default EDIT_EXERCISE_COMMENT diff --git a/graphql/resolvers.ts b/graphql/resolvers.ts index 037b665db..84b0266c3 100644 --- a/graphql/resolvers.ts +++ b/graphql/resolvers.ts @@ -48,7 +48,8 @@ import { import { getExerciseComments, addExerciseComment, - getChildComments + getChildComments, + editExerciseComment } from './resolvers/exerciseCommentCrud' import { updateUserNames, updateUserPassword } from './resolvers/userDataCrud' @@ -103,6 +104,7 @@ export default { removeExercise, unlinkDiscord, addExerciseComment, + editExerciseComment, updateUserNames, updateUserPassword } diff --git a/graphql/resolvers/exerciseCommentCrud.test.js b/graphql/resolvers/exerciseCommentCrud.test.js index d58c7f0b5..0ac5bb6c1 100644 --- a/graphql/resolvers/exerciseCommentCrud.test.js +++ b/graphql/resolvers/exerciseCommentCrud.test.js @@ -2,7 +2,8 @@ import prismaMock from '../../__tests__/utils/prismaMock' import { addExerciseComment, getExerciseComments, - getChildComments + getChildComments, + editExerciseComment } from './exerciseCommentCrud' describe('addExerciseComment resolver tests', () => { @@ -126,3 +127,65 @@ describe('getChildComments resolver tests', () => { }) }) }) + +describe('editExerciseComment resolver tests', () => { + test('Should throw error if user is invalid or not loggedin', async () => { + const mockContext = { req: { user: null } } + const mockArgs = { + id: 1, + content: 'no user' + } + + await expect( + editExerciseComment(undefined, mockArgs, mockContext) + ).rejects.toEqual(new Error('No user')) + }) + + test('Should throw error if authorID does not match original post', async () => { + const mockContext = { req: { user: { id: 1 } } } + const mockArgs = { id: 2, content: 'no user match' } + const mockExerciseComment = { + id: 2, + exerciseId: 1, + authorId: 2, + parentId: 1, + content: 'there is user', + userPic: null + } + + prismaMock.exerciseComment.findUnique.mockResolvedValue(mockExerciseComment) + await expect( + editExerciseComment(undefined, mockArgs, mockContext) + ).rejects.toEqual(new Error('Comment is not by user')) + + expect(prismaMock.exerciseComment.findUnique).toBeCalledWith({ + where: { + id: 2 + } + }) + }) + + test('Should edit existing comment with matching id in prisma', async () => { + const mockContext = { req: { user: { id: 1 } } } + const mockArgs = { id: 2, content: 'new content' } + const mockExerciseComment = { + id: 2, + exerciseId: 1, + authorId: 1, + content: 'old content', + userPic: null + } + + prismaMock.exerciseComment.findUnique.mockResolvedValue(mockExerciseComment) + prismaMock.exerciseComment.update.mockResolvedValue(mockExerciseComment) + + await expect( + editExerciseComment(undefined, mockArgs, mockContext) + ).resolves.toEqual(mockExerciseComment) + + expect(prismaMock.exerciseComment.update).toBeCalledWith({ + where: { id: mockArgs.id }, + data: { content: mockArgs.content } + }) + }) +}) diff --git a/graphql/resolvers/exerciseCommentCrud.ts b/graphql/resolvers/exerciseCommentCrud.ts index bdb5537c2..bf500728e 100644 --- a/graphql/resolvers/exerciseCommentCrud.ts +++ b/graphql/resolvers/exerciseCommentCrud.ts @@ -1,9 +1,12 @@ import { MutationAddExerciseCommentArgs, + MutationEditExerciseCommentArgs, QueryGetExerciseCommentsArgs, QueryGetChildCommentsArgs } from '..' import { Context } from '../../@types/helpers' +import type { ExerciseComment } from '@prisma/client' +import { withUserContainer } from '../../containers/withUserContainer' import prisma from '../../prisma' export const getExerciseComments = async ( @@ -44,3 +47,32 @@ export const addExerciseComment = async ( data: { authorId, content, exerciseId, parentId, userPic } }) } + +export const editExerciseComment = withUserContainer< + Promise, + MutationEditExerciseCommentArgs +>( + async ( + _parent: void, + { id, content }: MutationEditExerciseCommentArgs, + context: Context + ) => { + const authorId = context.req.user?.id + + const exerciseComment = await prisma.exerciseComment.findUnique({ + where: { + id + } + }) + + if (exerciseComment?.authorId !== authorId) + throw new Error('Comment is not by user') + + return prisma.exerciseComment.update({ + where: { + id + }, + data: { content } + }) + } +) diff --git a/graphql/typeDefs.ts b/graphql/typeDefs.ts index 453106381..e20624130 100644 --- a/graphql/typeDefs.ts +++ b/graphql/typeDefs.ts @@ -103,6 +103,7 @@ export default gql` parentId: Int userPic: String ): ExerciseComment! + editExerciseComment(id: Int!, content: String!): ExerciseComment! createLesson( description: String! docUrl: String