From 1090d17fdb365da3e592c20ea86b7f2062c0535c Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 26 Aug 2022 08:32:15 +0200 Subject: [PATCH 1/7] Remove OneToMany import. --- database/entity/0047-messages_tables/ContributionMessage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/database/entity/0047-messages_tables/ContributionMessage.ts b/database/entity/0047-messages_tables/ContributionMessage.ts index 3d9c04d89..e5226043d 100644 --- a/database/entity/0047-messages_tables/ContributionMessage.ts +++ b/database/entity/0047-messages_tables/ContributionMessage.ts @@ -5,7 +5,6 @@ import { Entity, JoinColumn, ManyToOne, - OneToMany, PrimaryGeneratedColumn, } from 'typeorm' import { Contribution } from '../Contribution' From 894140b41837e8ab2a0a3c7a7d1dcfcb97ed456a Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 26 Aug 2022 08:50:08 +0200 Subject: [PATCH 2/7] Remove unused imports. --- backend/src/graphql/resolver/ContributionResolver.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 1b7a8f630..f8300f164 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -11,10 +11,8 @@ import { ContributionType } from '@enum/ContributionType' import { ContributionStatus } from '@enum/ContributionStatus' import { Contribution, ContributionListResult } from '@model/Contribution' import { UnconfirmedContribution } from '@model/UnconfirmedContribution' -import { User } from '@model/User' import { validateContribution, getUserCreation, updateCreations } from './util/creations' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' -import { ContributionMessage } from '@entity/ContributionMessage' @Resolver() export class ContributionResolver { From 423989777b91898b5a9b698956c72537aacd1a8f Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 29 Aug 2022 12:19:25 +0200 Subject: [PATCH 3/7] Remove leftJoin on user, changed ContributionMessage array to count of ContributionMessage. --- backend/src/graphql/model/Contribution.ts | 9 +++------ backend/src/graphql/resolver/ContributionResolver.ts | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/model/Contribution.ts b/backend/src/graphql/model/Contribution.ts index c0e6d66f3..1f690a3d8 100644 --- a/backend/src/graphql/model/Contribution.ts +++ b/backend/src/graphql/model/Contribution.ts @@ -2,7 +2,6 @@ import { ObjectType, Field, Int } from 'type-graphql' import Decimal from 'decimal.js-light' import { Contribution as dbContribution } from '@entity/Contribution' import { User } from '@entity/User' -import { ContributionMessage } from './ContributionMessage' @ObjectType() export class Contribution { @@ -18,9 +17,7 @@ export class Contribution { this.confirmedBy = contribution.confirmedBy this.contributionDate = contribution.contributionDate this.state = contribution.contributionStatus - this.messages = contribution.messages - ? contribution.messages.map((message) => new ContributionMessage(message, message.user)) - : [] + this.messagesCount = contribution.messages ? contribution.messages.length : 0 } @Field(() => Number) @@ -53,8 +50,8 @@ export class Contribution { @Field(() => Date) contributionDate: Date - @Field(() => [ContributionMessage]) - messages: ContributionMessage[] + @Field(() => Number) + messagesCount: number @Field(() => String) state: string diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index f8300f164..fc93880f1 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -97,7 +97,6 @@ export class ContributionResolver { .select('c') .from(dbContribution, 'c') .leftJoinAndSelect('c.messages', 'm') - .leftJoinAndSelect('m.user', 'u') .where(where) .orderBy('c.createdAt', order) .limit(pageSize) From 214f85544b64c82911c1a5c85d8ac09578bb61ae Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 29 Aug 2022 12:24:09 +0200 Subject: [PATCH 4/7] ContributionMessage is only defined in 0047 so remove the OneToMany relation from 0046 User and add it to 0047 User. --- .../User.ts | 5 - database/entity/0047-messages_tables/User.ts | 116 ++++++++++++++++++ database/entity/User.ts | 2 +- 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 database/entity/0047-messages_tables/User.ts diff --git a/database/entity/0046-adapt_users_table_for_gradidoid/User.ts b/database/entity/0046-adapt_users_table_for_gradidoid/User.ts index a772a3c99..3f2547cad 100644 --- a/database/entity/0046-adapt_users_table_for_gradidoid/User.ts +++ b/database/entity/0046-adapt_users_table_for_gradidoid/User.ts @@ -8,7 +8,6 @@ import { JoinColumn, } from 'typeorm' import { Contribution } from '../Contribution' -import { ContributionMessage } from '../ContributionMessage' @Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) export class User extends BaseEntity { @@ -109,8 +108,4 @@ export class User extends BaseEntity { @OneToMany(() => Contribution, (contribution) => contribution.user) @JoinColumn({ name: 'user_id' }) contributions?: Contribution[] - - @OneToMany(() => ContributionMessage, (message) => message.user) - @JoinColumn({ name: 'user_id' }) - messages?: ContributionMessage[] } diff --git a/database/entity/0047-messages_tables/User.ts b/database/entity/0047-messages_tables/User.ts new file mode 100644 index 000000000..a772a3c99 --- /dev/null +++ b/database/entity/0047-messages_tables/User.ts @@ -0,0 +1,116 @@ +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + DeleteDateColumn, + OneToMany, + JoinColumn, +} from 'typeorm' +import { Contribution } from '../Contribution' +import { ContributionMessage } from '../ContributionMessage' + +@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) +export class User extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ + name: 'gradido_id', + length: 36, + nullable: false, + unique: true, + collation: 'utf8mb4_unicode_ci', + }) + gradidoID: string + + @Column({ + name: 'alias', + length: 20, + nullable: true, + unique: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + alias: string + + @Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true }) + pubKey: Buffer + + @Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true }) + privKey: Buffer + + @Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' }) + email: string + + @Column({ + name: 'first_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + firstName: string + + @Column({ + name: 'last_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + lastName: string + + @DeleteDateColumn() + deletedAt: Date | null + + @Column({ type: 'bigint', default: 0, unsigned: true }) + password: BigInt + + @Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true }) + emailHash: Buffer + + @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false }) + createdAt: Date + + @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false }) + emailChecked: boolean + + @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) + language: string + + @Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null }) + isAdmin: Date | null + + @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) + referrerId?: number | null + + @Column({ + name: 'contribution_link_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + contributionLinkId?: number | null + + @Column({ name: 'publisher_id', default: 0 }) + publisherId: number + + @Column({ + type: 'text', + name: 'passphrase', + collation: 'utf8mb4_unicode_ci', + nullable: true, + default: null, + }) + passphrase: string + + @OneToMany(() => Contribution, (contribution) => contribution.user) + @JoinColumn({ name: 'user_id' }) + contributions?: Contribution[] + + @OneToMany(() => ContributionMessage, (message) => message.user) + @JoinColumn({ name: 'user_id' }) + messages?: ContributionMessage[] +} diff --git a/database/entity/User.ts b/database/entity/User.ts index 02a99fcd1..7d15bf559 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0046-adapt_users_table_for_gradidoid/User' +export { User } from './0047-messages_tables/User' From 7156f1afd5a0c9a3166acc441eaf7fc61bd286d6 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 29 Aug 2022 12:58:01 +0200 Subject: [PATCH 5/7] Setup Test file for ContributionMessageResolver. --- .../ContributionMessageResolver.test.ts | 47 +++++++++++++++++++ backend/src/seeds/graphql/mutations.ts | 28 +++++++++++ backend/src/seeds/graphql/queries.ts | 23 +++++++++ 3 files changed, 98 insertions(+) create mode 100644 backend/src/graphql/resolver/ContributionMessageResolver.test.ts diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts new file mode 100644 index 000000000..f98e4f211 --- /dev/null +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' +import { cleanDB, resetToken, testEnvironment } from '@test/helpers' +import { GraphQLError } from 'graphql' +import { userFactory } from '@/seeds/factory/user' +import { creationFactory } from '@/seeds/factory/creation' +import { creations } from '@/seeds/creation/index' +import { peterLustig } from '@/seeds/users/peter-lustig' +import { createContributionMessage } from '@/seeds/graphql/mutations' + +let mutate: any, query: any, con: any +let testEnv: any +let result: any + +beforeAll(async () => { + testEnv = await testEnvironment() + mutate = testEnv.mutate + query = testEnv.query + con = testEnv.con + await cleanDB() +}) + +afterAll(async () => { + await cleanDB() + await con.close() +}) + +describe('ContributionMessageResolver', () => { + describe('createContributionMessage', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: createContributionMessage, + variables: { contributionId: 1, message: 'This is a test message' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + }) +}) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index bf898bd7d..e5f290645 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -261,3 +261,31 @@ export const deleteContribution = gql` deleteContribution(id: $id) } ` + +export const createContributionMessage = gql` + mutation ($contributionId: Float!, $message: String!) { + createContributionMessage(contributionId: $contributionId, message: $message) { + id + message + createdAt + updatedAt + type + userFirstName + userLastName + } + } +` + +export const adminCreateContributionMessage = gql` + mutation ($contributionId: Float!, $message: String!) { + adminCreateContributionMessage(contributionId: $contributionId, message: $message) { + id + message + createdAt + updatedAt + type + userFirstName + userLastName + } + } +` diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 3bd042ac2..60dffa21b 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -292,3 +292,26 @@ export const searchAdminUsers = gql` } } ` + +export const listContributionMessages = gql` + query ($contributionId: Float!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) { + listContributionMessages( + contributionId: $contributionId + pageSize: $pageSize + currentPage: $currentPage + order: $order + ) { + count + messages { + id + message + createdAt + updatedAt + type + userFirstName + userLastName + userId + } + } + } +` From 8b62696c91aaa8116dc5960360d586f99a6dc136 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 29 Aug 2022 13:07:24 +0200 Subject: [PATCH 6/7] Add test that listContributionMessages throws error for non authenticated user. --- .../ContributionMessageResolver.test.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index f98e4f211..8d65db306 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -1,18 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' -import { cleanDB, resetToken, testEnvironment } from '@test/helpers' +import { cleanDB, testEnvironment } from '@test/helpers' import { GraphQLError } from 'graphql' -import { userFactory } from '@/seeds/factory/user' -import { creationFactory } from '@/seeds/factory/creation' -import { creations } from '@/seeds/creation/index' -import { peterLustig } from '@/seeds/users/peter-lustig' import { createContributionMessage } from '@/seeds/graphql/mutations' +import { listContributionMessages } from '@/seeds/graphql/queries' let mutate: any, query: any, con: any let testEnv: any -let result: any beforeAll(async () => { testEnv = await testEnvironment() @@ -44,4 +39,21 @@ describe('ContributionMessageResolver', () => { }) }) }) + + describe('listContributionMessages', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: listContributionMessages, + variables: { contributionId: 1 }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + }) }) From ac4f7dc04a193b4594cbf37740d3e81bf85d4794 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 29 Aug 2022 13:16:44 +0200 Subject: [PATCH 7/7] Add test with non existing contributionId. --- .../ContributionMessageResolver.test.ts | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index 8d65db306..c66a85700 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { cleanDB, testEnvironment } from '@test/helpers' +import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { GraphQLError } from 'graphql' import { createContributionMessage } from '@/seeds/graphql/mutations' -import { listContributionMessages } from '@/seeds/graphql/queries' +import { listContributionMessages, login } from '@/seeds/graphql/queries' +import { userFactory } from '@/seeds/factory/user' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' let mutate: any, query: any, con: any let testEnv: any @@ -38,6 +40,39 @@ describe('ContributionMessageResolver', () => { ) }) }) + + describe('authenticated', () => { + beforeAll(async () => { + await userFactory(testEnv, bibiBloxberg) + await query({ + query: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('input not valid', () => { + it('throws error when contribution does not exist', async () => { + await expect( + mutate({ + mutation: createContributionMessage, + variables: { + contributionId: -1, + message: 'Test', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found')], + }), + ) + }) + }) + }) }) describe('listContributionMessages', () => { @@ -55,5 +90,20 @@ describe('ContributionMessageResolver', () => { ) }) }) + + describe('authenticated', () => { + beforeAll(async () => { + await userFactory(testEnv, bibiBloxberg) + await query({ + query: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + }) }) })