Merge branch 'master' into 2247-links-in-contribution-messages-clickable

This commit is contained in:
Alexander Friedland 2022-10-20 13:05:04 +02:00 committed by GitHub
commit 1e56dac726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 479 additions and 88 deletions

View File

@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.13.1](https://github.com/gradido/gradido/compare/1.13.0...1.13.1)
- Fix: correctly evaluate to EMAIL_TEST_MODE to false [`#2273`](https://github.com/gradido/gradido/pull/2273)
- Refactor: Contribution resolver logs and events [`#2231`](https://github.com/gradido/gradido/pull/2231)
#### [1.13.0](https://github.com/gradido/gradido/compare/1.12.1...1.13.0) #### [1.13.0](https://github.com/gradido/gradido/compare/1.12.1...1.13.0)
> 18 October 2022
- release: Version 1.13.0 [`#2269`](https://github.com/gradido/gradido/pull/2269)
- fix: Linked User Email in Transaction List [`#2268`](https://github.com/gradido/gradido/pull/2268) - fix: Linked User Email in Transaction List [`#2268`](https://github.com/gradido/gradido/pull/2268)
- concept capturing alias [`#2148`](https://github.com/gradido/gradido/pull/2148) - concept capturing alias [`#2148`](https://github.com/gradido/gradido/pull/2148)
- fix: 🍰 Daily Redeem Of Contribution Link [`#2265`](https://github.com/gradido/gradido/pull/2265) - fix: 🍰 Daily Redeem Of Contribution Link [`#2265`](https://github.com/gradido/gradido/pull/2265)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido", "description": "Administraion Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Moriz Wahl", "author": "Moriz Wahl",
"version": "1.13.0", "version": "1.13.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": false, "private": false,
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-backend", "name": "gradido-backend",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",

View File

@ -10,7 +10,7 @@ Decimal.set({
}) })
const constants = { const constants = {
DB_VERSION: '0049-add_user_contacts_table', DB_VERSION: '0050-add_messageId_to_event_protocol',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
@ -67,7 +67,7 @@ const loginServer = {
const email = { const email = {
EMAIL: process.env.EMAIL === 'true' || false, EMAIL: process.env.EMAIL === 'true' || false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || 'false', EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net', EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email', EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email',
EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net',

View File

@ -11,48 +11,67 @@ export class EventBasicUserId extends EventBasic {
} }
export class EventBasicTx extends EventBasicUserId { export class EventBasicTx extends EventBasicUserId {
xUserId: number
xCommunityId: number
transactionId: number transactionId: number
amount: decimal amount: decimal
} }
export class EventBasicTxX extends EventBasicTx {
xUserId: number
xCommunityId: number
}
export class EventBasicCt extends EventBasicUserId { export class EventBasicCt extends EventBasicUserId {
contributionId: number contributionId: number
amount: decimal amount: decimal
} }
export class EventBasicCtX extends EventBasicCt {
xUserId: number
xCommunityId: number
}
export class EventBasicRedeem extends EventBasicUserId { export class EventBasicRedeem extends EventBasicUserId {
transactionId?: number transactionId?: number
contributionId?: number contributionId?: number
} }
export class EventBasicCtMsg extends EventBasicCt {
messageId: number
}
export class EventVisitGradido extends EventBasic {} export class EventVisitGradido extends EventBasic {}
export class EventRegister extends EventBasicUserId {} export class EventRegister extends EventBasicUserId {}
export class EventRedeemRegister extends EventBasicRedeem {} export class EventRedeemRegister extends EventBasicRedeem {}
export class EventVerifyRedeem extends EventBasicRedeem {}
export class EventInactiveAccount extends EventBasicUserId {} export class EventInactiveAccount extends EventBasicUserId {}
export class EventSendConfirmationEmail extends EventBasicUserId {} export class EventSendConfirmationEmail extends EventBasicUserId {}
export class EventSendAccountMultiRegistrationEmail extends EventBasicUserId {} export class EventSendAccountMultiRegistrationEmail extends EventBasicUserId {}
export class EventSendForgotPasswordEmail extends EventBasicUserId {}
export class EventSendTransactionSendEmail extends EventBasicTxX {}
export class EventSendTransactionReceiveEmail extends EventBasicTxX {}
export class EventSendTransactionLinkRedeemEmail extends EventBasicTxX {}
export class EventSendAddedContributionEmail extends EventBasicCt {}
export class EventSendContributionConfirmEmail extends EventBasicCt {}
export class EventConfirmationEmail extends EventBasicUserId {} export class EventConfirmationEmail extends EventBasicUserId {}
export class EventRegisterEmailKlicktipp extends EventBasicUserId {} export class EventRegisterEmailKlicktipp extends EventBasicUserId {}
export class EventLogin extends EventBasicUserId {} export class EventLogin extends EventBasicUserId {}
export class EventLogout extends EventBasicUserId {}
export class EventRedeemLogin extends EventBasicRedeem {} export class EventRedeemLogin extends EventBasicRedeem {}
export class EventActivateAccount extends EventBasicUserId {} export class EventActivateAccount extends EventBasicUserId {}
export class EventPasswordChange extends EventBasicUserId {} export class EventPasswordChange extends EventBasicUserId {}
export class EventTransactionSend extends EventBasicTx {} export class EventTransactionSend extends EventBasicTxX {}
export class EventTransactionSendRedeem extends EventBasicTx {} export class EventTransactionSendRedeem extends EventBasicTxX {}
export class EventTransactionRepeateRedeem extends EventBasicTx {} export class EventTransactionRepeateRedeem extends EventBasicTxX {}
export class EventTransactionCreation extends EventBasicUserId { export class EventTransactionCreation extends EventBasicTx {}
transactionId: number export class EventTransactionReceive extends EventBasicTxX {}
amount: decimal export class EventTransactionReceiveRedeem extends EventBasicTxX {}
}
export class EventTransactionReceive extends EventBasicTx {}
export class EventTransactionReceiveRedeem extends EventBasicTx {}
export class EventContributionCreate extends EventBasicCt {} export class EventContributionCreate extends EventBasicCt {}
export class EventContributionConfirm extends EventBasicCt { export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
xUserId: number export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
xCommunityId: number export class EventContributionDelete extends EventBasicCt {}
} export class EventContributionUpdate extends EventBasicCt {}
export class EventContributionConfirm extends EventBasicCtX {}
export class EventContributionDeny extends EventBasicCtX {}
export class EventContributionLinkDefine extends EventBasicCt {} export class EventContributionLinkDefine extends EventBasicCt {}
export class EventContributionLinkActivateRedeem extends EventBasicCt {} export class EventContributionLinkActivateRedeem extends EventBasicCt {}
@ -100,6 +119,13 @@ export class Event {
return this return this
} }
public setEventVerifyRedeem(ev: EventVerifyRedeem): Event {
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
this.type = EventProtocolType.VERIFY_REDEEM
return this
}
public setEventInactiveAccount(ev: EventInactiveAccount): Event { public setEventInactiveAccount(ev: EventInactiveAccount): Event {
this.setByBasicUser(ev.userId) this.setByBasicUser(ev.userId)
this.type = EventProtocolType.INACTIVE_ACCOUNT this.type = EventProtocolType.INACTIVE_ACCOUNT
@ -118,7 +144,49 @@ export class Event {
ev: EventSendAccountMultiRegistrationEmail, ev: EventSendAccountMultiRegistrationEmail,
): Event { ): Event {
this.setByBasicUser(ev.userId) this.setByBasicUser(ev.userId)
this.type = EventProtocolType.SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL this.type = EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL
return this
}
public setEventSendForgotPasswordEmail(ev: EventSendForgotPasswordEmail): Event {
this.setByBasicUser(ev.userId)
this.type = EventProtocolType.SEND_FORGOT_PASSWORD_EMAIL
return this
}
public setEventSendTransactionSendEmail(ev: EventSendTransactionSendEmail): Event {
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.SEND_TRANSACTION_SEND_EMAIL
return this
}
public setEventSendTransactionReceiveEmail(ev: EventSendTransactionReceiveEmail): Event {
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.SEND_TRANSACTION_RECEIVE_EMAIL
return this
}
public setEventSendTransactionLinkRedeemEmail(ev: EventSendTransactionLinkRedeemEmail): Event {
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.SEND_TRANSACTION_LINK_REDEEM_EMAIL
return this
}
public setEventSendAddedContributionEmail(ev: EventSendAddedContributionEmail): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.SEND_ADDED_CONTRIBUTION_EMAIL
return this
}
public setEventSendContributionConfirmEmail(ev: EventSendContributionConfirmEmail): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.SEND_CONTRIBUTION_CONFIRM_EMAIL
return this return this
} }
@ -144,6 +212,13 @@ export class Event {
return this return this
} }
public setEventLogout(ev: EventLogout): Event {
this.setByBasicUser(ev.userId)
this.type = EventProtocolType.LOGOUT
return this
}
public setEventRedeemLogin(ev: EventRedeemLogin): Event { public setEventRedeemLogin(ev: EventRedeemLogin): Event {
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId) this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
this.type = EventProtocolType.REDEEM_LOGIN this.type = EventProtocolType.REDEEM_LOGIN
@ -166,44 +241,42 @@ export class Event {
} }
public setEventTransactionSend(ev: EventTransactionSend): Event { public setEventTransactionSend(ev: EventTransactionSend): Event {
this.setByBasicTx(ev.userId, ev.xUserId, ev.xCommunityId, ev.transactionId, ev.amount) this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.TRANSACTION_SEND this.type = EventProtocolType.TRANSACTION_SEND
return this return this
} }
public setEventTransactionSendRedeem(ev: EventTransactionSendRedeem): Event { public setEventTransactionSendRedeem(ev: EventTransactionSendRedeem): Event {
this.setByBasicTx(ev.userId, ev.xUserId, ev.xCommunityId, ev.transactionId, ev.amount) this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.TRANSACTION_SEND_REDEEM this.type = EventProtocolType.TRANSACTION_SEND_REDEEM
return this return this
} }
public setEventTransactionRepeateRedeem(ev: EventTransactionRepeateRedeem): Event { public setEventTransactionRepeateRedeem(ev: EventTransactionRepeateRedeem): Event {
this.setByBasicTx(ev.userId, ev.xUserId, ev.xCommunityId, ev.transactionId, ev.amount) this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.TRANSACTION_REPEATE_REDEEM this.type = EventProtocolType.TRANSACTION_REPEATE_REDEEM
return this return this
} }
public setEventTransactionCreation(ev: EventTransactionCreation): Event { public setEventTransactionCreation(ev: EventTransactionCreation): Event {
this.setByBasicUser(ev.userId) this.setByBasicTx(ev.userId, ev.transactionId, ev.amount)
if (ev.transactionId) this.transactionId = ev.transactionId
if (ev.amount) this.amount = ev.amount
this.type = EventProtocolType.TRANSACTION_CREATION this.type = EventProtocolType.TRANSACTION_CREATION
return this return this
} }
public setEventTransactionReceive(ev: EventTransactionReceive): Event { public setEventTransactionReceive(ev: EventTransactionReceive): Event {
this.setByBasicTx(ev.userId, ev.xUserId, ev.xCommunityId, ev.transactionId, ev.amount) this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.TRANSACTION_RECEIVE this.type = EventProtocolType.TRANSACTION_RECEIVE
return this return this
} }
public setEventTransactionReceiveRedeem(ev: EventTransactionReceiveRedeem): Event { public setEventTransactionReceiveRedeem(ev: EventTransactionReceiveRedeem): Event {
this.setByBasicTx(ev.userId, ev.xUserId, ev.xCommunityId, ev.transactionId, ev.amount) this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.TRANSACTION_RECEIVE_REDEEM this.type = EventProtocolType.TRANSACTION_RECEIVE_REDEEM
return this return this
@ -216,15 +289,48 @@ export class Event {
return this return this
} }
public setEventContributionConfirm(ev: EventContributionConfirm): Event { public setEventUserCreateContributionMessage(ev: EventUserCreateContributionMessage): Event {
this.setByBasicCtMsg(ev.userId, ev.contributionId, ev.amount, ev.messageId)
this.type = EventProtocolType.USER_CREATE_CONTRIBUTION_MESSAGE
return this
}
public setEventAdminCreateContributionMessage(ev: EventAdminCreateContributionMessage): Event {
this.setByBasicCtMsg(ev.userId, ev.contributionId, ev.amount, ev.messageId)
this.type = EventProtocolType.ADMIN_CREATE_CONTRIBUTION_MESSAGE
return this
}
public setEventContributionDelete(ev: EventContributionDelete): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount) this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
if (ev.xUserId) this.xUserId = ev.xUserId this.type = EventProtocolType.CONTRIBUTION_DELETE
if (ev.xCommunityId) this.xCommunityId = ev.xCommunityId
return this
}
public setEventContributionUpdate(ev: EventContributionUpdate): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.CONTRIBUTION_UPDATE
return this
}
public setEventContributionConfirm(ev: EventContributionConfirm): Event {
this.setByBasicCtX(ev.userId, ev.contributionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.CONTRIBUTION_CONFIRM this.type = EventProtocolType.CONTRIBUTION_CONFIRM
return this return this
} }
public setEventContributionDeny(ev: EventContributionDeny): Event {
this.setByBasicCtX(ev.userId, ev.contributionId, ev.amount, ev.xUserId, ev.xCommunityId)
this.type = EventProtocolType.CONTRIBUTION_DENY
return this
}
public setEventContributionLinkDefine(ev: EventContributionLinkDefine): Event { public setEventContributionLinkDefine(ev: EventContributionLinkDefine): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount) this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.CONTRIBUTION_LINK_DEFINE this.type = EventProtocolType.CONTRIBUTION_LINK_DEFINE
@ -246,26 +352,58 @@ export class Event {
return this return this
} }
setByBasicTx( setByBasicTx(userId: number, transactionId: number, amount: decimal): Event {
userId: number,
xUserId?: number,
xCommunityId?: number,
transactionId?: number,
amount?: decimal,
): Event {
this.setByBasicUser(userId) this.setByBasicUser(userId)
if (xUserId) this.xUserId = xUserId this.transactionId = transactionId
if (xCommunityId) this.xCommunityId = xCommunityId this.amount = amount
if (transactionId) this.transactionId = transactionId
if (amount) this.amount = amount
return this return this
} }
setByBasicCt(userId: number, contributionId: number, amount?: decimal): Event { setByBasicTxX(
userId: number,
transactionId: number,
amount: decimal,
xUserId: number,
xCommunityId: number,
): Event {
this.setByBasicTx(userId, transactionId, amount)
this.xUserId = xUserId
this.xCommunityId = xCommunityId
return this
}
setByBasicCt(userId: number, contributionId: number, amount: decimal): Event {
this.setByBasicUser(userId) this.setByBasicUser(userId)
if (contributionId) this.contributionId = contributionId this.contributionId = contributionId
if (amount) this.amount = amount this.amount = amount
return this
}
setByBasicCtMsg(
userId: number,
contributionId: number,
amount: decimal,
messageId: number,
): Event {
this.setByBasicCt(userId, contributionId, amount)
this.messageId = messageId
return this
}
setByBasicCtX(
userId: number,
contributionId: number,
amount: decimal,
xUserId: number,
xCommunityId: number,
): Event {
this.setByBasicCt(userId, contributionId, amount)
this.xUserId = xUserId
this.xCommunityId = xCommunityId
return this return this
} }
@ -278,27 +416,6 @@ export class Event {
return this return this
} }
setByEventTransactionCreation(event: EventTransactionCreation): Event {
this.type = event.type
this.createdAt = event.createdAt
this.userId = event.userId
this.transactionId = event.transactionId
this.amount = event.amount
return this
}
setByEventContributionConfirm(event: EventContributionConfirm): Event {
this.type = event.type
this.createdAt = event.createdAt
this.userId = event.userId
this.xUserId = event.xUserId
this.xCommunityId = event.xCommunityId
this.amount = event.amount
return this
}
id: number id: number
type: string type: string
createdAt: Date createdAt: Date
@ -308,4 +425,5 @@ export class Event {
transactionId?: number transactionId?: number
contributionId?: number contributionId?: number
amount?: decimal amount?: decimal
messageId?: number
} }

View File

@ -3,23 +3,36 @@ export enum EventProtocolType {
VISIT_GRADIDO = 'VISIT_GRADIDO', VISIT_GRADIDO = 'VISIT_GRADIDO',
REGISTER = 'REGISTER', REGISTER = 'REGISTER',
REDEEM_REGISTER = 'REDEEM_REGISTER', REDEEM_REGISTER = 'REDEEM_REGISTER',
VERIFY_REDEEM = 'VERIFY_REDEEM',
INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT', INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL', SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL', SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
CONFIRM_EMAIL = 'CONFIRM_EMAIL', CONFIRM_EMAIL = 'CONFIRM_EMAIL',
REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP', REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
LOGIN = 'LOGIN', LOGIN = 'LOGIN',
LOGOUT = 'LOGOUT',
REDEEM_LOGIN = 'REDEEM_LOGIN', REDEEM_LOGIN = 'REDEEM_LOGIN',
ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT', ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
PASSWORD_CHANGE = 'PASSWORD_CHANGE', PASSWORD_CHANGE = 'PASSWORD_CHANGE',
SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
TRANSACTION_SEND = 'TRANSACTION_SEND', TRANSACTION_SEND = 'TRANSACTION_SEND',
TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM', TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM', TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
TRANSACTION_CREATION = 'TRANSACTION_CREATION', TRANSACTION_CREATION = 'TRANSACTION_CREATION',
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE', TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM', TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE', CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM', CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE', CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM', CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
} }

View File

@ -17,6 +17,9 @@ import { userFactory } from '@/seeds/factory/user'
import { creationFactory } from '@/seeds/factory/creation' import { creationFactory } from '@/seeds/factory/creation'
import { creations } from '@/seeds/creation/index' import { creations } from '@/seeds/creation/index'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { EventProtocol } from '@entity/EventProtocol'
import { EventProtocolType } from '@/event/EventProtocolType'
import { logger } from '@test/testSetup'
let mutate: any, query: any, con: any let mutate: any, query: any, con: any
let testEnv: any let testEnv: any
@ -36,6 +39,8 @@ afterAll(async () => {
}) })
describe('ContributionResolver', () => { describe('ContributionResolver', () => {
let bibi: any
describe('createContribution', () => { describe('createContribution', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('returns an error', async () => { it('returns an error', async () => {
@ -55,7 +60,8 @@ describe('ContributionResolver', () => {
describe('authenticated with valid user', () => { describe('authenticated with valid user', () => {
beforeAll(async () => { beforeAll(async () => {
await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, bibiBloxberg)
await mutate({
bibi = await mutate({
mutation: login, mutation: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
}) })
@ -85,6 +91,10 @@ describe('ContributionResolver', () => {
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`memo text is too short: memo.length=4 < (5)`)
})
it('throws error when memo length greater than 255 chars', async () => { it('throws error when memo length greater than 255 chars', async () => {
const date = new Date() const date = new Date()
await expect( await expect(
@ -103,6 +113,10 @@ describe('ContributionResolver', () => {
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`memo text is too long: memo.length=259 > (255)`)
})
it('throws error when creationDate not-valid', async () => { it('throws error when creationDate not-valid', async () => {
await expect( await expect(
mutate({ mutate({
@ -122,6 +136,13 @@ describe('ContributionResolver', () => {
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'No information for available creations with the given creationDate=',
'Invalid Date',
)
})
it('throws error when creationDate 3 month behind', async () => { it('throws error when creationDate 3 month behind', async () => {
const date = new Date() const date = new Date()
await expect( await expect(
@ -141,20 +162,31 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'No information for available creations with the given creationDate=',
'Invalid Date',
)
})
}) })
describe('valid input', () => { describe('valid input', () => {
it('creates contribution', async () => { let contribution: any
await expect(
mutate({ beforeAll(async () => {
contribution = await mutate({
mutation: createContribution, mutation: createContribution,
variables: { variables: {
amount: 100.0, amount: 100.0,
memo: 'Test env contribution', memo: 'Test env contribution',
creationDate: new Date().toString(), creationDate: new Date().toString(),
}, },
}), })
).resolves.toEqual( })
it('creates contribution', async () => {
expect(contribution).toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
createContribution: { createContribution: {
@ -166,6 +198,17 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('stores the create contribution event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_CREATE,
amount: expect.decimalEqual(100),
contributionId: contribution.data.createContribution.id,
userId: bibi.data.login.id,
}),
)
})
}) })
}) })
}) })
@ -348,6 +391,10 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('No contribution found to given id')
})
}) })
describe('Memo length smaller than 5 chars', () => { describe('Memo length smaller than 5 chars', () => {
@ -369,6 +416,10 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('memo text is too short: memo.length=4 < (5)')
})
}) })
describe('Memo length greater than 255 chars', () => { describe('Memo length greater than 255 chars', () => {
@ -390,6 +441,10 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('memo text is too long: memo.length=259 > (255)')
})
}) })
describe('wrong user tries to update the contribution', () => { describe('wrong user tries to update the contribution', () => {
@ -421,6 +476,12 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'user of the pending contribution and send user does not correspond',
)
})
}) })
describe('admin tries to update a user contribution', () => { describe('admin tries to update a user contribution', () => {
@ -442,6 +503,8 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
// TODO check that the error is logged (need to modify AdminResolver, avoid conflicts)
}) })
describe('update too much so that the limit is exceeded', () => { describe('update too much so that the limit is exceeded', () => {
@ -473,6 +536,12 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
)
})
}) })
describe('update creation to a date that is older than 3 months', () => { describe('update creation to a date that is older than 3 months', () => {
@ -494,6 +563,13 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'No information for available creations with the given creationDate=',
'Invalid Date',
)
})
}) })
describe('valid input', () => { describe('valid input', () => {
@ -520,6 +596,22 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('stores the update contribution event in the database', async () => {
bibi = await query({
query: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
await expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_UPDATE,
amount: expect.decimalEqual(10),
contributionId: result.data.createContribution.id,
userId: bibi.data.login.id,
}),
)
})
}) })
}) })
}) })
@ -626,9 +718,11 @@ describe('ContributionResolver', () => {
}) })
describe('authenticated', () => { describe('authenticated', () => {
let peter: any
beforeAll(async () => { beforeAll(async () => {
await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig) peter = await userFactory(testEnv, peterLustig)
await mutate({ await mutate({
mutation: login, mutation: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
@ -663,9 +757,13 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('Contribution not found for given id')
})
}) })
describe('other user sends a deleteContribtuion', () => { describe('other user sends a deleteContribution', () => {
it('returns an error', async () => { it('returns an error', async () => {
await mutate({ await mutate({
mutation: login, mutation: login,
@ -684,6 +782,10 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('Can not delete contribution of another user')
})
}) })
describe('User deletes own contribution', () => { describe('User deletes own contribution', () => {
@ -697,6 +799,33 @@ describe('ContributionResolver', () => {
}), }),
).resolves.toBeTruthy() ).resolves.toBeTruthy()
}) })
it('stores the delete contribution event in the database', async () => {
const contribution = await mutate({
mutation: createContribution,
variables: {
amount: 166.0,
memo: 'Whatever contribution',
creationDate: new Date().toString(),
},
})
await mutate({
mutation: deleteContribution,
variables: {
id: contribution.data.createContribution.id,
},
})
await expect(EventProtocol.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_DELETE,
contributionId: contribution.data.createContribution.id,
amount: expect.decimalEqual(166),
userId: peter.id,
}),
)
})
}) })
describe('User deletes already confirmed contribution', () => { describe('User deletes already confirmed contribution', () => {
@ -728,6 +857,10 @@ describe('ContributionResolver', () => {
}), }),
) )
}) })
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted')
})
}) })
}) })
}) })

