diff --git a/admin/jest.config.js b/admin/jest.config.js index 9b9842bad..3e416e7b6 100644 --- a/admin/jest.config.js +++ b/admin/jest.config.js @@ -26,5 +26,5 @@ module.exports = { testMatch: ['**/?(*.)+(spec|test).js?(x)'], // snapshotSerializers: ['jest-serializer-vue'], transformIgnorePatterns: ['/node_modules/(?!vee-validate/dist/rules)'], - testEnvironment: 'jest-environment-jsdom-sixteen', + // testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen } diff --git a/admin/package.json b/admin/package.json index 5da80bd1f..98746be6d 100644 --- a/admin/package.json +++ b/admin/package.json @@ -70,7 +70,6 @@ "eslint-plugin-prettier": "3.3.1", "eslint-plugin-promise": "^5.1.1", "eslint-plugin-vue": "^7.20.0", - "jest-environment-jsdom-sixteen": "^2.0.0", "postcss": "^8.4.8", "postcss-html": "^1.3.0", "postcss-scss": "^4.0.3", diff --git a/admin/yarn.lock b/admin/yarn.lock index 09b543354..b5b76cee8 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -1282,17 +1282,6 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.1.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" - integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== - dependencies: - "@jest/types" "^25.5.0" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - lolex "^5.0.0" - "@jest/fake-timers@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" @@ -1504,16 +1493,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -4130,14 +4109,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4 escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -8012,16 +7983,6 @@ jest-environment-jsdom-fifteen@^1.0.2: jest-util "^24.0.0" jsdom "^15.2.1" -jest-environment-jsdom-sixteen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1" - integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ== - dependencies: - "@jest/fake-timers" "^25.1.0" - jest-mock "^25.1.0" - jest-util "^25.1.0" - jsdom "^16.2.1" - jest-environment-jsdom@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" @@ -8236,20 +8197,6 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" - integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - slash "^3.0.0" - stack-utils "^1.0.1" - jest-message-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" @@ -8272,13 +8219,6 @@ jest-mock@^24.0.0, jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.1.0, jest-mock@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" - integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== - dependencies: - "@jest/types" "^25.5.0" - jest-mock@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" @@ -8555,17 +8495,6 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.1.0, jest-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" - integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - make-dir "^3.0.0" - jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" @@ -8812,7 +8741,7 @@ jsdom@^15.2.1: ws "^7.0.0" xml-name-validator "^3.0.0" -jsdom@^16.2.1, jsdom@^16.4.0: +jsdom@^16.4.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -9167,13 +9096,6 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== - dependencies: - "@sinonjs/commons" "^1.7.0" - loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" diff --git a/backend/log4js-config.json b/backend/log4js-config.json index 451da56ab..848a4fa79 100644 --- a/backend/log4js-config.json +++ b/backend/log4js-config.json @@ -25,6 +25,14 @@ "keepFileExt" : true, "fileNameSep" : "_" }, + "klicktipp": + { + "type": "dateFile", + "filename": "../logs/backend/klicktipp.log", + "pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m", + "keepFileExt" : true, + "fileNameSep" : "_" + }, "errorFile": { "type": "dateFile", @@ -90,6 +98,17 @@ "level": "debug", "enableCallStack": true }, + "klicktipp": + { + "appenders": + [ + "klicktipp", + "out", + "errors" + ], + "level": "debug", + "enableCallStack": true + }, "http": { "appenders": diff --git a/backend/package.json b/backend/package.json index c56c8e960..d31d12eda 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,7 @@ "dependencies": { "@types/jest": "^27.0.2", "@types/lodash.clonedeep": "^4.5.6", + "@types/uuid": "^8.3.4", "apollo-server-express": "^2.25.2", "apollo-server-testing": "^2.25.2", "axios": "^0.21.1", @@ -39,7 +40,8 @@ "reflect-metadata": "^0.1.13", "sodium-native": "^3.3.0", "ts-jest": "^27.0.5", - "type-graphql": "^1.1.1" + "type-graphql": "^1.1.1", + "uuid": "^8.3.2" }, "devDependencies": { "@types/express": "^4.17.12", diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 101e28254..a58d0aa2b 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -30,6 +30,7 @@ export enum RIGHTS { LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS', LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS', UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION', + LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS', // Admin SEARCH_USERS = 'SEARCH_USERS', SET_USER_ROLE = 'SET_USER_ROLE', @@ -45,7 +46,6 @@ export enum RIGHTS { CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST', LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN', CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK', - LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS', DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK', UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK', COMMUNITY_STATISTICS = 'COMMUNITY_STATISTICS', diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 9dcba0a4b..500c8bec4 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -28,6 +28,7 @@ export const ROLE_USER = new Role('user', [ RIGHTS.LIST_CONTRIBUTIONS, RIGHTS.LIST_ALL_CONTRIBUTIONS, RIGHTS.UPDATE_CONTRIBUTION, + RIGHTS.LIST_CONTRIBUTION_LINKS, ]) export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index f44aa584c..282d458ce 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0044-insert_missing_contributions', + DB_VERSION: '0046-adapt_users_table_for_gradidoid', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/backend/src/graphql/enum/ContributionStatus.ts b/backend/src/graphql/enum/ContributionStatus.ts new file mode 100644 index 000000000..67cdf5398 --- /dev/null +++ b/backend/src/graphql/enum/ContributionStatus.ts @@ -0,0 +1,14 @@ +import { registerEnumType } from 'type-graphql' + +export enum ContributionStatus { + PENDING = 'PENDING', + DELETED = 'DELETED', + IN_PROGRESS = 'IN_PROGRESS', + DENIED = 'DENIED', + CONFIRMED = 'CONFIRMED', +} + +registerEnumType(ContributionStatus, { + name: 'ContributionStatus', + description: 'Name of the Type of the Contribution Status', +}) diff --git a/backend/src/graphql/enum/ContributionType.ts b/backend/src/graphql/enum/ContributionType.ts new file mode 100644 index 000000000..e8529edc4 --- /dev/null +++ b/backend/src/graphql/enum/ContributionType.ts @@ -0,0 +1,12 @@ +import { registerEnumType } from 'type-graphql' + +export enum ContributionType { + ADMIN = 'ADMIN', + USER = 'USER', + LINK = 'LINK', +} + +registerEnumType(ContributionType, { + name: 'ContributionType', + description: 'Name of the Type of the Contribution', +}) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 0642be630..1ec33b27d 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -8,6 +8,8 @@ import { FULL_CREATION_AVAILABLE } from '../resolver/const/const' export class User { constructor(user: dbUser, creation: Decimal[] = FULL_CREATION_AVAILABLE) { this.id = user.id + this.gradidoID = user.gradidoID + this.alias = user.alias this.email = user.email this.firstName = user.firstName this.lastName = user.lastName @@ -28,6 +30,12 @@ export class User { // `public_key` binary(32) DEFAULT NULL, // `privkey` binary(80) DEFAULT NULL, + @Field(() => String) + gradidoID: string + + @Field(() => String, { nullable: true }) + alias: string + // TODO privacy issue here @Field(() => String) email: string diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 7b1c6ffcd..f0ce064b4 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -1857,11 +1857,17 @@ describe('AdminResolver', () => { }) }) + // TODO: Set this test in new location to have datas describe('listContributionLinks', () => { - it('returns an error', async () => { + it('returns an empty object', async () => { await expect(query({ query: listContributionLinks })).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], + data: { + listContributionLinks: { + count: 0, + links: [], + }, + }, }), ) }) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 84ae09cf8..e70fe71ee 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -36,6 +36,8 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User as dbUser } from '@entity/User' import { User } from '@model/User' import { TransactionTypeId } from '@enum/TransactionTypeId' +import { ContributionType } from '@enum/ContributionType' +import { ContributionStatus } from '@enum/ContributionStatus' import Decimal from 'decimal.js-light' import { Decay } from '@model/Decay' import Paginated from '@arg/Paginated' @@ -260,6 +262,8 @@ export class AdminResolver { contribution.contributionDate = creationDateObj contribution.memo = memo contribution.moderatorId = moderator.id + contribution.contributionType = ContributionType.ADMIN + contribution.contributionStatus = ContributionStatus.PENDING logger.trace('contribution to save', contribution) await Contribution.save(contribution) @@ -337,6 +341,7 @@ export class AdminResolver { contributionToUpdate.memo = memo contributionToUpdate.contributionDate = new Date(creationDate) contributionToUpdate.moderatorId = moderator.id + contributionToUpdate.contributionStatus = ContributionStatus.PENDING await Contribution.save(contributionToUpdate) const result = new AdminUpdateContribution() @@ -387,6 +392,8 @@ export class AdminResolver { if (!contribution) { throw new Error('Contribution not found for given id.') } + contribution.contributionStatus = ContributionStatus.DELETED + await contribution.save() const res = await contribution.softRemove() return !!res } @@ -454,6 +461,7 @@ export class AdminResolver { contribution.confirmedAt = receivedCallDate contribution.confirmedBy = moderatorUser.id contribution.transactionId = transaction.id + contribution.contributionStatus = ContributionStatus.CONFIRMED await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution) await queryRunner.commitTransaction() @@ -501,7 +509,7 @@ export class AdminResolver { order: { updatedAt: 'DESC' }, }) - optInCode = await checkOptInCode(optInCode, user.id) + optInCode = await checkOptInCode(optInCode, user) // eslint-disable-next-line @typescript-eslint/no-unused-vars const emailSent = await sendAccountActivationEmail({ diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 3307252e4..8056ffde3 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -7,6 +7,8 @@ import { FindOperator, IsNull, getConnection } from '@dbTools/typeorm' import ContributionArgs from '@arg/ContributionArgs' import Paginated from '@arg/Paginated' import { Order } from '@enum/Order' +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' @@ -43,6 +45,8 @@ export class ContributionResolver { contribution.createdAt = new Date() contribution.contributionDate = creationDateObj contribution.memo = memo + contribution.contributionType = ContributionType.USER + contribution.contributionStatus = ContributionStatus.PENDING logger.trace('contribution to save', contribution) await dbContribution.save(contribution) @@ -66,6 +70,8 @@ export class ContributionResolver { if (contribution.confirmedAt) { throw new Error('A confirmed contribution can not be deleted') } + contribution.contributionStatus = ContributionStatus.DELETED + await contribution.save() const res = await contribution.softRemove() return !!res } @@ -164,6 +170,7 @@ export class ContributionResolver { contributionToUpdate.amount = amount contributionToUpdate.memo = memo contributionToUpdate.contributionDate = new Date(creationDate) + contributionToUpdate.contributionStatus = ContributionStatus.PENDING dbContribution.save(contributionToUpdate) return new UnconfirmedContribution(contributionToUpdate, user, creations) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 8696065ed..ccc0f628d 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -26,6 +26,8 @@ import { User } from '@model/User' import { calculateDecay } from '@/util/decay' import { executeTransaction } from './TransactionResolver' import { Order } from '@enum/Order' +import { ContributionType } from '@enum/ContributionType' +import { ContributionStatus } from '@enum/ContributionStatus' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { getUserCreation, validateContribution } from './util/creations' @@ -231,6 +233,9 @@ export class TransactionLinkResolver { contribution.memo = contributionLink.memo contribution.amount = contributionLink.amount contribution.contributionLinkId = contributionLink.id + contribution.contributionType = ContributionType.LINK + contribution.contributionStatus = ContributionStatus.CONFIRMED + await queryRunner.manager.insert(DbContribution, contribution) const lastTransaction = await queryRunner.manager diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index a2a499224..35a569c4b 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -20,6 +20,7 @@ import { ContributionLink } from '@model/ContributionLink' // import { TransactionLink } from '@entity/TransactionLink' import { logger } from '@test/testSetup' +import { validate as validateUUID, version as versionUUID } from 'uuid' // import { klicktippSignIn } from '@/apis/KlicktippController' @@ -111,6 +112,8 @@ describe('UserResolver', () => { expect(user).toEqual([ { id: expect.any(Number), + gradidoID: expect.any(String), + alias: null, email: 'peter@lustig.de', firstName: 'Peter', lastName: 'Lustig', @@ -129,6 +132,10 @@ describe('UserResolver', () => { contributionLinkId: null, }, ]) + const valUUID = validateUUID(user[0].gradidoID) + const verUUID = versionUUID(user[0].gradidoID) + expect(valUUID).toEqual(true) + expect(verUUID).toEqual(4) }) it('creates an email optin', () => { @@ -198,7 +205,7 @@ describe('UserResolver', () => { it('sets "de" as default language', async () => { await mutate({ mutation: createUser, - variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' }, + variables: { ...variables, email: 'bibi@bloxberg.de', language: 'fr' }, }) await expect(User.find()).resolves.toEqual( expect.arrayContaining([ diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index a89a8cb0b..aa32197b8 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -32,6 +32,7 @@ import { EventSendConfirmationEmail, } from '@/event/Event' import { getUserCreation } from './util/creations' +import { v4 as uuidv4 } from 'uuid' // eslint-disable-next-line @typescript-eslint/no-var-requires const sodium = require('sodium-native') @@ -43,7 +44,7 @@ const isPassword = (password: string): boolean => { return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/) } -const LANGUAGES = ['de', 'en'] +const LANGUAGES = ['de', 'en', 'es'] const DEFAULT_LANGUAGE = 'de' const isLanguage = (language: string): boolean => { return LANGUAGES.includes(language) @@ -187,7 +188,7 @@ const newEmailOptIn = (userId: number): LoginEmailOptIn => { // if optIn does not exits, it is created export const checkOptInCode = async ( optInCode: LoginEmailOptIn | undefined, - userId: number, + user: DbUser, optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER, ): Promise => { logger.info(`checkOptInCode... ${optInCode}`) @@ -207,15 +208,18 @@ export const checkOptInCode = async ( optInCode.updatedAt = new Date() optInCode.resendCount++ } else { - logger.trace('create new OptIn for userId=' + userId) - optInCode = newEmailOptIn(userId) + logger.trace('create new OptIn for userId=' + user.id) + optInCode = newEmailOptIn(user.id) + } + + if (user.emailChecked) { + optInCode.emailOptInTypeId = optInType } - optInCode.emailOptInTypeId = optInType await LoginEmailOptIn.save(optInCode).catch(() => { logger.error('Unable to save optin code= ' + optInCode) throw new Error('Unable to save optin code.') }) - logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${userId}`) + logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${user.id}`) return optInCode } @@ -224,6 +228,19 @@ export const activationLink = (optInCode: LoginEmailOptIn): string => { return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, optInCode.verificationCode.toString()) } +const newGradidoID = async (): Promise => { + let gradidoId: string + let countIds: number + do { + gradidoId = uuidv4() + countIds = await DbUser.count({ where: { gradidoID: gradidoId } }) + if (countIds > 0) { + logger.info('Gradido-ID creation conflict...') + } + } while (countIds > 0) + return gradidoId +} + @Resolver() export class UserResolver { @Authorized([RIGHTS.VERIFY_LOGIN]) @@ -344,11 +361,13 @@ export class UserResolver { logger.info(`DbUser.findOne(email=${email}) = ${userFound}`) if (userFound) { - logger.info('User already exists with this email=' + email) + // ATTENTION: this logger-message will be exactly expected during tests + logger.info(`User already exists with this email=${email}`) // TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent. const user = new User(communityDbUser) user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in? + user.gradidoID = uuidv4() user.email = email user.firstName = firstName user.lastName = lastName @@ -378,11 +397,13 @@ export class UserResolver { // const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash // const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1]) const emailHash = getEmailHash(email) + const gradidoID = await newGradidoID() const eventRegister = new EventRegister() const eventRedeemRegister = new EventRedeemRegister() const eventSendConfirmEmail = new EventSendConfirmationEmail() const dbUser = new DbUser() + dbUser.gradidoID = gradidoID dbUser.email = email dbUser.firstName = firstName dbUser.lastName = lastName @@ -493,7 +514,7 @@ export class UserResolver { userId: user.id, }) - optInCode = await checkOptInCode(optInCode, user.id, OptInType.EMAIL_OPT_IN_RESET_PASSWORD) + optInCode = await checkOptInCode(optInCode, user, OptInType.EMAIL_OPT_IN_RESET_PASSWORD) logger.info(`optInCode for ${email}=${optInCode}`) // eslint-disable-next-line @typescript-eslint/no-unused-vars const emailSent = await sendResetPasswordEmailMailer({ diff --git a/backend/src/middleware/klicktippMiddleware.ts b/backend/src/middleware/klicktippMiddleware.ts index b3699f29b..6bdaa63fd 100644 --- a/backend/src/middleware/klicktippMiddleware.ts +++ b/backend/src/middleware/klicktippMiddleware.ts @@ -2,6 +2,7 @@ import { MiddlewareFn } from 'type-graphql' import { /* klicktippSignIn, */ getKlickTippUser } from '@/apis/KlicktippController' import { KlickTipp } from '@model/KlickTipp' import CONFIG from '@/config' +import { klickTippLogger as logger } from '@/server/logger' // export const klicktippRegistrationMiddleware: MiddlewareFn = async ( // // Only for demo @@ -29,7 +30,9 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async ( if (klickTippUser) { klickTipp = new KlickTipp(klickTippUser) } - } catch (err) {} + } catch (err) { + logger.error(`There is no user for (email='${result.email}') ${err}`) + } } result.klickTipp = klickTipp return result diff --git a/backend/src/server/logger.ts b/backend/src/server/logger.ts index cbc8c9b9b..0cfa5689b 100644 --- a/backend/src/server/logger.ts +++ b/backend/src/server/logger.ts @@ -12,7 +12,8 @@ log4js.configure(options) const apolloLogger = log4js.getLogger('apollo') const backendLogger = log4js.getLogger('backend') +const klickTippLogger = log4js.getLogger('klicktipp') backendLogger.addContext('user', 'unknown') -export { apolloLogger, backendLogger } +export { apolloLogger, backendLogger, klickTippLogger } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 1a84c2cdf..d850c5382 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -6,6 +6,8 @@ import { User } from '@model/User' const communityDbUser: dbUser = { id: -1, + gradidoID: '11111111-2222-4333-4444-55555555', + alias: '', email: 'support@gradido.net', firstName: 'Gradido', lastName: 'Akademie', diff --git a/backend/yarn.lock b/backend/yarn.lock index 53a53cb9b..dd84e2ce5 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1000,6 +1000,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/validator@^13.1.3": version "13.6.3" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.3.tgz#31ca2e997bf13a0fffca30a25747d5b9f7dbb7de" @@ -5437,7 +5442,7 @@ uuid@^3.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0: +uuid@^8.0.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== diff --git a/database/entity/0045-add_denied_type_and_status_to_contributions/Contribution.ts b/database/entity/0045-add_denied_type_and_status_to_contributions/Contribution.ts new file mode 100644 index 000000000..c376ae53e --- /dev/null +++ b/database/entity/0045-add_denied_type_and_status_to_contributions/Contribution.ts @@ -0,0 +1,83 @@ +import Decimal from 'decimal.js-light' +import { + BaseEntity, + Column, + Entity, + PrimaryGeneratedColumn, + DeleteDateColumn, + JoinColumn, + ManyToOne, +} from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { User } from '../User' + +@Entity('contributions') +export class Contribution extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ unsigned: true, nullable: false, name: 'user_id' }) + userId: number + + @ManyToOne(() => User, (user) => user.contributions) + @JoinColumn({ name: 'user_id' }) + user: User + + @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' }) + createdAt: Date + + @Column({ type: 'datetime', nullable: false, name: 'contribution_date' }) + contributionDate: Date + + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + memo: string + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + amount: Decimal + + @Column({ unsigned: true, nullable: true, name: 'moderator_id' }) + moderatorId: number + + @Column({ unsigned: true, nullable: true, name: 'contribution_link_id' }) + contributionLinkId: number + + @Column({ unsigned: true, nullable: true, name: 'confirmed_by' }) + confirmedBy: number + + @Column({ nullable: true, name: 'confirmed_at' }) + confirmedAt: Date + + @Column({ unsigned: true, nullable: true, name: 'denied_by' }) + deniedBy: number + + @Column({ nullable: true, name: 'denied_at' }) + deniedAt: Date + + @Column({ + name: 'contribution_type', + length: 12, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + contributionType: string + + @Column({ + name: 'contribution_status', + length: 12, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + contributionStatus: string + + @Column({ unsigned: true, nullable: true, name: 'transaction_id' }) + transactionId: number + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt: Date | null +} diff --git a/database/entity/0046-adapt_users_table_for_gradidoid/User.ts b/database/entity/0046-adapt_users_table_for_gradidoid/User.ts new file mode 100644 index 000000000..3f2547cad --- /dev/null +++ b/database/entity/0046-adapt_users_table_for_gradidoid/User.ts @@ -0,0 +1,111 @@ +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + DeleteDateColumn, + OneToMany, + JoinColumn, +} from 'typeorm' +import { Contribution } from '../Contribution' + +@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[] +} diff --git a/database/entity/Contribution.ts b/database/entity/Contribution.ts index 82dd6478c..800e7f9cd 100644 --- a/database/entity/Contribution.ts +++ b/database/entity/Contribution.ts @@ -1 +1 @@ -export { Contribution } from './0039-contributions_table/Contribution' +export { Contribution } from './0045-add_denied_type_and_status_to_contributions/Contribution' diff --git a/database/entity/User.ts b/database/entity/User.ts index 99b8c8ca9..02a99fcd1 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0040-add_contribution_link_id_to_user/User' +export { User } from './0046-adapt_users_table_for_gradidoid/User' diff --git a/database/migrations/0045-add_denied_type_and_status_to_contributions.ts b/database/migrations/0045-add_denied_type_and_status_to_contributions.ts new file mode 100644 index 000000000..b3653589b --- /dev/null +++ b/database/migrations/0045-add_denied_type_and_status_to_contributions.ts @@ -0,0 +1,39 @@ +/* MIGRATION TO ADD denied_by, denied_at, contribution_type and contrinution_status +FIELDS TO contributions */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `contributions` ADD COLUMN `denied_at` datetime DEFAULT NULL AFTER `confirmed_at`;', + ) + await queryFn( + 'ALTER TABLE `contributions` ADD COLUMN `denied_by` int(10) unsigned DEFAULT NULL AFTER `denied_at`;', + ) + await queryFn( + 'ALTER TABLE `contributions` ADD COLUMN `contribution_type` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT "ADMIN" AFTER `denied_by`;', + ) + await queryFn( + 'ALTER TABLE `contributions` ADD COLUMN `contribution_status` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT "PENDING" AFTER `contribution_type`;', + ) + await queryFn( + 'UPDATE `contributions` SET `contribution_type` = "LINK" WHERE `contribution_link_id` IS NOT NULL;', + ) + await queryFn( + 'UPDATE `contributions` SET `contribution_type` = "USER" WHERE `contribution_link_id` IS NULL AND `moderator_id` IS NULL;', + ) + await queryFn( + 'UPDATE `contributions` SET `contribution_status` = "CONFIRMED" WHERE `confirmed_at` IS NOT NULL;', + ) + await queryFn( + 'UPDATE `contributions` SET `contribution_status` = "DELETED" WHERE `deleted_at` IS NOT NULL;', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `contributions` DROP COLUMN `contribution_status`;') + await queryFn('ALTER TABLE `contributions` DROP COLUMN `contribution_type`;') + await queryFn('ALTER TABLE `contributions` DROP COLUMN `denied_by`;') + await queryFn('ALTER TABLE `contributions` DROP COLUMN `denied_at`;') +} diff --git a/database/migrations/0046-adapt_users_table_for_gradidoid.ts b/database/migrations/0046-adapt_users_table_for_gradidoid.ts new file mode 100644 index 000000000..8e8372efa --- /dev/null +++ b/database/migrations/0046-adapt_users_table_for_gradidoid.ts @@ -0,0 +1,44 @@ +/* MIGRATION TO ADD GRADIDO_ID + * + * This migration adds new columns to the table `users` and creates the + * new table `user_contacts` + */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { v4 as uuidv4 } from 'uuid' + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // First add gradido_id as nullable column without Default + await queryFn('ALTER TABLE `users` ADD COLUMN `gradido_id` CHAR(36) NULL AFTER `id`;') + + // Second update gradido_id with ensured unique uuidv4 + const usersToUpdate = await queryFn('SELECT `id`, `gradido_id` FROM `users`') // WHERE 'u.gradido_id' is null`,) + for (const id in usersToUpdate) { + const user = usersToUpdate[id] + let gradidoId = null + let countIds = null + do { + gradidoId = uuidv4() + countIds = await queryFn( + `SELECT COUNT(*) FROM \`users\` WHERE \`gradido_id\` = "${gradidoId}"`, + ) + } while (countIds[0] > 0) + await queryFn( + `UPDATE \`users\` SET \`gradido_id\` = "${gradidoId}" WHERE \`id\` = "${user.id}"`, + ) + } + + // third modify gradido_id to not nullable and unique + await queryFn('ALTER TABLE `users` MODIFY COLUMN `gradido_id` CHAR(36) NOT NULL UNIQUE;') + + await queryFn( + 'ALTER TABLE `users` ADD COLUMN `alias` varchar(20) NULL UNIQUE AFTER `gradido_id`;', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE users DROP COLUMN gradido_id;') + await queryFn('ALTER TABLE users DROP COLUMN alias;') +} diff --git a/database/package.json b/database/package.json index 05e1c2ac8..837e61438 100644 --- a/database/package.json +++ b/database/package.json @@ -37,6 +37,7 @@ "typescript": "^4.3.5" }, "dependencies": { + "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "crypto": "^1.0.1", "decimal.js-light": "^2.5.1", @@ -44,6 +45,7 @@ "mysql2": "^2.3.0", "reflect-metadata": "^0.1.13", "ts-mysql-migrate": "^1.0.2", - "typeorm": "^0.2.38" + "typeorm": "^0.2.38", + "uuid": "^8.3.2" } } diff --git a/database/yarn.lock b/database/yarn.lock index e5d74929c..b30db4595 100644 --- a/database/yarn.lock +++ b/database/yarn.lock @@ -137,6 +137,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/zen-observable@0.8.3": version "0.8.3" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" @@ -2088,6 +2093,11 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" diff --git a/docu/Locales/GRADIDO_register_page_spanish.xlsx b/docu/Locales/GRADIDO_register_page_spanish.xlsx new file mode 100644 index 000000000..6b6ec70c0 Binary files /dev/null and b/docu/Locales/GRADIDO_register_page_spanish.xlsx differ diff --git a/frontend/jest.config.js b/frontend/jest.config.js index 2a52ec707..a32330f3b 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.js @@ -23,5 +23,5 @@ module.exports = { testMatch: ['**/?(*.)+(spec|test).js?(x)'], // snapshotSerializers: ['jest-serializer-vue'], transformIgnorePatterns: ['/node_modules/(?!vee-validate/dist/rules)'], - testEnvironment: 'jest-environment-jsdom-sixteen', + // testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen } diff --git a/frontend/package.json b/frontend/package.json index 71baf4764..8121e2ca2 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,6 @@ "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", "jest-canvas-mock": "^2.3.1", - "jest-environment-jsdom-sixteen": "^2.0.0", "jwt-decode": "^3.1.2", "portal-vue": "^2.1.7", "prettier": "^2.2.1", diff --git a/frontend/src/components/Contributions/ContributionForm.spec.js b/frontend/src/components/Contributions/ContributionForm.spec.js index eb1509328..cf47577a3 100644 --- a/frontend/src/components/Contributions/ContributionForm.spec.js +++ b/frontend/src/components/Contributions/ContributionForm.spec.js @@ -46,46 +46,274 @@ describe('ContributionForm', () => { }) describe('empty form data', () => { - describe('buttons', () => { - it('has reset enabled', () => { - expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy() + describe('button', () => { + it('has submit disabled', () => { + expect(wrapper.find('button[data-test="button-submit"]').attributes('disabled')).toBe( + 'disabled', + ) + }) + }) + }) + + describe('dates', () => { + beforeEach(async () => { + await wrapper.setData({ + form: { + id: null, + date: '', + memo: '', + amount: '', + }, + }) + }) + + describe('actual date', () => { + describe('same month', () => { + beforeEach(async () => { + const now = new Date().toISOString() + await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + }) + + describe('isThisMonth', () => { + it('has true', () => { + expect(wrapper.vm.isThisMonth).toBe(true) + }) + }) }) - it('has submit disabled', () => { - expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled') + describe('month before', () => { + beforeEach(async () => { + await wrapper + .findComponent({ name: 'BFormDatepicker' }) + .vm.$emit('input', wrapper.vm.minimalDate) + }) + + describe('isThisMonth', () => { + it('has false', () => { + expect(wrapper.vm.isThisMonth).toBe(false) + }) + }) + }) + }) + + describe('date in middle of year', () => { + describe('same month', () => { + beforeEach(async () => { + // jest.useFakeTimers('modern') + // jest.setSystemTime(new Date('2020-07-06')) + // await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + await wrapper.setData({ + maximalDate: new Date(2020, 6, 6), + form: { date: new Date(2020, 6, 6) }, + }) + }) + + describe('minimalDate', () => { + it('has "2020-06-01T00:00:00.000Z"', () => { + expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z') + }) + }) + + describe('isThisMonth', () => { + it('has true', () => { + expect(wrapper.vm.isThisMonth).toBe(true) + }) + }) + }) + + describe('month before', () => { + beforeEach(async () => { + // jest.useFakeTimers('modern') + // jest.setSystemTime(new Date('2020-07-06')) + // console.log('middle of year date – now:', wrapper.vm.minimalDate) + // await wrapper + // .findComponent({ name: 'BFormDatepicker' }) + // .vm.$emit('input', wrapper.vm.minimalDate) + await wrapper.setData({ + maximalDate: new Date(2020, 6, 6), + form: { date: new Date(2020, 5, 6) }, + }) + }) + + describe('minimalDate', () => { + it('has "2020-06-01T00:00:00.000Z"', () => { + expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z') + }) + }) + + describe('isThisMonth', () => { + it('has false', () => { + expect(wrapper.vm.isThisMonth).toBe(false) + }) + }) + }) + }) + + describe('date in january', () => { + describe('same month', () => { + beforeEach(async () => { + await wrapper.setData({ + maximalDate: new Date(2020, 0, 6), + form: { date: new Date(2020, 0, 6) }, + }) + }) + + describe('minimalDate', () => { + it('has "2019-12-01T00:00:00.000Z"', () => { + expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z') + }) + }) + + describe('isThisMonth', () => { + it('has true', () => { + expect(wrapper.vm.isThisMonth).toBe(true) + }) + }) + }) + + describe('month before', () => { + beforeEach(async () => { + // jest.useFakeTimers('modern') + // jest.setSystemTime(new Date('2020-07-06')) + // console.log('middle of year date – now:', wrapper.vm.minimalDate) + // await wrapper + // .findComponent({ name: 'BFormDatepicker' }) + // .vm.$emit('input', wrapper.vm.minimalDate) + await wrapper.setData({ + maximalDate: new Date(2020, 0, 6), + form: { date: new Date(2019, 11, 6) }, + }) + }) + + describe('minimalDate', () => { + it('has "2019-12-01T00:00:00.000Z"', () => { + expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z') + }) + }) + + describe('isThisMonth', () => { + it('has false', () => { + expect(wrapper.vm.isThisMonth).toBe(false) + }) + }) }) }) }) describe('set contrubtion', () => { - describe('fill in form data', () => { + describe('fill in form data with "id === null"', () => { const now = new Date().toISOString() beforeEach(async () => { await wrapper.setData({ form: { id: null, - date: now, - memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', - amount: '200', + date: '', + memo: '', + amount: '', }, }) }) - describe('buttons', () => { - it('has reset enabled', () => { - expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy() + describe('invalid form data', () => { + beforeEach(async () => { + await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + await wrapper.find('#contribution-amount').find('input').setValue('200') }) - it('has submit enabled', () => { - expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy() + describe('memo lenght < 5, others are valid', () => { + beforeEach(async () => { + await wrapper.find('#contribution-memo').find('textarea').setValue('1234') + }) + + describe('button', () => { + describe('submit', () => { + it('has disabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBe('disabled') + }) + }) + }) + }) + + describe('memo lenght > 255, others are valid', () => { + beforeEach(async () => { + await wrapper + .find('#contribution-memo') + .find('textarea') + .setValue( + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + '01234567890123456789012345678901234567890123456789012345', + ) + await wrapper.vm.$nextTick() + }) + + describe('button', () => { + describe('submit', () => { + it('has disabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBe('disabled') + }) + }) + }) + }) + }) + + describe('valid form data', () => { + beforeEach(async () => { + await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + await wrapper + .find('#contribution-memo') + .find('textarea') + .setValue('Mein Beitrag zur Gemeinschaft für diesen Monat ...') + await wrapper.find('#contribution-amount').find('input').setValue('200') + }) + + describe('button', () => { + describe('submit', () => { + it('has enabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBeFalsy() + }) + + it('has label "contribution.submit"', () => { + expect(wrapper.find('button[data-test="button-submit"]').text()).toContain( + 'contribution.submit', + ) + }) + }) + }) + + describe('on trigger submit', () => { + beforeEach(async () => { + await wrapper.find('form').trigger('submit') + }) + + it('emits "set-contribution"', () => { + expect(wrapper.emitted('set-contribution')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + id: null, + date: now, + memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', + amount: '200', + }, + ]), + ]), + ) + }) }) }) }) }) describe('update contrubtion', () => { - describe('fill in form data and "id"', () => { + describe('fill in form data with set "id"', () => { const now = new Date().toISOString() beforeEach(async () => { @@ -93,19 +321,104 @@ describe('ContributionForm', () => { form: { id: 2, date: now, - memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', - amount: '200', + memo: 'Mein kommerzieller Beitrag für diesen Monat ...', + amount: '100', }, }) }) - describe('buttons', () => { - it('has reset enabled', () => { - expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy() + describe('invalid form data', () => { + beforeEach(async () => { + await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + await wrapper.find('#contribution-amount').find('input').setValue('200') }) - it('has submit enabled', () => { - expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy() + describe('memo lenght < 5, others are valid', () => { + beforeEach(async () => { + await wrapper.find('#contribution-memo').find('textarea').setValue('1234') + }) + + describe('button', () => { + describe('submit', () => { + it('has disabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBe('disabled') + }) + }) + }) + }) + + describe('memo lenght > 255, others are valid', () => { + beforeEach(async () => { + await wrapper + .find('#contribution-memo') + .find('textarea') + .setValue( + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + '01234567890123456789012345678901234567890123456789012345', + ) + await wrapper.vm.$nextTick() + }) + + describe('button', () => { + describe('submit', () => { + it('has disabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBe('disabled') + }) + }) + }) + }) + }) + + describe('valid form data', () => { + beforeEach(async () => { + await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) + await wrapper + .find('#contribution-memo') + .find('textarea') + .setValue('Mein Beitrag zur Gemeinschaft für diesen Monat ...') + await wrapper.find('#contribution-amount').find('input').setValue('200') + }) + + describe('button', () => { + describe('submit', () => { + it('has enabled', () => { + expect( + wrapper.find('button[data-test="button-submit"]').attributes('disabled'), + ).toBeFalsy() + }) + + it('has label "form.change"', () => { + expect(wrapper.find('button[data-test="button-submit"]').text()).toContain( + 'form.change', + ) + }) + }) + }) + + describe('on trigger submit', () => { + beforeEach(async () => { + await wrapper.find('form').trigger('submit') + }) + + it('emits "update-contribution"', () => { + expect(wrapper.emitted('update-contribution')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + id: 2, + date: now, + memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', + amount: '200', + }, + ]), + ]), + ) + }) }) }) }) diff --git a/frontend/src/components/Contributions/ContributionForm.vue b/frontend/src/components/Contributions/ContributionForm.vue index d0de061d3..3a9010ec2 100644 --- a/frontend/src/components/Contributions/ContributionForm.vue +++ b/frontend/src/components/Contributions/ContributionForm.vue @@ -4,8 +4,8 @@

{{ $t('contribution.formText.yourContribution') }}

{{ $t('contribution.formText.bringYourTalentsTo') }}
    -
  • -
  • +
  • +
@@ -13,7 +13,7 @@
- + - + {{ error }} - - + +
- + {{ $t('form.cancel') }} - + {{ form.id ? $t('form.change') : $t('contribution.submit') }} +

{{ $t('math.asterisk') }} {{ $t('form.mandatoryField') }}

diff --git a/frontend/src/routes/router.test.js b/frontend/src/routes/router.test.js index 2eefaeb36..d3f9cf992 100644 --- a/frontend/src/routes/router.test.js +++ b/frontend/src/routes/router.test.js @@ -50,7 +50,7 @@ describe('router', () => { }) it('has sixteen routes defined', () => { - expect(routes).toHaveLength(17) + expect(routes).toHaveLength(18) }) describe('overview', () => { @@ -108,6 +108,17 @@ describe('router', () => { }) }) + describe('gdt', () => { + it('requires authorization', () => { + expect(routes.find((r) => r.path === '/gdt').meta.requiresAuth).toBeTruthy() + }) + + it('loads the "GDT" page', async () => { + const component = await routes.find((r) => r.path === '/gdt').component() + expect(component.default.name).toBe('Transactions') + }) + }) + describe('login', () => { it('loads the "Login" page', async () => { const component = await routes.find((r) => r.path === '/login/:code?').component() diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index 540ef9d69..2c727304b 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -34,6 +34,15 @@ const routes = [ { path: '/transactions', component: () => import('@/pages/Transactions.vue'), + props: { gdt: false }, + meta: { + requiresAuth: true, + }, + }, + { + path: '/gdt', + component: () => import('@/pages/Transactions.vue'), + props: { gdt: true }, meta: { requiresAuth: true, }, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 829b63dcf..fe7ab7947 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2156,17 +2156,6 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.1.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" - integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== - dependencies: - "@jest/types" "^25.5.0" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - lolex "^5.0.0" - "@jest/fake-timers@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" @@ -2378,16 +2367,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -2482,11 +2461,6 @@ string-width "^2.0.0" strip-ansi "^5" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7": version "7.1.17" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64" @@ -3293,11 +3267,6 @@ acorn@^8.0.5: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== -acorn@^8.2.4: - version "8.6.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" - integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== - acorn@^8.5.0, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -3308,13 +3277,6 @@ address@^1.0.3: resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -4657,14 +4619,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4 escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -4995,7 +4949,7 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -5578,13 +5532,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -5599,6 +5546,13 @@ debug@^4.0.1, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -7162,15 +7116,6 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -7864,15 +7809,6 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - http-proxy-middleware@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" @@ -7906,14 +7842,6 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -8430,11 +8358,6 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - is-regex@^1.0.4, is-regex@^1.1.1, is-regex@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" @@ -8849,16 +8772,6 @@ jest-environment-jsdom-fifteen@^1.0.2: jest-util "^24.0.0" jsdom "^15.2.1" -jest-environment-jsdom-sixteen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1" - integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ== - dependencies: - "@jest/fake-timers" "^25.1.0" - jest-mock "^25.1.0" - jest-util "^25.1.0" - jsdom "^16.2.1" - jest-environment-jsdom@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" @@ -9073,20 +8986,6 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" - integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - slash "^3.0.0" - stack-utils "^1.0.1" - jest-message-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" @@ -9109,13 +9008,6 @@ jest-mock@^24.0.0, jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.1.0, jest-mock@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" - integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== - dependencies: - "@jest/types" "^25.5.0" - jest-mock@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" @@ -9392,17 +9284,6 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.1.0, jest-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" - integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - make-dir "^3.0.0" - jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" @@ -9655,39 +9536,6 @@ jsdom@^15.2.1: ws "^7.0.0" xml-name-validator "^3.0.0" -jsdom@^16.2.1: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - jsdom@^16.4.0: version "16.5.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.5.1.tgz#4ced6bbd7b77d67fb980e64d9e3e6fb900f97dd6" @@ -10050,7 +9898,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.7.0: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -10067,13 +9915,6 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== - dependencies: - "@sinonjs/commons" "^1.7.0" - loose-envify@^1.0.0, loose-envify@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -13848,13 +13689,6 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -14792,15 +14626,6 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^6.1.0" -whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -14923,7 +14748,7 @@ ws@^6.0.0, ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.0.0, ws@^7.4.6: +ws@^7.0.0: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==