Merge branch 'master' into 2589-Missing-Message-on-old-Transactions

This commit is contained in:
Alexander Friedland 2023-02-09 16:34:28 +01:00 committed by GitHub
commit dfb1a638e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 322 additions and 295 deletions

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=v1.2022-03-18
GRAPHQL_URI=http://localhost:4000/graphql
WALLET_AUTH_URL=http://localhost/authenticate?token={token}
WALLET_URL=http://localhost/login

View File

@ -86,5 +86,10 @@
"> 1%",
"last 2 versions",
"not ie <= 10"
]
],
"nodemonConfig": {
"ignore": [
"**/*.spec.js"
]
}
}

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=v14.2022-12-22
# Server
PORT=4000
JWT_SECRET=secret123
@ -55,9 +53,6 @@ EMAIL_CODE_REQUEST_TIME=10
# Webhook
WEBHOOK_ELOPAGE_SECRET=secret
# EventProtocol
EVENT_PROTOCOL_DISABLED=false
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
# LOG_LEVEL=info

View File

@ -54,9 +54,6 @@ EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME
# Webhook
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
# EventProtocol
EVENT_PROTOCOL_DISABLED=$EVENT_PROTOCOL_DISABLED
# Federation
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED

View File

@ -72,5 +72,8 @@
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.14.0",
"typescript": "^4.3.4"
},
"nodemonConfig": {
"ignore": ["**/*.test.ts"]
}
}

View File

@ -17,7 +17,7 @@ const constants = {
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v14.2022-12-22',
EXPECTED: 'v15.2023-02-07',
CURRENT: '',
},
}
@ -99,11 +99,6 @@ const webhook = {
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
}
const eventProtocol = {
// global switch to enable writing of EventProtocol-Entries
EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
}
// This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET
@ -139,7 +134,6 @@ const CONFIG = {
...email,
...loginServer,
...webhook,
...eventProtocol,
...federation,
}

View File

