From 6e5540b863fbf1edc02205a6e59245c867630b1b Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 29 Jun 2022 10:49:53 +0200 Subject: [PATCH] Move all util methods to isContributionValid file, add const file for Resolvers. --- backend/src/graphql/resolver/AdminResolver.ts | 140 ++++-------------- .../graphql/resolver/ContributionResolver.ts | 3 +- backend/src/graphql/resolver/const/const.ts | 12 ++ .../graphql/resolver/util/getUserCreation.ts | 9 -- .../resolver/util/isContributionValid.ts | 103 ++++++++++++- 5 files changed, 137 insertions(+), 130 deletions(-) create mode 100644 backend/src/graphql/resolver/const/const.ts delete mode 100644 backend/src/graphql/resolver/util/getUserCreation.ts diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 841303bf6..5476fd8a1 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -46,21 +46,23 @@ import { checkOptInCode, activationLink, printTimeDuration } from './UserResolve import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import CONFIG from '@/config' -import { getUserCreation } from './util/getUserCreation' -import { isContributionValid } from './util/isContributionValid' +import { + getCreationIndex, + getUserCreation, + getUserCreations, + isContributionValid, + isStartEndDateValid, +} from './util/isContributionValid' +import { + CONTRIBUTIONLINK_MEMO_MAX_CHARS, + CONTRIBUTIONLINK_MEMO_MIN_CHARS, + CONTRIBUTIONLINK_NAME_MAX_CHARS, + CONTRIBUTIONLINK_NAME_MIN_CHARS, + FULL_CREATION_AVAILABLE, +} from './const/const' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? -const MAX_CREATION_AMOUNT = new Decimal(1000) -export const FULL_CREATION_AVAILABLE = [ - MAX_CREATION_AMOUNT, - MAX_CREATION_AMOUNT, - MAX_CREATION_AMOUNT, -] -const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100 -const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5 -const CONTRIBUTIONLINK_MEMO_MAX_CHARS = 255 -const CONTRIBUTIONLINK_MEMO_MIN_CHARS = 5 @Resolver() export class AdminResolver { @@ -250,18 +252,17 @@ export class AdminResolver { const creations = await getUserCreation(user.id) logger.trace('creations', creations) const creationDateObj = new Date(creationDate) - if (isContributionValid(creations, amount, creationDateObj)) { - const contribution = Contribution.create() - contribution.userId = user.id - contribution.amount = amount - contribution.createdAt = new Date() - contribution.contributionDate = creationDateObj - contribution.memo = memo - contribution.moderatorId = moderator.id + isContributionValid(creations, amount, creationDateObj) + const contribution = Contribution.create() + contribution.userId = user.id + contribution.amount = amount + contribution.createdAt = new Date() + contribution.contributionDate = creationDateObj + contribution.memo = memo + contribution.moderatorId = moderator.id - logger.trace('contribution to save', contribution) - await Contribution.save(contribution) - } + logger.trace('contribution to save', contribution) + await Contribution.save(contribution) return getUserCreation(user.id) } @@ -404,9 +405,7 @@ export class AdminResolver { if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a contribution.') const creations = await getUserCreation(contribution.userId, false) - if (!isContributionValid(creations, contribution.amount, contribution.contributionDate)) { - throw new Error('Creation is not valid!!') - } + isContributionValid(creations, contribution.amount, contribution.contributionDate) const receivedCallDate = new Date() @@ -690,61 +689,6 @@ export class AdminResolver { } } -interface CreationMap { - id: number - creations: Decimal[] -} - -export async function getUserCreations( - ids: number[], - includePending = true, -): Promise { - logger.trace('getUserCreations:', ids, includePending) - const months = getCreationMonths() - logger.trace('getUserCreations months', months) - - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - - const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' - logger.trace('getUserCreations dateFilter', dateFilter) - - const unionString = includePending - ? ` - UNION - SELECT contribution_date AS date, amount AS amount, user_id AS userId FROM contributions - WHERE user_id IN (${ids.toString()}) - AND contribution_date >= ${dateFilter} - AND confirmed_at IS NULL AND deleted_at IS NULL` - : '' - - const unionQuery = await queryRunner.manager.query(` - SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM - (SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions - WHERE user_id IN (${ids.toString()}) - AND type_id = ${TransactionTypeId.CREATION} - AND creation_date >= ${dateFilter} - ${unionString}) AS result - GROUP BY month, userId - ORDER BY date DESC - `) - - await queryRunner.release() - - return ids.map((id) => { - return { - id, - creations: months.map((month) => { - const creation = unionQuery.find( - (raw: { month: string; id: string; creation: number[] }) => - parseInt(raw.month) === month && parseInt(raw.id) === id, - ) - return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) - }), - } - }) -} - function updateCreations(creations: Decimal[], contribution: Contribution): Decimal[] { const index = getCreationIndex(contribution.contributionDate.getMonth()) @@ -754,37 +698,3 @@ function updateCreations(creations: Decimal[], contribution: Contribution): Deci creations[index] = creations[index].plus(contribution.amount.toString()) return creations } - -const isStartEndDateValid = ( - startDate: string | null | undefined, - endDate: string | null | undefined, -): void => { - if (!startDate) { - logger.error('Start-Date is not initialized. A Start-Date must be set!') - throw new Error('Start-Date is not initialized. A Start-Date must be set!') - } - - if (!endDate) { - logger.error('End-Date is not initialized. An End-Date must be set!') - throw new Error('End-Date is not initialized. An End-Date must be set!') - } - - // check if endDate is before startDate - if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) { - logger.error(`The value of validFrom must before or equals the validTo!`) - throw new Error(`The value of validFrom must before or equals the validTo!`) - } -} - -const getCreationMonths = (): number[] => { - const now = new Date(Date.now()) - return [ - now.getMonth() + 1, - new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1, - new Date(now.getFullYear(), now.getMonth() - 2, 1).getMonth() + 1, - ].reverse() -} - -export const getCreationIndex = (month: number): number => { - return getCreationMonths().findIndex((el) => el === month + 1) -} diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index b083305e9..9d1dfc219 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -4,8 +4,7 @@ import { backendLogger as logger } from '@/server/logger' import { Contribution } from '@entity/Contribution' import { Args, Authorized, Ctx, Mutation, Resolver } from 'type-graphql' import ContributionArgs from '../arg/ContributionArgs' -import { getUserCreation } from './util/getUserCreation' -import { isContributionValid } from './util/isContributionValid' +import { isContributionValid, getUserCreation } from './util/isContributionValid' @Resolver() export class ContributionResolver { diff --git a/backend/src/graphql/resolver/const/const.ts b/backend/src/graphql/resolver/const/const.ts new file mode 100644 index 000000000..d5ba08784 --- /dev/null +++ b/backend/src/graphql/resolver/const/const.ts @@ -0,0 +1,12 @@ +import Decimal from 'decimal.js-light' + +export const MAX_CREATION_AMOUNT = new Decimal(1000) +export const FULL_CREATION_AVAILABLE = [ + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, +] +export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100 +export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5 +export const CONTRIBUTIONLINK_MEMO_MAX_CHARS = 255 +export const CONTRIBUTIONLINK_MEMO_MIN_CHARS = 5 diff --git a/backend/src/graphql/resolver/util/getUserCreation.ts b/backend/src/graphql/resolver/util/getUserCreation.ts deleted file mode 100644 index 445e25b9c..000000000 --- a/backend/src/graphql/resolver/util/getUserCreation.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { backendLogger as logger } from '@/server/logger' -import Decimal from 'decimal.js-light' -import { getUserCreations, FULL_CREATION_AVAILABLE } from '../AdminResolver' - -export const getUserCreation = async (id: number, includePending = true): Promise => { - logger.trace('getUserCreation', id, includePending) - const creations = await getUserCreations([id], includePending) - return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE -} diff --git a/backend/src/graphql/resolver/util/isContributionValid.ts b/backend/src/graphql/resolver/util/isContributionValid.ts index 5ae7755c1..9dd59e6c9 100644 --- a/backend/src/graphql/resolver/util/isContributionValid.ts +++ b/backend/src/graphql/resolver/util/isContributionValid.ts @@ -1,12 +1,19 @@ +import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId' import { backendLogger as logger } from '@/server/logger' +import { getConnection } from '@dbTools/typeorm' import Decimal from 'decimal.js-light' -import { getCreationIndex } from '../AdminResolver' +import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '../const/const' + +interface CreationMap { + id: number + creations: Decimal[] +} export const isContributionValid = ( creations: Decimal[], amount: Decimal, creationDate: Date, -): boolean => { +): void => { logger.trace('isContributionValid', creations, amount, creationDate) const index = getCreationIndex(creationDate.getMonth()) @@ -19,6 +26,94 @@ export const isContributionValid = ( `The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`, ) } - - return true +} + +export async function getUserCreations( + ids: number[], + includePending = true, +): Promise { + logger.trace('getUserCreations:', ids, includePending) + const months = getCreationMonths() + logger.trace('getUserCreations months', months) + + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + + const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' + logger.trace('getUserCreations dateFilter', dateFilter) + + const unionString = includePending + ? ` + UNION + SELECT contribution_date AS date, amount AS amount, user_id AS userId FROM contributions + WHERE user_id IN (${ids.toString()}) + AND contribution_date >= ${dateFilter} + AND confirmed_at IS NULL AND deleted_at IS NULL` + : '' + + const unionQuery = await queryRunner.manager.query(` + SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM + (SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions + WHERE user_id IN (${ids.toString()}) + AND type_id = ${TransactionTypeId.CREATION} + AND creation_date >= ${dateFilter} + ${unionString}) AS result + GROUP BY month, userId + ORDER BY date DESC + `) + + await queryRunner.release() + + return ids.map((id) => { + return { + id, + creations: months.map((month) => { + const creation = unionQuery.find( + (raw: { month: string; id: string; creation: number[] }) => + parseInt(raw.month) === month && parseInt(raw.id) === id, + ) + return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) + }), + } + }) +} + +export const getUserCreation = async (id: number, includePending = true): Promise => { + logger.trace('getUserCreation', id, includePending) + const creations = await getUserCreations([id], includePending) + return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE +} + +export const getCreationMonths = (): number[] => { + const now = new Date(Date.now()) + return [ + now.getMonth() + 1, + new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1, + new Date(now.getFullYear(), now.getMonth() - 2, 1).getMonth() + 1, + ].reverse() +} + +export const getCreationIndex = (month: number): number => { + return getCreationMonths().findIndex((el) => el === month + 1) +} + +export const isStartEndDateValid = ( + startDate: string | null | undefined, + endDate: string | null | undefined, +): void => { + if (!startDate) { + logger.error('Start-Date is not initialized. A Start-Date must be set!') + throw new Error('Start-Date is not initialized. A Start-Date must be set!') + } + + if (!endDate) { + logger.error('End-Date is not initialized. An End-Date must be set!') + throw new Error('End-Date is not initialized. An End-Date must be set!') + } + + // check if endDate is before startDate + if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) { + logger.error(`The value of validFrom must before or equals the validTo!`) + throw new Error(`The value of validFrom must before or equals the validTo!`) + } }