Merge remote-tracking branch 'origin/master' into

1798-feature-gradidoid-1-adapt-and-migrate-database-schema
This commit is contained in:
Claus-Peter Hübner 2022-09-23 01:32:44 +02:00
commit b3bf7fecc4
20 changed files with 403 additions and 202 deletions

View File

@ -8,10 +8,7 @@ import { Context, getUser } from '@/server/context'
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository, getConnection } from '@dbTools/typeorm'
import {
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
} from '@/mailer/sendTransactionReceivedEmail'
import { sendTransactionReceivedEmail } from '@/mailer/sendTransactionReceivedEmail'
import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
@ -39,6 +36,7 @@ import Decimal from 'decimal.js-light'
import { BalanceResolver } from './BalanceResolver'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
import { findUserByEmail } from './UserResolver'
import { sendTransactionLinkRedeemedEmail } from '@/mailer/sendTransactionLinkRedeemed'
export const executeTransaction = async (
amount: Decimal,
@ -155,7 +153,6 @@ export const executeTransaction = async (
email: recipient.emailContact.email,
senderEmail: sender.emailContact.email,
amount,
memo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
})
if (transactionLink) {

View File

@ -4,7 +4,14 @@
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { userFactory } from '@/seeds/factory/user'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { createUser, setPassword, forgotPassword, updateUserInfos } from '@/seeds/graphql/mutations'
import {
createUser,
setPassword,
forgotPassword,
updateUserInfos,
createContribution,
confirmContribution,
} from '@/seeds/graphql/mutations'
import { login, logout, verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries'
import { GraphQLError } from 'graphql'
import { User } from '@entity/User'
@ -14,16 +21,19 @@ import { sendAccountMultiRegistrationEmail } from '@/mailer/sendAccountMultiRegi
import { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail'
import { printTimeDuration, activationLink } from './UserResolver'
import { contributionLinkFactory } from '@/seeds/factory/contributionLink'
// import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
import { ContributionLink } from '@model/ContributionLink'
// import { TransactionLink } from '@entity/TransactionLink'
import { TransactionLink } from '@entity/TransactionLink'
import { EventProtocolType } from '@/event/EventProtocolType'
import { EventProtocol } from '@entity/EventProtocol'
import { logger } from '@test/testSetup'
import { validate as validateUUID, version as versionUUID } from 'uuid'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { UserContact } from '@entity/UserContact'
import { OptInType } from '../enum/OptInType'
import { UserContactType } from '../enum/UserContactType'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
// import { klicktippSignIn } from '@/apis/KlicktippController'
@ -175,6 +185,15 @@ describe('UserResolver', () => {
duration: expect.any(String),
})
})
it('stores the send confirmation event in the database', () => {
expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
userId: user[0].id,
}),
)
})
})
describe('email already exists', () => {
@ -215,13 +234,12 @@ describe('UserResolver', () => {
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'it' },
})
await expect(User.find({ relations: ['emailContact'] })).resolves.toContain(
expect.objectContaining({
emailContact: expect.objectContaining({
email: 'bibi@bloxberg.de',
}),
language: 'de',
expect.objectContaining({
emailContact: expect.objectContaining({
email: 'bibi@bloxberg.de',
}),
]),
language: 'de',
}),
)
})
})
@ -246,33 +264,47 @@ describe('UserResolver', () => {
})
describe('redeem codes', () => {
let result: any
let link: ContributionLink
describe('contribution link', () => {
let link: ContributionLink
beforeAll(async () => {
// activate account of admin Peter Lustig
await mutate({
mutation: setPassword,
variables: { code: emailVerificationCode, password: 'Aa12345_' },
})
// make Peter Lustig Admin
const peter = await User.findOneOrFail({ id: user[0].id })
peter.isAdmin = new Date()
await peter.save()
// date statement
const actualDate = new Date()
const futureDate = new Date() // Create a future day from the executed day
futureDate.setDate(futureDate.getDate() + 1)
// factory logs in as Peter Lustig
link = await contributionLinkFactory(testEnv, {
name: 'Dokumenta 2022',
memo: 'Vielen Dank für deinen Besuch bei der Dokumenta 2022',
amount: 200,
validFrom: new Date(2022, 5, 18),
validTo: new Date(2022, 8, 25),
validFrom: actualDate,
validTo: futureDate,
})
resetToken()
await mutate({
result = await mutate({
mutation: createUser,
variables: { ...variables, email: 'ein@besucher.de', redeemCode: 'CL-' + link.code },
})
})
afterAll(async () => {
await cleanDB()
})
it('sets the contribution link id', async () => {
await expect(
UserContact.findOne({ email: 'ein@besucher.de' }, { relations: ['user'] }),
@ -284,6 +316,104 @@ describe('UserResolver', () => {
}),
)
})
it('stores the account activated event in the database', () => {
expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.ACTIVATE_ACCOUNT,
userId: user[0].id,
}),
)
})
it('stores the redeem register event in the database', () => {
expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER,
userId: result.data.createUser.id,
contributionId: link.id,
}),
)
})
})
describe('transaction link', () => {
let contribution: any
let bob: any
let transactionLink: TransactionLink
let newUser: any
const bobData = {
email: 'bob@baumeister.de',
password: 'Aa12345_',
publisherId: 1234,
}
const peterData = {
email: 'peter@lustig.de',
password: 'Aa12345_',
publisherId: 1234,
}
beforeAll(async () => {
await userFactory(testEnv, peterLustig)
await userFactory(testEnv, bobBaumeister)
await query({ query: login, variables: bobData })
// create contribution as user bob
contribution = await mutate({
mutation: createContribution,
variables: { amount: 1000, memo: 'testing', creationDate: new Date().toISOString() },
})
// login as admin
await query({ query: login, variables: peterData })
// confirm the contribution
contribution = await mutate({
mutation: confirmContribution,
variables: { id: contribution.data.createContribution.id },
})
// login as user bob
bob = await query({ query: login, variables: bobData })
// create transaction link
await transactionLinkFactory(testEnv, {
email: 'bob@baumeister.de',
amount: 19.99,
memo: `testing transaction link`,
})
transactionLink = await TransactionLink.findOneOrFail()
resetToken()
// create new user using transaction link of bob
newUser = await mutate({
mutation: createUser,
variables: {
...variables,
email: 'which@ever.de',
redeemCode: transactionLink.code,
},
})
})
it('sets the referrer id to bob baumeister id', async () => {
await expect(User.findOne({ email: 'which@ever.de' })).resolves.toEqual(
expect.objectContaining({ referrerId: bob.data.login.id }),
)
})
it('stores the redeem register event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER,
userId: newUser.data.createUser.id,
}),
)
})
})
/* A transaction link requires GDD on account
@ -295,7 +425,7 @@ describe('UserResolver', () => {
email: 'peter@lustig.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
bei Gradidio sei dabei!`,
})
const transactionLink = await TransactionLink.findOneOrFail()
resetToken()
@ -304,14 +434,14 @@ bei Gradidio sei dabei!`,
variables: { ...variables, email: 'neuer@user.de', redeemCode: transactionLink.code },
})
})
it('sets the referrer id to Peter Lustigs id', async () => {
await expect(User.findOne({ email: 'neuer@user.de' })).resolves.toEqual(expect.objectContaining({
referrerId: user[0].id,
}))
})
})
*/
})
})
@ -399,6 +529,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Password entered is lexically invalid')
})
})
describe('no valid optin code', () => {
@ -421,6 +555,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('Could not login with emailVerificationCode')
})
})
})
@ -449,6 +587,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('User with email=bibi@bloxberg.de does not exist')
})
})
describe('user is in database and correct login data', () => {
@ -491,6 +633,7 @@ bei Gradidio sei dabei!`,
describe('user is in database and wrong password', () => {
beforeAll(async () => {
await userFactory(testEnv, bibiBloxberg)
result = await query({ query: login, variables: { ...variables, password: 'wrong' } })
})
afterAll(async () => {
@ -498,14 +641,16 @@ bei Gradidio sei dabei!`,
})
it('returns an error', () => {
expect(
query({ query: login, variables: { ...variables, password: 'wrong' } }),
).resolves.toEqual(
expect(result).toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
})
})
})
@ -578,6 +723,8 @@ bei Gradidio sei dabei!`,
})
describe('authenticated', () => {
let user: User[]
const variables = {
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
@ -585,6 +732,7 @@ bei Gradidio sei dabei!`,
beforeAll(async () => {
await query({ query: login, variables })
user = await User.find()
})
afterAll(() => {
@ -611,6 +759,15 @@ bei Gradidio sei dabei!`,
}),
)
})
it('stores the login event in the database', () => {
expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.LOGIN,
userId: user[0].id,
}),
)
})
})
})
})
@ -695,6 +852,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`email already sent less than 10 minutes minutes ago`)
})
})
})
})
@ -805,7 +966,7 @@ bei Gradidio sei dabei!`,
})
describe('language is not valid', () => {
it('thows an error', async () => {
it('throws an error', async () => {
await expect(
mutate({
mutation: updateUserInfos,
@ -819,6 +980,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`"not-valid" isn't a valid language`)
})
})
describe('password', () => {
@ -838,6 +1003,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`Old password is invalid`)
})
})
describe('invalid new password', () => {
@ -860,6 +1029,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('newPassword does not fullfil the rules')
})
})
describe('correct old and new password', () => {
@ -879,7 +1052,7 @@ bei Gradidio sei dabei!`,
)
})
it('can login wtih new password', async () => {
it('can login with new password', async () => {
await expect(
query({
query: login,
@ -899,7 +1072,7 @@ bei Gradidio sei dabei!`,
)
})
it('cannot login wtih old password', async () => {
it('cannot login with old password', async () => {
await expect(
query({
query: login,
@ -914,6 +1087,10 @@ bei Gradidio sei dabei!`,
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
})
})
})
})

View File

@ -30,6 +30,7 @@ import {
EventRegister,
EventSendAccountMultiRegistrationEmail,
EventSendConfirmationEmail,
EventActivateAccount,
} from '@/event/Event'
import { getUserCreation } from './util/creations'
import { UserContactType } from '../enum/UserContactType'
@ -558,10 +559,10 @@ export class UserResolver {
if (redeemCode) {
eventRedeemRegister.userId = dbUser.id
eventProtocol.writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
await eventProtocol.writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
} else {
eventRegister.userId = dbUser.id
eventProtocol.writeEvent(event.setEventRegister(eventRegister))
await eventProtocol.writeEvent(event.setEventRegister(eventRegister))
}
return new User(dbUser)
@ -620,6 +621,7 @@ export class UserResolver {
logger.info(`setPassword(${code}, ***)...`)
// Validate Password
if (!isPassword(password)) {
logger.error('Password entered is lexically invalid')
throw new Error(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
)
@ -688,6 +690,8 @@ export class UserResolver {
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
const event = new Event()
try {
// Save user
await queryRunner.manager.save(user).catch((error) => {
@ -702,6 +706,10 @@ export class UserResolver {
await queryRunner.commitTransaction()
logger.info('User and UserContact data written successfully...')
const eventActivateAccount = new EventActivateAccount()
eventActivateAccount.userId = user.id
eventProtocol.writeEvent(event.setEventActivateAccount(eventActivateAccount))
} catch (e) {
await queryRunner.rollbackTransaction()
logger.error('Error on writing User and UserContact data:' + e)
@ -816,6 +824,7 @@ export class UserResolver {
try {
await queryRunner.manager.save(userEntity).catch((error) => {
logger.error('error saving user: ' + error)
throw new Error('error saving user: ' + error)
})

View File

@ -0,0 +1,44 @@
import { sendEMail } from './sendEMail'
import Decimal from 'decimal.js-light'
import { sendTransactionLinkRedeemedEmail } from './sendTransactionLinkRedeemed'
jest.mock('./sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
describe('sendTransactionLinkRedeemedEmail', () => {
beforeEach(async () => {
await sendTransactionLinkRedeemedEmail({
email: 'bibi@bloxberg.de',
senderFirstName: 'Peter',
senderLastName: 'Lustig',
recipientFirstName: 'Bibi',
recipientLastName: 'Bloxberg',
senderEmail: 'peter@lustig.de',
amount: new Decimal(42.0),
memo: 'Vielen Dank dass Du dabei bist',
overviewURL: 'http://localhost/overview',
})
})
it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({
to: `Bibi Bloxberg <bibi@bloxberg.de>`,
subject: 'Gradido-Link wurde eingelöst',
text:
expect.stringContaining('Hallo Bibi Bloxberg') &&
expect.stringContaining(
'Peter Lustig (peter@lustig.de) hat soeben deinen Link eingelöst.',
) &&
expect.stringContaining('Betrag: 42,00 GDD,') &&
expect.stringContaining('Memo: Vielen Dank dass Du dabei bist') &&
expect.stringContaining(
'Details zur Transaktion findest du in deinem Gradido-Konto: http://localhost/overview',
) &&
expect.stringContaining('Bitte antworte nicht auf diese E-Mail!'),
})
})
})

View File

@ -0,0 +1,28 @@
import { backendLogger as logger } from '@/server/logger'
import Decimal from 'decimal.js-light'
import { sendEMail } from './sendEMail'
import { transactionLinkRedeemed } from './text/transactionLinkRedeemed'
export const sendTransactionLinkRedeemedEmail = (data: {
email: string
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): Promise<boolean> => {
logger.info(
`sendEmail(): to=${data.recipientFirstName} ${data.recipientLastName},
<${data.email}>,
subject=${transactionLinkRedeemed.de.subject},
text=${transactionLinkRedeemed.de.text(data)}`,
)
return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,
subject: transactionLinkRedeemed.de.subject,
text: transactionLinkRedeemed.de.text(data),
})
}

View File

@ -19,7 +19,6 @@ describe('sendTransactionReceivedEmail', () => {
email: 'peter@lustig.de',
senderEmail: 'bibi@bloxberg.de',
amount: new Decimal(42.0),
memo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
overviewURL: 'http://localhost/overview',
})
})
@ -33,7 +32,6 @@ describe('sendTransactionReceivedEmail', () => {
expect.stringContaining('42,00 GDD') &&
expect.stringContaining('Bibi Bloxberg') &&
expect.stringContaining('(bibi@bloxberg.de)') &&
expect.stringContaining('Vielen herzlichen Dank für den neuen Hexenbesen!') &&
expect.stringContaining('http://localhost/overview'),
})
})

View File

@ -1,7 +1,7 @@
import { backendLogger as logger } from '@/server/logger'
import Decimal from 'decimal.js-light'
import { sendEMail } from './sendEMail'
import { transactionLinkRedeemed, transactionReceived } from './text/transactionReceived'
import { transactionReceived } from './text/transactionReceived'
export const sendTransactionReceivedEmail = (data: {
senderFirstName: string
@ -11,7 +11,6 @@ export const sendTransactionReceivedEmail = (data: {
email: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): Promise<boolean> => {
logger.info(
@ -26,27 +25,3 @@ export const sendTransactionReceivedEmail = (data: {
text: transactionReceived.de.text(data),
})
}
export const sendTransactionLinkRedeemedEmail = (data: {
email: string
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): Promise<boolean> => {
logger.info(
`sendEmail(): to=${data.recipientFirstName} ${data.recipientLastName},
<${data.email}>,
subject=${transactionLinkRedeemed.de.subject},
text=${transactionLinkRedeemed.de.text(data)}`,
)
return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,
subject: transactionLinkRedeemed.de.subject,
text: transactionLinkRedeemed.de.text(data),
})
}

View File

@ -14,9 +14,10 @@ export const contributionConfirmed = {
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName},
Dein Gradido Schöpfungsantrag "${data.contributionMemo}" wurde soeben von ${data.senderFirstName} ${
data.senderLastName
} bestätigt.
Dein eingereichter Gemeinwohl-Beitrag "${data.contributionMemo}" wurde soeben von ${
data.senderFirstName
} ${data.senderLastName} bestätigt.
Betrag: ${data.contributionAmount.toFixed(2).replace('.', ',')} GDD
Bitte antworte nicht auf diese E-Mail!

View File

@ -14,17 +14,15 @@ export const contributionMessageReceived = {
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName},
Du hast soeben zu deinem eingereichten Gradido Schöpfungsantrag "${data.contributionMemo}" eine Rückfrage von ${data.senderFirstName} ${data.senderLastName} erhalten.
Die Rückfrage lautet:
du hast soeben zu deinem eingereichten Gemeinwohl-Beitrag "${data.contributionMemo}" eine Rückfrage von ${data.senderFirstName} ${data.senderLastName} erhalten.
${data.message}
Bitte beantworte die Rückfrage in deinem Gradido-Konto im Menü "Gemeinschaft" im Tab "Meine Beiträge zum Gemeinwohl"!
Link zu deinem Konto: ${data.overviewURL}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team
Link zu deinem Konto: ${data.overviewURL}`,
dein Gradido-Team`,
},
}

View File

@ -0,0 +1,33 @@
import Decimal from 'decimal.js-light'
export const transactionLinkRedeemed = {
de: {
subject: 'Gradido-Link wurde eingelöst',
text: (data: {
email: string
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName}
${data.senderFirstName} ${data.senderLastName} (${
data.senderEmail
}) hat soeben deinen Link eingelöst.
Betrag: ${data.amount.toFixed(2).replace('.', ',')} GDD,
Memo: ${data.memo}
Details zur Transaktion findest du in deinem Gradido-Konto: ${data.overviewURL}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team`,
},
}

View File

@ -11,7 +11,6 @@ export const transactionReceived = {
email: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName}
@ -19,47 +18,12 @@ export const transactionReceived = {
Du hast soeben ${data.amount.toFixed(2).replace('.', ',')} GDD von ${data.senderFirstName} ${
data.senderLastName
} (${data.senderEmail}) erhalten.
${data.senderFirstName} ${data.senderLastName} schreibt:
${data.memo}
Details zur Transaktion findest du in deinem Gradido-Konto: ${data.overviewURL}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team
Link zu deinem Konto: ${data.overviewURL}`,
},
}
export const transactionLinkRedeemed = {
de: {
subject: 'Gradido link eingelösst',
text: (data: {
email: string
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
senderEmail: string
amount: Decimal
memo: string
overviewURL: string
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName}
${data.senderFirstName} ${data.senderLastName} (${
data.senderEmail
}) hat soeben deinen Link eingelösst.
Betrag: ${data.amount.toFixed(2).replace('.', ',')} GDD,
Memo: ${data.memo}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team
Link zu deinem Konto: ${data.overviewURL}`,
dein Gradido-Team`,
},
}

View File

@ -6,30 +6,41 @@ With the business event protocol the gradido application will capture and persis
The different event types will be defined as Enum. The following list is a first draft and will grow with further event types in the future.
| EventType | Value | Description |
| ----------------------------------- | ----- | ------------------------------------------------------------------------------------------------------ |
| BasicEvent | 0 | the basic event is the root of all further extending event types |
| VisitGradidoEvent | 10 | if a user visits a gradido page without login or register |
| RegisterEvent | 20 | the user presses the register button |
| RedeemRegisterEvent | 21 | the user presses the register button initiated by the redeem link |
| InActiveAccountEvent | 22 | the systems create an inactive account during the register process |
| SendConfirmEmailEvent | 23 | the system send a confirmation email to the user during the register process |
| ConfirmEmailEvent | 24 | the user confirms his email during the register process |
| RegisterEmailKlickTippEvent | 25 | the system registers the confirmed email at klicktipp |
| LoginEvent | 30 | the user presses the login button |
| RedeemLoginEvent | 31 | the user presses the login button initiated by the redeem link |
| ActivateAccountEvent | 32 | the system activates the users account during the first login process |
| PasswordChangeEvent | 33 | the user changes his password |
| TransactionSendEvent | 40 | the user creates a transaction and sends it online |
| TransactionSendRedeemEvent | 41 | the user creates a transaction and sends it per redeem link |
| TransactionRepeateRedeemEvent | 42 | the user recreates a redeem link of a still open transaction |
| TransactionCreationEvent | 50 | the user receives a creation transaction for his confirmed contribution |
| TransactionReceiveEvent | 51 | the user receives a transaction from an other user and posts the amount on his account |
| TransactionReceiveRedeemEvent | 52 | the user activates the redeem link and receives the transaction and posts the amount on his account |
| ContributionCreateEvent | 60 | the user enters his contribution and asks for confirmation |
| ContributionConfirmEvent | 61 | the user confirms a contribution of an other user (for future multi confirmation from several users) |
| ContributionLinkDefineEvent | 70 | the admin user defines a contributionLink, which could be send per Link/QR-Code on an other medium |
| ContributionLinkActivateRedeemEvent | 71 | the user activates a received contributionLink to create a contribution entry for the contributionLink |
| EventType | Description |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| BasicEvent | the basic event is the root of all further extending event types |
| VisitGradidoEvent | if a user visits a gradido page without login or register; possible as soon as a request-response-loop for the first page will be invoked |
| RegisterEvent | the user presses the register button |
| LoginEvent | the user presses the login button |
| VerifyRedeemEvent | the user presses a redeem link independent from transaction or contribution redeem |
| RedeemRegisterEvent | the user presses the register-button initiated by the redeem link |
| RedeemLoginEvent | the user presses the login-button initiated by the redeem link |
| ActivateAccountEvent | the system activates the users account after a successful confirmEmail-Event or during a reactivation of a deactivated account |
| InActiveAccountEvent | the systems creates an inactive account during the register process or an active account will be reset to inactive |
| SetPasswordEvent | the system sets a new password after ConfirmEmailEvent or SendForgotPasswordEvent |
| RegisterEmailKlickTippEvent | the system registers the confirmed email at klicktipp |
| PasswordChangeEvent | the user changes his password in his Profile |
| TransactionSendEvent | the user creates a transaction and sends it online; paired with TransactionReceiveEvent |
| TransactionLinkCreateEvent | the user creates a transaction link |
| TransactionReceiveEvent | the user receives a transaction from an other user and posts the amount on his account; paired with TransactionSendEvent |
| TransactionLinkRedeemEvent | the user activates the redeem link and receives the transaction and posts the amount on his account |
| ContributionCreateEvent | the user enters his contribution and asks for confirmation |
| ContributionConfirmEvent | the admin user confirms a contribution of an other user (for future multi confirmation from several users) |
| ContributionDenyEvent | the admin user denies a contribution of an other user |
| ContributionLinkDefineEvent | the admin user defines a contributionLink, which could be send per Link/QR-Code on an other medium |
| ContributionLinkRedeemEvent | the user activates a received contributionLink to create a contribution entry for the contributionLink |
| UserCreateContributionMessageEvent | the user captures a new message for a contribution |
| AdminCreateContributionMessageEvent | the admin user captures a new message for a contribution |
| LogoutEvent | the user invokes a logout |
| SendConfirmEmailEvent | the system sends a confirmation email to the user during the registration process |
| SendAccountMultiRegistrationEmailEvent | the system sends a info email to the user, that an other user tries to register with his existing email address |
| SendForgotPasswordEmailEvent | the system sends the forgot password email including a special link to start the forgot password process |
| SendTransactionSendEmailEvent | the system sends an email to inform the user about his transaction was sent to an other user |
| SendTransactionReceiveEmailEvent | the system sends an email to inform the user about a received transaction from an other user |
| SendAddedContributionEmailEvent | the system sends an email to inform the user about the creation of his captured contribution |
| SendContributionConfirmEmailEvent | the system sends an email to inform the user about the confirmation of his contribution |
| SendTransactionLinkRedeemEmailEvent | the system sends an email to the user, who created the transactionlink, that the link was redeemed |
| | |
## EventProtocol - Entity
@ -49,32 +60,44 @@ The business events will be stored in database in the new table `EventProtocol`.
## Event Types
The following table lists for each event type the mandatory attributes, which have to be initialized at event occurence and to be written in the database event protocol table:
The following table lists for each event type the mapping between old and new key, the mandatory attributes, which have to be initialized at event occurence and to be written in the database event protocol table:
| EventType | id | type | createdAt | userID | XuserID | XCommunityID | transactionID | contribID | amount |
| :---------------------------------- | :-: | :--: | :-------: | :----: | :-----: | :----------: | :-----------: | :-------: | :----: |
| BasicEvent | x | x | x | | | | | | |
| VisitGradidoEvent | x | x | x | | | | | | |
| RegisterEvent | x | x | x | x | | | | | |
| RedeemRegisterEvent | x | x | x | x | | | (x) | (x) | |
| InActiveAccountEvent | x | x | x | x | | | | | |
| SendConfirmEmailEvent | x | x | x | x | | | | | |
| ConfirmEmailEvent | x | x | x | x | | | | | |
| RegisterEmailKlickTippEvent | x | x | x | x | | | | | |
| LoginEvent | x | x | x | x | | | | | |
| RedeemLoginEvent | x | x | x | x | | | (x) | (x) | |
| ActivateAccountEvent | x | x | x | x | | | | | |
| PasswordChangeEvent | x | x | x | x | | | | | |
| TransactionSendEvent | x | x | x | x | x | x | x | | x |
| TransactionSendRedeemEvent | x | x | x | x | x | x | x | | x |
| TransactionRepeateRedeemEvent | x | x | x | x | x | x | x | | x |
| TransactionCreationEvent | x | x | x | x | | | x | | x |
| TransactionReceiveEvent | x | x | x | x | x | x | x | | x |
| TransactionReceiveRedeemEvent | x | x | x | x | x | x | x | | x |
| ContributionCreateEvent | x | x | x | x | | | | x | x |
| ContributionConfirmEvent | x | x | x | x | x | x | | x | x |
| ContributionLinkDefineEvent | x | x | x | x | | | | | x |
| ContributionLinkActivateRedeemEvent | x | x | x | x | | | | x | x |
| EventType - old key | EventType - new key | id | type | createdAt | userID | XuserID | XCommunityID | transactionID | contribID | amount |
| :-------------------------------- | :------------------------------------- | :-: | :--: | :-------: | :----: | :-----: | :----------: | :-----------: | :-------: | :----: |
| BASIC | BasicEvent | x | x | x | | | | | | |
| VISIT_GRADIDO | VisitGradidoEvent | x | x | x | | | | | | |
| REGISTER | RegisterEvent | x | x | x | x | | | | | |
| LOGIN | LoginEvent | x | x | x | x | | | | | |
| | VerifyRedeemEvent | | | | | | | | | |
| REDEEM_REGISTER | RedeemRegisterEvent | x | x | x | x | | | (x) | (x) | |
| REDEEM_LOGIN | RedeemLoginEvent | x | x | x | x | | | (x) | (x) | |
| ACTIVATE_ACCOUNT | ActivateAccountEvent | x | x | x | x | | | | | |
| INACTIVE_ACCOUNT | InActiveAccountEvent | x | x | x | x | | | | | |
| CONFIRM_EMAIL | SetPasswordEvent | x | x | x | x | | | | | |
| REGISTER_EMAIL_KLICKTIPP | RegisterEmailKlickTippEvent | x | x | x | x | | | | | |
| PASSWORD_CHANGE | PasswordChangeEvent | x | x | x | x | | | | | |
| TRANSACTION_SEND | TransactionSendEvent | x | x | x | x | x | x | x | | x |
| TRANSACTION_CREATION | TransactionLinkCreateEvent | x | x | x | x | | | x | | x |
| TRANSACTION_RECEIVE | TransactionReceiveEvent | x | x | x | x | x | x | x | | x |
| TRANSACTION_SEND_REDEEM | TransactionLinkRedeemEvent | x | x | x | x | x | x | x | | x |
| CONTRIBUTION_CREATE | ContributionCreateEvent | x | x | x | x | | | | x | x |
| CONTRIBUTION_CONFIRM | ContributionConfirmEvent | x | x | x | x | x | x | | x | x |
| | ContributionDenyEvent | x | x | x | x | x | x | | x | x |
| CONTRIBUTION_LINK_DEFINE | ContributionLinkDefineEvent | x | x | x | x | | | | | x |
| CONTRIBUTION_LINK_ACTIVATE_REDEEM | ContributionLinkRedeemEvent | x | x | x | x | | | | x | x |
| | UserCreateContributionMessageEvent | x | x | x | x | | | | x | x |
| | AdminCreateContributionMessageEvent | x | x | x | x | | | | x | x |
| | LogoutEvent | x | x | x | x | | | | x | x |
| SEND_CONFIRMATION_EMAIL | SendConfirmEmailEvent | x | x | x | x | | | | | |
| | SendAccountMultiRegistrationEmailEvent | x | x | x | x | | | | | |
| | SendForgotPasswordEmailEvent | x | x | x | x | | | | | |
| | SendTransactionSendEmailEvent | x | x | x | x | x | x | x | | x |
| | SendTransactionReceiveEmailEvent | x | x | x | x | x | x | x | | x |
| | SendAddedContributionEmailEvent | x | x | x | x | | | | x | x |
| | SendContributionConfirmEmailEvent | x | x | x | x | | | | x | x |
| | SendTransactionLinkRedeemEmailEvent | x | x | x | x | x | x | x | | x |
| TRANSACTION_REPEATE_REDEEM | - | | | | | | | | | |
| TRANSACTION_RECEIVE_REDEEM | - | | | | | | | | | |
## Event creation

View File

@ -3,7 +3,7 @@
<redeem-information v-bind="linkData" :isContributionLink="isContributionLink" />
<b-jumbotron>
<div class="mb-3 text-center">
<b-button variant="primary" @click="$emit('redeem-link', linkData.amount)" size="lg">
<b-button variant="primary" @click="$emit('mutation-link', linkData.amount)" size="lg">
{{ $t('gdd_per_link.redeem') }}
</b-button>
</div>

View File

@ -178,7 +178,6 @@
"no-redeem": "Du darfst deinen eigenen Link nicht einlösen!",
"not-copied": "Dein Gerät lässt das Kopieren leider nicht zu! Bitte kopiere den Link von Hand!",
"redeem": "Einlösen",
"redeem-text": "Willst du den Betrag jetzt einlösen?",
"redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.",
"redeemed-at": "Der Link wurde bereits am {date} eingelöst.",
"redeemed-title": "eingelöst",

View File

@ -178,7 +178,6 @@
"no-redeem": "You not allowed to redeem your own link!",
"not-copied": "Unfortunately, your device does not allow copying! Please copy the link by hand!",
"redeem": "Redeem",
"redeem-text": "Do you want to redeem the amount now?",
"redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.",
"redeemed-at": "The link was already redeemed on {date}.",
"redeemed-title": "redeemed",

View File

@ -180,7 +180,6 @@
"no-redeem": "No puedes canjear tu propio enlace!",
"not-copied": "¡Desafortunadamente, su dispositivo no permite copiar! Copie el enlace manualmente!",
"redeem": "Canjear",
"redeem-text": "¿Quieres canjear el importe ahora?",
"redeemed": "¡Canjeado con éxito! Tu cuenta ha sido acreditada con {n} GDD.",
"redeemed-at": "El enlace ya se canjeó el {date}.",
"redeemed-title": "canjeado",

View File

@ -180,7 +180,6 @@
"no-redeem": "Vous n´êtes pas autorisé à percevoir votre propre lien!",
"not-copied": "Malheureusement votre appareil ne permet pas de copier! Veuillez copier le lien manuellement svp!",
"redeem": "Encaisser",
"redeem-text": "Voulez-vous percevoir le montant maintenant?",
"redeemed": "Encaissé avec succès! Votre compte est crédité de {n} GDD.",
"redeemed-at": "Le lien a déjà été perçu le {date}.",
"redeemed-title": "encaisser",

View File

@ -180,7 +180,6 @@
"no-redeem": "Je mag je eigen link niet inwisselen!",
"not-copied": "Jouw apparaat laat het kopiëren helaas niet toe! Kopieer de link alsjeblieft met de hand!",
"redeem": "Inwisselen",
"redeem-text": "Wil je het bedrag nu inwisselen?",
"redeemed": "Succesvol ingewisseld! Op jouw rekening werden {n} GDD bijgeschreven.",
"redeemed-at": "De link werd al op {date} ingewisseld.",
"redeemed-title": "ingewisseld",

View File

@ -282,19 +282,11 @@ describe('TransactionLink', () => {
})
describe('redeem link with success', () => {
let spy
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockResolvedValue()
spy.mockImplementation(() => Promise.resolve(true))
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
})
it('opens the modal', () => {
expect(spy).toBeCalledWith('gdd_per_link.redeem-text')
})
it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
@ -316,37 +308,9 @@ describe('TransactionLink', () => {
})
})
describe('cancel redeem link', () => {
let spy
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockResolvedValue()
spy.mockImplementation(() => Promise.resolve(false))
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
})
it('does not call the API', () => {
expect(apolloMutateMock).not.toBeCalled()
})
it('does not toasts a success message', () => {
expect(mocks.$t).not.toBeCalledWith('gdd_per_link.redeemed', { n: '22' })
expect(toastSuccessSpy).not.toBeCalled()
})
it('does not push the route', () => {
expect(routerPushMock).not.toBeCalled()
})
})
describe('redeem link with error', () => {
let spy
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockRejectedValue({ message: 'Oh Noo!' })
spy.mockImplementation(() => Promise.resolve(true))
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
})

View File

@ -14,7 +14,7 @@
<redeem-valid
:linkData="linkData"
:isContributionLink="isContributionLink"
@redeem-link="redeemLink"
@mutation-link="mutationLink"
/>
</template>
@ -98,11 +98,6 @@ export default {
this.$router.push('/overview')
})
},
redeemLink(amount) {
this.$bvModal.msgBoxConfirm(this.$t('gdd_per_link.redeem-text')).then((value) => {
if (value) this.mutationLink(amount)
})
},
},
computed: {
isContributionLink() {