View File

@ -13,6 +13,13 @@ import { Contribution, ContributionListResult } from '@model/Contribution'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { validateContribution, getUserCreation, updateCreations } from './util/creations' import { validateContribution, getUserCreation, updateCreations } from './util/creations'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
import {
Event,
EventContributionCreate,
EventContributionDelete,
EventContributionUpdate,
} from '@/event/Event'
import { eventProtocol } from '@/event/EventProtocolEmitter'
@Resolver() @Resolver()
export class ContributionResolver { export class ContributionResolver {
@ -23,15 +30,17 @@ export class ContributionResolver {
@Ctx() context: Context, @Ctx() context: Context,
): Promise<UnconfirmedContribution> { ): Promise<UnconfirmedContribution> {
if (memo.length > MEMO_MAX_CHARS) { if (memo.length > MEMO_MAX_CHARS) {
logger.error(`memo text is too long: memo.length=${memo.length} > (${MEMO_MAX_CHARS}`) logger.error(`memo text is too long: memo.length=${memo.length} > (${MEMO_MAX_CHARS})`)
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
} }
if (memo.length < MEMO_MIN_CHARS) { if (memo.length < MEMO_MIN_CHARS) {
logger.error(`memo text is too short: memo.length=${memo.length} < (${MEMO_MIN_CHARS}`) logger.error(`memo text is too short: memo.length=${memo.length} < (${MEMO_MIN_CHARS})`)
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
} }
const event = new Event()
const user = getUser(context) const user = getUser(context)
const creations = await getUserCreation(user.id) const creations = await getUserCreation(user.id)
logger.trace('creations', creations) logger.trace('creations', creations)
@ -49,6 +58,13 @@ export class ContributionResolver {
logger.trace('contribution to save', contribution) logger.trace('contribution to save', contribution)
await dbContribution.save(contribution) await dbContribution.save(contribution)
const eventCreateContribution = new EventContributionCreate()
eventCreateContribution.userId = user.id
eventCreateContribution.amount = amount
eventCreateContribution.contributionId = contribution.id
await eventProtocol.writeEvent(event.setEventContributionCreate(eventCreateContribution))
return new UnconfirmedContribution(contribution, user, creations) return new UnconfirmedContribution(contribution, user, creations)
} }
@ -58,19 +74,32 @@ export class ContributionResolver {
@Arg('id', () => Int) id: number, @Arg('id', () => Int) id: number,
@Ctx() context: Context, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const event = new Event()
const user = getUser(context) const user = getUser(context)
const contribution = await dbContribution.findOne(id) const contribution = await dbContribution.findOne(id)
if (!contribution) { if (!contribution) {
logger.error('Contribution not found for given id')
throw new Error('Contribution not found for given id.') throw new Error('Contribution not found for given id.')
} }
if (contribution.userId !== user.id) { if (contribution.userId !== user.id) {
logger.error('Can not delete contribution of another user')
throw new Error('Can not delete contribution of another user') throw new Error('Can not delete contribution of another user')
} }
if (contribution.confirmedAt) { if (contribution.confirmedAt) {
logger.error('A confirmed contribution can not be deleted')
throw new Error('A confirmed contribution can not be deleted') throw new Error('A confirmed contribution can not be deleted')
} }
contribution.contributionStatus = ContributionStatus.DELETED contribution.contributionStatus = ContributionStatus.DELETED
contribution.deletedAt = new Date()
await contribution.save() await contribution.save()
const eventDeleteContribution = new EventContributionDelete()
eventDeleteContribution.userId = user.id
eventDeleteContribution.contributionId = contribution.id
eventDeleteContribution.amount = contribution.amount
await eventProtocol.writeEvent(event.setEventContributionDelete(eventDeleteContribution))
const res = await contribution.softRemove() const res = await contribution.softRemove()
return !!res return !!res
} }
@ -154,9 +183,11 @@ export class ContributionResolver {
where: { id: contributionId, confirmedAt: IsNull() }, where: { id: contributionId, confirmedAt: IsNull() },
}) })
if (!contributionToUpdate) { if (!contributionToUpdate) {
logger.error('No contribution found to given id')
throw new Error('No contribution found to given id.') throw new Error('No contribution found to given id.')
} }
if (contributionToUpdate.userId !== user.id) { if (contributionToUpdate.userId !== user.id) {
logger.error('user of the pending contribution and send user does not correspond')
throw new Error('user of the pending contribution and send user does not correspond') throw new Error('user of the pending contribution and send user does not correspond')
} }
@ -177,6 +208,14 @@ export class ContributionResolver {
contributionToUpdate.contributionStatus = ContributionStatus.PENDING contributionToUpdate.contributionStatus = ContributionStatus.PENDING
dbContribution.save(contributionToUpdate) dbContribution.save(contributionToUpdate)
const event = new Event()
const eventUpdateContribution = new EventContributionUpdate()
eventUpdateContribution.userId = user.id
eventUpdateContribution.contributionId = contributionId
eventUpdateContribution.amount = amount
await eventProtocol.writeEvent(event.setEventContributionUpdate(eventUpdateContribution))
return new UnconfirmedContribution(contributionToUpdate, user, creations) return new UnconfirmedContribution(contributionToUpdate, user, creations)
} }
} }

