diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 27b51b47d..961d83219 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0060-update_communities_table', + DB_VERSION: '0061-event_refactoring', 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/event/Event.ts b/backend/src/event/Event.ts index 8e65d85f2..7ed75fd45 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -1,212 +1,217 @@ -import { EventProtocol as DbEvent } from '@entity/EventProtocol' +import { Event as DbEvent } from '@entity/Event' +import { User as DbUser } from '@entity/User' +import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage' +import { Contribution as DbContribution } from '@entity/Contribution' +import { Transaction as DbTransaction } from '@entity/Transaction' import Decimal from 'decimal.js-light' import { EventProtocolType } from './EventProtocolType' export const Event = ( type: EventProtocolType, - userId: number, - xUserId: number | null = null, - xCommunityId: number | null = null, - transactionId: number | null = null, - contributionId: number | null = null, + affectedUser: DbUser, + actingUser: DbUser, + involvedUser: DbUser | null = null, + involvedTransaction: DbTransaction | null = null, + involvedContribution: DbContribution | null = null, + involvedContributionMessage: DbContributionMessage | null = null, amount: Decimal | null = null, - messageId: number | null = null, ): DbEvent => { const event = new DbEvent() event.type = type - event.userId = userId - event.xUserId = xUserId - event.xCommunityId = xCommunityId - event.transactionId = transactionId - event.contributionId = contributionId + event.affectedUser = affectedUser + event.actingUser = actingUser + event.involvedUser = involvedUser + event.involvedTransaction = involvedTransaction + event.involvedContribution = involvedContribution + event.involvedContributionMessage = involvedContributionMessage event.amount = amount - event.messageId = messageId return event } export const EVENT_CONTRIBUTION_CREATE = async ( - userId: number, - contributionId: number, + user: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.CONTRIBUTION_CREATE, - userId, + user, + user, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_CONTRIBUTION_DELETE = async ( - userId: number, - contributionId: number, + user: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.CONTRIBUTION_DELETE, - userId, + user, + user, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_CONTRIBUTION_UPDATE = async ( - userId: number, - contributionId: number, + user: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.CONTRIBUTION_UPDATE, - userId, + user, + user, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_ADMIN_CONTRIBUTION_CREATE = async ( - userId: number, - contributionId: number, + user: DbUser, + moderator: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.ADMIN_CONTRIBUTION_CREATE, - userId, + user, + moderator, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async ( - userId: number, - contributionId: number, + user: DbUser, + moderator: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId, + user, + moderator, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_ADMIN_CONTRIBUTION_DELETE = async ( - userId: number, - contributionId: number, + user: DbUser, + moderator: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.ADMIN_CONTRIBUTION_DELETE, - userId, + user, + moderator, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_CONTRIBUTION_CONFIRM = async ( - userId: number, - contributionId: number, + user: DbUser, + moderator: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.CONTRIBUTION_CONFIRM, - userId, + user, + moderator, null, null, + contribution, null, - contributionId, amount, ).save() export const EVENT_ADMIN_CONTRIBUTION_DENY = async ( - userId: number, - xUserId: number, - contributionId: number, + user: DbUser, + moderator: DbUser, + contribution: DbContribution, amount: Decimal, ): Promise => Event( EventProtocolType.ADMIN_CONTRIBUTION_DENY, - userId, - xUserId, + user, + moderator, null, null, - contributionId, + contribution, + null, amount, ).save() export const EVENT_TRANSACTION_SEND = async ( - userId: number, - xUserId: number, - transactionId: number, + user: DbUser, + involvedUser: DbUser, + transaction: DbTransaction, amount: Decimal, ): Promise => Event( EventProtocolType.TRANSACTION_SEND, - userId, - xUserId, + user, + user, + involvedUser, + transaction, null, - transactionId, null, amount, ).save() export const EVENT_TRANSACTION_RECEIVE = async ( - userId: number, - xUserId: number, - transactionId: number, + user: DbUser, + involvedUser: DbUser, + transaction: DbTransaction, amount: Decimal, ): Promise => Event( EventProtocolType.TRANSACTION_RECEIVE, - userId, - xUserId, + user, + involvedUser, + involvedUser, + transaction, null, - transactionId, null, amount, ).save() -export const EVENT_LOGIN = async (userId: number): Promise => - Event(EventProtocolType.LOGIN, userId, null, null, null, null, null, null).save() +export const EVENT_LOGIN = async (user: DbUser): Promise => + Event(EventProtocolType.LOGIN, user, user).save() -export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async ( - userId: number, -): Promise => Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, userId).save() +export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async (user: DbUser): Promise => + Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, user, { id: 0 } as DbUser).save() -export const EVENT_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise => - Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, userId).save() +export const EVENT_SEND_CONFIRMATION_EMAIL = async (user: DbUser): Promise => + Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, user, user).save() -export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise => - Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save() +export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async ( + user: DbUser, + moderator: DbUser, +): Promise => + Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, user, moderator).save() -/* export const EVENT_REDEEM_REGISTER = async ( - userId: number, - transactionId: number | null = null, - contributionId: number | null = null, -): Promise => - Event( - EventProtocolType.REDEEM_REGISTER, - userId, - null, - null, - transactionId, - contributionId, - ).save() -*/ +export const EVENT_REGISTER = async (user: DbUser): Promise => + Event(EventProtocolType.REGISTER, user, user).save() -export const EVENT_REGISTER = async (userId: number): Promise => - Event(EventProtocolType.REGISTER, userId).save() - -export const EVENT_ACTIVATE_ACCOUNT = async (userId: number): Promise => - Event(EventProtocolType.ACTIVATE_ACCOUNT, userId).save() +export const EVENT_ACTIVATE_ACCOUNT = async (user: DbUser): Promise => + Event(EventProtocolType.ACTIVATE_ACCOUNT, user, user).save() diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 274067ba0..ae017b7e5 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -46,7 +46,7 @@ import { userFactory } from '@/seeds/factory/user' import { creationFactory } from '@/seeds/factory/creation' import { creations } from '@/seeds/creation/index' import { peterLustig } from '@/seeds/users/peter-lustig' -import { EventProtocol } from '@entity/EventProtocol' +import { Event as DbEvent } from '@entity/Event' import { Contribution } from '@entity/Contribution' import { Transaction as DbTransaction } from '@entity/Transaction' import { User } from '@entity/User' @@ -279,12 +279,13 @@ describe('ContributionResolver', () => { }) it('stores the CONTRIBUTION_CREATE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CREATE, + affectedUserId: bibi.id, + actingUserId: bibi.id, + involvedContributionId: pendingContribution.data.createContribution.id, amount: expect.decimalEqual(100), - contributionId: pendingContribution.data.createContribution.id, - userId: bibi.id, }), ) }) @@ -584,12 +585,13 @@ describe('ContributionResolver', () => { variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_UPDATE, + affectedUserId: bibi.id, + actingUserId: bibi.id, + involvedContributionId: pendingContribution.data.createContribution.id, amount: expect.decimalEqual(10), - contributionId: pendingContribution.data.createContribution.id, - userId: bibi.id, }), ) }) @@ -814,12 +816,12 @@ describe('ContributionResolver', () => { }) it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, - userId: bibi.id, - xUserId: admin.id, - contributionId: contributionToDeny.data.createContribution.id, + affectedUserId: bibi.id, + actingUserId: admin.id, + involvedContributionId: contributionToDeny.data.createContribution.id, amount: expect.decimalEqual(100), }), ) @@ -942,12 +944,13 @@ describe('ContributionResolver', () => { }) it('stores the CONTRIBUTION_DELETE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_DELETE, - contributionId: contributionToDelete.data.createContribution.id, + affectedUserId: bibi.id, + actingUserId: bibi.id, + involvedContributionId: contributionToDelete.data.createContribution.id, amount: expect.decimalEqual(100), - userId: bibi.id, }), ) }) @@ -2031,10 +2034,11 @@ describe('ContributionResolver', () => { }) it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE, - userId: admin.id, + affectedUserId: bibi.id, + actingUserId: admin.id, amount: expect.decimalEqual(200), }), ) @@ -2232,7 +2236,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminUpdateContribution, variables: { - id: creation ? creation.id : -1, + id: creation?.id, email: 'peter@lustig.de', amount: new Decimal(300), memo: 'Danke Peter!', @@ -2256,10 +2260,11 @@ describe('ContributionResolver', () => { }) it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: admin.id, + affectedUserId: creation?.userId, + actingUserId: admin.id, amount: 300, }), ) @@ -2273,7 +2278,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminUpdateContribution, variables: { - id: creation ? creation.id : -1, + id: creation?.id, email: 'peter@lustig.de', amount: new Decimal(200), memo: 'Das war leider zu Viel!', @@ -2297,10 +2302,11 @@ describe('ContributionResolver', () => { }) it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: admin.id, + affectedUserId: creation?.userId, + actingUserId: admin.id, amount: expect.decimalEqual(200), }), ) @@ -2371,7 +2377,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminDeleteContribution, variables: { - id: creation ? creation.id : -1, + id: creation?.id, }, }), ).resolves.toEqual( @@ -2382,10 +2388,12 @@ describe('ContributionResolver', () => { }) it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, - userId: admin.id, + affectedUserId: creation?.userId, + actingUserId: admin.id, + involvedContributionId: creation?.id, amount: expect.decimalEqual(200), }), ) @@ -2538,7 +2546,7 @@ describe('ContributionResolver', () => { }) it('stores the CONTRIBUTION_CONFIRM event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CONFIRM, }), @@ -2570,7 +2578,7 @@ describe('ContributionResolver', () => { }) it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.SEND_CONFIRMATION_EMAIL, }), diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 88bfde902..f0e8cbd14 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -91,7 +91,7 @@ export class ContributionResolver { logger.trace('contribution to save', contribution) await DbContribution.save(contribution) - await EVENT_CONTRIBUTION_CREATE(user.id, contribution.id, amount) + await EVENT_CONTRIBUTION_CREATE(user, contribution, amount) return new UnconfirmedContribution(contribution, user, creations) } @@ -119,7 +119,7 @@ export class ContributionResolver { contribution.deletedAt = new Date() await contribution.save() - await EVENT_CONTRIBUTION_DELETE(user.id, contribution.id, contribution.amount) + await EVENT_CONTRIBUTION_DELETE(user, contribution, contribution.amount) const res = await contribution.softRemove() return !!res @@ -249,7 +249,7 @@ export class ContributionResolver { contributionToUpdate.updatedAt = new Date() await DbContribution.save(contributionToUpdate) - await EVENT_CONTRIBUTION_UPDATE(user.id, contributionId, amount) + await EVENT_CONTRIBUTION_UPDATE(user, contributionToUpdate, amount) return new UnconfirmedContribution(contributionToUpdate, user, creations) } @@ -306,7 +306,7 @@ export class ContributionResolver { await DbContribution.save(contribution) - await EVENT_ADMIN_CONTRIBUTION_CREATE(moderator.id, contribution.id, amount) + await EVENT_ADMIN_CONTRIBUTION_CREATE(emailContact.user, moderator, contribution, amount) return getUserCreation(emailContact.userId, clientTimezoneOffset) } @@ -374,7 +374,12 @@ export class ContributionResolver { result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset) - await EVENT_ADMIN_CONTRIBUTION_UPDATE(emailContact.user.id, contributionToUpdate.id, amount) + await EVENT_ADMIN_CONTRIBUTION_UPDATE( + emailContact.user, + moderator, + contributionToUpdate, + amount, + ) return result } @@ -432,7 +437,12 @@ export class ContributionResolver { await contribution.save() const res = await contribution.softRemove() - await EVENT_ADMIN_CONTRIBUTION_DELETE(contribution.userId, contribution.id, contribution.amount) + await EVENT_ADMIN_CONTRIBUTION_DELETE( + { id: contribution.userId } as DbUser, + moderator, + contribution, + contribution.amount, + ) void sendContributionDeletedEmail({ firstName: user.firstName, @@ -545,7 +555,7 @@ export class ContributionResolver { await queryRunner.release() } - await EVENT_CONTRIBUTION_CONFIRM(user.id, contribution.id, contribution.amount) + await EVENT_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount) } finally { releaseLock() } @@ -632,9 +642,9 @@ export class ContributionResolver { const res = await contributionToUpdate.save() await EVENT_ADMIN_CONTRIBUTION_DENY( - contributionToUpdate.userId, - moderator.id, - contributionToUpdate.id, + user, + moderator, + contributionToUpdate, contributionToUpdate.amount, ) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index c660a72da..6c1b4be7f 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -18,7 +18,7 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' -import { EventProtocol } from '@entity/EventProtocol' +import { Event as DbEvent } from '@entity/Event' import { Transaction } from '@entity/Transaction' import { User } from '@entity/User' import { cleanDB, testEnvironment } from '@test/helpers' @@ -341,12 +341,13 @@ describe('send coins', () => { memo: 'unrepeatable memo', }) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.TRANSACTION_SEND, - userId: user[1].id, - transactionId: transaction[0].id, - xUserId: user[0].id, + affectedUserId: user[1].id, + actingUserId: user[1].id, + involvedUserId: user[0].id, + involvedTransactionId: transaction[0].id, }), ) }) @@ -358,12 +359,13 @@ describe('send coins', () => { memo: 'unrepeatable memo', }) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.TRANSACTION_RECEIVE, - userId: user[0].id, - transactionId: transaction[0].id, - xUserId: user[1].id, + affectedUserId: user[0].id, + actingUserId: user[1].id, + involvedUserId: user[1].id, + involvedTransactionId: transaction[0].id, }), ) }) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index a11c5b377..5b10c098f 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -138,17 +138,12 @@ export const executeTransaction = async ( await queryRunner.commitTransaction() logger.info(`commit Transaction successful...`) - await EVENT_TRANSACTION_SEND( - transactionSend.userId, - transactionSend.linkedUserId, - transactionSend.id, - transactionSend.amount.mul(-1), - ) + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) await EVENT_TRANSACTION_RECEIVE( - transactionReceive.userId, - transactionReceive.linkedUserId, - transactionReceive.id, + recipient, + sender, + transactionReceive, transactionReceive.amount, ) } catch (e) { diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index a57346583..b91ed8709 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -40,7 +40,7 @@ import { transactionLinkFactory } from '@/seeds/factory/transactionLink' import { ContributionLink } from '@model/ContributionLink' import { TransactionLink } from '@entity/TransactionLink' import { EventProtocolType } from '@/event/EventProtocolType' -import { EventProtocol } from '@entity/EventProtocol' +import { Event as DbEvent } from '@entity/Event' import { validate as validateUUID, version as versionUUID } from 'uuid' import { peterLustig } from '@/seeds/users/peter-lustig' import { UserContact } from '@entity/UserContact' @@ -187,10 +187,11 @@ describe('UserResolver', () => { { email: 'peter@lustig.de' }, { relations: ['user'] }, ) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.REGISTER, - userId: userConatct.user.id, + affectedUserId: userConatct.user.id, + actingUserId: userConatct.user.id, }), ) }) @@ -216,10 +217,11 @@ describe('UserResolver', () => { }) it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.SEND_CONFIRMATION_EMAIL, - userId: user[0].id, + affectedUserId: user[0].id, + actingUserId: user[0].id, }), ) }) @@ -261,10 +263,11 @@ describe('UserResolver', () => { { email: 'peter@lustig.de' }, { relations: ['user'] }, ) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, - userId: userConatct.user.id, + affectedUserId: userConatct.user.id, + actingUserId: 0, }), ) }) @@ -361,20 +364,22 @@ describe('UserResolver', () => { }) it('stores the ACTIVATE_ACCOUNT event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ACTIVATE_ACCOUNT, - userId: user[0].id, + affectedUserId: user[0].id, + actingUserId: user[0].id, }), ) }) it('stores the REDEEM_REGISTER event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.REDEEM_REGISTER, - userId: result.data.createUser.id, - contributionId: link.id, + affectedUserId: result.data.createUser.id, + actingUserId: result.data.createUser.id, + involvedContributionId: link.id, }), ) }) @@ -454,10 +459,12 @@ describe('UserResolver', () => { }) it('stores the REDEEM_REGISTER event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.REDEEM_REGISTER, - userId: newUser.data.createUser.id, + affectedUserId: newUser.data.createUser.id, + actingUserId: newUser.data.createUser.id, + involvedTransactionId: transactionLink.id, }), ) }) @@ -685,10 +692,11 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.LOGIN, - userId: userConatct.user.id, + affectedUserId: userConatct.user.id, + actingUserId: userConatct.user.id, }), ) }) @@ -933,10 +941,11 @@ describe('UserResolver', () => { }) it('stores the LOGIN event in the database', async () => { - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.LOGIN, - userId: user[0].id, + affectedUserId: user[0].id, + actingUserId: user[0].id, }), ) }) @@ -1852,10 +1861,11 @@ describe('UserResolver', () => { { email: 'bibi@bloxberg.de' }, { relations: ['user'] }, ) - await expect(EventProtocol.find()).resolves.toContainEqual( + await expect(DbEvent.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, - userId: userConatct.user.id, + affectedUserId: userConatct.user.id, + actingUserId: admin.id, }), ) }) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 078a29a8e..b3bf1e4ee 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -20,7 +20,9 @@ import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeor import { User as DbUser } from '@entity/User' import { UserContact as DbUserContact } from '@entity/UserContact' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' +import { Transaction as DbTransaction } from '@entity/Transaction' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' +import { Contribution as DbContribution } from '@entity/Contribution' import { UserRepository } from '@repository/User' import { User } from '@model/User' @@ -182,7 +184,7 @@ export class UserResolver { value: encode(dbUser.gradidoID), }) - await EVENT_LOGIN(user.id) + await EVENT_LOGIN(dbUser) logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`) return user } @@ -252,7 +254,7 @@ export class UserResolver { language: foundUser.language, // use language of the emails owner for sending }) - await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser.id) + await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser) logger.info( `sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`, @@ -270,7 +272,11 @@ export class UserResolver { const gradidoID = await newGradidoID() - const eventRegisterRedeem = Event(EventProtocolType.REDEEM_REGISTER, 0) + const eventRegisterRedeem = Event( + EventProtocolType.REDEEM_REGISTER, + { id: 0 } as DbUser, + { id: 0 } as DbUser, + ) let dbUser = new DbUser() dbUser.gradidoID = gradidoID dbUser.firstName = firstName @@ -287,14 +293,16 @@ export class UserResolver { logger.info('redeemCode found contributionLink', contributionLink) if (contributionLink) { dbUser.contributionLinkId = contributionLink.id - eventRegisterRedeem.contributionId = contributionLink.id + // TODO this is so wrong + eventRegisterRedeem.involvedContribution = { id: contributionLink.id } as DbContribution } } else { const transactionLink = await DbTransactionLink.findOne({ code: redeemCode }) logger.info('redeemCode found transactionLink', transactionLink) if (transactionLink) { dbUser.referrerId = transactionLink.userId - eventRegisterRedeem.transactionId = transactionLink.id + // TODO this is so wrong + eventRegisterRedeem.involvedTransaction = { id: transactionLink.id } as DbTransaction } } } @@ -333,7 +341,7 @@ export class UserResolver { }) logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`) - await EVENT_SEND_CONFIRMATION_EMAIL(dbUser.id) + await EVENT_SEND_CONFIRMATION_EMAIL(dbUser) if (!emailSent) { logger.debug(`Account confirmation link: ${activationLink}`) @@ -350,10 +358,11 @@ export class UserResolver { logger.info('createUser() successful...') if (redeemCode) { - eventRegisterRedeem.userId = dbUser.id + eventRegisterRedeem.affectedUser = dbUser + eventRegisterRedeem.actingUser = dbUser await eventRegisterRedeem.save() } else { - await EVENT_REGISTER(dbUser.id) + await EVENT_REGISTER(dbUser) } return new User(dbUser) @@ -469,7 +478,7 @@ export class UserResolver { await queryRunner.commitTransaction() logger.info('User and UserContact data written successfully...') - await EVENT_ACTIVATE_ACCOUNT(user.id) + await EVENT_ACTIVATE_ACCOUNT(user) } catch (e) { await queryRunner.rollbackTransaction() throw new LogError('Error on writing User and User Contact data', e) @@ -779,9 +788,13 @@ export class UserResolver { return null } + // TODO this is an admin function - needs refactor @Authorized([RIGHTS.SEND_ACTIVATION_EMAIL]) @Mutation(() => Boolean) - async sendActivationEmail(@Arg('email') email: string): Promise { + async sendActivationEmail( + @Arg('email') email: string, + @Ctx() context: Context, + ): Promise { email = email.trim().toLowerCase() // const user = await dbUser.findOne({ id: emailContact.userId }) const user = await findUserByEmail(email) @@ -806,7 +819,7 @@ export class UserResolver { if (!emailSent) { logger.info(`Account confirmation link: ${activationLink}`) } else { - await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user.id) + await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user, getUser(context)) } return true diff --git a/database/entity/0061-event_refactoring/Event.ts b/database/entity/0061-event_refactoring/Event.ts new file mode 100644 index 000000000..ab75c2d94 --- /dev/null +++ b/database/entity/0061-event_refactoring/Event.ts @@ -0,0 +1,83 @@ +import { Contribution } from '../Contribution' +import { ContributionMessage } from '../ContributionMessage' +import { User } from '../User' +import { Transaction } from '../Transaction' +import Decimal from 'decimal.js-light' +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' + +@Entity('events') +export class Event extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ length: 100, nullable: false, collation: 'utf8mb4_unicode_ci' }) + type: string + + @CreateDateColumn({ + name: 'created_at', + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP(3)', + nullable: false, + }) + createdAt: Date + + @Column({ name: 'affected_user_id', unsigned: true, nullable: false }) + affectedUserId: number + + @ManyToOne(() => User) + @JoinColumn({ name: 'affected_user_id', referencedColumnName: 'id' }) + affectedUser: User + + @Column({ name: 'acting_user_id', unsigned: true, nullable: false }) + actingUserId: number + + @ManyToOne(() => User) + @JoinColumn({ name: 'acting_user_id', referencedColumnName: 'id' }) + actingUser: User + + @Column({ name: 'involved_user_id', type: 'int', unsigned: true, nullable: true }) + involvedUserId: number | null + + @ManyToOne(() => User) + @JoinColumn({ name: 'involved_user_id', referencedColumnName: 'id' }) + involvedUser: User | null + + @Column({ name: 'involved_transaction_id', type: 'int', unsigned: true, nullable: true }) + involvedTransactionId: number | null + + @ManyToOne(() => Transaction) + @JoinColumn({ name: 'involved_transaction_id', referencedColumnName: 'id' }) + involvedTransaction: Transaction | null + + @Column({ name: 'involved_contribution_id', type: 'int', unsigned: true, nullable: true }) + involvedContributionId: number | null + + @ManyToOne(() => Contribution) + @JoinColumn({ name: 'involved_contribution_id', referencedColumnName: 'id' }) + involvedContribution: Contribution | null + + @Column({ name: 'involved_contribution_message_id', type: 'int', unsigned: true, nullable: true }) + involvedContributionMessageId: number | null + + @ManyToOne(() => ContributionMessage) + @JoinColumn({ name: 'involved_contribution_message_id', referencedColumnName: 'id' }) + involvedContributionMessage: ContributionMessage | null + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: true, + transformer: DecimalTransformer, + }) + amount: Decimal | null +} diff --git a/database/entity/Event.ts b/database/entity/Event.ts new file mode 100644 index 000000000..e53085f2f --- /dev/null +++ b/database/entity/Event.ts @@ -0,0 +1 @@ +export { Event } from './0061-event_refactoring/Event' diff --git a/database/entity/EventProtocol.ts b/database/entity/EventProtocol.ts deleted file mode 100644 index 6ebbd3d68..000000000 --- a/database/entity/EventProtocol.ts +++ /dev/null @@ -1 +0,0 @@ -export { EventProtocol } from './0050-add_messageId_to_event_protocol/EventProtocol' diff --git a/database/entity/index.ts b/database/entity/index.ts index a58afb816..2d9d04c3b 100644 --- a/database/entity/index.ts +++ b/database/entity/index.ts @@ -7,21 +7,21 @@ import { TransactionLink } from './TransactionLink' import { User } from './User' import { UserContact } from './UserContact' import { Contribution } from './Contribution' -import { EventProtocol } from './EventProtocol' +import { Event } from './Event' import { ContributionMessage } from './ContributionMessage' import { Community } from './Community' export const entities = [ + Community, Contribution, ContributionLink, + ContributionMessage, + Event, LoginElopageBuys, LoginEmailOptIn, Migration, Transaction, TransactionLink, User, - EventProtocol, - ContributionMessage, UserContact, - Community, ] diff --git a/database/migrations/0061-event_refactoring.ts b/database/migrations/0061-event_refactoring.ts new file mode 100644 index 000000000..019d3272b --- /dev/null +++ b/database/migrations/0061-event_refactoring.ts @@ -0,0 +1,98 @@ +/* MIGRATION TO REFACTOR THE EVENT_PROTOCOL TABLE + * + * This migration refactors the `event_protocol` table. + * It renames the table to `event`, introduces new fields and renames others. + */ + +/* 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('RENAME TABLE `event_protocol` TO `events`;') + await queryFn('ALTER TABLE `events` RENAME COLUMN `user_id` TO `affected_user_id`;') + + await queryFn( + 'ALTER TABLE `events` ADD COLUMN `acting_user_id` int(10) unsigned DEFAULT NULL AFTER `affected_user_id`;', + ) + await queryFn('UPDATE `events` SET `acting_user_id` = `affected_user_id`;') + await queryFn( + 'ALTER TABLE `events` MODIFY COLUMN `acting_user_id` int(10) unsigned NOT NULL AFTER `affected_user_id`;', + ) + + await queryFn('ALTER TABLE `events` RENAME COLUMN `x_user_id` TO `involved_user_id`;') + await queryFn('ALTER TABLE `events` DROP COLUMN `x_community_id`;') + await queryFn('ALTER TABLE `events` RENAME COLUMN `transaction_id` TO `involved_transaction_id`;') + await queryFn( + 'ALTER TABLE `events` RENAME COLUMN `contribution_id` TO `involved_contribution_id`;', + ) + await queryFn( + 'ALTER TABLE `events` MODIFY COLUMN `message_id` int(10) unsigned DEFAULT NULL AFTER `involved_contribution_id`;', + ) + await queryFn( + 'ALTER TABLE `events` RENAME COLUMN `message_id` TO `involved_contribution_message_id`;', + ) + + // Moderator id was saved in former user_id + await queryFn( + 'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET affected_user_id=contributions.user_id WHERE `type` = "ADMIN_CONTRIBUTION_CREATE";', + ) + + // inconsistent data on this type, since not all data can be reconstructed + await queryFn( + 'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=0 WHERE `type` = "ADMIN_CONTRIBUTION_UPDATE";', + ) + + await queryFn( + 'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=contributions.deleted_by WHERE `type` = "ADMIN_CONTRIBUTION_DELETE";', + ) + + await queryFn( + 'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=contributions.confirmed_by WHERE `type` = "CONTRIBUTION_CONFIRM";', + ) + + await queryFn( + 'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET involved_user_id=NULL, acting_user_id=contributions.denied_by WHERE `type` = "ADMIN_CONTRIBUTION_DENY";', + ) + + await queryFn( + 'UPDATE `events` SET acting_user_id=involved_user_id WHERE `type` = "TRANSACTION_RECEIVE";', + ) + + await queryFn('UPDATE `events` SET amount = amount * -1 WHERE `type` = "TRANSACTION_SEND";') + + await queryFn( + 'ALTER TABLE `events` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3);', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `events` MODIFY COLUMN `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP();', + ) + + await queryFn('UPDATE `events` SET amount = amount * -1 WHERE `type` = "TRANSACTION_SEND";') + + await queryFn( + 'UPDATE `events` SET involved_user_id=acting_user_id WHERE `type` = "ADMIN_CONTRIBUTION_DENY";', + ) + await queryFn( + 'UPDATE `events` SET affected_user_id=acting_user_id WHERE `type` = "ADMIN_CONTRIBUTION_CREATE";', + ) + await queryFn( + 'ALTER TABLE `events` RENAME COLUMN `involved_contribution_message_id` TO `message_id`;', + ) + await queryFn( + 'ALTER TABLE `events` MODIFY COLUMN `message_id` int(10) unsigned DEFAULT NULL AFTER `amount`;', + ) + await queryFn( + 'ALTER TABLE `events` RENAME COLUMN `involved_contribution_id` TO `contribution_id`;', + ) + await queryFn('ALTER TABLE `events` RENAME COLUMN `involved_transaction_id` TO `transaction_id`;') + await queryFn( + 'ALTER TABLE `events` ADD COLUMN `x_community_id` int(10) unsigned DEFAULT NULL AFTER `involved_user_id`;', + ) + await queryFn('ALTER TABLE `events` RENAME COLUMN `involved_user_id` TO `x_user_id`;') + await queryFn('ALTER TABLE `events` DROP COLUMN `acting_user_id`;') + await queryFn('ALTER TABLE `events` RENAME COLUMN `affected_user_id` TO `user_id`;') + await queryFn('RENAME TABLE `events` TO `event_protocol`;') +} diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 8210ddfcc..6ba9493ac 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -3,7 +3,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0060-update_communities_table', + DB_VERSION: '0061-event_refactoring', LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL || 'info', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index c8a841315..52c73ef0a 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -11,7 +11,7 @@ Decimal.set({ */ const constants = { - DB_VERSION: '0060-update_communities_table', + DB_VERSION: '0061-event_refactoring', // 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