From b513ea7906363c53c33dae7169b5120b92efaea2 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 09:30:27 +0100 Subject: [PATCH 01/15] set timezone to UTC for unti tests --- backend/jest.config.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 9d99c68f6..981475807 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,15 +1,18 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { - verbose: true, - preset: 'ts-jest', - collectCoverage: true, - collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], - moduleNameMapper: { - '@entity/(.*)': '/../database/build/entity/$1', - // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state - '@dbTools/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/src/$1' - : '/../database/build/src/$1', - }, +module.exports = async () => { + process.env.TZ = 'UTC' + return { + verbose: true, + preset: 'ts-jest', + collectCoverage: true, + collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], + moduleNameMapper: { + '@entity/(.*)': '/../database/build/entity/$1', + // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state + '@dbTools/(.*)': + process.env.NODE_ENV === 'development' + ? '/../database/src/$1' + : '/../database/build/src/$1', + }, + } } From 4bac268de394ea99f21e05c4fe89ce0fbdcad071 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 09:30:53 +0100 Subject: [PATCH 02/15] test setPassword with success --- backend/src/apis/KlicktippController.ts | 2 +- .../src/graphql/resolver/KlicktippResolver.ts | 4 +- .../src/graphql/resolver/UserResolver.test.ts | 103 ++++++++++++++++++ backend/src/graphql/resolver/UserResolver.ts | 9 +- backend/src/middleware/klicktippMiddleware.ts | 4 +- 5 files changed, 115 insertions(+), 7 deletions(-) diff --git a/backend/src/apis/KlicktippController.ts b/backend/src/apis/KlicktippController.ts index 544b39d97..0777211ad 100644 --- a/backend/src/apis/KlicktippController.ts +++ b/backend/src/apis/KlicktippController.ts @@ -5,7 +5,7 @@ import CONFIG from '../config' const klicktippConnector = new KlicktippConnector() -export const signIn = async ( +export const klicktippSignIn = async ( email: string, language: string, firstName?: string, diff --git a/backend/src/graphql/resolver/KlicktippResolver.ts b/backend/src/graphql/resolver/KlicktippResolver.ts index fdffb940a..0ba2387e3 100644 --- a/backend/src/graphql/resolver/KlicktippResolver.ts +++ b/backend/src/graphql/resolver/KlicktippResolver.ts @@ -6,7 +6,7 @@ import { getKlickTippUser, getKlicktippTagMap, unsubscribe, - signIn, + klicktippSignIn, } from '../../apis/KlicktippController' import { RIGHTS } from '../../auth/RIGHTS' import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs' @@ -36,6 +36,6 @@ export class KlicktippResolver { async subscribeNewsletter( @Args() { email, language }: SubscribeNewsletterArgs, ): Promise { - return await signIn(email, language) + return await klicktippSignIn(email, language) } } diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 1f0bce30f..fb4e42bf6 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -13,6 +13,7 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User } from '@entity/User' import CONFIG from '../../config' import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail' +import { klicktippSignIn } from '../../apis/KlicktippController' jest.mock('../../mailer/sendAccountActivationEmail', () => { return { @@ -21,6 +22,13 @@ jest.mock('../../mailer/sendAccountActivationEmail', () => { } }) +jest.mock('../../apis/KlicktippController', () => { + return { + __esModule: true, + klicktippSignIn: jest.fn(), + } +}) + let mutate: any let con: any @@ -220,6 +228,101 @@ describe('UserResolver', () => { }) }) }) + + describe('setPassword', () => { + const createUserMutation = gql` + mutation ( + $email: String! + $firstName: String! + $lastName: String! + $language: String! + $publisherId: Int + ) { + createUser( + email: $email + firstName: $firstName + lastName: $lastName + language: $language + publisherId: $publisherId + ) + } + ` + + const createUserVariables = { + email: 'peter@lustig.de', + firstName: 'Peter', + lastName: 'Lustig', + language: 'de', + publisherId: 1234, + } + + const setPasswordMutation = gql` + mutation ($code: String!, $password: String!) { + setPassword(code: $code, password: $password) + } + ` + + describe('valid optin code and valid password', () => { + let emailOptIn: string + let result: any + let loginUser: any + let newLoginUser: any + let newUser: any + + beforeAll(async () => { + await mutate({ mutation: createUserMutation, variables: createUserVariables }) + const loginEmailOptIn = await getRepository(LoginEmailOptIn) + .createQueryBuilder('login_email_optin') + .getMany() + loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany() + emailOptIn = loginEmailOptIn[0].verificationCode.toString() + result = await mutate({ + mutation: setPasswordMutation, + variables: { code: emailOptIn, password: 'Aa12345_' }, + }) + newLoginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany() + newUser = await getRepository(User).createQueryBuilder('state_user').getMany() + }) + + afterAll(async () => { + await resetDB() + }) + + it('updates the password', () => { + expect(newLoginUser[0].password).toEqual('3917921995996627700') + }) + + it('updates the public Key on both user tables', () => { + expect(newLoginUser[0].pubKey).toEqual(expect.any(Buffer)) + expect(newLoginUser[0].pubKey).not.toEqual(loginUser[0].pubKey) + expect(newLoginUser[0].pubKey).toEqual(newUser[0].pubkey) + }) + + it('updates the private Key', () => { + expect(newLoginUser[0].privKey).toEqual(expect.any(Buffer)) + expect(newLoginUser[0].privKey).not.toEqual(loginUser[0].privKey) + }) + + it('removes the optin', async () => { + await expect( + getRepository(LoginEmailOptIn).createQueryBuilder('login_email_optin').getMany(), + ).resolves.toHaveLength(0) + }) + + it('calls the klicktipp API', () => { + expect(klicktippSignIn).toBeCalledWith( + loginUser[0].email, + loginUser[0].language, + loginUser[0].firstName, + loginUser[0].lastName, + ) + }) + + it('returns true', () => { + expect(result).toBeTruthy() + }) + }) + }) }) afterAll(async () => { diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index b751eb633..c17bb06e9 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -23,7 +23,7 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { sendResetPasswordEmail } from '../../mailer/sendResetPasswordEmail' import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail' import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys' -import { signIn } from '../../apis/KlicktippController' +import { klicktippSignIn } from '../../apis/KlicktippController' import { RIGHTS } from '../../auth/RIGHTS' import { ServerUserRepository } from '../../typeorm/repository/ServerUser' import { ROLE_ADMIN } from '../../auth/ROLES' @@ -641,7 +641,12 @@ export class UserResolver { // TODO do we always signUp the user? How to handle things with old users? if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) { try { - await signIn(loginUser.email, loginUser.language, loginUser.firstName, loginUser.lastName) + await klicktippSignIn( + loginUser.email, + loginUser.language, + loginUser.firstName, + loginUser.lastName, + ) } catch { // TODO is this a problem? // eslint-disable-next-line no-console diff --git a/backend/src/middleware/klicktippMiddleware.ts b/backend/src/middleware/klicktippMiddleware.ts index 69a74480d..d0fde8195 100644 --- a/backend/src/middleware/klicktippMiddleware.ts +++ b/backend/src/middleware/klicktippMiddleware.ts @@ -1,5 +1,5 @@ import { MiddlewareFn } from 'type-graphql' -import { /* signIn, */ getKlickTippUser } from '../apis/KlicktippController' +import { /* klicktippSignIn, */ getKlickTippUser } from '../apis/KlicktippController' import { KlickTipp } from '../graphql/model/KlickTipp' import CONFIG from '../config/index' @@ -12,7 +12,7 @@ import CONFIG from '../config/index' // // Do Something here before resolver is called // const result = await next() // // Do Something here after resolver is completed -// await signIn(result.email, result.language, result.firstName, result.lastName) +// await klicktippSignIn(result.email, result.language, result.firstName, result.lastName) // return result // } From 8d800fc7da3b06caadb2db1277be9a0c52a77581 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 09:55:09 +0100 Subject: [PATCH 03/15] test some error cases --- .../src/graphql/resolver/UserResolver.test.ts | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index fb4e42bf6..1a535ae72 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -261,10 +261,10 @@ describe('UserResolver', () => { setPassword(code: $code, password: $password) } ` + let result: any + let emailOptIn: string describe('valid optin code and valid password', () => { - let emailOptIn: string - let result: any let loginUser: any let newLoginUser: any let newUser: any @@ -288,6 +288,10 @@ describe('UserResolver', () => { await resetDB() }) + it('sets email checked to true', () => { + expect(newLoginUser[0].emailChecked).toBeTruthy() + }) + it('updates the password', () => { expect(newLoginUser[0].password).toEqual('3917921995996627700') }) @@ -322,6 +326,58 @@ describe('UserResolver', () => { expect(result).toBeTruthy() }) }) + + describe('no valid password', () => { + beforeAll(async () => { + await mutate({ mutation: createUserMutation, variables: createUserVariables }) + const loginEmailOptIn = await getRepository(LoginEmailOptIn) + .createQueryBuilder('login_email_optin') + .getMany() + emailOptIn = loginEmailOptIn[0].verificationCode.toString() + result = await mutate({ + mutation: setPasswordMutation, + variables: { code: emailOptIn, password: 'not-valid' }, + }) + }) + + afterAll(async () => { + await resetDB() + }) + + it('throws an error', () => { + expect(result).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!', + ), + ], + }), + ) + }) + }) + + describe('no valid optin code', () => { + beforeAll(async () => { + await mutate({ mutation: createUserMutation, variables: createUserVariables }) + result = await mutate({ + mutation: setPasswordMutation, + variables: { code: 'not valid', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await resetDB() + }) + + it('throws an error', () => { + expect(result).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Could not login with emailVerificationCode')], + }), + ) + }) + }) }) }) From 3bef3f3b946f9e936d6ca40e80ab1f5c71fc9ff7 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 09:56:11 +0100 Subject: [PATCH 04/15] coverage backend to 45% --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e243c9184..863f433db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -520,7 +520,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 41 + min_coverage: 45 token: ${{ github.token }} ############################################################################## From bfd40a0ca783ad2f5aee3706936f4945d9e3b013 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 11:49:29 +0100 Subject: [PATCH 05/15] seed 96 random users --- database/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/database/src/index.ts b/database/src/index.ts index 033a36d8b..181a4e59b 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -7,6 +7,7 @@ import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed' import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed' import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed' import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed' +import { CreateUserSeed } from './seeds/create-user.seed' import { DecayStartBlockSeed } from './seeds/decay-start-block.seed' import { resetDB, pool, migration } from './helpers' @@ -45,6 +46,9 @@ const run = async (command: string) => { await runSeeder(CreateBibiBloxbergSeed) await runSeeder(CreateRaeuberHotzenplotzSeed) await runSeeder(CreateBobBaumeisterSeed) + Array.apply(null, Array(96)).forEach(async () => { + await runSeeder(CreateUserSeed) + }) await runSeeder(CreateGarrickOllivanderSeed) break default: From 44e3e38937133f6b0c2dc45811c0ceb4c7a6c338 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 12:16:22 +0100 Subject: [PATCH 06/15] link email addresses --- database/src/index.ts | 4 +++- database/src/seeds/create-user.seed.ts | 7 ++----- database/src/seeds/helpers/user-helpers.ts | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/database/src/index.ts b/database/src/index.ts index 181a4e59b..ff14e9238 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -46,7 +46,9 @@ const run = async (command: string) => { await runSeeder(CreateBibiBloxbergSeed) await runSeeder(CreateRaeuberHotzenplotzSeed) await runSeeder(CreateBobBaumeisterSeed) - Array.apply(null, Array(96)).forEach(async () => { + // eslint-disable-next-line no-case-declarations + const arr = new Array(96) + arr.forEach(async () => { await runSeeder(CreateUserSeed) }) await runSeeder(CreateGarrickOllivanderSeed) diff --git a/database/src/seeds/create-user.seed.ts b/database/src/seeds/create-user.seed.ts index ca3a182c4..69488a790 100644 --- a/database/src/seeds/create-user.seed.ts +++ b/database/src/seeds/create-user.seed.ts @@ -1,11 +1,8 @@ import { Factory, Seeder } from 'typeorm-seeding' -import { User } from '../../entity/User' -// import { LoginUser } from '../../entity/LoginUser' +import { userSeeder } from './helpers/user-helpers' export class CreateUserSeed implements Seeder { public async run(factory: Factory): Promise { - // const loginUser = await factory(LoginUser)().make() - // console.log(loginUser.email) - await factory(User)().create() + await userSeeder(factory, {}) } } diff --git a/database/src/seeds/helpers/user-helpers.ts b/database/src/seeds/helpers/user-helpers.ts index bd46ecdee..e1e33a94b 100644 --- a/database/src/seeds/helpers/user-helpers.ts +++ b/database/src/seeds/helpers/user-helpers.ts @@ -27,6 +27,7 @@ import { Factory } from 'typeorm-seeding' export const userSeeder = async (factory: Factory, userData: UserInterface): Promise => { const user = await factory(User)(createUserContext(userData)).create() + if (!userData.email) userData.email = user.email const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create() await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create() From 8c031a562a118e505cb440115684688d647948d8 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 12:27:58 +0100 Subject: [PATCH 07/15] fix linting --- database/src/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/database/src/index.ts b/database/src/index.ts index ff14e9238..fefdea217 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -46,9 +46,8 @@ const run = async (command: string) => { await runSeeder(CreateBibiBloxbergSeed) await runSeeder(CreateRaeuberHotzenplotzSeed) await runSeeder(CreateBobBaumeisterSeed) - // eslint-disable-next-line no-case-declarations - const arr = new Array(96) - arr.forEach(async () => { + // eslint-disable-next-line prefer-spread + Array.apply(null, Array(96)).forEach(async () => { await runSeeder(CreateUserSeed) }) await runSeeder(CreateGarrickOllivanderSeed) From 2b6dd2af87ae0b159de2ea4b4d6e750d2235027f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 13 Jan 2022 17:00:01 +0100 Subject: [PATCH 08/15] implement fake pagination for searchUsers --- backend/src/graphql/arg/SearchUsersArgs.ts | 16 ++++++++++++++++ backend/src/graphql/resolver/AdminResolver.ts | 10 +++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 backend/src/graphql/arg/SearchUsersArgs.ts diff --git a/backend/src/graphql/arg/SearchUsersArgs.ts b/backend/src/graphql/arg/SearchUsersArgs.ts new file mode 100644 index 000000000..5b40fd9ca --- /dev/null +++ b/backend/src/graphql/arg/SearchUsersArgs.ts @@ -0,0 +1,16 @@ +import { ArgsType, Field, Int } from 'type-graphql' + +@ArgsType() +export default class SearchUsersArgs { + @Field(() => String) + searchText: string + + @Field(() => Int, { nullable: true }) + currentPage?: number + + @Field(() => Int, { nullable: true }) + pageSize?: number + + @Field(() => Boolean, { nullable: true }) + notActivated?: boolean +} diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 11d8c99cd..1cf50197c 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -14,6 +14,7 @@ import { LoginPendingTasksAdminRepository } from '../../typeorm/repository/Login import { UserRepository } from '../../typeorm/repository/User' import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs' +import SearchUsersArgs from '../arg/SearchUsersArgs' import moment from 'moment' import { Transaction } from '@entity/Transaction' import { TransactionCreation } from '@entity/TransactionCreation' @@ -27,10 +28,12 @@ import { LoginUserRepository } from '../../typeorm/repository/LoginUser' export class AdminResolver { @Authorized([RIGHTS.SEARCH_USERS]) @Query(() => [UserAdmin]) - async searchUsers(@Arg('searchText') searchText: string): Promise { + async searchUsers( + @Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs, + ): Promise { const userRepository = getCustomRepository(UserRepository) const users = await userRepository.findBySearchCriteria(searchText) - const adminUsers = await Promise.all( + let adminUsers = await Promise.all( users.map(async (user) => { const adminUser = new UserAdmin() adminUser.userId = user.id @@ -42,7 +45,8 @@ export class AdminResolver { return adminUser }), ) - return adminUsers + if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked) + return adminUsers.slice(currentPage - 1, currentPage + pageSize - 1) } @Authorized([RIGHTS.CREATE_PENDING_CREATION]) From 9244fc4e1aa54cd2f763dc437afa9542ab25e64b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jan 2022 13:12:49 +0100 Subject: [PATCH 09/15] Add pagination buttons to search users --- admin/src/graphql/searchUsers.js | 19 +++++++++++-------- admin/src/locales/de.json | 3 ++- admin/src/locales/en.json | 3 ++- admin/src/pages/Creation.vue | 2 +- admin/src/pages/UserSearch.vue | 29 +++++++++++++++++++++++------ 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index cf6225338..643d6f188 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -1,14 +1,17 @@ import gql from 'graphql-tag' export const searchUsers = gql` - query ($searchText: String!) { - searchUsers(searchText: $searchText) { - userId - firstName - lastName - email - creation - emailChecked + query ($searchText: String!, $currentPage: Int, $notActivated: Boolean) { + searchUsers(searchText: $searchText, currentPage: $currentPage, notActivated: $notActivated) { + userCount + userList { + userId + firstName + lastName + email + creation + emailChecked + } } } ` diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 58f7b6714..72305cf3b 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -1,4 +1,5 @@ { + "all_emails": "Alle E-Mails", "bookmark": "bookmark", "confirmed": "bestätigt", "creation_form": { @@ -53,7 +54,7 @@ "transactionlist": { "title": "Alle geschöpften Transaktionen für den Nutzer" }, - "unregistered_emails": "Unregistrierte E-Mails", + "unregistered_emails": "Nur unregistrierte E-Mails", "unregister_mail": { "button": "Registrierungs-Email bestätigen, jetzt senden", "error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index a02782267..dad2a0ff5 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -1,4 +1,5 @@ { + "all_emails": "All e-mails", "bookmark": "Remember", "confirmed": "confirmed", "creation_form": { @@ -53,7 +54,7 @@ "transactionlist": { "title": "All creation-transactions for the user" }, - "unregistered_emails": "Unregistered e-mails", + "unregistered_emails": "Only unregistered e-mails", "unregister_mail": { "button": "Confirm registration email, send now", "error": "Error sending the confirmation link to the user: {message}", diff --git a/admin/src/pages/Creation.vue b/admin/src/pages/Creation.vue index 65e0b2f2f..5ab0954eb 100644 --- a/admin/src/pages/Creation.vue +++ b/admin/src/pages/Creation.vue @@ -116,7 +116,7 @@ export default { }, }) .then((result) => { - this.itemsList = result.data.searchUsers.map((user) => { + this.itemsList = result.data.searchUsers.userList.map((user) => { return { ...user, showDetails: false, diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index a5d4c05cf..403860238 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -3,7 +3,7 @@
- {{ $t('unregistered_emails') }} + {{ filterCheckedEmails ? $t('all_emails') : $t('unregistered_emails') }}
@@ -21,6 +21,14 @@ :fieldsTable="fields" :criteria="criteria" /> +
@@ -67,14 +75,15 @@ export default { beforeLastMonth: { short: this.$moment().subtract(2, 'month').format('MMMM'), }, + filterCheckedEmails: false, + rows: 0, + currentPage: 1, } }, - methods: { unconfirmedRegisterMails() { - this.searchResult = this.searchResult.filter((user) => { - return !user.emailChecked - }) + this.filterCheckedEmails = !this.filterCheckedEmails + this.getUsers() }, getUsers() { this.$apollo @@ -82,16 +91,24 @@ export default { query: searchUsers, variables: { searchText: this.criteria, + currentPage: this.currentPage, + notActivated: this.filterCheckedEmails, }, }) .then((result) => { - this.searchResult = result.data.searchUsers + this.rows = result.data.searchUsers.userCount + this.searchResult = result.data.searchUsers.userList }) .catch((error) => { this.$toasted.error(error.message) }) }, }, + watch: { + currentPage() { + this.getUsers() + }, + }, created() { this.getUsers() }, From d441931125fc3cc91fd65832c4a3becb57ee3ea2 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jan 2022 13:13:48 +0100 Subject: [PATCH 10/15] get pagination working --- backend/src/graphql/model/UserAdmin.ts | 11 ++++++++++- backend/src/graphql/resolver/AdminResolver.ts | 12 ++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index befc203a5..9e28a33d6 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -1,4 +1,4 @@ -import { ObjectType, Field } from 'type-graphql' +import { ObjectType, Field, Int } from 'type-graphql' @ObjectType() export class UserAdmin { @@ -20,3 +20,12 @@ export class UserAdmin { @Field(() => Boolean) emailChecked: boolean } + +@ObjectType() +export class SearchUsersResult { + @Field(() => Int) + userCount: number + + @Field(() => [UserAdmin]) + userList: UserAdmin[] +} diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 1cf50197c..8a7e034a5 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -3,7 +3,7 @@ import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql' import { getCustomRepository, Raw } from 'typeorm' -import { UserAdmin } from '../model/UserAdmin' +import { UserAdmin, SearchUsersResult } from '../model/UserAdmin' import { PendingCreation } from '../model/PendingCreation' import { CreatePendingCreations } from '../model/CreatePendingCreations' import { UpdatePendingCreation } from '../model/UpdatePendingCreation' @@ -27,10 +27,10 @@ import { LoginUserRepository } from '../../typeorm/repository/LoginUser' @Resolver() export class AdminResolver { @Authorized([RIGHTS.SEARCH_USERS]) - @Query(() => [UserAdmin]) + @Query(() => SearchUsersResult) async searchUsers( @Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs, - ): Promise { + ): Promise { const userRepository = getCustomRepository(UserRepository) const users = await userRepository.findBySearchCriteria(searchText) let adminUsers = await Promise.all( @@ -46,7 +46,11 @@ export class AdminResolver { }), ) if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked) - return adminUsers.slice(currentPage - 1, currentPage + pageSize - 1) + const first = (currentPage - 1) * pageSize + return { + userCount: adminUsers.length, + userList: adminUsers.slice(first, first + pageSize), + } } @Authorized([RIGHTS.CREATE_PENDING_CREATION]) From 5d9cc6ae24a8d3a583d4c4509c81a071986e461d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jan 2022 13:18:34 +0100 Subject: [PATCH 11/15] get the tests working again --- admin/src/pages/Creation.spec.js | 35 ++++++++++++++++-------------- admin/src/pages/UserSearch.spec.js | 21 ++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/admin/src/pages/Creation.spec.js b/admin/src/pages/Creation.spec.js index fc8cdbaf0..9feffda45 100644 --- a/admin/src/pages/Creation.spec.js +++ b/admin/src/pages/Creation.spec.js @@ -6,22 +6,25 @@ const localVue = global.localVue const apolloQueryMock = jest.fn().mockResolvedValue({ data: { - searchUsers: [ - { - userId: 1, - firstName: 'Bibi', - lastName: 'Bloxberg', - email: 'bibi@bloxberg.de', - creation: [200, 400, 600], - }, - { - userId: 2, - firstName: 'Benjamin', - lastName: 'Blümchen', - email: 'benjamin@bluemchen.de', - creation: [800, 600, 400], - }, - ], + searchUsers: { + userCount: 2, + userList: [ + { + userId: 1, + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + creation: [200, 400, 600], + }, + { + userId: 2, + firstName: 'Benjamin', + lastName: 'Blümchen', + email: 'benjamin@bluemchen.de', + creation: [800, 600, 400], + }, + ], + }, }, }) diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js index b6d01b254..4beda0c49 100644 --- a/admin/src/pages/UserSearch.spec.js +++ b/admin/src/pages/UserSearch.spec.js @@ -5,15 +5,18 @@ const localVue = global.localVue const apolloQueryMock = jest.fn().mockResolvedValue({ data: { - searchUsers: [ - { - firstName: 'Bibi', - lastName: 'Bloxberg', - email: 'bibi@bloxberg.de', - creation: [200, 400, 600], - emailChecked: false, - }, - ], + searchUsers: { + userCount: 1, + userList: [ + { + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + creation: [200, 400, 600], + emailChecked: false, + }, + ], + }, }, }) From d88c16a66c81cb59a94cccadc4babf76fcb7502c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jan 2022 13:32:44 +0100 Subject: [PATCH 12/15] pagination for user list on creation page --- admin/src/pages/Creation.vue | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/admin/src/pages/Creation.vue b/admin/src/pages/Creation.vue index 5ab0954eb..60d9c427e 100644 --- a/admin/src/pages/Creation.vue +++ b/admin/src/pages/Creation.vue @@ -18,6 +18,13 @@ :creation="creation" @update-item="updateItem" /> + { + this.rows = result.data.searchUsers.userCount this.itemsList = result.data.searchUsers.userList.map((user) => { return { ...user, @@ -153,5 +164,13 @@ export default { this.itemsMassCreation = [] }, }, + watch: { + currentPage() { + this.getUsers() + }, + criteria() { + this.getUsers() + }, + }, } From 0c6d63331f465a17c1cd5058e8bf01ca217d3a23 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jan 2022 13:39:34 +0100 Subject: [PATCH 13/15] test watchers --- admin/src/pages/Creation.spec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/admin/src/pages/Creation.spec.js b/admin/src/pages/Creation.spec.js index 9feffda45..01a884f54 100644 --- a/admin/src/pages/Creation.spec.js +++ b/admin/src/pages/Creation.spec.js @@ -230,6 +230,22 @@ describe('Creation', () => { }) }) + describe('watchers', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('calls API when criteria changes', async () => { + await wrapper.setData({ criteria: 'XX' }) + expect(apolloQueryMock).toBeCalled() + }) + + it('calls API when currentPage changes', async () => { + await wrapper.setData({ currentPage: 2 }) + expect(apolloQueryMock).toBeCalled() + }) + }) + describe('apollo returns error', () => { beforeEach(() => { apolloQueryMock.mockRejectedValue({ From 9b87b26fa2079fbe7583a05a4a6a724d79173b17 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 18 Jan 2022 12:47:00 +0100 Subject: [PATCH 14/15] change text to users --- admin/src/locales/de.json | 4 ++-- admin/src/locales/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 72305cf3b..a9a2bc382 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -1,5 +1,5 @@ { - "all_emails": "Alle E-Mails", + "all_emails": "Alle Nutzer", "bookmark": "bookmark", "confirmed": "bestätigt", "creation_form": { @@ -54,7 +54,7 @@ "transactionlist": { "title": "Alle geschöpften Transaktionen für den Nutzer" }, - "unregistered_emails": "Nur unregistrierte E-Mails", + "unregistered_emails": "Nur unregistrierte Nutzer", "unregister_mail": { "button": "Registrierungs-Email bestätigen, jetzt senden", "error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index dad2a0ff5..ee2b9b36d 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -1,5 +1,5 @@ { - "all_emails": "All e-mails", + "all_emails": "All users", "bookmark": "Remember", "confirmed": "confirmed", "creation_form": { @@ -54,7 +54,7 @@ "transactionlist": { "title": "All creation-transactions for the user" }, - "unregistered_emails": "Only unregistered e-mails", + "unregistered_emails": "Only unregistered users", "unregister_mail": { "button": "Confirm registration email, send now", "error": "Error sending the confirmation link to the user: {message}", From ddd9e4ecc0654ad942368709ef021109402c447a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 18 Jan 2022 12:51:59 +0100 Subject: [PATCH 15/15] perPage as data parameter --- admin/src/graphql/searchUsers.js | 9 +++++++-- admin/src/pages/Creation.vue | 4 +++- admin/src/pages/UserSearch.vue | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/admin/src/graphql/searchUsers.js b/admin/src/graphql/searchUsers.js index 643d6f188..2bd6d2004 100644 --- a/admin/src/graphql/searchUsers.js +++ b/admin/src/graphql/searchUsers.js @@ -1,8 +1,13 @@ import gql from 'graphql-tag' export const searchUsers = gql` - query ($searchText: String!, $currentPage: Int, $notActivated: Boolean) { - searchUsers(searchText: $searchText, currentPage: $currentPage, notActivated: $notActivated) { + query ($searchText: String!, $currentPage: Int, $pageSize: Int, $notActivated: Boolean) { + searchUsers( + searchText: $searchText + currentPage: $currentPage + pageSize: $pageSize + notActivated: $notActivated + ) { userCount userList { userId diff --git a/admin/src/pages/Creation.vue b/admin/src/pages/Creation.vue index 60d9c427e..0de804288 100644 --- a/admin/src/pages/Creation.vue +++ b/admin/src/pages/Creation.vue @@ -21,7 +21,7 @@ @@ -110,6 +110,7 @@ export default { creation: [null, null, null], rows: 0, currentPage: 1, + perPage: 25, } }, async created() { @@ -123,6 +124,7 @@ export default { variables: { searchText: this.criteria, currentPage: this.currentPage, + pageSize: this.perPage, }, }) .then((result) => { diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index 403860238..4b9bcae8a 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -25,7 +25,7 @@ pills size="lg" v-model="currentPage" - per-page="25" + per-page="perPage" :total-rows="rows" align="center" > @@ -78,6 +78,7 @@ export default { filterCheckedEmails: false, rows: 0, currentPage: 1, + perPage: 25, } }, methods: { @@ -92,6 +93,7 @@ export default { variables: { searchText: this.criteria, currentPage: this.currentPage, + pageSize: this.perPage, notActivated: this.filterCheckedEmails, }, })