mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch 'master' into e2e-test-setup
This commit is contained in:
commit
b34fd7e76f
48
CHANGELOG.md
48
CHANGELOG.md
@ -4,8 +4,56 @@ 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).
|
||||
|
||||
#### [1.12.1](https://github.com/gradido/gradido/compare/1.12.0...1.12.1)
|
||||
|
||||
- fix: 🍰 Show Not Icons In `allContribution` List [`#2195`](https://github.com/gradido/gradido/pull/2195)
|
||||
|
||||
#### [1.12.0](https://github.com/gradido/gradido/compare/1.11.0...1.12.0)
|
||||
|
||||
> 12 September 2022
|
||||
|
||||
- release: v1.12.0 [`#2191`](https://github.com/gradido/gradido/pull/2191)
|
||||
- if message empty else disabled button [`#2189`](https://github.com/gradido/gradido/pull/2189)
|
||||
- messages show if Confirmed [`#2185`](https://github.com/gradido/gradido/pull/2185)
|
||||
- text in messages smaller [`#2186`](https://github.com/gradido/gradido/pull/2186)
|
||||
- feat: 🍰 Klicktipp retrieve not registered email [`#2181`](https://github.com/gradido/gradido/pull/2181)
|
||||
- fix: 🍰 isModerator on messages to switch the messages side in the messages overview [`#2182`](https://github.com/gradido/gradido/pull/2182)
|
||||
- Refactor locales for Nederlands [`#2174`](https://github.com/gradido/gradido/pull/2174)
|
||||
- Add is moderator to contribution message [`#2180`](https://github.com/gradido/gradido/pull/2180)
|
||||
- feat: 🍰 Moderator Cannot Answer Himself [`#2178`](https://github.com/gradido/gradido/pull/2178)
|
||||
- refactor: Improve Statistics Query [`#2170`](https://github.com/gradido/gradido/pull/2170)
|
||||
- fix: Remove Statistics from Wallet [`#2171`](https://github.com/gradido/gradido/pull/2171)
|
||||
- feat: 🍰 Contribution Messages In Frontend [`#2164`](https://github.com/gradido/gradido/pull/2164)
|
||||
- feat: 🚀 CRUD For Contribution Messages [`#2149`](https://github.com/gradido/gradido/pull/2149)
|
||||
- fix: 🍰 Decay Calculation In Community Statistics [`#2167`](https://github.com/gradido/gradido/pull/2167)
|
||||
- chore: 🍰 Remove Fetch Policy Network Only From Statistics [`#2159`](https://github.com/gradido/gradido/pull/2159)
|
||||
- feat: 🍰 Remove Some Statistics Data From Frontend [`#2153`](https://github.com/gradido/gradido/pull/2153)
|
||||
- feat: 🍰 Add Toogle Collaps On Language Name [`#2156`](https://github.com/gradido/gradido/pull/2156)
|
||||
- 2145 corrections style for frontend [`#2147`](https://github.com/gradido/gradido/pull/2147)
|
||||
- 2072 feature usecase contribution messaging [`#2073`](https://github.com/gradido/gradido/pull/2073)
|
||||
- 2151 add hint to redeem link [`#2158`](https://github.com/gradido/gradido/pull/2158)
|
||||
- 🍰 Create `contribution messages` table [`#2137`](https://github.com/gradido/gradido/pull/2137)
|
||||
- feat: 🍰 Add The Languages French And Dutch [`#2138`](https://github.com/gradido/gradido/pull/2138)
|
||||
- 1973 list open contribution links in the wallet [`#1975`](https://github.com/gradido/gradido/pull/1975)
|
||||
- feat: 🍰 Admin Interface Displays Statistics [`#2124`](https://github.com/gradido/gradido/pull/2124)
|
||||
- feat: Statistics Resolver [`#2041`](https://github.com/gradido/gradido/pull/2041)
|
||||
- 2116 retrieve admin and moderators [`#2127`](https://github.com/gradido/gradido/pull/2127)
|
||||
- 2125 feature gradido id: new column gradidoid in users table [`#2126`](https://github.com/gradido/gradido/pull/2126)
|
||||
- 2119 new menu item gdt [`#2120`](https://github.com/gradido/gradido/pull/2120)
|
||||
- feat: Migrate Contributions Table [`#2136`](https://github.com/gradido/gradido/pull/2136)
|
||||
- chore: 🍰 Refactor Contribution Form Logic And Write Tests [`#2092`](https://github.com/gradido/gradido/pull/2092)
|
||||
- fix: 🍰 Add `emailChecked` Before Changing `optIn` State & Log Error On klicktipp Middleware [`#2107`](https://github.com/gradido/gradido/pull/2107)
|
||||
- Add RIGHTS.LIST_CONTRIBUTION_LINKS to ROLE_USER [`#2123`](https://github.com/gradido/gradido/pull/2123)
|
||||
- 2121 translate locales to spanish [`#2122`](https://github.com/gradido/gradido/pull/2122)
|
||||
- add formatter on input amount replace point and comma [`#2115`](https://github.com/gradido/gradido/pull/2115)
|
||||
- remove required from form.memo [`#2114`](https://github.com/gradido/gradido/pull/2114)
|
||||
- Fix pagination ellipsis [`#2104`](https://github.com/gradido/gradido/pull/2104)
|
||||
|
||||
#### [1.11.0](https://github.com/gradido/gradido/compare/1.10.1...1.11.0)
|
||||
|
||||
> 28 July 2022
|
||||
|
||||
- release: Version 1.11.0 [`#2103`](https://github.com/gradido/gradido/pull/2103)
|
||||
- Fix navbar community item [`#2102`](https://github.com/gradido/gradido/pull/2102)
|
||||
- Add validation date info to copied text after transaction link creation [`#2101`](https://github.com/gradido/gradido/pull/2101)
|
||||
- Remove member area from menu (desktop and mobile), when user has no elopage account [`#2099`](https://github.com/gradido/gradido/pull/2099)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div>
|
||||
<div class="mt-5">
|
||||
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
@ -14,7 +14,9 @@
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary">{{ $t('form.submit') }}</b-button>
|
||||
<b-button type="submit" variant="primary" :disabled="disabled">
|
||||
{{ $t('form.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
@ -63,5 +65,13 @@ export default {
|
||||
this.form.text = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
if (this.form.text !== '') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<is-moderator v-if="isModerator" :message="message"></is-moderator>
|
||||
<is-moderator v-if="message.isModerator" :message="message"></is-moderator>
|
||||
<is-not-moderator v-else :message="message"></is-not-moderator>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,10 +23,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isModerator() {
|
||||
return this.$store.state.moderator.id === this.message.userId
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('moderator') }}</small>
|
||||
<div class="mt-2 text-bold h4">{{ message.message }}</div>
|
||||
<div class="mt-2">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -2,11 +2,9 @@
|
||||
<div class="slot-is-not-moderator">
|
||||
<div>
|
||||
<b-avatar :text="initialLetters" variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2 text-bold">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<div class="mt-2 text-bold h4">{{ message.message }}</div>
|
||||
<div class="mt-2">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -12,33 +12,42 @@
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(editCreation)="row">
|
||||
<b-button
|
||||
v-if="row.item.moderator"
|
||||
variant="info"
|
||||
size="md"
|
||||
@click="rowToggleDetails(row, 0)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messageCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messageCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="light"
|
||||
></b-icon>
|
||||
</b-button>
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<b-button
|
||||
v-if="row.item.moderator"
|
||||
variant="info"
|
||||
size="md"
|
||||
@click="rowToggleDetails(row, 0)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messageCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messageCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="light"
|
||||
></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(confirm)="row">
|
||||
<b-button variant="success" size="md" @click="$emit('show-overlay', row.item)" class="mr-2">
|
||||
<b-icon icon="check" scale="2" variant=""></b-icon>
|
||||
</b-button>
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<b-button
|
||||
variant="success"
|
||||
size="md"
|
||||
@click="$emit('show-overlay', row.item)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon icon="check" scale="2" variant=""></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #row-details="row">
|
||||
<row-details
|
||||
|
||||
@ -18,6 +18,7 @@ export const listContributionMessages = gql`
|
||||
userFirstName
|
||||
userLastName
|
||||
userId
|
||||
isModerator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ export const listUnconfirmedContributions = gql`
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
userId
|
||||
email
|
||||
amount
|
||||
memo
|
||||
|
||||
@ -14,21 +14,23 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
id: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
userId: 99,
|
||||
email: 'bibi@bloxberg.de',
|
||||
amount: 500,
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 2,
|
||||
moderator: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
firstName: 'Räuber',
|
||||
lastName: 'Hotzenplotz',
|
||||
userId: 100,
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
amount: 1000000,
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 2,
|
||||
moderator: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -41,6 +43,15 @@ const mocks = {
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
commit: storeCommitMock,
|
||||
state: {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
isAdmin: '2022-08-30T07:41:31.000Z',
|
||||
id: 263,
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -14,7 +14,8 @@
|
||||
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts"
|
||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
||||
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0047-messages_tables',
|
||||
DB_VERSION: '0048-add_is_moderator_to_contribution_messages',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
@ -13,6 +13,7 @@ export class ContributionMessage {
|
||||
this.userFirstName = user.firstName
|
||||
this.userLastName = user.lastName
|
||||
this.userId = user.id
|
||||
this.isModerator = contributionMessage.isModerator
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@ -38,6 +39,9 @@ export class ContributionMessage {
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
userId: number | null
|
||||
|
||||
@Field(() => Boolean)
|
||||
isModerator: boolean
|
||||
}
|
||||
@ObjectType()
|
||||
export class ContributionMessageListResult {
|
||||
|
||||
@ -40,6 +40,7 @@ import Decimal from 'decimal.js-light'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
jest.mock('@/mailer/sendAccountActivationEmail', () => {
|
||||
@ -49,6 +50,14 @@ jest.mock('@/mailer/sendAccountActivationEmail', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
jest.mock('@/mailer/sendContributionConfirmedEmail', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
sendContributionConfirmedEmail: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
let mutate: any, query: any, con: any
|
||||
let testEnv: any
|
||||
|
||||
@ -1450,6 +1459,20 @@ describe('AdminResolver', () => {
|
||||
expect(transaction[0].linkedUserId).toEqual(null)
|
||||
expect(transaction[0].typeId).toEqual(1)
|
||||
})
|
||||
|
||||
it('calls sendContributionConfirmedEmail', async () => {
|
||||
expect(sendContributionConfirmedEmail).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
contributionMemo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
overviewURL: 'http://localhost/overview',
|
||||
recipientEmail: 'bibi@bloxberg.de',
|
||||
recipientFirstName: 'Bibi',
|
||||
recipientLastName: 'Bloxberg',
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm two creations one after the other quickly', () => {
|
||||
|
||||
@ -66,6 +66,8 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
|
||||
import ContributionMessageArgs from '@arg/ContributionMessageArgs'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { ContributionMessage } from '@model/ContributionMessage'
|
||||
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
||||
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
|
||||
|
||||
// const EMAIL_OPT_IN_REGISTER = 1
|
||||
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
|
||||
@ -470,6 +472,16 @@ export class AdminResolver {
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info('creation commited successfuly.')
|
||||
sendContributionConfirmedEmail({
|
||||
senderFirstName: moderatorUser.firstName,
|
||||
senderLastName: moderatorUser.lastName,
|
||||
recipientFirstName: user.firstName,
|
||||
recipientLastName: user.lastName,
|
||||
recipientEmail: user.email,
|
||||
contributionMemo: contribution.memo,
|
||||
contributionAmount: contribution.amount,
|
||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||
})
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error(`Creation was not successful: ${e}`)
|
||||
@ -713,15 +725,22 @@ export class AdminResolver {
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
const contributionMessage = DbContributionMessage.create()
|
||||
try {
|
||||
const contribution = await Contribution.findOne({ id: contributionId })
|
||||
const contribution = await Contribution.findOne({
|
||||
where: { id: contributionId },
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!contribution) {
|
||||
throw new Error('Contribution not found')
|
||||
}
|
||||
if (contribution.userId === user.id) {
|
||||
throw new Error('Admin can not answer on own contribution')
|
||||
}
|
||||
contributionMessage.contributionId = contributionId
|
||||
contributionMessage.createdAt = new Date()
|
||||
contributionMessage.message = message
|
||||
contributionMessage.userId = user.id
|
||||
contributionMessage.type = ContributionMessageType.DIALOG
|
||||
contributionMessage.isModerator = true
|
||||
await queryRunner.manager.insert(DbContributionMessage, contributionMessage)
|
||||
|
||||
if (
|
||||
@ -733,6 +752,18 @@ export class AdminResolver {
|
||||
await queryRunner.manager.update(Contribution, { id: contributionId }, contribution)
|
||||
}
|
||||
await queryRunner.commitTransaction()
|
||||
|
||||
await sendAddedContributionMessageEmail({
|
||||
senderFirstName: user.firstName,
|
||||
senderLastName: user.lastName,
|
||||
recipientFirstName: contribution.user.firstName,
|
||||
recipientLastName: contribution.user.lastName,
|
||||
recipientEmail: contribution.user.email,
|
||||
senderEmail: user.email,
|
||||
contributionMemo: contribution.memo,
|
||||
message,
|
||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||
})
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error(`ContributionMessage was not successful: ${e}`)
|
||||
|
||||
@ -12,6 +12,14 @@ import { listContributionMessages, login } from '@/seeds/graphql/queries'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
|
||||
|
||||
jest.mock('@/mailer/sendAddedContributionMessageEmail', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
sendAddedContributionMessageEmail: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
let mutate: any, query: any, con: any
|
||||
let testEnv: any
|
||||
@ -93,6 +101,38 @@ describe('ContributionMessageResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('throws error when contribution.userId equals user.id', async () => {
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
const result2 = await mutate({
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
amount: 100.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: result2.data.createContribution.id,
|
||||
message: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'ContributionMessage was not successful: Error: Admin can not answer on own contribution',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid input', () => {
|
||||
@ -119,6 +159,20 @@ describe('ContributionMessageResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls sendAddedContributionMessageEmail', async () => {
|
||||
expect(sendAddedContributionMessageEmail).toBeCalledWith({
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
recipientFirstName: 'Bibi',
|
||||
recipientLastName: 'Bloxberg',
|
||||
recipientEmail: 'bibi@bloxberg.de',
|
||||
senderEmail: 'peter@lustig.de',
|
||||
contributionMemo: 'Test env contribution',
|
||||
message: 'Admin Test',
|
||||
overviewURL: 'http://localhost/overview',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -39,6 +39,7 @@ export class ContributionMessageResolver {
|
||||
contributionMessage.message = message
|
||||
contributionMessage.userId = user.id
|
||||
contributionMessage.type = ContributionMessageType.DIALOG
|
||||
contributionMessage.isModerator = false
|
||||
await queryRunner.manager.insert(DbContributionMessage, contributionMessage)
|
||||
|
||||
if (contribution.contributionStatus === ContributionStatus.IN_PROGRESS) {
|
||||
|
||||
@ -7,49 +7,48 @@ import { getConnection } from '@dbTools/typeorm'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@Resolver()
|
||||
export class StatisticsResolver {
|
||||
@Authorized([RIGHTS.COMMUNITY_STATISTICS])
|
||||
@Query(() => CommunityStatistics)
|
||||
async communityStatistics(): Promise<CommunityStatistics> {
|
||||
const allUsers = await DbUser.find({ withDeleted: true })
|
||||
|
||||
let totalUsers = 0
|
||||
let activeUsers = 0
|
||||
let deletedUsers = 0
|
||||
const allUsers = await DbUser.count({ withDeleted: true })
|
||||
const totalUsers = await DbUser.count()
|
||||
const deletedUsers = allUsers - totalUsers
|
||||
|
||||
let totalGradidoAvailable: Decimal = new Decimal(0)
|
||||
let totalGradidoUnbookedDecayed: Decimal = new Decimal(0)
|
||||
|
||||
const receivedCallDate = new Date()
|
||||
|
||||
for (let i = 0; i < allUsers.length; i++) {
|
||||
if (allUsers[i].deletedAt) {
|
||||
deletedUsers++
|
||||
} else {
|
||||
totalUsers++
|
||||
const lastTransaction = await DbTransaction.findOne({
|
||||
where: { userId: allUsers[i].id },
|
||||
order: { balanceDate: 'DESC' },
|
||||
})
|
||||
if (lastTransaction) {
|
||||
activeUsers++
|
||||
const decay = calculateDecay(
|
||||
lastTransaction.balance,
|
||||
lastTransaction.balanceDate,
|
||||
receivedCallDate,
|
||||
)
|
||||
if (decay) {
|
||||
totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString())
|
||||
totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
|
||||
const lastUserTransactions = await queryRunner.manager
|
||||
.createQueryBuilder(DbUser, 'user')
|
||||
.select('transaction.balance', 'balance')
|
||||
.addSelect('transaction.balance_date', 'balanceDate')
|
||||
.innerJoin(DbTransaction, 'transaction', 'user.id = transaction.user_id')
|
||||
.where(
|
||||
`transaction.balance_date = (SELECT MAX(t.balance_date) FROM transactions AS t WHERE t.user_id = user.id)`,
|
||||
)
|
||||
.orderBy('transaction.balance_date', 'DESC')
|
||||
.addOrderBy('transaction.id', 'DESC')
|
||||
.getRawMany()
|
||||
|
||||
const activeUsers = lastUserTransactions.length
|
||||
|
||||
lastUserTransactions.forEach(({ balance, balanceDate }) => {
|
||||
const decay = calculateDecay(new Decimal(balance), new Date(balanceDate), receivedCallDate)
|
||||
if (decay) {
|
||||
totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString())
|
||||
totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString())
|
||||
}
|
||||
})
|
||||
|
||||
const { totalGradidoCreated } = await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('SUM(transaction.amount) AS totalGradidoCreated')
|
||||
|
||||
@ -35,6 +35,7 @@ import Decimal from 'decimal.js-light'
|
||||
|
||||
import { BalanceResolver } from './BalanceResolver'
|
||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||
import { sendTransactionLinkRedeemedEmail } from '@/mailer/sendTransactionLinkRedeemed'
|
||||
|
||||
export const executeTransaction = async (
|
||||
amount: Decimal,
|
||||
@ -151,9 +152,21 @@ export const executeTransaction = async (
|
||||
email: recipient.email,
|
||||
senderEmail: sender.email,
|
||||
amount,
|
||||
memo,
|
||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||
})
|
||||
if (transactionLink) {
|
||||
await sendTransactionLinkRedeemedEmail({
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
recipientFirstName: sender.firstName,
|
||||
recipientLastName: sender.lastName,
|
||||
email: sender.email,
|
||||
senderEmail: recipient.email,
|
||||
amount,
|
||||
memo,
|
||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||
})
|
||||
}
|
||||
logger.info(`finished executeTransaction successfully`)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ import { contributionLinkFactory } from '@/seeds/factory/contributionLink'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
// 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'
|
||||
@ -169,6 +171,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', () => {
|
||||
@ -245,18 +256,26 @@ describe('UserResolver', () => {
|
||||
mutation: setPassword,
|
||||
variables: { code: emailOptIn, 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({
|
||||
mutation: createUser,
|
||||
@ -271,6 +290,15 @@ 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,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/* A transaction link requires GDD on account
|
||||
@ -383,6 +411,10 @@ bei Gradidio sei dabei!`,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Password entered is lexically invalid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('no valid optin code', () => {
|
||||
@ -405,6 +437,10 @@ bei Gradidio sei dabei!`,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith('Could not login with emailVerificationCode')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -433,6 +469,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', () => {
|
||||
@ -475,6 +515,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 () => {
|
||||
@ -482,14 +523,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.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -562,6 +605,8 @@ bei Gradidio sei dabei!`,
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
let user: User[]
|
||||
|
||||
const variables = {
|
||||
email: 'bibi@bloxberg.de',
|
||||
password: 'Aa12345_',
|
||||
@ -569,6 +614,7 @@ bei Gradidio sei dabei!`,
|
||||
|
||||
beforeAll(async () => {
|
||||
await query({ query: login, variables })
|
||||
user = await User.find()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -595,6 +641,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,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -649,13 +704,17 @@ bei Gradidio sei dabei!`,
|
||||
})
|
||||
|
||||
describe('request reset password again', () => {
|
||||
it('thows an error', async () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('email already sent less than 10 minutes minutes ago')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`email already sent less than 10 minutes minutes ago`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -766,7 +825,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,
|
||||
@ -780,6 +839,10 @@ bei Gradidio sei dabei!`,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`"not-valid" isn't a valid language`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('password', () => {
|
||||
@ -799,6 +862,10 @@ bei Gradidio sei dabei!`,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`Old password is invalid`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid new password', () => {
|
||||
@ -821,6 +888,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', () => {
|
||||
@ -840,7 +911,7 @@ bei Gradidio sei dabei!`,
|
||||
)
|
||||
})
|
||||
|
||||
it('can login wtih new password', async () => {
|
||||
it('can login with new password', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: login,
|
||||
@ -860,7 +931,7 @@ bei Gradidio sei dabei!`,
|
||||
)
|
||||
})
|
||||
|
||||
it('cannot login wtih old password', async () => {
|
||||
it('cannot login with old password', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: login,
|
||||
@ -875,6 +946,10 @@ bei Gradidio sei dabei!`,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
EventRedeemRegister,
|
||||
EventRegister,
|
||||
EventSendConfirmationEmail,
|
||||
EventActivateAccount,
|
||||
} from '@/event/Event'
|
||||
import { getUserCreation } from './util/creations'
|
||||
import { UserRepository } from '@/typeorm/repository/User'
|
||||
@ -273,7 +274,7 @@ export class UserResolver {
|
||||
logger.info(`login with ${email}, ***, ${publisherId} ...`)
|
||||
email = email.trim().toLowerCase()
|
||||
const dbUser = await DbUser.findOneOrFail({ email }, { withDeleted: true }).catch(() => {
|
||||
logger.error(`User with email=${email} does not exists`)
|
||||
logger.error(`User with email=${email} does not exist`)
|
||||
throw new Error('No user with this credentials')
|
||||
})
|
||||
if (dbUser.deletedAt) {
|
||||
@ -389,7 +390,7 @@ export class UserResolver {
|
||||
/* uncomment this, when you need the activation link on the console */
|
||||
// In case EMails are disabled log the activation link for the user
|
||||
if (!emailSent) {
|
||||
logger.debug(`Email not send!`)
|
||||
logger.debug(`Email not sent!`)
|
||||
}
|
||||
logger.info('createUser() faked and send multi registration mail...')
|
||||
|
||||
@ -548,6 +549,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!',
|
||||
)
|
||||
@ -610,6 +612,8 @@ export class UserResolver {
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
|
||||
const event = new Event()
|
||||
|
||||
try {
|
||||
// Save user
|
||||
await queryRunner.manager.save(user).catch((error) => {
|
||||
@ -618,6 +622,11 @@ export class UserResolver {
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
|
||||
const eventActivateAccount = new EventActivateAccount()
|
||||
eventActivateAccount.userId = user.id
|
||||
eventProtocol.writeEvent(event.setEventActivateAccount(eventActivateAccount))
|
||||
|
||||
logger.info('User data written successfully...')
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
@ -727,6 +736,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)
|
||||
})
|
||||
|
||||
|
||||
40
backend/src/mailer/sendAddedContributionMessageEmail.test.ts
Normal file
40
backend/src/mailer/sendAddedContributionMessageEmail.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { sendAddedContributionMessageEmail } from './sendAddedContributionMessageEmail'
|
||||
import { sendEMail } from './sendEMail'
|
||||
|
||||
jest.mock('./sendEMail', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
sendEMail: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('sendAddedContributionMessageEmail', () => {
|
||||
beforeEach(async () => {
|
||||
await sendAddedContributionMessageEmail({
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
recipientFirstName: 'Bibi',
|
||||
recipientLastName: 'Bloxberg',
|
||||
recipientEmail: 'bibi@bloxberg.de',
|
||||
senderEmail: 'peter@lustig.de',
|
||||
contributionMemo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
|
||||
message: 'Was für ein Besen ist es geworden?',
|
||||
overviewURL: 'http://localhost/overview',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls sendEMail', () => {
|
||||
expect(sendEMail).toBeCalledWith({
|
||||
to: `Bibi Bloxberg <bibi@bloxberg.de>`,
|
||||
subject: 'Gradido Frage zur Schöpfung',
|
||||
text:
|
||||
expect.stringContaining('Hallo Bibi Bloxberg') &&
|
||||
expect.stringContaining('Peter Lustig') &&
|
||||
expect.stringContaining(
|
||||
'Du hast soeben zu deinem eingereichten Gradido Schöpfungsantrag "Vielen herzlichen Dank für den neuen Hexenbesen!" eine Rückfrage von Peter Lustig erhalten.',
|
||||
) &&
|
||||
expect.stringContaining('Was für ein Besen ist es geworden?') &&
|
||||
expect.stringContaining('http://localhost/overview'),
|
||||
})
|
||||
})
|
||||
})
|
||||
26
backend/src/mailer/sendAddedContributionMessageEmail.ts
Normal file
26
backend/src/mailer/sendAddedContributionMessageEmail.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { sendEMail } from './sendEMail'
|
||||
import { contributionMessageReceived } from './text/contributionMessageReceived'
|
||||
|
||||
export const sendAddedContributionMessageEmail = (data: {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
recipientFirstName: string
|
||||
recipientLastName: string
|
||||
recipientEmail: string
|
||||
senderEmail: string
|
||||
contributionMemo: string
|
||||
message: string
|
||||
overviewURL: string
|
||||
}): Promise<boolean> => {
|
||||
logger.info(
|
||||
`sendEmail(): to=${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>,
|
||||
subject=${contributionMessageReceived.de.subject},
|
||||
text=${contributionMessageReceived.de.text(data)}`,
|
||||
)
|
||||
return sendEMail({
|
||||
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>`,
|
||||
subject: contributionMessageReceived.de.subject,
|
||||
text: contributionMessageReceived.de.text(data),
|
||||
})
|
||||
}
|
||||
39
backend/src/mailer/sendContributionConfirmedEmail.test.ts
Normal file
39
backend/src/mailer/sendContributionConfirmedEmail.test.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { sendContributionConfirmedEmail } from './sendContributionConfirmedEmail'
|
||||
import { sendEMail } from './sendEMail'
|
||||
|
||||
jest.mock('./sendEMail', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
sendEMail: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('sendContributionConfirmedEmail', () => {
|
||||
beforeEach(async () => {
|
||||
await sendContributionConfirmedEmail({
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
recipientFirstName: 'Bibi',
|
||||
recipientLastName: 'Bloxberg',
|
||||
recipientEmail: 'bibi@bloxberg.de',
|
||||
contributionMemo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
|
||||
contributionAmount: new Decimal(200.0),
|
||||
overviewURL: 'http://localhost/overview',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls sendEMail', () => {
|
||||
expect(sendEMail).toBeCalledWith({
|
||||
to: 'Bibi Bloxberg <bibi@bloxberg.de>',
|
||||
subject: 'Schöpfung wurde bestätigt',
|
||||
text:
|
||||
expect.stringContaining('Hallo Bibi Bloxberg') &&
|
||||
expect.stringContaining(
|
||||
'Dein Gradido Schöpfungsantrag "Vielen herzlichen Dank für den neuen Hexenbesen!" wurde soeben bestätigt.',
|
||||
) &&
|
||||
expect.stringContaining('Betrag: 200,00 GDD') &&
|
||||
expect.stringContaining('Link zu deinem Konto: http://localhost/overview'),
|
||||
})
|
||||
})
|
||||
})
|
||||
26
backend/src/mailer/sendContributionConfirmedEmail.ts
Normal file
26
backend/src/mailer/sendContributionConfirmedEmail.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { sendEMail } from './sendEMail'
|
||||
import { contributionConfirmed } from './text/contributionConfirmed'
|
||||
|
||||
export const sendContributionConfirmedEmail = (data: {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
recipientFirstName: string
|
||||
recipientLastName: string
|
||||
recipientEmail: string
|
||||
contributionMemo: string
|
||||
contributionAmount: Decimal
|
||||
overviewURL: string
|
||||
}): Promise<boolean> => {
|
||||
logger.info(
|
||||
`sendEmail(): to=${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>,
|
||||
subject=${contributionConfirmed.de.subject},
|
||||
text=${contributionConfirmed.de.text(data)}`,
|
||||
)
|
||||
return sendEMail({
|
||||
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>`,
|
||||
subject: contributionConfirmed.de.subject,
|
||||
text: contributionConfirmed.de.text(data),
|
||||
})
|
||||
}
|
||||
44
backend/src/mailer/sendTransactionLinkRedeemed.test.ts
Normal file
44
backend/src/mailer/sendTransactionLinkRedeemed.test.ts
Normal 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!'),
|
||||
})
|
||||
})
|
||||
})
|
||||
28
backend/src/mailer/sendTransactionLinkRedeemed.ts
Normal file
28
backend/src/mailer/sendTransactionLinkRedeemed.ts
Normal 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),
|
||||
})
|
||||
}
|
||||
@ -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'),
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,7 +11,6 @@ export const sendTransactionReceivedEmail = (data: {
|
||||
email: string
|
||||
senderEmail: string
|
||||
amount: Decimal
|
||||
memo: string
|
||||
overviewURL: string
|
||||
}): Promise<boolean> => {
|
||||
logger.info(
|
||||
|
||||
31
backend/src/mailer/text/contributionConfirmed.ts
Normal file
31
backend/src/mailer/text/contributionConfirmed.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
export const contributionConfirmed = {
|
||||
de: {
|
||||
subject: 'Schöpfung wurde bestätigt',
|
||||
text: (data: {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
recipientFirstName: string
|
||||
recipientLastName: string
|
||||
contributionMemo: string
|
||||
contributionAmount: Decimal
|
||||
overviewURL: string
|
||||
}): string =>
|
||||
`Hallo ${data.recipientFirstName} ${data.recipientLastName},
|
||||
|
||||
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!
|
||||
|
||||
Mit freundlichen Grüßen,
|
||||
dein Gradido-Team
|
||||
|
||||
|
||||
Link zu deinem Konto: ${data.overviewURL}`,
|
||||
},
|
||||
}
|
||||
28
backend/src/mailer/text/contributionMessageReceived.ts
Normal file
28
backend/src/mailer/text/contributionMessageReceived.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export const contributionMessageReceived = {
|
||||
de: {
|
||||
subject: 'Gradido Frage zur Schöpfung',
|
||||
text: (data: {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
recipientFirstName: string
|
||||
recipientLastName: string
|
||||
recipientEmail: string
|
||||
senderEmail: string
|
||||
contributionMemo: string
|
||||
message: string
|
||||
overviewURL: string
|
||||
}): string =>
|
||||
`Hallo ${data.recipientFirstName} ${data.recipientLastName},
|
||||
|
||||
du hast soeben zu deinem eingereichten Gemeinwohl-Beitrag "${data.contributionMemo}" eine Rückfrage von ${data.senderFirstName} ${data.senderLastName} erhalten.
|
||||
|
||||
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`,
|
||||
},
|
||||
}
|
||||
33
backend/src/mailer/text/transactionLinkRedeemed.ts
Normal file
33
backend/src/mailer/text/transactionLinkRedeemed.ts
Normal 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`,
|
||||
},
|
||||
}
|
||||
@ -11,7 +11,6 @@ export const transactionReceived = {
|
||||
email: string
|
||||
senderEmail: string
|
||||
amount: Decimal
|
||||
memo: string
|
||||
overviewURL: string
|
||||
}): string =>
|
||||
`Hallo ${data.recipientFirstName} ${data.recipientLastName}
|
||||
@ -19,16 +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}`,
|
||||
dein Gradido-Team`,
|
||||
},
|
||||
}
|
||||
|
||||
28
backend/src/util/klicktipp.ts
Normal file
28
backend/src/util/klicktipp.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import connection from '@/typeorm/connection'
|
||||
import { getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
export async function retrieveNotRegisteredEmails(): Promise<string[]> {
|
||||
const con = await connection()
|
||||
if (!con) {
|
||||
throw new Error('No connection to database')
|
||||
}
|
||||
const users = await User.find()
|
||||
const notRegisteredUser = []
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = users[i]
|
||||
try {
|
||||
await getKlickTippUser(user.email)
|
||||
} catch (err) {
|
||||
notRegisteredUser.push(user.email)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${user.email}`)
|
||||
}
|
||||
}
|
||||
await con.close()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('User die nicht bei KlickTipp vorhanden sind: ', notRegisteredUser)
|
||||
return notRegisteredUser
|
||||
}
|
||||
|
||||
retrieveNotRegisteredEmails()
|
||||
@ -0,0 +1,54 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from '../Contribution'
|
||||
import { User } from '../User'
|
||||
|
||||
@Entity('contribution_messages', {
|
||||
engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
})
|
||||
export class ContributionMessage extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'contribution_id', unsigned: true, nullable: false })
|
||||
contributionId: number
|
||||
|
||||
@ManyToOne(() => Contribution, (contribution) => contribution.messages)
|
||||
@JoinColumn({ name: 'contribution_id' })
|
||||
contribution: Contribution
|
||||
|
||||
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||
userId: number
|
||||
|
||||
@ManyToOne(() => User, (user) => user.messages)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: User
|
||||
|
||||
@Column({ length: 2000, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
message: string
|
||||
|
||||
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ type: 'datetime', default: null, nullable: true, name: 'updated_at' })
|
||||
updatedAt: Date
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at' })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Column({ name: 'deleted_by', default: null, unsigned: true, nullable: true })
|
||||
deletedBy: number
|
||||
|
||||
@Column({ length: 12, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
type: string
|
||||
|
||||
@Column({ name: 'is_moderator', type: 'bool', nullable: false, default: false })
|
||||
isModerator: boolean
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { ContributionMessage } from './0047-messages_tables/ContributionMessage'
|
||||
export { ContributionMessage } from './0048-add_is_moderator_to_contribution_messages/ContributionMessage'
|
||||
|
||||
@ -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 \`contribution_messages\` ADD COLUMN \`is_moderator\` boolean NOT NULL DEFAULT false;`,
|
||||
)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`ALTER TABLE \`contribution_messages\` DROP COLUMN \`is_moderator\`;`)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
|
||||
@ -61,7 +61,7 @@ EVENT_PROTOCOL_DISABLED=false
|
||||
DATABASE_CONFIG_VERSION=v1.2022-03-18
|
||||
|
||||
# frontend
|
||||
FRONTEND_CONFIG_VERSION=v2.2022-04-07
|
||||
FRONTEND_CONFIG_VERSION=v3.2022-09-16
|
||||
|
||||
GRAPHQL_URI=https://stage1.gradido.net/graphql
|
||||
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
|
||||
@ -77,6 +77,8 @@ META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natü
|
||||
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||
|
||||
SUPPORT_MAIL=support@supportmail.com
|
||||
|
||||
# admin
|
||||
ADMIN_CONFIG_VERSION=v1.2022-03-18
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ pm2 startup
|
||||
sudo apt-get install -y certbot
|
||||
sudo apt-get install -y python3-certbot-nginx
|
||||
sudo certbot
|
||||
> Enter email address (used for urgent renewal and security notices) > support@gradido.net
|
||||
> Enter email address (used for urgent renewal and security notices) > e.g. support@supportmail.com
|
||||
> Please read the Terms of Service at > Y
|
||||
> Would you be willing, once your first certificate is successfully issued, to > N
|
||||
> No names were found in your configuration files. Please enter in your domain > stage1.gradido.net
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
CONFIG_VERSION=v2.2022-04-07
|
||||
CONFIG_VERSION=v3.2022-09-16
|
||||
|
||||
# Environment
|
||||
DEFAULT_PUBLISHER_ID=2896
|
||||
@ -21,4 +21,7 @@ META_DESCRIPTION_DE="Dankbarkeit ist die Währung der neuen Zeit. Immer mehr Men
|
||||
META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more people are unleashing their potential and shaping a good future for all."
|
||||
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
|
||||
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||
|
||||
# Support Mail
|
||||
SUPPORT_MAIL=support@supportmail.com
|
||||
@ -21,4 +21,7 @@ META_DESCRIPTION_DE=$META_DESCRIPTION_DE
|
||||
META_DESCRIPTION_EN=$META_DESCRIPTION_EN
|
||||
META_KEYWORDS_DE=$META_KEYWORDS_DE
|
||||
META_KEYWORDS_EN=$META_KEYWORDS_EN
|
||||
META_AUTHOR=$META_AUTHOR
|
||||
META_AUTHOR=$META_AUTHOR
|
||||
|
||||
# Support Mail
|
||||
SUPPORT_MAIL=$SUPPORT_MAIL
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary">{{ $t('form.reply') }}</b-button>
|
||||
<b-button type="submit" variant="primary" :disabled="disabled">
|
||||
{{ $t('form.reply') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
@ -63,5 +65,13 @@ export default {
|
||||
this.form.text = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
if (this.form.text !== '') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
</b-container>
|
||||
<contribution-messages-formular
|
||||
v-if="['PENDING', 'IN_PROGRESS'].includes(state)"
|
||||
class="mt-5"
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('community.moderator') }}</small>
|
||||
<div class="mt-2 h3">{{ message.message }}</div>
|
||||
<div class="mt-2">{{ message.message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@ -2,11 +2,9 @@
|
||||
<div class="slot-is-not-moderator">
|
||||
<div class="text-right">
|
||||
<b-avatar :text="initialLetters" variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2 text-bold">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<div class="mt-2 h3">{{ message.message }}</div>
|
||||
<div class="mt-2">{{ message.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
<contribution-list-item
|
||||
v-bind="item"
|
||||
:contributionId="item.id"
|
||||
:allContribution="allContribution"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
@ -44,6 +45,11 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
pageSize: { type: Number, default: 25 },
|
||||
allContribution: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -27,12 +27,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="mr-2">{{ memo }}</div>
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !firstName"
|
||||
class="d-flex flex-row-reverse"
|
||||
>
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state)"
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution"
|
||||
class="pointer ml-5"
|
||||
@click="
|
||||
$emit('update-contribution-form', {
|
||||
@ -46,7 +43,7 @@
|
||||
<b-icon icon="pencil" class="h2"></b-icon>
|
||||
</div>
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state)"
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution"
|
||||
class="pointer"
|
||||
@click="deleteContribution({ id })"
|
||||
>
|
||||
@ -144,6 +141,11 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
allContribution: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -45,7 +45,7 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('with locales en, de and es', () => {
|
||||
describe('with locales en, de, es, fr, and nl', () => {
|
||||
describe('empty store', () => {
|
||||
describe('navigator language is "en-US"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
@ -94,11 +94,11 @@ describe('LanguageSwitch', () => {
|
||||
describe('navigator language is "nl-NL"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows Dutch as language ', async () => {
|
||||
it('shows Nederlands as language ', async () => {
|
||||
languageGetter.mockReturnValue('nl-NL')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Holandés - nl')
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Nederlands - nl')
|
||||
})
|
||||
})
|
||||
|
||||
@ -153,16 +153,16 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
|
||||
describe('language "nl" in store', () => {
|
||||
it('shows Dutch as language', async () => {
|
||||
it('shows Nederlands as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'nl'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Holandés - nl')
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Nederlands - nl')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dropdown menu', () => {
|
||||
it('has English and German as languages to choose', () => {
|
||||
it('has five languages to choose from', () => {
|
||||
expect(wrapper.findAll('li')).toHaveLength(5)
|
||||
})
|
||||
|
||||
@ -174,16 +174,16 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('li').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
|
||||
it('has Español as second language to choose', () => {
|
||||
it('has Español as third language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(2).text()).toBe('Español')
|
||||
})
|
||||
|
||||
it('has French as second language to choose', () => {
|
||||
it('has French as fourth language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(3).text()).toBe('Français')
|
||||
})
|
||||
|
||||
it('has Dutch as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(4).text()).toBe('Holandés')
|
||||
it('has Nederlands as fith language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(4).text()).toBe('Nederlands')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -46,10 +46,11 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.find('div.language-switch').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('with locales en and de', () => {
|
||||
describe('with locales en, de, es, fr, and nl', () => {
|
||||
describe('empty store', () => {
|
||||
describe('navigator language is "en-US"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows English as default navigator langauge', async () => {
|
||||
languageGetter.mockReturnValue('en-US')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -57,8 +58,10 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "de-DE"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows Deutsch as language ', async () => {
|
||||
languageGetter.mockReturnValue('de-DE')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -66,8 +69,10 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "es-ES"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows Español as language ', async () => {
|
||||
languageGetter.mockReturnValue('es-ES')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -75,8 +80,10 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "fr-FR"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows French as language ', async () => {
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -84,17 +91,21 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Français')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "nl-NL"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows Dutch as language ', async () => {
|
||||
|
||||
it('shows Nederlands as language ', async () => {
|
||||
languageGetter.mockReturnValue('nl-NL')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Holandés')
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Nederlands')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "it-IT" (not supported)', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue('it-IT')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -102,8 +113,10 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
})
|
||||
})
|
||||
|
||||
describe('no navigator langauge', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue(null)
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
@ -112,6 +125,7 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "de" in store', () => {
|
||||
it('shows Deutsch as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'de'
|
||||
@ -120,6 +134,7 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('English')
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "es" in store', () => {
|
||||
it('shows Español as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'es'
|
||||
@ -128,6 +143,7 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Deutsch')
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "fr" in store', () => {
|
||||
it('shows French as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'fr'
|
||||
@ -136,43 +152,77 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "nl" in store', () => {
|
||||
it('shows Dutch as language', async () => {
|
||||
it('shows Nederlands as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'nl'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Français')
|
||||
})
|
||||
})
|
||||
|
||||
describe('language menu', () => {
|
||||
it('has English, German and Español as languages to choose', () => {
|
||||
beforeAll(async () => {
|
||||
wrapper.vm.$store.state.language = 'en'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('has five languages to choose from', () => {
|
||||
expect(wrapper.findAll('span.locales')).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('has English as first language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('Holandés')
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
})
|
||||
it('has German as second language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('English')
|
||||
|
||||
it('has Deutsch as second language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
|
||||
it('has Español as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Deutsch')
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
it('has French as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Español')
|
||||
|
||||
it('has Français as fourth language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(3).text()).toBe('Français')
|
||||
})
|
||||
it('has Dutch as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Français')
|
||||
|
||||
it('has Nederlands as fifth language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(4).text()).toBe('Nederlands')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('calls the API', () => {
|
||||
it("with locale 'de'", () => {
|
||||
wrapper.findAll('span.locales').at(2).trigger('click')
|
||||
wrapper.findAll('span.locales').at(1).trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({ variables: { locale: 'de' } }),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'es'", () => {
|
||||
wrapper.findAll('span.locales').at(2).trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({ variables: { locale: 'es' } }),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'fr'", () => {
|
||||
wrapper.findAll('span.locales').at(3).trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({ variables: { locale: 'fr' } }),
|
||||
)
|
||||
})
|
||||
|
||||
it("with locale 'nl'", () => {
|
||||
wrapper.findAll('span.locales').at(4).trigger('click')
|
||||
expect(updateUserInfosMutationMock).toBeCalledWith(
|
||||
expect.objectContaining({ variables: { locale: 'nl' } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@ const constants = {
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v2.2022-04-07',
|
||||
EXPECTED: 'v3.2022-09-16',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
@ -60,6 +60,10 @@ const meta = {
|
||||
META_AUTHOR: process.env.META_AUTHOR || 'Bernd Hückstädt - Gradido-Akademie',
|
||||
}
|
||||
|
||||
const supportmail = {
|
||||
SUPPORT_MAIL: process.env.SUPPORT_MAIL || 'support@supportmail.com',
|
||||
}
|
||||
|
||||
// Check config version
|
||||
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
||||
if (
|
||||
@ -79,6 +83,7 @@ const CONFIG = {
|
||||
...endpoints,
|
||||
...community,
|
||||
...meta,
|
||||
...supportmail,
|
||||
}
|
||||
|
||||
module.exports = CONFIG
|
||||
|
||||
@ -253,7 +253,7 @@
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Dutch",
|
||||
"nl": "Nederlands",
|
||||
"success": "Deine Sprache wurde erfolgreich geändert."
|
||||
},
|
||||
"name": {
|
||||
|
||||
@ -253,7 +253,7 @@
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Holandés",
|
||||
"nl": "Nederlands",
|
||||
"success": "Your language has been successfully updated."
|
||||
},
|
||||
"name": {
|
||||
|
||||
@ -254,8 +254,8 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Francés",
|
||||
"nl": "Holandés",
|
||||
"fr": "Français",
|
||||
"nl": "Nederlands",
|
||||
"success": "Tu idioma ha sido cambiado con éxito."
|
||||
},
|
||||
"name": {
|
||||
|
||||
@ -251,11 +251,11 @@
|
||||
"settings": {
|
||||
"language": {
|
||||
"changeLanguage": "Changer la langue",
|
||||
"de": "Allemand",
|
||||
"en": "Anglais",
|
||||
"es": "Espagnol",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Néerlandais",
|
||||
"nl": "Nederlands",
|
||||
"success": "Votre langue de préférence a bien été actualisée."
|
||||
},
|
||||
"name": {
|
||||
|
||||
@ -24,7 +24,7 @@ const locales = [
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'Holandés',
|
||||
name: 'Nederlands',
|
||||
code: 'nl',
|
||||
iso: 'nl-NL',
|
||||
enabled: true,
|
||||
|
||||
@ -251,10 +251,10 @@
|
||||
"settings": {
|
||||
"language": {
|
||||
"changeLanguage": "Taal veranderen",
|
||||
"de": "Duits",
|
||||
"en": "Engels",
|
||||
"es": "Spaans",
|
||||
"fr": "Frans",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"nl": "Nederlands",
|
||||
"success": "Jouw taal werd succesvol veranderd."
|
||||
},
|
||||
|
||||
@ -73,6 +73,7 @@
|
||||
:contributionCount="contributionCountAll"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSizeAll"
|
||||
:allContribution="true"
|
||||
/>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
|
||||
@ -83,7 +83,7 @@ export default {
|
||||
countAdminUser: null,
|
||||
itemsContributionLinks: [],
|
||||
itemsAdminUser: [],
|
||||
supportMail: 'support@supportemail.de',
|
||||
supportMail: CONFIG.SUPPORT_MAIL,
|
||||
membersCount: '1203',
|
||||
totalUsers: null,
|
||||
totalGradidoCreated: null,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user