diff --git a/backend/src/event/EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE.ts b/backend/src/event/EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE.ts new file mode 100644 index 000000000..f07d38e98 --- /dev/null +++ b/backend/src/event/EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE.ts @@ -0,0 +1,23 @@ +import { User as DbUser } from '@entity/User' +import { Contribution as DbContribution } from '@entity/Contribution' +import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE = async ( + user: DbUser, + moderator: DbUser, + contribution: DbContribution, + contributionMessage: DbContributionMessage, +): Promise => + Event( + EventType.ADMIN_CONTRIBUTION_MESSAGE_CREATE, + user, + moderator, + null, + null, + contribution, + contributionMessage, + null, + null, + ).save() diff --git a/backend/src/event/EVENT_CONTRIBUTION_LINK_REDEEM.ts b/backend/src/event/EVENT_CONTRIBUTION_LINK_REDEEM.ts new file mode 100644 index 000000000..395772ac9 --- /dev/null +++ b/backend/src/event/EVENT_CONTRIBUTION_LINK_REDEEM.ts @@ -0,0 +1,27 @@ +import Decimal from 'decimal.js-light' +import { User as DbUser } from '@entity/User' +import { Transaction as DbTransaction } from '@entity/Transaction' +import { Contribution as DbContribution } from '@entity/Contribution' +import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_CONTRIBUTION_LINK_REDEEM = async ( + user: DbUser, + transaction: DbTransaction, + contribution: DbContribution, + contributionLink: DbContributionLink, + amount: Decimal, +): Promise => + Event( + EventType.CONTRIBUTION_LINK_REDEEM, + user, + user, + null, + transaction, + contribution, + null, + null, + contributionLink, + amount, + ).save() diff --git a/backend/src/event/EVENT_CONTRIBUTION_MESSAGE_CREATE.ts b/backend/src/event/EVENT_CONTRIBUTION_MESSAGE_CREATE.ts new file mode 100644 index 000000000..b06685a6d --- /dev/null +++ b/backend/src/event/EVENT_CONTRIBUTION_MESSAGE_CREATE.ts @@ -0,0 +1,22 @@ +import { User as DbUser } from '@entity/User' +import { Contribution as DbContribution } from '@entity/Contribution' +import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_CONTRIBUTION_MESSAGE_CREATE = async ( + user: DbUser, + contribution: DbContribution, + contributionMessage: DbContributionMessage, +): Promise => + Event( + EventType.CONTRIBUTION_MESSAGE_CREATE, + user, + user, + null, + null, + contribution, + contributionMessage, + null, + null, + ).save() diff --git a/backend/src/event/EVENT_TRANSACTION_LINK_CREATE.ts b/backend/src/event/EVENT_TRANSACTION_LINK_CREATE.ts new file mode 100644 index 000000000..36fdb3ff0 --- /dev/null +++ b/backend/src/event/EVENT_TRANSACTION_LINK_CREATE.ts @@ -0,0 +1,23 @@ +import Decimal from 'decimal.js-light' +import { User as DbUser } from '@entity/User' +import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_TRANSACTION_LINK_CREATE = async ( + user: DbUser, + transactionLink: DbTransactionLink, + amount: Decimal, +): Promise => + Event( + EventType.TRANSACTION_LINK_CREATE, + user, + user, + null, + null, + null, + null, + transactionLink, + null, + amount, + ).save() diff --git a/backend/src/event/EVENT_TRANSACTION_LINK_DELETE.ts b/backend/src/event/EVENT_TRANSACTION_LINK_DELETE.ts new file mode 100644 index 000000000..d15c786a8 --- /dev/null +++ b/backend/src/event/EVENT_TRANSACTION_LINK_DELETE.ts @@ -0,0 +1,19 @@ +import { User as DbUser } from '@entity/User' +import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_TRANSACTION_LINK_DELETE = async ( + user: DbUser, + transactionLink: DbTransactionLink, +): Promise => + Event( + EventType.TRANSACTION_LINK_DELETE, + user, + user, + null, + null, + null, + null, + transactionLink, + ).save() diff --git a/backend/src/event/EVENT_TRANSACTION_LINK_REDEEM.ts b/backend/src/event/EVENT_TRANSACTION_LINK_REDEEM.ts new file mode 100644 index 000000000..58307a4e1 --- /dev/null +++ b/backend/src/event/EVENT_TRANSACTION_LINK_REDEEM.ts @@ -0,0 +1,24 @@ +import Decimal from 'decimal.js-light' +import { User as DbUser } from '@entity/User' +import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' +import { Event as DbEvent } from '@entity/Event' +import { Event, EventType } from './Event' + +export const EVENT_TRANSACTION_LINK_REDEEM = async ( + user: DbUser, + senderUser: DbUser, + transactionLink: DbTransactionLink, + amount: Decimal, +): Promise => + Event( + EventType.TRANSACTION_LINK_REDEEM, + user, + user, + senderUser, + null, + null, + null, + transactionLink, + null, + amount, + ).save() diff --git a/backend/src/event/Event.ts b/backend/src/event/Event.ts index 2e7cca6af..cdb05748c 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -45,13 +45,19 @@ export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDA export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE' export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE' export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE' +export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE' export { EVENT_ADMIN_SEND_CONFIRMATION_EMAIL } from './EVENT_ADMIN_SEND_CONFIRMATION_EMAIL' export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE' export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE' export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE' +export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE' +export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM' export { EVENT_LOGIN } from './EVENT_LOGIN' export { EVENT_REGISTER } from './EVENT_REGISTER' export { EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL } from './EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL' export { EVENT_SEND_CONFIRMATION_EMAIL } from './EVENT_SEND_CONFIRMATION_EMAIL' export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND' export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE' +export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE' +export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE' +export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM' diff --git a/backend/src/event/EventType.ts b/backend/src/event/EventType.ts index b219a49ba..47056f05e 100644 --- a/backend/src/event/EventType.ts +++ b/backend/src/event/EventType.ts @@ -9,10 +9,13 @@ export enum EventType { ADMIN_CONTRIBUTION_LINK_CREATE = 'ADMIN_CONTRIBUTION_LINK_CREATE', ADMIN_CONTRIBUTION_LINK_DELETE = 'ADMIN_CONTRIBUTION_LINK_DELETE', ADMIN_CONTRIBUTION_LINK_UPDATE = 'ADMIN_CONTRIBUTION_LINK_UPDATE', + ADMIN_CONTRIBUTION_MESSAGE_CREATE = 'ADMIN_CONTRIBUTION_MESSAGE_CREATE', ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL', CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE', CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE', CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE', + CONTRIBUTION_MESSAGE_CREATE = 'CONTRIBUTION_MESSAGE_CREATE', + CONTRIBUTION_LINK_REDEEM = 'CONTRIBUTION_LINK_REDEEM', LOGIN = 'LOGIN', REGISTER = 'REGISTER', REDEEM_REGISTER = 'REDEEM_REGISTER', @@ -20,6 +23,9 @@ export enum EventType { SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL', TRANSACTION_SEND = 'TRANSACTION_SEND', TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE', + TRANSACTION_LINK_CREATE = 'TRANSACTION_LINK_CREATE', + TRANSACTION_LINK_DELETE = 'TRANSACTION_LINK_DELETE', + TRANSACTION_LINK_REDEEM = 'TRANSACTION_LINK_REDEEM', // VISIT_GRADIDO = 'VISIT_GRADIDO', // VERIFY_REDEEM = 'VERIFY_REDEEM', // INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT', diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index 8b5c5a0a7..3f10adae6 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -20,6 +20,8 @@ import { userFactory } from '@/seeds/factory/user' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants' +import { EventType } from '@/event/Event' +import { Event as DbEvent } from '@entity/Event' jest.mock('@/emails/sendEmailVariants', () => { const originalModule = jest.requireActual('@/emails/sendEmailVariants') @@ -197,6 +199,18 @@ describe('ContributionMessageResolver', () => { contributionMemo: 'Test env contribution', }) }) + + it('stores the ADMIN_CONTRIBUTION_MESSAGE_CREATE event in the database', async () => { + await expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.ADMIN_CONTRIBUTION_MESSAGE_CREATE, + affectedUserId: expect.any(Number), + actingUserId: expect.any(Number), + involvedContributionId: result.data.createContribution.id, + involvedContributionMessageId: expect.any(Number), + }), + ) + }) }) }) }) @@ -322,6 +336,18 @@ describe('ContributionMessageResolver', () => { }), ) }) + + it('stores the CONTRIBUTION_MESSAGE_CREATE event in the database', async () => { + await expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.CONTRIBUTION_MESSAGE_CREATE, + affectedUserId: expect.any(Number), + actingUserId: expect.any(Number), + involvedContributionId: result.data.createContribution.id, + involvedContributionMessageId: expect.any(Number), + }), + ) + }) }) }) }) diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.ts b/backend/src/graphql/resolver/ContributionMessageResolver.ts index e9258490e..999ccc2b1 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.ts @@ -4,7 +4,8 @@ import { getConnection } from '@dbTools/typeorm' import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage' import { Contribution as DbContribution } from '@entity/Contribution' -import { UserContact } from '@entity/UserContact' +import { UserContact as DbUserContact } from '@entity/UserContact' +import { User as DbUser } from '@entity/User' import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage' import ContributionMessageArgs from '@arg/ContributionMessageArgs' @@ -17,6 +18,10 @@ import { RIGHTS } from '@/auth/RIGHTS' import { Context, getUser } from '@/server/context' import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants' import LogError from '@/server/LogError' +import { + EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE, + EVENT_CONTRIBUTION_MESSAGE_CREATE, +} from '@/event/Event' @Resolver() export class ContributionMessageResolver { @@ -57,6 +62,11 @@ export class ContributionMessageResolver { await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution) } await queryRunner.commitTransaction() + await EVENT_CONTRIBUTION_MESSAGE_CREATE( + user, + { id: contributionMessage.contributionId } as DbContribution, + contributionMessage, + ) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e) @@ -98,7 +108,7 @@ export class ContributionMessageResolver { @Args() { contributionId, message }: ContributionMessageArgs, @Ctx() context: Context, ): Promise { - const user = getUser(context) + const moderator = getUser(context) const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() @@ -112,18 +122,18 @@ export class ContributionMessageResolver { if (!contribution) { throw new LogError('Contribution not found', contributionId) } - if (contribution.userId === user.id) { + if (contribution.userId === moderator.id) { throw new LogError('Admin can not answer on his own contribution', contributionId) } if (!contribution.user.emailContact) { - contribution.user.emailContact = await UserContact.findOneOrFail({ + contribution.user.emailContact = await DbUserContact.findOneOrFail({ where: { id: contribution.user.emailId }, }) } contributionMessage.contributionId = contributionId contributionMessage.createdAt = new Date() contributionMessage.message = message - contributionMessage.userId = user.id + contributionMessage.userId = moderator.id contributionMessage.type = ContributionMessageType.DIALOG contributionMessage.isModerator = true await queryRunner.manager.insert(DbContributionMessage, contributionMessage) @@ -142,17 +152,23 @@ export class ContributionMessageResolver { lastName: contribution.user.lastName, email: contribution.user.emailContact.email, language: contribution.user.language, - senderFirstName: user.firstName, - senderLastName: user.lastName, + senderFirstName: moderator.firstName, + senderLastName: moderator.lastName, contributionMemo: contribution.memo, }) await queryRunner.commitTransaction() + await EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE( + { id: contribution.userId } as DbUser, + moderator, + contribution, + contributionMessage, + ) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e) } finally { await queryRunner.release() } - return new ContributionMessage(contributionMessage, user) + return new ContributionMessage(contributionMessage, moderator) } } diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index d4f059832..ceb062e8c 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -2545,7 +2545,7 @@ describe('ContributionResolver', () => { ) }) - it('stores the CONTRIBUTION_CONFIRM event in the database', async () => { + it('stores the ADMIN_CONTRIBUTION_CONFIRM event in the database', async () => { await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventType.ADMIN_CONTRIBUTION_CONFIRM, diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 55d90dab6..1cc097152 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -90,7 +90,6 @@ export class ContributionResolver { logger.trace('contribution to save', contribution) await DbContribution.save(contribution) - await EVENT_CONTRIBUTION_CREATE(user, contribution, amount) return new UnconfirmedContribution(contribution, user, creations) @@ -118,7 +117,6 @@ export class ContributionResolver { contribution.deletedBy = user.id contribution.deletedAt = new Date() await contribution.save() - await EVENT_CONTRIBUTION_DELETE(user, contribution, contribution.amount) const res = await contribution.softRemove() @@ -299,11 +297,8 @@ export class ContributionResolver { contribution.moderatorId = moderator.id contribution.contributionType = ContributionType.ADMIN contribution.contributionStatus = ContributionStatus.PENDING - logger.trace('contribution to save', contribution) - await DbContribution.save(contribution) - await EVENT_ADMIN_CONTRIBUTION_CREATE(emailContact.user, moderator, contribution, amount) return getUserCreation(emailContact.userId, clientTimezoneOffset) @@ -369,9 +364,7 @@ export class ContributionResolver { result.amount = amount result.memo = contributionToUpdate.memo result.date = contributionToUpdate.contributionDate - result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset) - await EVENT_ADMIN_CONTRIBUTION_UPDATE( emailContact.user, moderator, @@ -436,7 +429,6 @@ export class ContributionResolver { contribution.deletedBy = moderator.id await contribution.save() const res = await contribution.softRemove() - await EVENT_ADMIN_CONTRIBUTION_DELETE( { id: contribution.userId } as DbUser, moderator, @@ -554,7 +546,6 @@ export class ContributionResolver { } finally { await queryRunner.release() } - await EVENT_ADMIN_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount) } finally { releaseLock() @@ -613,7 +604,6 @@ export class ContributionResolver { contributionToUpdate.deniedBy = moderator.id contributionToUpdate.deniedAt = new Date() const res = await contributionToUpdate.save() - await EVENT_ADMIN_CONTRIBUTION_DENY( user, moderator, diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 14c5b350b..e1c73b98c 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -33,6 +33,9 @@ import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { logger } from '@test/testSetup' +import { EventType } from '@/event/Event' +import { Event as DbEvent } from '@entity/Event' +import { UserContact } from '@entity/UserContact' // mock semaphore to allow use fake timers jest.mock('@/util/TRANSACTIONS_LOCK') @@ -445,6 +448,24 @@ describe('TransactionLinkResolver', () => { }) }) + it('stores the CONTRIBUTION_LINK_REDEEM event in the database', async () => { + const userConatct = await UserContact.findOneOrFail( + { email: 'bibi@bloxberg.de' }, + { relations: ['user'] }, + ) + await expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.CONTRIBUTION_LINK_REDEEM, + affectedUserId: userConatct.user.id, + actingUserId: userConatct.user.id, + involvedTransactionId: expect.any(Number), + involvedContributionId: expect.any(Number), + involvedContributionLinkId: contributionLink?.id, + amount: contributionLink?.amount, + }), + ) + }) + it('does not allow the user to redeem the contribution link a second time on the same day', async () => { jest.clearAllMocks() await expect( diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 9e365ab51..774a7317f 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -35,6 +35,12 @@ import LogError from '@/server/LogError' import { getLastTransaction } from './util/getLastTransaction' import transactionLinkList from './util/transactionLinkList' +import { + EVENT_CONTRIBUTION_LINK_REDEEM, + EVENT_TRANSACTION_LINK_CREATE, + EVENT_TRANSACTION_LINK_DELETE, + EVENT_TRANSACTION_LINK_REDEEM, +} from '@/event/Event' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { @@ -89,6 +95,7 @@ export class TransactionLinkResolver { await DbTransactionLink.save(transactionLink).catch((e) => { throw new LogError('Unable to save transaction link', e) }) + await EVENT_TRANSACTION_LINK_CREATE(user, transactionLink, amount) return new TransactionLink(transactionLink, new User(user)) } @@ -122,6 +129,8 @@ export class TransactionLinkResolver { throw new LogError('Transaction link could not be deleted', e) }) + await EVENT_TRANSACTION_LINK_DELETE(user, transactionLink) + return true } @@ -272,7 +281,13 @@ export class TransactionLinkResolver { await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) await queryRunner.commitTransaction() - logger.info('creation from contribution link commited successfuly.') + await EVENT_CONTRIBUTION_LINK_REDEEM( + user, + transaction, + contribution, + contributionLink, + contributionLink.amount, + ) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError('Creation from contribution link was not successful', e) @@ -321,6 +336,12 @@ export class TransactionLinkResolver { user, transactionLink, ) + await EVENT_TRANSACTION_LINK_REDEEM( + user, + { id: transactionLink.userId } as DbUser, + transactionLink, + transactionLink.amount, + ) return true }