@ -10,6 +10,7 @@ import {
sendAccountMultiRegistrationEmail,
sendContributionConfirmedEmail,
sendContributionDeniedEmail,
sendContributionDeletedEmail,
sendResetPasswordEmail,
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
@ -438,6 +439,84 @@ describe('sendEmailVariants', () => {
})
})
describe('sendContributionDeletedEmail', () => {
beforeAll(async () => {
result = await sendContributionDeletedEmail({
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
})
})
describe('calls "sendEmailTranslated"', () => {
it('with expected parameters', () => {
expect(sendEmailTranslated).toBeCalledWith({
receiver: {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionDeleted',
locals: {
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
it('has expected result', () => {
expect(result).toMatchObject({
envelope: {
from: 'info@gradido.net',
to: ['peter@lustig.de'],
},
message: expect.any(String),
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (do not answer) <info@gradido.net>',
attachments: [],
subject: 'Gradido: Your common good contribution was deleted',
html: expect.any(String),
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS DELETED'),
}),
})
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
expect(result.originalMessage.html).toContain('<html lang="en">')
expect(result.originalMessage.html).toContain(
'<title>Gradido: Your common good contribution was deleted</title>',
)
expect(result.originalMessage.html).toContain(
'>Gradido: Your common good contribution was deleted</h1>',
)
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
expect(result.originalMessage.html).toContain(
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
)
expect(result.originalMessage.html).toContain(
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
)
expect(result.originalMessage.html).toContain(
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
expect(result.originalMessage.html).toContain('—————')
expect(result.originalMessage.html).toContain(
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
)
})
})
})
describe('sendResetPasswordEmail', () => {
beforeAll(async () => {
result = await sendResetPasswordEmail({

View File

@ -103,6 +103,32 @@ export const sendContributionConfirmedEmail = (data: {
})
}
export const sendContributionDeletedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionDeniedEmail = (data: {
firstName: string
lastName: string

View File

@ -0,0 +1,16 @@
doctype html
html(lang=locale)
head
title= t('emails.contributionDeleted.subject')
body
h1(style='margin-bottom: 24px;')= t('emails.contributionDeleted.subject')
#container.col
include ../hello.pug
p= t('emails.contributionDeleted.commonGoodContributionDeleted', { senderFirstName, senderLastName, contributionMemo })
p= t('emails.contributionDeleted.toSeeContributionsAndMessages')
p
= t('emails.general.linkToYourAccount')
= " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
include ../greatingFormularImprint.pug

View File

@ -0,0 +1 @@
= t('emails.contributionDeleted.subject')

View File

@ -1,4 +1,3 @@
import { EventProtocol } from '@entity/EventProtocol'
import decimal from 'decimal.js-light'
import { EventProtocolType } from './EventProtocolType'
@ -68,6 +67,7 @@ export class EventTransactionReceiveRedeem extends EventBasicTxX {}
export class EventContributionCreate extends EventBasicCt {}
export class EventAdminContributionCreate extends EventBasicCt {}
export class EventAdminContributionDelete extends EventBasicCt {}
export class EventAdminContributionDeny extends EventBasicCt {}
export class EventAdminContributionUpdate extends EventBasicCt {}
export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
@ -87,21 +87,6 @@ export class EventDeleteContributionLink extends EventBasicCt {}
export class EventUpdateContributionLink extends EventBasicCt {}
export class Event {
constructor()
constructor(event?: EventProtocol) {
if (event) {
this.id = event.id
this.type = event.type
this.createdAt = event.createdAt
this.userId = event.userId
this.xUserId = event.xUserId
this.xCommunityId = event.xCommunityId
this.transactionId = event.transactionId
this.contributionId = event.contributionId
this.amount = event.amount
}
}
public setEventBasic(): Event {
this.type = EventProtocolType.BASIC
this.createdAt = new Date()
@ -314,6 +299,13 @@ export class Event {
return this
}
public setEventAdminContributionDeny(ev: EventAdminContributionDeny): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.ADMIN_CONTRIBUTION_DENY
return this
}
public setEventAdminContributionUpdate(ev: EventAdminContributionUpdate): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.ADMIN_CONTRIBUTION_UPDATE

View File

@ -1,41 +1,17 @@
import { Event } from '@/event/Event'
import { backendLogger as logger } from '@/server/logger'
import { EventProtocol } from '@entity/EventProtocol'
import CONFIG from '@/config'
class EventProtocolEmitter {
/* }extends EventEmitter { */
private events: Event[]
/*
public addEvent(event: Event) {
this.events.push(event)
}
public getEvents(): Event[] {
return this.events
}
*/
public isDisabled() {
logger.info(`EventProtocol - isDisabled=${CONFIG.EVENT_PROTOCOL_DISABLED}`)
return CONFIG.EVENT_PROTOCOL_DISABLED === true
}
public async writeEvent(event: Event): Promise<void> {
if (!eventProtocol.isDisabled()) {
logger.info(`writeEvent(${JSON.stringify(event)})`)
const dbEvent = new EventProtocol()
dbEvent.type = event.type
dbEvent.createdAt = event.createdAt
dbEvent.userId = event.userId
if (event.xUserId) dbEvent.xUserId = event.xUserId
if (event.xCommunityId) dbEvent.xCommunityId = event.xCommunityId
if (event.contributionId) dbEvent.contributionId = event.contributionId
if (event.transactionId) dbEvent.transactionId = event.transactionId
if (event.amount) dbEvent.amount = event.amount
await dbEvent.save()
}
}
export const writeEvent = async (event: Event): Promise<EventProtocol | null> => {
logger.info('writeEvent', event)
const dbEvent = new EventProtocol()
dbEvent.type = event.type
dbEvent.createdAt = event.createdAt
dbEvent.userId = event.userId
dbEvent.xUserId = event.xUserId || null
dbEvent.xCommunityId = event.xCommunityId || null
dbEvent.contributionId = event.contributionId || null
dbEvent.transactionId = event.transactionId || null
dbEvent.amount = event.amount || null
return dbEvent.save()
}
export const eventProtocol = new EventProtocolEmitter()

View File

@ -35,6 +35,7 @@ export enum EventProtocolType {
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',

View File

@ -15,6 +15,8 @@ import { calculateDecay } from '@/util/decay'
import { RIGHTS } from '@/auth/RIGHTS'
import { GdtResolver } from './GdtResolver'
import { getLastTransaction } from './util/getLastTransaction'
@Resolver()
export class BalanceResolver {
@Authorized([RIGHTS.BALANCE])
@ -32,7 +34,7 @@ export class BalanceResolver {
const lastTransaction = context.lastTransaction
? context.lastTransaction
: await dbTransaction.findOne({ userId: user.id }, { order: { id: 'DESC' } })
: await getLastTransaction(user.id)
logger.debug(`lastTransaction=${lastTransaction}`)

View File

@ -246,6 +246,7 @@ describe('Contribution Links', () => {
})
it('returns an error if missing startDate', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -270,6 +271,7 @@ describe('Contribution Links', () => {
})
it('returns an error if missing endDate', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -292,6 +294,7 @@ describe('Contribution Links', () => {
})
it('returns an error if endDate is before startDate', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -316,27 +319,8 @@ describe('Contribution Links', () => {
)
})
it('returns an error if name is an empty string', async () => {
await expect(
mutate({
mutation: createContributionLink,
variables: {
...variables,
name: '',
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('The name must be initialized!')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('The name must be initialized!')
})
it('returns an error if name is shorter than 5 characters', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -347,22 +331,17 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new GraphQLError(
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
),
],
errors: [new GraphQLError('The value of name is too short')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
)
expect(logger.error).toBeCalledWith('The value of name is too short', 3)
})
it('returns an error if name is longer than 100 characters', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -373,42 +352,17 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new GraphQLError(
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
),
],
errors: [new GraphQLError('The value of name is too long')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
)
})
it('returns an error if memo is an empty string', async () => {
await expect(
mutate({
mutation: createContributionLink,
variables: {
...variables,
memo: '',
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('The memo must be initialized!')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('The memo must be initialized!')
expect(logger.error).toBeCalledWith('The value of name is too long', 101)
})
it('returns an error if memo is shorter than 5 characters', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -419,22 +373,17 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new GraphQLError(
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
),
],
errors: [new GraphQLError('The value of memo is too short')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
)
expect(logger.error).toBeCalledWith('The value of memo is too short', 3)
})
it('returns an error if memo is longer than 255 characters', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -445,22 +394,17 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new GraphQLError(
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
),
],
errors: [new GraphQLError('The value of memo is too long')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
)
expect(logger.error).toBeCalledWith('The value of memo is too long', 256)
})
it('returns an error if amount is not positive', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionLink,
@ -471,15 +415,13 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('The amount=0 must be initialized with a positiv value!')],
errors: [new GraphQLError('The amount must be a positiv value')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'The amount=0 must be initialized with a positiv value!',
)
expect(logger.error).toBeCalledWith('The amount must be a positiv value', new Decimal(0))
})
})
@ -530,14 +472,14 @@ describe('Contribution Links', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Contribution Link not found to given id.')],
errors: [new GraphQLError('Contribution Link not found')],
}),
)
})
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
expect(logger.error).toBeCalledWith('Contribution Link not found', -1)
})
describe('valid id', () => {
@ -601,13 +543,13 @@ describe('Contribution Links', () => {
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Contribution Link not found to given id.')],
errors: [new GraphQLError('Contribution Link not found')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
expect(logger.error).toBeCalledWith('Contribution Link not found', -1)
})
})

View File

@ -20,6 +20,7 @@ import Paginated from '@arg/Paginated'
// TODO: this is a strange construct
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
import LogError from '@/server/LogError'
@Resolver()
export class ContributionLinkResolver {
@ -39,35 +40,22 @@ export class ContributionLinkResolver {
}: ContributionLinkArgs,
): Promise<ContributionLink> {
isStartEndDateValid(validFrom, validTo)
if (!name) {
logger.error(`The name must be initialized!`)
throw new Error(`The name must be initialized!`)
if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) {
throw new LogError('The value of name is too short', name.length)
}
if (
name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS ||
name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS
) {
const msg = `The value of 'name' with a length of ${name.length} did not fulfill the requested bounderies min=${CONTRIBUTIONLINK_NAME_MIN_CHARS} and max=${CONTRIBUTIONLINK_NAME_MAX_CHARS}`
logger.error(`${msg}`)
throw new Error(`${msg}`)
if (name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS) {
throw new LogError('The value of name is too long', name.length)
}
if (!memo) {
logger.error(`The memo must be initialized!`)
throw new Error(`The memo must be initialized!`)
if (memo.length < MEMO_MIN_CHARS) {
throw new LogError('The value of memo is too short', memo.length)
}
if (memo.length < MEMO_MIN_CHARS || memo.length > MEMO_MAX_CHARS) {
const msg = `The value of 'memo' with a length of ${memo.length} did not fulfill the requested bounderies min=${MEMO_MIN_CHARS} and max=${MEMO_MAX_CHARS}`
logger.error(`${msg}`)
throw new Error(`${msg}`)
}
if (!amount) {
logger.error(`The amount must be initialized!`)
throw new Error('The amount must be initialized!')
if (memo.length > MEMO_MAX_CHARS) {
throw new LogError('The value of memo is too long', memo.length)
}
if (!new Decimal(amount).isPositive()) {
logger.error(`The amount=${amount} must be initialized with a positiv value!`)
throw new Error(`The amount=${amount} must be initialized with a positiv value!`)
throw new LogError('The amount must be a positiv value', amount)
}
const dbContributionLink = new DbContributionLink()
dbContributionLink.amount = amount
dbContributionLink.name = name
@ -107,8 +95,7 @@ export class ContributionLinkResolver {
async deleteContributionLink(@Arg('id', () => Int) id: number): Promise<Date | null> {
const contributionLink = await DbContributionLink.findOne(id)
if (!contributionLink) {
logger.error(`Contribution Link not found to given id: ${id}`)
throw new Error('Contribution Link not found to given id.')
throw new LogError('Contribution Link not found', id)
}
await contributionLink.softRemove()
logger.debug(`deleteContributionLink successful!`)
@ -134,8 +121,7 @@ export class ContributionLinkResolver {
): Promise<ContributionLink> {
const dbContributionLink = await DbContributionLink.findOne(id)
if (!dbContributionLink) {
logger.error(`Contribution Link not found to given id: ${id}`)
throw new Error('Contribution Link not found to given id.')
throw new LogError('Contribution Link not found', id)
}
dbContributionLink.amount = amount
dbContributionLink.name = name

View File

@ -88,6 +88,7 @@ describe('ContributionMessageResolver', () => {
describe('input not valid', () => {
it('throws error when contribution does not exist', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminCreateContributionMessage,
@ -100,14 +101,22 @@ describe('ContributionMessageResolver', () => {
expect.objectContaining({
errors: [
new GraphQLError(
'ContributionMessage was not successful: Error: Contribution not found',
'ContributionMessage was not sent successfully: Error: Contribution not found',
),
],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Contribution not found',
new Error('Contribution not found'),
)
})
it('throws error when contribution.userId equals user.id', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
@ -132,12 +141,19 @@ describe('ContributionMessageResolver', () => {
expect.objectContaining({
errors: [
new GraphQLError(
'ContributionMessage was not successful: Error: Admin can not answer on own contribution',
'ContributionMessage was not sent successfully: Error: Admin can not answer on his own contribution',
),
],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Admin can not answer on his own contribution',
new Error('Admin can not answer on his own contribution'),
)
})
})
describe('valid input', () => {
@ -210,6 +226,7 @@ describe('ContributionMessageResolver', () => {
describe('input not valid', () => {
it('throws error when contribution does not exist', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContributionMessage,
@ -222,14 +239,22 @@ describe('ContributionMessageResolver', () => {
expect.objectContaining({
errors: [
new GraphQLError(
'ContributionMessage was not successful: Error: Contribution not found',
'ContributionMessage was not sent successfully: Error: Contribution not found',
),
],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Contribution not found',
new Error('Contribution not found'),
)
})
it('throws error when other user tries to send createContributionMessage', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
@ -246,12 +271,19 @@ describe('ContributionMessageResolver', () => {
expect.objectContaining({
errors: [
new GraphQLError(
'ContributionMessage was not successful: Error: Can not send message to contribution of another user',
'ContributionMessage was not sent successfully: Error: Can not send message to contribution of another user',
),
],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Can not send message to contribution of another user',
new Error('Can not send message to contribution of another user'),
)
})
})
describe('valid input', () => {

View File

@ -12,10 +12,10 @@ import { ContributionStatus } from '@enum/ContributionStatus'
import { Order } from '@enum/Order'
import Paginated from '@arg/Paginated'
import { backendLogger as logger } from '@/server/logger'
import { RIGHTS } from '@/auth/RIGHTS'
import { Context, getUser } from '@/server/context'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import LogError from '@/server/LogError'
@Resolver()
export class ContributionMessageResolver {
@ -54,8 +54,7 @@ export class ContributionMessageResolver {
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
logger.error(`ContributionMessage was not successful: ${e}`)
throw new Error(`ContributionMessage was not successful: ${e}`)
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
} finally {
await queryRunner.release()
}
@ -95,9 +94,7 @@ export class ContributionMessageResolver {
@Ctx() context: Context,
): Promise<ContributionMessage> {
const user = getUser(context)
if (!user.emailContact) {
user.emailContact = await UserContact.findOneOrFail({ where: { id: user.emailId } })
}
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
@ -108,12 +105,10 @@ export class ContributionMessageResolver {
relations: ['user'],
})
if (!contribution) {
logger.error('Contribution not found')
throw new Error('Contribution not found')
throw new LogError('Contribution not found', contributionId)
}
if (contribution.userId === user.id) {
logger.error('Admin can not answer on own contribution')
throw new Error('Admin can not answer on own contribution')
throw new LogError('Admin can not answer on his own contribution', contributionId)
}
if (!contribution.user.emailContact) {
contribution.user.emailContact = await UserContact.findOneOrFail({
@ -149,8 +144,7 @@ export class ContributionMessageResolver {
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
logger.error(`ContributionMessage was not successful: ${e}`)
throw new Error(`ContributionMessage was not successful: ${e}`)
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
} finally {
await queryRunner.release()
}

View File

@ -22,11 +22,7 @@ import {
listContributions,
listUnconfirmedContributions,
} from '@/seeds/graphql/queries'
import {
// sendAccountActivationEmail,
sendContributionConfirmedEmail,
// sendContributionRejectedEmail,
} from '@/emails/sendEmailVariants'
import { sendContributionConfirmedEmail } from '@/emails/sendEmailVariants'
import {
cleanDB,
resetToken,
@ -47,7 +43,6 @@ import { EventProtocolType } from '@/event/EventProtocolType'
import { logger, i18n as localization } from '@test/testSetup'
import { UserInputError } from 'apollo-server-express'
// mock account activation email to avoid console spam
// mock account activation email to avoid console spam
jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants')

View File

@ -44,16 +44,20 @@ import {
EventContributionConfirm,
EventAdminContributionCreate,
EventAdminContributionDelete,
EventAdminContributionDeny,
EventAdminContributionUpdate,
} from '@/event/Event'
import { eventProtocol } from '@/event/EventProtocolEmitter'
import { writeEvent } from '@/event/EventProtocolEmitter'
import { calculateDecay } from '@/util/decay'
import {
sendContributionConfirmedEmail,
sendContributionDeletedEmail,
sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { getLastTransaction } from './util/getLastTransaction'
@Resolver()
export class ContributionResolver {
@Authorized([RIGHTS.CREATE_CONTRIBUTION])
@ -97,7 +101,7 @@ export class ContributionResolver {
eventCreateContribution.userId = user.id
eventCreateContribution.amount = amount
eventCreateContribution.contributionId = contribution.id
await eventProtocol.writeEvent(event.setEventContributionCreate(eventCreateContribution))
await writeEvent(event.setEventContributionCreate(eventCreateContribution))
return new UnconfirmedContribution(contribution, user, creations)
}
@ -133,7 +137,7 @@ export class ContributionResolver {
eventDeleteContribution.userId = user.id
eventDeleteContribution.contributionId = contribution.id
eventDeleteContribution.amount = contribution.amount
await eventProtocol.writeEvent(event.setEventContributionDelete(eventDeleteContribution))
await writeEvent(event.setEventContributionDelete(eventDeleteContribution))
const res = await contribution.softRemove()
return !!res
@ -290,7 +294,7 @@ export class ContributionResolver {
eventUpdateContribution.userId = user.id
eventUpdateContribution.contributionId = contributionId
eventUpdateContribution.amount = amount
await eventProtocol.writeEvent(event.setEventContributionUpdate(eventUpdateContribution))
await writeEvent(event.setEventContributionUpdate(eventUpdateContribution))
return new UnconfirmedContribution(contributionToUpdate, user, creations)
}
@ -357,9 +361,7 @@ export class ContributionResolver {
eventAdminCreateContribution.userId = moderator.id
eventAdminCreateContribution.amount = amount
eventAdminCreateContribution.contributionId = contribution.id
await eventProtocol.writeEvent(
event.setEventAdminContributionCreate(eventAdminCreateContribution),
)
await writeEvent(event.setEventAdminContributionCreate(eventAdminCreateContribution))
return getUserCreation(emailContact.userId, clientTimezoneOffset)
}
@ -469,9 +471,7 @@ export class ContributionResolver {
eventAdminContributionUpdate.userId = user.id
eventAdminContributionUpdate.amount = amount
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
await eventProtocol.writeEvent(
event.setEventAdminContributionUpdate(eventAdminContributionUpdate),
)
await writeEvent(event.setEventAdminContributionUpdate(eventAdminContributionUpdate))
return result
}
@ -549,10 +549,8 @@ export class ContributionResolver {
eventAdminContributionDelete.userId = contribution.userId
eventAdminContributionDelete.amount = contribution.amount
eventAdminContributionDelete.contributionId = contribution.id
await eventProtocol.writeEvent(
event.setEventAdminContributionDelete(eventAdminContributionDelete),
)
sendContributionDeniedEmail({
await writeEvent(event.setEventAdminContributionDelete(eventAdminContributionDelete))
sendContributionDeletedEmail({
firstName: user.firstName,
lastName: user.lastName,
email: user.emailContact.email,
@ -613,16 +611,11 @@ export class ContributionResolver {
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
try {
const lastTransaction = await queryRunner.manager
.createQueryBuilder()
.select('transaction')
.from(DbTransaction, 'transaction')
.where('transaction.userId = :id', { id: contribution.userId })
.orderBy('transaction.id', 'DESC')
.getOne()
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
const lastTransaction = await getLastTransaction(contribution.userId)
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
try {
let newBalance = new Decimal(0)
let decay: Decay | null = null
if (lastTransaction) {
@ -679,7 +672,7 @@ export class ContributionResolver {
eventContributionConfirm.userId = user.id
eventContributionConfirm.amount = contribution.amount
eventContributionConfirm.contributionId = contribution.id
await eventProtocol.writeEvent(event.setEventContributionConfirm(eventContributionConfirm))
await writeEvent(event.setEventContributionConfirm(eventContributionConfirm))
} finally {
releaseLock()
}
@ -773,6 +766,13 @@ export class ContributionResolver {
contributionToUpdate.deniedAt = new Date()
const res = await contributionToUpdate.save()
const event = new Event()
const eventAdminContributionDeny = new EventAdminContributionDeny()
eventAdminContributionDeny.userId = contributionToUpdate.userId
eventAdminContributionDeny.amount = contributionToUpdate.amount
eventAdminContributionDeny.contributionId = contributionToUpdate.id
await writeEvent(event.setEventAdminContributionDeny(eventAdminContributionDeny))
sendContributionDeniedEmail({
firstName: user.firstName,
lastName: user.lastName,

View File

@ -33,6 +33,8 @@ import { executeTransaction } from './TransactionResolver'
import QueryLinkResult from '@union/QueryLinkResult'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { getLastTransaction } from './util/getLastTransaction'
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
const time = date.getTime().toString(16)
@ -275,13 +277,7 @@ export class TransactionLinkResolver {
await queryRunner.manager.insert(DbContribution, contribution)
const lastTransaction = await queryRunner.manager
.createQueryBuilder()
.select('transaction')
.from(DbTransaction, 'transaction')
.where('transaction.userId = :id', { id: user.id })
.orderBy('transaction.id', 'DESC')
.getOne()
const lastTransaction = await getLastTransaction(user.id)
let newBalance = new Decimal(0)
let decay: Decay | null = null

View File

@ -30,7 +30,7 @@ import {
sendTransactionReceivedEmail,
} from '@/emails/sendEmailVariants'
import { Event, EventTransactionReceive, EventTransactionSend } from '@/event/Event'
import { eventProtocol } from '@/event/EventProtocolEmitter'
import { writeEvent } from '@/event/EventProtocolEmitter'
import { BalanceResolver } from './BalanceResolver'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
@ -38,6 +38,8 @@ import { findUserByEmail } from './UserResolver'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { getLastTransaction } from './util/getLastTransaction'
export const executeTransaction = async (
amount: Decimal,
memo: string,
@ -144,16 +146,14 @@ export const executeTransaction = async (
eventTransactionSend.xUserId = transactionSend.linkedUserId
eventTransactionSend.transactionId = transactionSend.id
eventTransactionSend.amount = transactionSend.amount.mul(-1)
await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
await writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
const eventTransactionReceive = new EventTransactionReceive()
eventTransactionReceive.userId = transactionReceive.userId
eventTransactionReceive.xUserId = transactionReceive.linkedUserId
eventTransactionReceive.transactionId = transactionReceive.id
eventTransactionReceive.amount = transactionReceive.amount
await eventProtocol.writeEvent(
new Event().setEventTransactionReceive(eventTransactionReceive),
)
await writeEvent(new Event().setEventTransactionReceive(eventTransactionReceive))
} catch (e) {
await queryRunner.rollbackTransaction()
logger.error(`Transaction was not successful: ${e}`)
@ -208,10 +208,7 @@ export class TransactionResolver {
logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.emailId})`)
// find current balance
const lastTransaction = await dbTransaction.findOne(
{ userId: user.id },
{ order: { id: 'DESC' }, relations: ['contribution'] },
)
const lastTransaction = await getLastTransaction(user.id, ['contribution'])
logger.debug(`lastTransaction=${lastTransaction}`)
const balanceResolver = new BalanceResolver()

View File

@ -48,7 +48,7 @@ import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddle
import { klicktippSignIn } from '@/apis/KlicktippController'
import { RIGHTS } from '@/auth/RIGHTS'
import { hasElopageBuys } from '@/util/hasElopageBuys'
import { eventProtocol } from '@/event/EventProtocolEmitter'
import { writeEvent } from '@/event/EventProtocolEmitter'
import {
Event,
EventLogin,
@ -179,7 +179,7 @@ export class UserResolver {
})
const ev = new EventLogin()
ev.userId = user.id
eventProtocol.writeEvent(new Event().setEventLogin(ev))
writeEvent(new Event().setEventLogin(ev))
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
return user
}
@ -251,9 +251,7 @@ export class UserResolver {
})
const eventSendAccountMultiRegistrationEmail = new EventSendAccountMultiRegistrationEmail()
eventSendAccountMultiRegistrationEmail.userId = foundUser.id
eventProtocol.writeEvent(
event.setEventSendConfirmationEmail(eventSendAccountMultiRegistrationEmail),
)
writeEvent(event.setEventSendConfirmationEmail(eventSendAccountMultiRegistrationEmail))
logger.info(
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
)
@ -336,7 +334,7 @@ export class UserResolver {
})
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
eventSendConfirmEmail.userId = dbUser.id
eventProtocol.writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmEmail))
writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmEmail))
if (!emailSent) {
logger.debug(`Account confirmation link: ${activationLink}`)
@ -354,10 +352,10 @@ export class UserResolver {
if (redeemCode) {
eventRedeemRegister.userId = dbUser.id
await eventProtocol.writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
await writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
} else {
eventRegister.userId = dbUser.id
await eventProtocol.writeEvent(event.setEventRegister(eventRegister))
await writeEvent(event.setEventRegister(eventRegister))
}
return new User(dbUser)
@ -477,7 +475,7 @@ export class UserResolver {
const eventActivateAccount = new EventActivateAccount()
eventActivateAccount.userId = user.id
eventProtocol.writeEvent(event.setEventActivateAccount(eventActivateAccount))
writeEvent(event.setEventActivateAccount(eventActivateAccount))
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Error on writing User and User Contact data', e)
@ -824,9 +822,7 @@ export class UserResolver {
const event = new Event()
const eventSendConfirmationEmail = new EventSendConfirmationEmail()
eventSendConfirmationEmail.userId = user.id
await eventProtocol.writeEvent(
event.setEventSendConfirmationEmail(eventSendConfirmationEmail),
)
await writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmationEmail))
}
return true

View File

@ -0,0 +1,14 @@
import { Transaction as DbTransaction } from '@entity/Transaction'
export const getLastTransaction = async (
userId: number,
relations?: string[],
): Promise<DbTransaction | undefined> => {
return DbTransaction.findOne(
{ userId },
{
order: { balanceDate: 'DESC', id: 'DESC' },
relations,
},
)
}

View File

@ -23,8 +23,13 @@
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
},
"contributionRejected": {
"commonGoodContributionRejected": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
"contributionDeleted": {
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
},
"contributionDenied": {
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
},

View File

@ -23,6 +23,11 @@
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
"subject": "Gradido: Your contribution to the common good was confirmed"
},
"contributionDeleted": {
"commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was deleted",
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
},
"contributionDenied": {
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was rejected",

View File

@ -1,10 +1,10 @@
import { calculateDecay } from './decay'
import Decimal from 'decimal.js-light'
import { Transaction } from '@entity/Transaction'
import { Decay } from '@model/Decay'
import { getCustomRepository } from '@dbTools/typeorm'
import { TransactionLinkRepository } from '@repository/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { getLastTransaction } from '../graphql/resolver/util/getLastTransaction'
function isStringBoolean(value: string): boolean {
const lowerValue = value.toLowerCase()
@ -20,7 +20,7 @@ async function calculateBalance(
time: Date,
transactionLink?: dbTransactionLink | null,
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
const lastTransaction = await Transaction.findOne({ userId }, { order: { id: 'DESC' } })
const lastTransaction = await getLastTransaction(userId)
if (!lastTransaction) return null
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=v1.2022-03-18
DB_HOST=localhost
DB_PORT=3306
DB_USER=root

View File

@ -16,17 +16,17 @@ export class EventProtocol extends BaseEntity {
@Column({ name: 'user_id', unsigned: true, nullable: false })
userId: number
@Column({ name: 'x_user_id', unsigned: true, nullable: true })
xUserId: number
@Column({ name: 'x_user_id', type: 'int', unsigned: true, nullable: true })
xUserId: number | null
@Column({ name: 'x_community_id', unsigned: true, nullable: true })
xCommunityId: number
@Column({ name: 'x_community_id', type: 'int', unsigned: true, nullable: true })
xCommunityId: number | null
@Column({ name: 'transaction_id', unsigned: true, nullable: true })
transactionId: number
@Column({ name: 'transaction_id', type: 'int', unsigned: true, nullable: true })
transactionId: number | null
@Column({ name: 'contribution_id', unsigned: true, nullable: true })
contributionId: number
@Column({ name: 'contribution_id', type: 'int', unsigned: true, nullable: true })
contributionId: number | null
@Column({
type: 'decimal',
@ -35,8 +35,8 @@ export class EventProtocol extends BaseEntity {
nullable: true,
transformer: DecimalTransformer,
})
amount: Decimal
amount: Decimal | null
@Column({ name: 'message_id', unsigned: true, nullable: true })
messageId: number
@Column({ name: 'message_id', type: 'int', unsigned: true, nullable: true })
messageId: number | null
}

View File

@ -27,7 +27,7 @@ COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# backend
BACKEND_CONFIG_VERSION=v14.2022-12-22
BACKEND_CONFIG_VERSION=v15.2023-02-07
JWT_EXPIRES_IN=10m
GDT_API_URL=https://gdt.gradido.net
@ -56,9 +56,6 @@ EMAIL_CODE_REQUEST_TIME=10
WEBHOOK_ELOPAGE_SECRET=secret
# EventProtocol
EVENT_PROTOCOL_DISABLED=false
# Federation
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=v1.2023-01-01
# Database
DB_HOST=localhost
DB_PORT=3306
@ -8,9 +6,6 @@ DB_PASSWORD=
DB_DATABASE=gradido_community
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.dht-node.log
# EventProtocol
EVENT_PROTOCOL_DISABLED=false
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
# LOG_LEVEL=info

View File

@ -8,9 +8,6 @@ DB_PASSWORD=$DB_PASSWORD
DB_DATABASE=gradido_community
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
# EventProtocol
EVENT_PROTOCOL_DISABLED=$EVENT_PROTOCOL_DISABLED
# Federation
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED

View File

@ -9,7 +9,7 @@ const constants = {
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v1.2023-01-01',
EXPECTED: 'v2.2023-02-07',
CURRENT: '',
},
}
@ -28,11 +28,6 @@ const database = {
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
}
const eventProtocol = {
// global switch to enable writing of EventProtocol-Entries
EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
}
const federation = {
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
@ -55,7 +50,6 @@ const CONFIG = {
...constants,
...server,
...database,
...eventProtocol,
...federation,
}

View File

@ -52,10 +52,6 @@ const community = {
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
}
*/
// const eventProtocol = {
// global switch to enable writing of EventProtocol-Entries
// EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
// }
// This is needed by graphql-directive-auth
// process.env.APP_SECRET = server.JWT_SECRET

View File

@ -1,5 +1,3 @@
CONFIG_VERSION=v4.2022-12-20
# Environment
DEFAULT_PUBLISHER_ID=2896

View File

@ -104,5 +104,10 @@
],
"author": "Gradido-Akademie - https://www.gradido.net/",
"license": "Apache-2.0",
"description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur."
"description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur.",
"nodemonConfig": {
"ignore": [
"**/*.spec.js"
]
}
}