View File

@ -21,7 +21,7 @@ export const validateContribution = (
if (index < 0) { if (index < 0) {
logger.error( logger.error(
'No information for available creations with the given creationDate=', 'No information for available creations with the given creationDate=',
creationDate, creationDate.toString(),
) )
throw new Error('No information for available creations for the given date') throw new Error('No information for available creations for the given date')
} }

View File

@ -29,6 +29,7 @@ describe('sendEMail', () => {
let result: boolean let result: boolean
describe('config email is false', () => { describe('config email is false', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks()
result = await sendEMail({ result = await sendEMail({
to: 'receiver@mail.org', to: 'receiver@mail.org',
cc: 'support@gradido.net', cc: 'support@gradido.net',
@ -48,6 +49,7 @@ describe('sendEMail', () => {
describe('config email is true', () => { describe('config email is true', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks()
CONFIG.EMAIL = true CONFIG.EMAIL = true
result = await sendEMail({ result = await sendEMail({
to: 'receiver@mail.org', to: 'receiver@mail.org',
@ -73,7 +75,7 @@ describe('sendEMail', () => {
it('calls sendMail of transporter', () => { it('calls sendMail of transporter', () => {
expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({ expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${CONFIG.EMAIL_TEST_RECEIVER}`, to: 'receiver@mail.org',
cc: 'support@gradido.net', cc: 'support@gradido.net',
subject: 'Subject', subject: 'Subject',
text: 'Text text text', text: 'Text text text',
@ -84,4 +86,28 @@ describe('sendEMail', () => {
expect(result).toBeTruthy() expect(result).toBeTruthy()
}) })
}) })
describe('with email EMAIL_TEST_MODUS true', () => {
beforeEach(async () => {
jest.clearAllMocks()
CONFIG.EMAIL = true
CONFIG.EMAIL_TEST_MODUS = true
result = await sendEMail({
to: 'receiver@mail.org',
cc: 'support@gradido.net',
subject: 'Subject',
text: 'Text text text',
})
})
it('calls sendMail of transporter with faked to', () => {
expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: CONFIG.EMAIL_TEST_RECEIVER,
cc: 'support@gradido.net',
subject: 'Subject',
text: 'Text text text',
})
})
})
}) })

View File

@ -0,0 +1,42 @@
import Decimal from 'decimal.js-light'
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
@Entity('event_protocol')
export class EventProtocol extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ length: 100, nullable: false, collation: 'utf8mb4_unicode_ci' })
type: string
@Column({ name: 'created_at', type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date
@Column({ name: 'user_id', unsigned: true, nullable: false })
userId: number
@Column({ name: 'x_user_id', unsigned: true, nullable: true })
xUserId: number
@Column({ name: 'x_community_id', unsigned: true, nullable: true })
xCommunityId: number
@Column({ name: 'transaction_id', unsigned: true, nullable: true })
transactionId: number
@Column({ name: 'contribution_id', unsigned: true, nullable: true })
contributionId: number
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: true,
transformer: DecimalTransformer,
})
amount: Decimal
@Column({ name: 'message_id', unsigned: true, nullable: true })
messageId: number
}

View File

@ -1 +1 @@
export { EventProtocol } from './0043-add_event_protocol_table/EventProtocol' export { EventProtocol } from './0050-add_messageId_to_event_protocol/EventProtocol'

View File

@ -0,0 +1,12 @@
/* 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<Array<any>>) {
await queryFn(
`ALTER TABLE \`event_protocol\` ADD COLUMN \`message_id\` int(10) unsigned NULL DEFAULT NULL;`,
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`ALTER TABLE \`event_protocol\` DROP COLUMN \`message_id\`;`)
}

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-database", "name": "gradido-database",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido Database Tool to execute database migrations", "description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database", "repository": "https://github.com/gradido/gradido/database",

View File

@ -1,6 +1,6 @@
{ {
"name": "bootstrap-vue-gradido-wallet", "name": "bootstrap-vue-gradido-wallet",
"version": "1.13.0", "version": "1.13.1",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node run/server.js", "start": "node run/server.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido", "name": "gradido",
"version": "1.13.0", "version": "1.13.1",
"description": "Gradido", "description": "Gradido",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:gradido/gradido.git", "repository": "git@github.com:gradido/gradido.git",