mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch 'master' into cypress-reporting
This commit is contained in:
commit
672b2e5fb8
70
CHANGELOG.md
70
CHANGELOG.md
@ -4,8 +4,78 @@ 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.22.1](https://github.com/gradido/gradido/compare/1.22.0...1.22.1)
|
||||
|
||||
- fix(backend): use base url from config in email templates [`#3114`](https://github.com/gradido/gradido/pull/3114)
|
||||
- feat(frontend): test right side layout template [`#3052`](https://github.com/gradido/gradido/pull/3052)
|
||||
- feat(backend): remove iota from backend [`#3115`](https://github.com/gradido/gradido/pull/3115)
|
||||
- refactor(frontend): text juni to juli [`#3116`](https://github.com/gradido/gradido/pull/3116)
|
||||
- refactor(frontend): refactor changes incorporated [`#3113`](https://github.com/gradido/gradido/pull/3113)
|
||||
- refactor(frontend): date from deploy changed to infotext [`#3108`](https://github.com/gradido/gradido/pull/3108)
|
||||
- fix(frontend): add alias to the verifyLogin GraphQL answer. [`#3107`](https://github.com/gradido/gradido/pull/3107)
|
||||
- fix(backend): moderator message don't send email to user [`#3106`](https://github.com/gradido/gradido/pull/3106)
|
||||
|
||||
#### [1.22.0](https://github.com/gradido/gradido/compare/1.21.0...1.22.0)
|
||||
|
||||
> 30 June 2023
|
||||
|
||||
- chore(release): v1.22.0 [`#3101`](https://github.com/gradido/gradido/pull/3101)
|
||||
- fix(backend): yarn.lock after typeorm update [`#3097`](https://github.com/gradido/gradido/pull/3097)
|
||||
- feat(frontend): new style for settings page [`#3040`](https://github.com/gradido/gradido/pull/3040)
|
||||
- feat(admin): query users on contributions [`#3094`](https://github.com/gradido/gradido/pull/3094)
|
||||
- feat(backend): user query on find contributions [`#3091`](https://github.com/gradido/gradido/pull/3091)
|
||||
- fix(backend): double redeem transaction link [`#3093`](https://github.com/gradido/gradido/pull/3093)
|
||||
- feat(admin): message type admin frontend [`#3073`](https://github.com/gradido/gradido/pull/3073)
|
||||
- feat(other): end-to-end test scenarios for deleted and not registered user [`#3077`](https://github.com/gradido/gradido/pull/3077)
|
||||
- feat(database): update typeorm [`#3078`](https://github.com/gradido/gradido/pull/3078)
|
||||
- refactor(other): disable cypress test retries [`#3092`](https://github.com/gradido/gradido/pull/3092)
|
||||
- test(other): update cypress [`#3056`](https://github.com/gradido/gradido/pull/3056)
|
||||
- feat(other): add definition of cron-job for klicktipp export. [`#3051`](https://github.com/gradido/gradido/pull/3051)
|
||||
- fix(backend): forget password not for deleted users [`#3090`](https://github.com/gradido/gradido/pull/3090)
|
||||
- fix(backend): gdt server error & gdt refetch policy [`#3086`](https://github.com/gradido/gradido/pull/3086)
|
||||
- feat(backend): contribution message type moderator [`#3072`](https://github.com/gradido/gradido/pull/3072)
|
||||
- fix(other): linting in e2e tests directory does not work [`#3089`](https://github.com/gradido/gradido/pull/3089)
|
||||
- feat(other): end-to-end test feature send coins [`#3070`](https://github.com/gradido/gradido/pull/3070)
|
||||
- refactor(dht): move typescript related packages in dev Dependencies [`#3085`](https://github.com/gradido/gradido/pull/3085)
|
||||
- fix(backend): jest environment [`#3084`](https://github.com/gradido/gradido/pull/3084)
|
||||
- feat(frontend): transaction list page change style for small device [`#3081`](https://github.com/gradido/gradido/pull/3081)
|
||||
- refactor(other): refactor state to status [`#3082`](https://github.com/gradido/gradido/pull/3082)
|
||||
- feat(admin): change deny contribution btn-warning color and background-color to #e1a908 [`#3080`](https://github.com/gradido/gradido/pull/3080)
|
||||
- feat(backend): bootstrap and Hello World Test [`#3041`](https://github.com/gradido/gradido/pull/3041)
|
||||
- feat(frontend): change the info text on the start page [`#3049`](https://github.com/gradido/gradido/pull/3049)
|
||||
- refactor(dht): eslint dht n [`#3045`](https://github.com/gradido/gradido/pull/3045)
|
||||
- refactor(dht): eslint dht comments [`#3046`](https://github.com/gradido/gradido/pull/3046)
|
||||
- fix(frontend): auto logout messages autohide time 5000 [`#2959`](https://github.com/gradido/gradido/pull/2959)
|
||||
- feat(federation): introduce private key in community table [`#3024`](https://github.com/gradido/gradido/pull/3024)
|
||||
- refactor(backend): removed klicktipp middleware and replace it with a function [`#3035`](https://github.com/gradido/gradido/pull/3035)
|
||||
- feat(admin): order user search desc, fix pagination [`#3059`](https://github.com/gradido/gradido/pull/3059)
|
||||
- refactor(database): eslint database eslint comments [`#3039`](https://github.com/gradido/gradido/pull/3039)
|
||||
- refactor(database): eslint database import [`#3038`](https://github.com/gradido/gradido/pull/3038)
|
||||
- feat(backend): apply design template to HTML e-mails [`#2938`](https://github.com/gradido/gradido/pull/2938)
|
||||
- refactor(database): eslint database n [`#3037`](https://github.com/gradido/gradido/pull/3037)
|
||||
- refactor(backend): sodium native with types [`#3032`](https://github.com/gradido/gradido/pull/3032)
|
||||
- refactor(federation): expose private key to write home community [`#3023`](https://github.com/gradido/gradido/pull/3023)
|
||||
- refactor(dht): eslint dht base configuration [`#3042`](https://github.com/gradido/gradido/pull/3042)
|
||||
- refactor(federation): eslint federation base configuration [`#3047`](https://github.com/gradido/gradido/pull/3047)
|
||||
- refactor(database): eslint database base configuration [`#3036`](https://github.com/gradido/gradido/pull/3036)
|
||||
- feat(workflow): dht as package for lint pr workflow [`#3043`](https://github.com/gradido/gradido/pull/3043)
|
||||
- refactor(federation): removed (potential) duplicate db query [`#3019`](https://github.com/gradido/gradido/pull/3019)
|
||||
- refactor(federation): remove function getSeed [`#3022`](https://github.com/gradido/gradido/pull/3022)
|
||||
- refactor(federation): refactor federation to use inheritance [`#2992`](https://github.com/gradido/gradido/pull/2992)
|
||||
- refactor(backend): random-bigint types [`#3033`](https://github.com/gradido/gradido/pull/3033)
|
||||
- fix(frontend): incorrect errormessage for wrong contribution link [`#2958`](https://github.com/gradido/gradido/pull/2958)
|
||||
- refactor(federation): remove export from CommunityApi [`#3021`](https://github.com/gradido/gradido/pull/3021)
|
||||
- refactor(federation): simplify newCommunityUuid [`#3020`](https://github.com/gradido/gradido/pull/3020)
|
||||
- refactor(database): remove duplicate comments [`#3025`](https://github.com/gradido/gradido/pull/3025)
|
||||
- feat(federation): x com 3 introduce business communities [`#2955`](https://github.com/gradido/gradido/pull/2955)
|
||||
- refactor(backend): remove to do after creating issue [`#3000`](https://github.com/gradido/gradido/pull/3000)
|
||||
- refactor(backend): camelcase exception for FederationClient_XX_X [`#3016`](https://github.com/gradido/gradido/pull/3016)
|
||||
|
||||
#### [1.21.0](https://github.com/gradido/gradido/compare/1.20.0...1.21.0)
|
||||
|
||||
> 19 May 2023
|
||||
|
||||
- chore(release): v1.21.0 [`#2998`](https://github.com/gradido/gradido/pull/2998)
|
||||
- feat(frontend): preserve email after login [`#2994`](https://github.com/gradido/gradido/pull/2994)
|
||||
- feat(frontend): send coins via identifier [`#2989`](https://github.com/gradido/gradido/pull/2989)
|
||||
- feat(backend): export user events to klicktipp [`#2916`](https://github.com/gradido/gradido/pull/2916)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.1",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesFormular from './ContributionMessagesFormular'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -34,6 +35,7 @@ describe('ContributionMessagesFormular', () => {
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-formular', () => {
|
||||
@ -73,13 +75,65 @@ describe('ContributionMessagesFormular', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
it('emitted "update-status" with data', async () => {
|
||||
expect(wrapper.emitted('update-status')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('send DIALOG contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-dialog"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `DIALOG`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'DIALOG',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send MODERATOR contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-moderator"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `MODERATOR`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'MODERATOR',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
@ -91,21 +145,5 @@ describe('ContributionMessagesFormular', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div class="mt-5">
|
||||
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
|
||||
<b-form @reset.prevent="onReset" @submit="onSubmit(messageType.DIALOG)">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
v-model="form.text"
|
||||
@ -12,8 +12,27 @@
|
||||
<b-col>
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-center">
|
||||
<b-button
|
||||
type="button"
|
||||
variant="warning"
|
||||
class="text-black"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.MODERATOR)"
|
||||
data-test="submit-moderator"
|
||||
>
|
||||
{{ $t('moderator.notice') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary" :disabled="disabled">
|
||||
<b-button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.DIALOG)"
|
||||
data-test="submit-dialog"
|
||||
>
|
||||
{{ $t('form.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
@ -39,10 +58,14 @@ export default {
|
||||
text: '',
|
||||
},
|
||||
loading: false,
|
||||
messageType: {
|
||||
DIALOG: 'DIALOG',
|
||||
MODERATOR: 'MODERATOR',
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
onSubmit(mType) {
|
||||
this.loading = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
@ -50,11 +73,12 @@ export default {
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
message: this.form.text,
|
||||
messageType: mType,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.$emit('update-status', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.request'))
|
||||
this.loading = false
|
||||
|
||||
@ -1,26 +1,102 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import { createMockClient } from 'mock-apollo-client'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const mockClient = createMockClient()
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: mockClient,
|
||||
})
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue()
|
||||
localVue.use(VueApollo)
|
||||
|
||||
const defaultData = () => {
|
||||
return {
|
||||
adminListContributionMessages: {
|
||||
count: 4,
|
||||
messages: [
|
||||
{
|
||||
id: 43,
|
||||
message: 'A DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
message: 'Another DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
message: `DATE
|
||||
---
|
||||
A HISTORY message
|
||||
---
|
||||
AMOUNT`,
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'HISTORY',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
message: 'A MODERATOR message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const adminListContributionMessagessMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
adminListContributionMessages,
|
||||
adminListContributionMessagessMock
|
||||
.mockRejectedValueOnce({ message: 'Auaa!' })
|
||||
.mockResolvedValue({ data: defaultData() }),
|
||||
)
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionState: 'PENDING',
|
||||
contributionUserId: 108,
|
||||
contributionStatus: 'PENDING',
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$n: jest.fn((n) => n),
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -28,30 +104,34 @@ describe('ContributionMessagesList', () => {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
apolloProvider,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('sends query to Apollo when created', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
contributionId: propsData.contributionId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
describe('server response for admin list contribution messages is error', () => {
|
||||
it('toast an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Auaa!')
|
||||
})
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
describe('server response is succes', () => {
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
it('has 4 messages', () => {
|
||||
expect(wrapper.findAll('div.contribution-messages-list-item')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
<contribution-messages-list-item
|
||||
:message="message"
|
||||
:contributionUserId="contributionUserId"
|
||||
/>
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<div v-if="contributionState === 'PENDING' || contributionState === 'IN_PROGRESS'">
|
||||
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
@get-list-contribution-messages="$apollo.queries.Messages.refetch()"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,7 +20,7 @@
|
||||
<script>
|
||||
import ContributionMessagesListItem from './slots/ContributionMessagesListItem'
|
||||
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular'
|
||||
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesList',
|
||||
@ -31,39 +33,43 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contributionState: {
|
||||
contributionStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getListContributionMessages(id) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionMessages,
|
||||
variables: {
|
||||
contributionId: id,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then((result) => {
|
||||
this.messages = result.data.listContributionMessages.messages
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
apollo: {
|
||||
Messages: {
|
||||
query() {
|
||||
return adminListContributionMessages
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
contributionId: this.contributionId,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
update({ adminListContributionMessages }) {
|
||||
this.messages = adminListContributionMessages.messages
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getListContributionMessages(this.contributionId)
|
||||
methods: {
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -13,11 +13,20 @@ describe('ContributionMessagesListItem', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: dateMock,
|
||||
$n: numberMock,
|
||||
$store: {
|
||||
state: {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('if message author has moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 111,
|
||||
@ -51,27 +60,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(2)').text()).toBe(
|
||||
'Peter Lustig',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-name"]').text()).toBe('Peter Lustig')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="moderator-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:23:27 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the moderator label', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > small:nth-child(4)').text()).toBe(
|
||||
'moderator',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-label"]').text()).toBe('moderator.moderator')
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > div:nth-child(5)').text()).toBe(
|
||||
'Lorem ipsum?',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-message"]').text()).toBe('Lorem ipsum?')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -79,6 +82,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
describe('if message author does not have moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 113,
|
||||
@ -107,23 +111,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has a DIV .text-left.is-not-moderator', () => {
|
||||
expect(wrapper.find('div.text-left.is-not-moderator').exists()).toBe(true)
|
||||
expect(wrapper.find('div.text-left.is-user').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(2)').text()).toBe(
|
||||
'Bibi Bloxberg',
|
||||
)
|
||||
expect(wrapper.find('[data-test="user-name"]').text()).toBe('Bibi Bloxberg')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="user-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:25:34 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)').text()).toBe(
|
||||
expect(wrapper.find('[data-test="user-message"]').text()).toBe(
|
||||
'Asda sdad ad asdasd, das Ass das Das.',
|
||||
)
|
||||
})
|
||||
@ -132,6 +134,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
|
||||
describe('links in contribtion message', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'Lorem ipsum?',
|
||||
@ -159,7 +162,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
beforeEach(() => {
|
||||
propsData.message.message = 'https://gradido.net/de/'
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the link as text', () => {
|
||||
@ -176,7 +179,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
propsData.message.message = `Here you find all you need to know about Gradido: https://gradido.net/de/
|
||||
and here is the link to the repository: https://github.com/gradido/gradido`
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the whole text', () => {
|
||||
@ -196,6 +199,7 @@ and here is the link to the repository: https://github.com/gradido/gradido`)
|
||||
|
||||
describe('contribution message type HISTORY', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: `Sun Nov 13 2022 13:05:48 GMT+0100 (Central European Standard Time)
|
||||
@ -227,7 +231,7 @@ This message also contains a link: https://gradido.net/de/
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = itemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper
|
||||
})
|
||||
|
||||
it('renders the date', () => {
|
||||
|
||||
@ -1,17 +1,37 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<div v-if="message.isModerator" class="text-right is-moderator">
|
||||
<div v-if="isModeratorMessage" class="text-right p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<small class="ml-4" data-test="moderator-label">
|
||||
{{ $t('moderator.moderator') }}
|
||||
</small>
|
||||
<small class="ml-2" data-test="moderator-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<span class="ml-2 mr-2" data-test="moderator-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<b-avatar square variant="warning"></b-avatar>
|
||||
<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>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
|
||||
<parse-message v-bind="message" data-test="moderator-message"></parse-message>
|
||||
<small v-if="isModeratorHiddenMessage">
|
||||
<hr />
|
||||
{{ $t('moderator.request') }}
|
||||
</small>
|
||||
</div>
|
||||
<div v-else class="text-left is-not-moderator">
|
||||
<div v-else class="text-left p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<b-avatar variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
<span class="ml-2 mr-2" data-test="user-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<small class="ml-2" data-test="user-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<small v-if="isHistory">
|
||||
<hr />
|
||||
{{ $t('moderator.history') }}
|
||||
<hr />
|
||||
</small>
|
||||
<parse-message v-bind="message" data-test="user-message"></parse-message>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -28,22 +48,50 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isModeratorMessage() {
|
||||
return this.contributionUserId !== this.message.userId
|
||||
},
|
||||
isModeratorHiddenMessage() {
|
||||
return this.message.type === 'MODERATOR'
|
||||
},
|
||||
isHistory() {
|
||||
return this.message.type === 'HISTORY'
|
||||
},
|
||||
boxClass() {
|
||||
if (this.isModeratorHiddenMessage) return 'is-moderator is-moderator-hidden-message'
|
||||
if (this.isHistory) return 'is-user is-user-history-message'
|
||||
if (this.isModeratorMessage) return 'is-moderator is-moderator-message'
|
||||
return 'is-user is-user-message'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.is-not-moderator {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
/* background-color: rgb(261, 204, 221); */
|
||||
}
|
||||
.is-moderator {
|
||||
clear: both;
|
||||
float: right;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
/* background-color: rgb(255, 255, 128); */
|
||||
}
|
||||
.is-moderator-message {
|
||||
background-color: rgb(228, 237, 245);
|
||||
}
|
||||
.is-moderator-hidden-message {
|
||||
background-color: rgb(217, 161, 228);
|
||||
}
|
||||
.is-user {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
}
|
||||
.is-user-message {
|
||||
background-color: rgb(236, 235, 213);
|
||||
}
|
||||
.is-user-history-message {
|
||||
background-color: rgb(235, 226, 57);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -28,7 +28,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -39,6 +39,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -50,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -61,6 +62,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
{{ $t('help.transactionlist.confirmed') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.state') }}
|
||||
{{ $t('transactionlist.status') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.status') }}
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
@ -78,8 +78,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'state',
|
||||
label: this.$t('transactionlist.state'),
|
||||
key: 'status',
|
||||
label: this.$t('transactionlist.status'),
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
|
||||
@ -131,13 +131,13 @@ describe('OpenCreationsTable', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('call updateState', () => {
|
||||
describe('call updateStatus', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState(4)
|
||||
wrapper.vm.updateStatus(4)
|
||||
})
|
||||
|
||||
it('emits update-state', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
|
||||
it('emits update-status', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-status', 4)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
stacked="md"
|
||||
:tbody-tr-class="rowClass"
|
||||
>
|
||||
<template #cell(state)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
|
||||
<template #cell(status)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.status)"></b-icon>
|
||||
</template>
|
||||
<template #cell(bookmark)="row">
|
||||
<div v-if="!myself(row.item)">
|
||||
@ -39,12 +39,12 @@
|
||||
<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.messagesCount > 0"
|
||||
v-if="row.item.status === 'PENDING' && row.item.messagesCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
v-if="row.item.status === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="warning"
|
||||
class="pl-1"
|
||||
@ -102,8 +102,9 @@
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contributionId="row.item.id"
|
||||
:contributionState="row.item.state"
|
||||
@update-state="updateState"
|
||||
:contributionStatus="row.item.status"
|
||||
:contributionUserId="row.item.userId"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -154,15 +155,21 @@ export default {
|
||||
},
|
||||
rowClass(item, type) {
|
||||
if (!item || type !== 'row') return
|
||||
if (item.state === 'CONFIRMED') return 'table-success'
|
||||
if (item.state === 'DENIED') return 'table-warning'
|
||||
if (item.state === 'DELETED') return 'table-danger'
|
||||
if (item.state === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.state === 'PENDING') return 'table-primary'
|
||||
if (item.status === 'CONFIRMED') return 'table-success'
|
||||
if (item.status === 'DENIED') return 'table-warning'
|
||||
if (item.status === 'DELETED') return 'table-danger'
|
||||
if (item.status === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.status === 'PENDING') return 'table-primary'
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.btn-warning {
|
||||
background-color: #e1a908;
|
||||
border-color: #e1a908;
|
||||
}
|
||||
</style>
|
||||
|
||||
37
admin/src/components/UserQuery.vue
Normal file
37
admin/src/components/UserQuery.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
type="text"
|
||||
class="test-input-criteria"
|
||||
v-model="currentValue"
|
||||
:placeholder="$t('user_search')"
|
||||
></b-form-input>
|
||||
<b-input-group-append class="test-click-clear-criteria" @click="currentValue = ''">
|
||||
<b-input-group-text class="pointer">
|
||||
<b-icon icon="x" />
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserQuery',
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentValue() {
|
||||
if (this.value !== this.currentValue) {
|
||||
this.$emit('input', this.currentValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,8 +1,12 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
|
||||
adminCreateContributionMessage(
|
||||
contributionId: $contributionId
|
||||
message: $message
|
||||
messageType: $messageType
|
||||
) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
export const adminListContributionMessages = gql`
|
||||
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
adminListContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
@ -7,6 +7,7 @@ export const adminListContributions = gql`
|
||||
$order: Order = DESC
|
||||
$statusFilter: [ContributionStatus!]
|
||||
$userId: Int
|
||||
$query: String
|
||||
) {
|
||||
adminListContributions(
|
||||
currentPage: $currentPage
|
||||
@ -14,6 +15,7 @@ export const adminListContributions = gql`
|
||||
order: $order
|
||||
statusFilter: $statusFilter
|
||||
userId: $userId
|
||||
query: $query
|
||||
) {
|
||||
contributionCount
|
||||
contributionList {
|
||||
@ -26,7 +28,7 @@ export const adminListContributions = gql`
|
||||
contributionDate
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const searchUsers = gql`
|
||||
query ($searchText: String!, $currentPage: Int, $pageSize: Int, $filters: SearchUsersFilters) {
|
||||
query (
|
||||
$query: String!
|
||||
$filters: SearchUsersFilters
|
||||
$currentPage: Int = 0
|
||||
$pageSize: Int = 25
|
||||
$order: Order = ASC
|
||||
) {
|
||||
searchUsers(
|
||||
searchText: $searchText
|
||||
query: $query
|
||||
filters: $filters
|
||||
currentPage: $currentPage
|
||||
pageSize: $pageSize
|
||||
filters: $filters
|
||||
order: $order
|
||||
) {
|
||||
userCount
|
||||
userList {
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
"transactionlist": {
|
||||
"confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.",
|
||||
"periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.",
|
||||
"state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"status": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"submitted": "Wann wurde es vom Mitglied eingereicht"
|
||||
}
|
||||
},
|
||||
@ -108,7 +108,12 @@
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator Notiz",
|
||||
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatische Beiträge",
|
||||
@ -184,7 +189,7 @@
|
||||
"confirmed": "Bestätigt",
|
||||
"memo": "Nachricht",
|
||||
"period": "Zeitraum",
|
||||
"state": "Status",
|
||||
"status": "Status",
|
||||
"submitted": "Eingereicht",
|
||||
"title": "Alle geschöpften Transaktionen für den Nutzer"
|
||||
},
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
"transactionlist": {
|
||||
"confirmed": "When was it confirmed by a moderator / admin.",
|
||||
"periods": "For what period was it submitted by the member.",
|
||||
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"status": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"submitted": "When was it submitted by the member"
|
||||
}
|
||||
},
|
||||
@ -108,7 +108,12 @@
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "The data has been changed. This is the old data.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator note",
|
||||
"request": "This message is only visible to the moderators!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatic Contributions",
|
||||
@ -184,7 +189,7 @@
|
||||
"confirmed": "Confirmed",
|
||||
"memo": "Message",
|
||||
"period": "Period",
|
||||
"state": "State",
|
||||
"status": "State",
|
||||
"submitted": "Submitted",
|
||||
"title": "All creation-transactions for the user"
|
||||
},
|
||||
|
||||
@ -51,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -73,7 +73,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -341,6 +341,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['CONFIRMED'],
|
||||
})
|
||||
})
|
||||
@ -356,6 +357,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
@ -372,6 +374,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['DENIED'],
|
||||
})
|
||||
})
|
||||
@ -388,6 +391,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['DELETED'],
|
||||
})
|
||||
})
|
||||
@ -404,6 +408,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||
})
|
||||
})
|
||||
@ -424,6 +429,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 2,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||
})
|
||||
})
|
||||
@ -439,6 +445,7 @@ describe('CreationConfirm', () => {
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
@ -449,14 +456,48 @@ describe('CreationConfirm', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('user query', () => {
|
||||
describe('with user query', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', 'query')
|
||||
})
|
||||
|
||||
it('calls the API with query', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: 'query',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('reset query', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', '')
|
||||
})
|
||||
|
||||
it('calls the API with empty query', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update status', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-status', 2)
|
||||
})
|
||||
|
||||
it('updates the status', () => {
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1)
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS')
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).status).toBe('IN_PROGRESS')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys -->
|
||||
<template>
|
||||
<div class="creation-confirm">
|
||||
<user-query class="mb-4 mt-2" v-model="query" />
|
||||
<div>
|
||||
<b-tabs v-model="tabIndex" content-class="mt-3" fill>
|
||||
<b-tab active :title-link-attributes="{ 'data-test': 'open' }">
|
||||
@ -43,7 +44,7 @@
|
||||
:items="items"
|
||||
:fields="fields"
|
||||
@show-overlay="showOverlay"
|
||||
@update-state="updateStatus"
|
||||
@update-status="updateStatus"
|
||||
@update-contributions="$apollo.queries.ListAllContributions.refetch()"
|
||||
/>
|
||||
|
||||
@ -85,6 +86,7 @@
|
||||
<script>
|
||||
import Overlay from '../components/Overlay'
|
||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
|
||||
import UserQuery from '../components/UserQuery'
|
||||
import { adminListContributions } from '../graphql/adminListContributions'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
@ -103,6 +105,7 @@ export default {
|
||||
components: {
|
||||
OpenCreationsTable,
|
||||
Overlay,
|
||||
UserQuery,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -114,6 +117,7 @@ export default {
|
||||
rows: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -187,7 +191,7 @@ export default {
|
||||
},
|
||||
updateStatus(id) {
|
||||
this.items.find((obj) => obj.id === id).messagesCount++
|
||||
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||
this.items.find((obj) => obj.id === id).status = 'IN_PROGRESS'
|
||||
},
|
||||
formatDateOrDash(value) {
|
||||
return value ? this.$d(new Date(value), 'short') : '—'
|
||||
@ -217,7 +221,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'moderatorId', label: this.$t('moderator') },
|
||||
{ key: 'moderatorId', label: this.$t('moderator.moderator') },
|
||||
{ key: 'editCreation', label: this.$t('chat') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
],
|
||||
@ -254,7 +258,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -290,7 +294,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deniedBy', label: this.$t('moderator') },
|
||||
{ key: 'deniedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -326,12 +330,12 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deletedBy', label: this.$t('moderator') },
|
||||
{ key: 'deletedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
// all contributions
|
||||
{ key: 'state', label: this.$t('status') },
|
||||
{ key: 'status', label: this.$t('status') },
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -363,7 +367,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
][this.tabIndex]
|
||||
@ -409,6 +413,7 @@ export default {
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
statusFilter: this.statusFilter,
|
||||
query: this.query,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
|
||||
@ -43,7 +43,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -65,7 +65,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
|
||||
@ -10,11 +10,20 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
userCount: 4,
|
||||
userList: [
|
||||
{
|
||||
userId: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
userId: 4,
|
||||
firstName: 'New',
|
||||
lastName: 'User',
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
@ -28,23 +37,14 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
userId: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
userId: 4,
|
||||
firstName: 'New',
|
||||
lastName: 'User',
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -79,9 +79,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -100,9 +101,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: false,
|
||||
byDeleted: null,
|
||||
@ -122,9 +124,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: true,
|
||||
@ -144,9 +147,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 2,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -166,9 +170,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: 'search string',
|
||||
query: 'search string',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -181,13 +186,14 @@ describe('UserSearch', () => {
|
||||
describe('reset the search field', () => {
|
||||
it('calls the API with empty criteria', async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('.test-click-clear-criteria').trigger('click')
|
||||
await wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', '')
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
|
||||
@ -23,21 +23,7 @@
|
||||
</b-button>
|
||||
</div>
|
||||
<label>{{ $t('user_search') }}</label>
|
||||
<div>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
type="text"
|
||||
class="test-input-criteria"
|
||||
v-model="criteria"
|
||||
:placeholder="$t('user_search')"
|
||||
></b-form-input>
|
||||
<b-input-group-append class="test-click-clear-criteria" @click="criteria = ''">
|
||||
<b-input-group-text class="pointer">
|
||||
<b-icon icon="x" />
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
<user-query class="mb-4 mt-2" v-model="criteria" />
|
||||
<search-user-table
|
||||
type="PageUserSearch"
|
||||
:items="searchResult"
|
||||
@ -49,7 +35,7 @@
|
||||
pills
|
||||
size="lg"
|
||||
v-model="currentPage"
|
||||
per-page="perPage"
|
||||
:per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
@ -61,12 +47,14 @@
|
||||
import SearchUserTable from '../components/Tables/SearchUserTable'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
import { creationMonths } from '../mixins/creationMonths'
|
||||
import UserQuery from '../components/UserQuery'
|
||||
|
||||
export default {
|
||||
name: 'UserSearch',
|
||||
mixins: [creationMonths],
|
||||
components: {
|
||||
SearchUserTable,
|
||||
UserQuery,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -97,10 +85,11 @@ export default {
|
||||
.query({
|
||||
query: searchUsers,
|
||||
variables: {
|
||||
searchText: this.criteria,
|
||||
query: this.criteria,
|
||||
filters: this.filters,
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.perPage,
|
||||
filters: this.filters,
|
||||
order: 'DESC',
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
|
||||
@ -25,10 +25,12 @@ module.exports = {
|
||||
},
|
||||
node: true,
|
||||
},
|
||||
// the parser cannot handle the split sodium import
|
||||
'import/ignore': ['sodium-native'],
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
camelcase: ['error', { allow: ['FederationClient_*'] }],
|
||||
camelcase: ['error', { allow: ['FederationClient_*', 'crypto_*', 'randombytes_random'] }],
|
||||
'no-debugger': 'error',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
@ -38,7 +40,7 @@ module.exports = {
|
||||
],
|
||||
// import
|
||||
'import/export': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
// 'import/no-deprecated': 'error',
|
||||
'import/no-empty-named-blocks': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
@ -58,7 +60,10 @@ module.exports = {
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-internal-modules': 'off',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-relative-parent-imports': ['error', { ignore: ['@/*', 'random-bigint'] }],
|
||||
'import/no-relative-parent-imports': [
|
||||
'error',
|
||||
{ ignore: ['@/*', 'random-bigint', 'sodium-native'] },
|
||||
],
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-unresolved': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
@ -192,6 +197,9 @@ module.exports = {
|
||||
{
|
||||
files: ['*.test.ts'],
|
||||
plugins: ['jest'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
|
||||
8
backend/@types/sodium-native/index.d.ts
vendored
Normal file
8
backend/@types/sodium-native/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from '@/node_modules/@types/sodium-native'
|
||||
|
||||
declare module 'sodium-native' {
|
||||
export function crypto_hash_sha512_init(state: Buffer, key?: Buffer, outlen?: Buffer): void
|
||||
export function crypto_hash_sha512_update(state: Buffer, input: Buffer): void
|
||||
export function crypto_hash_sha512_final(state: Buffer, out: Buffer): void
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.1",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -55,6 +55,7 @@
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/sodium-native": "^2.3.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
|
||||
@ -53,4 +53,5 @@ export enum RIGHTS {
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
|
||||
ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
}
|
||||
|
||||
@ -12,14 +12,14 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0066-x-community-sendcoins-transactions_table',
|
||||
DB_VERSION: '0067-private_key_in_community_table',
|
||||
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
|
||||
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v15.2023-02-07',
|
||||
EXPECTED: 'v17.2023-07-03',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
|
||||
1521
backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap
Normal file
1521
backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -94,11 +94,11 @@ describe('sendEmailTranslated', () => {
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'receiver@mail.org',
|
||||
cc: 'support@gradido.net',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Gradido: Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
@ -142,11 +142,11 @@ describe('sendEmailTranslated', () => {
|
||||
originalMessage: expect.objectContaining({
|
||||
to: CONFIG.EMAIL_TEST_RECEIVER,
|
||||
cc: 'support@gradido.net',
|
||||
from: `Gradido (do not answer) <${CONFIG.EMAIL_SENDER}>`,
|
||||
attachments: [],
|
||||
subject: 'Gradido: Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Gradido: Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
from: `Gradido (emails.general.doNotAnswer) <${CONFIG.EMAIL_SENDER}>`,
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
@ -70,7 +70,36 @@ export const sendEmailTranslated = async ({
|
||||
const resultSend = await email
|
||||
.send({
|
||||
template: path.join(__dirname, 'templates', template),
|
||||
message: receiver,
|
||||
message: {
|
||||
...receiver,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'gradido-header.jpeg',
|
||||
path: path.join(__dirname, 'templates/includes/gradido-header.jpeg'),
|
||||
cid: 'gradidoheader',
|
||||
},
|
||||
{
|
||||
filename: 'facebook-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/facebook-icon.png'),
|
||||
cid: 'facebookicon',
|
||||
},
|
||||
{
|
||||
filename: 'telegram-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/telegram-icon.png'),
|
||||
cid: 'telegramicon',
|
||||
},
|
||||
{
|
||||
filename: 'twitter-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/twitter-icon.png'),
|
||||
cid: 'twittericon',
|
||||
},
|
||||
{
|
||||
filename: 'youtube-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/youtube-icon.png'),
|
||||
cid: 'youtubeicon',
|
||||
},
|
||||
],
|
||||
},
|
||||
locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale'
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
|
||||
@ -34,11 +34,9 @@ let testEnv: {
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger, localization)
|
||||
con = testEnv.con
|
||||
// await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await cleanDB()
|
||||
await con.close()
|
||||
})
|
||||
|
||||
@ -87,8 +85,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -97,37 +97,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Message about your common good contribution',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Message about your common good contribution',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: MESSAGE ABOUT YOUR COMMON GOOD CONTRIBUTION'),
|
||||
text: expect.stringContaining('MESSAGE ABOUT YOUR COMMON GOOD CONTRIBUTION'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Message about your common good contribution</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Message about your common good contribution</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -163,8 +143,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -173,41 +155,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Email Verification',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Email Verification',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: EMAIL VERIFICATION'),
|
||||
text: expect.stringContaining('EMAIL VERIFICATION'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain('<title>Gradido: Email Verification</title>')
|
||||
expect(result.originalMessage.html).toContain('>Gradido: Email Verification</h1>')
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your email address has just been registered with Gradido.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Please click on this link to complete the registration and activate your Gradido account:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<a href="http://localhost/checkEmail/6627633878930542284">http://localhost/checkEmail/6627633878930542284</a>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -240,54 +198,28 @@ describe('sendEmailVariants', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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: Try To Register Again With Your Email',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
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 (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Try To Register Again With Your Email</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Try To Register Again With Your Email</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your email address has just been used again to register an account with Gradido.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'However, an account already exists for your email address.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Please click on the following link if you have forgotten your password:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'If you are not the one who tried to register again, please contact our support:<br><a href="mailto:support@supportmail.com">support@supportmail.com</a>',
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -327,8 +259,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -337,37 +271,17 @@ describe('sendEmailVariants', () => {
|
||||
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 contribution to the common good was confirmed',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your contribution to the common good was confirmed',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining(
|
||||
'GRADIDO: YOUR CONTRIBUTION TO THE COMMON GOOD WAS CONFIRMED',
|
||||
),
|
||||
text: expect.stringContaining('YOUR CONTRIBUTION TO THE COMMON GOOD WAS CONFIRMED'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Your contribution to the common good was confirmed</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Your contribution to the common good was confirmed</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your public good contribution “My contribution.” has just been confirmed by Bibi Bloxberg and credited to your Gradido account.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Amount: 23.54 GDD')
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -405,7 +319,9 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('result', () => {
|
||||
it('has expected result', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
@ -415,37 +331,17 @@ describe('sendEmailVariants', () => {
|
||||
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 rejected',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your common good contribution was rejected',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS REJECTED'),
|
||||
text: expect.stringContaining('YOUR COMMON GOOD CONTRIBUTION WAS REJECTED'),
|
||||
}),
|
||||
})
|
||||
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 rejected</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Your common good contribution was rejected</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -483,8 +379,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -493,37 +391,17 @@ describe('sendEmailVariants', () => {
|
||||
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',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your common good contribution was deleted',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS DELETED'),
|
||||
text: expect.stringContaining('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 “Creation” menu in your Gradido account and click on the “My contributions” 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -559,8 +437,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -569,39 +449,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Reset password',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Reset password',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: RESET PASSWORD'),
|
||||
text: expect.stringContaining('RESET PASSWORD'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain('<title>Gradido: Reset password</title>')
|
||||
expect(result.originalMessage.html).toContain('>Gradido: Reset password</h1>')
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'You, or someone else, requested a password reset for this account.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('If it was you, please click on the link:')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<a href="http://localhost/reset-password/3762660021544901417">http://localhost/reset-password/3762660021544901417</a>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -643,8 +501,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -653,36 +513,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Bibi Bloxberg has redeemed your Gradido link',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Bibi Bloxberg has redeemed your Gradido link',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('BIBI BLOXBERG HAS REDEEMED YOUR GRADIDO LINK'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Bibi Bloxberg has redeemed your Gradido link</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Bibi Bloxberg has redeemed your Gradido link</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Amount: 17.65 GDD')
|
||||
expect(result.originalMessage.html).toContain('Message: You deserve it! 🙏🏼')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`You can find transaction details in your Gradido 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -722,8 +563,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -732,34 +575,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Bibi Bloxberg has sent you 37.40 Gradido',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
|
||||
text: expect.stringContaining('BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'You have just received 37.40 GDD from Bibi Bloxberg (bibi@bloxberg.de).',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`You can find transaction details in your Gradido 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.accountActivation.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.accountActivation.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.accountActivation.emailRegistered')
|
||||
p
|
||||
= t('emails.accountActivation.pleaseClickLink')
|
||||
br
|
||||
a(href=activationLink) #{activationLink}
|
||||
br
|
||||
= t('emails.general.orCopyLink')
|
||||
p
|
||||
= t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.accountActivation.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.accountActivation.emailRegistered')
|
||||
.content
|
||||
h2= t('emails.general.completeRegistration')
|
||||
div(class="p_content")= t('emails.accountActivation.pleaseClickLink')
|
||||
a.button-3(href=activationLink) #{t('emails.accountActivation.activateAccount')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=activationLink) #{activationLink}
|
||||
|
||||
include ../includes/requestNewLink.pug
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.accountMultiRegistration.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.accountMultiRegistration.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p
|
||||
= t('emails.accountMultiRegistration.emailReused')
|
||||
br
|
||||
= t('emails.accountMultiRegistration.emailExists')
|
||||
p
|
||||
= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
br
|
||||
= t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
|
||||
p
|
||||
= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
||||
br
|
||||
a(href='mailto:' + supportEmail)= supportEmail
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.accountMultiRegistration.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p
|
||||
= t('emails.accountMultiRegistration.emailReused')
|
||||
br
|
||||
= t('emails.accountMultiRegistration.emailExists')
|
||||
.content
|
||||
h2= t('emails.resetPassword.title')
|
||||
div(class="p_content")= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
||||
a.button-3(href=resendLink) #{t('emails.general.reset')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=resendLink) #{resendLink}
|
||||
|
||||
h2(style="color: red")= t('emails.accountMultiRegistration.contactSupport')
|
||||
div(class="p_content")= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
||||
|
||||
a.clink(href='mailto:' + supportEmail)= supportEmail
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.addedContributionMessage.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.addedContributionMessage.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.addedContributionMessage.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
||||
.content
|
||||
h2= t('emails.addedContributionMessage.readMessage')
|
||||
div(class="p_content")= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
||||
|
||||
a.button-3(href=`${communityURL}contribution`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.contributionConfirmed.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.contributionConfirmed.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.general.amountGDD', { amountGDD: contributionAmount })
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionConfirmed.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { contributionMemo, senderFirstName, senderLastName, amountGDD: contributionAmount })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
@ -1,16 +1,10 @@
|
||||
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
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionDeleted.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionDeleted.commonGoodContributionDeleted', { contributionMemo, senderFirstName, senderLastName })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.contributionDenied.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.contributionDenied.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.contributionDenied.commonGoodContributionDenied', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.contributionDenied.toSeeContributionsAndMessages')
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionDenied.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionDenied.commonGoodContributionDenied', { contributionMemo, senderFirstName, senderLastName })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
p(style='margin-top: 24px;')
|
||||
= t('emails.general.sincerelyYours')
|
||||
br
|
||||
= t('emails.general.yourGradidoTeam')
|
||||
p(style='margin-top: 24px;')= '—————'
|
||||
p(style='margin-top: 24px;')
|
||||
if t('general.imprintImageURL').length > 0
|
||||
div(style='position: relative; left: -22px;')
|
||||
img(src=t('general.imprintImageURL'), width='200', alt=t('general.imprintImageAlt'))
|
||||
br
|
||||
each line in t('general.imprint').split(/\n/)
|
||||
= line
|
||||
br
|
||||
a(href='mailto:' + supportEmail)= supportEmail
|
||||
br
|
||||
a(href=communityURL)= communityURL
|
||||
@ -0,0 +1,7 @@
|
||||
//-
|
||||
h2= t('emails.general.contributionDetails')
|
||||
div(class="p_content")= t('emails.contribution.toSeeContributionsAndMessages')
|
||||
a.button-3(href=`${communityURL}community/contributions`) #{t('emails.general.toAccount')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=`${communityURL}community/contributions`) #{`${communityURL}community/contribution`}
|
||||
1
backend/src/emails/templates/includes/doNotReply.pug
Normal file
1
backend/src/emails/templates/includes/doNotReply.pug
Normal file
@ -0,0 +1 @@
|
||||
div(class="p_content")= t('emails.general.pleaseDoNotReply')
|
||||
216
backend/src/emails/templates/includes/email.css
Normal file
216
backend/src/emails/templates/includes/email.css
Normal file
@ -0,0 +1,216 @@
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
backend/src/emails/templates/includes/facebook-icon.png
Normal file
BIN
backend/src/emails/templates/includes/facebook-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
73
backend/src/emails/templates/includes/footer.pug
Normal file
73
backend/src/emails/templates/includes/footer.pug
Normal file
@ -0,0 +1,73 @@
|
||||
footer
|
||||
.w-container(class="footer_01")
|
||||
.socialmedia
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://www.facebook.com/groups/Gradido/"
|
||||
)
|
||||
img.bi-facebook(
|
||||
alt="facebook"
|
||||
loading="lazy"
|
||||
src="cid:facebookicon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://t.me/GradidoGruppe"
|
||||
)
|
||||
img.bi-telegram(
|
||||
alt="Telegram"
|
||||
loading="lazy"
|
||||
src="cid:telegramicon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://twitter.com/gradido"
|
||||
)
|
||||
img.bi-twitter(
|
||||
alt="Twitter"
|
||||
loading="lazy"
|
||||
src="cid:twittericon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://www.youtube.com/c/GradidoNet"
|
||||
)
|
||||
img.bi-youtube(
|
||||
alt="youtube"
|
||||
loading="lazy"
|
||||
src="cid:youtubeicon"
|
||||
)
|
||||
.line
|
||||
.footer
|
||||
div(class="footer_p1")= t("emails.footer.contactOurSupport")
|
||||
div(class="footer_p2")= t("emails.footer.supportEmail")
|
||||
img.image(
|
||||
alt="Gradido Logo"
|
||||
src="https://gdd.gradido.net/img/brand/green.png"
|
||||
)
|
||||
div
|
||||
a(
|
||||
class="terms_of_use"
|
||||
href="https://gradido.net/de/impressum/"
|
||||
target="_blank"
|
||||
)= t("emails.footer.imprint")
|
||||
br
|
||||
a(
|
||||
class="terms_of_use"
|
||||
href="https://gradido.net/de/datenschutz/"
|
||||
target="_blank"
|
||||
)= t("emails.footer.privacyPolicy")
|
||||
div(class="footer_p1")
|
||||
| Gradido-Akademie
|
||||
br
|
||||
| Institut für Wirtschaftsbionik
|
||||
br
|
||||
| Pfarrweg 2
|
||||
br
|
||||
| 74653 Künzelsau
|
||||
br
|
||||
| Deutschland
|
||||
br
|
||||
br
|
||||
br
|
||||
|
||||
BIN
backend/src/emails/templates/includes/gradido-header.jpeg
Normal file
BIN
backend/src/emails/templates/includes/gradido-header.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
6
backend/src/emails/templates/includes/greeting.pug
Normal file
6
backend/src/emails/templates/includes/greeting.pug
Normal file
@ -0,0 +1,6 @@
|
||||
//- This sets the greeting at the end of every e-mail
|
||||
.text-block
|
||||
p
|
||||
= t('emails.general.sincerelyYours')
|
||||
br
|
||||
= t('emails.general.yourGradidoTeam')
|
||||
13
backend/src/emails/templates/includes/header.pug
Normal file
13
backend/src/emails/templates/includes/header.pug
Normal file
@ -0,0 +1,13 @@
|
||||
header
|
||||
.head
|
||||
//- TODO
|
||||
//- when https://gdd.gradido.net/img/gradido-email-header.jpg is on production,
|
||||
//- replace this URL by https://gdd.gradido.net/img/brand/gradido-email-header.png
|
||||
img.head-logo(
|
||||
alt="Gradido Logo"
|
||||
loading="lazy"
|
||||
src="cid:gradidoheader"
|
||||
)
|
||||
|
||||
|
||||
|
||||
10
backend/src/emails/templates/includes/requestNewLink.pug
Normal file
10
backend/src/emails/templates/includes/requestNewLink.pug
Normal file
@ -0,0 +1,10 @@
|
||||
//-
|
||||
requestNewLink
|
||||
h2= t('emails.general.requestNewLink')
|
||||
|
||||
if timeDurationObject.minutes == 0
|
||||
div(class="p_content")= t('emails.general.linkValidity', { hours: timeDurationObject.hours })
|
||||
else
|
||||
div(class="p_content")= t('emails.general.linkValidityWithMinutes', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
|
||||
a.button-4(href=resendLink) #{t('emails.general.newLink')}
|
||||
BIN
backend/src/emails/templates/includes/telegram-icon.png
Normal file
BIN
backend/src/emails/templates/includes/telegram-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
backend/src/emails/templates/includes/twitter-icon.png
Normal file
BIN
backend/src/emails/templates/includes/twitter-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
136
backend/src/emails/templates/includes/webflow.css
Normal file
136
backend/src/emails/templates/includes/webflow.css
Normal file
@ -0,0 +1,136 @@
|
||||
body{
|
||||
display: block;
|
||||
font-family: "Work Sans", sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
text-align: -webkit-center;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 15px;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
width: 78%;
|
||||
margin: 40px 1% 40px 1%;
|
||||
padding: 20px 10% 40px 10%;
|
||||
border-radius: 24px;
|
||||
background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);
|
||||
}
|
||||
|
||||
.p_content{
|
||||
margin: 15px 0 15px 0;
|
||||
line-height: 26px;
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.clink {
|
||||
line-break: anywhere;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.button-3,
|
||||
.button-4 {
|
||||
display: inline-block;
|
||||
padding: 9px 15px;
|
||||
color: white;
|
||||
border: 0;
|
||||
line-height: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38);
|
||||
box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3);
|
||||
margin: 25px 0 25px 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.button-4 {
|
||||
background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);
|
||||
}
|
||||
|
||||
.socialmedia {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
max-width: 600px;
|
||||
|
||||
}
|
||||
.slink{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.footer_p1 {
|
||||
margin-top: 30px;
|
||||
color: #9ca0a8;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.footer_p2 {
|
||||
color: #383838;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
width: 200px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.div-block {
|
||||
display: table;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 40px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.terms_of_use {
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.text-block-3 {
|
||||
color: #9ca0a8;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.line_image,
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 13px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.line_image {
|
||||
background-image: linear-gradient(90deg, #c58d38, #c58d38 0%, #f3cd7c 35%, #dbb056 54%, #eec05f 63%, #cc9d3d);
|
||||
}
|
||||
|
||||
.line {
|
||||
background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);
|
||||
}
|
||||
BIN
backend/src/emails/templates/includes/youtube-icon.png
Normal file
BIN
backend/src/emails/templates/includes/youtube-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
26
backend/src/emails/templates/layout.pug
Normal file
26
backend/src/emails/templates/layout.pug
Normal file
@ -0,0 +1,26 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
meta(
|
||||
content="multipart/html; charset=UTF-8"
|
||||
http-equiv="content-type"
|
||||
)
|
||||
meta(
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
)
|
||||
style.
|
||||
.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}
|
||||
style
|
||||
include includes/email.css
|
||||
include includes/webflow.css
|
||||
|
||||
body
|
||||
div.container
|
||||
include includes/header.pug
|
||||
|
||||
.wrapper
|
||||
block content
|
||||
include includes/greeting.pug
|
||||
|
||||
include includes/footer.pug
|
||||
@ -1,20 +1,16 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.resetPassword.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.resetPassword.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
||||
p
|
||||
= t('emails.resetPassword.pleaseClickLink')
|
||||
br
|
||||
a(href=resetLink) #{resetLink}
|
||||
br
|
||||
= t('emails.general.orCopyLink')
|
||||
p
|
||||
= t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
include ../greatingFormularImprint.pug
|
||||
extends ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.resetPassword.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
||||
.content
|
||||
h2= t('emails.resetPassword.title')
|
||||
div(class="p_content")= t('emails.resetPassword.pleaseClickLink')
|
||||
a.button-3(href=resetLink) #{t('emails.general.reset')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=resetLink) #{resetLink}
|
||||
|
||||
include ../includes/requestNewLink.pug
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.transactionLinkRedeemed.subject', { senderFirstName, senderLastName })
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.transactionLinkRedeemed.subject', { senderFirstName, senderLastName })
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
||||
p
|
||||
= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
||||
br
|
||||
= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
||||
p
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.transactionLinkRedeemed.title', { senderFirstName, senderLastName })
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
||||
.content
|
||||
h2= t('emails.general.transactionDetails')
|
||||
div(class="p_content")= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
||||
br
|
||||
= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
||||
br
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
|
||||
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
||||
p
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.transactionReceived.title', { senderFirstName, senderLastName, transactionAmount })
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
||||
.content
|
||||
h2= t('emails.general.transactionDetails')
|
||||
div(class="p_content")= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
|
||||
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export class ContributionMessageArgs {
|
||||
@ -8,4 +10,7 @@ export class ContributionMessageArgs {
|
||||
|
||||
@Field(() => String)
|
||||
message: string
|
||||
|
||||
@Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG })
|
||||
messageType: ContributionMessageType
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class CreateUserArgs {
|
||||
@Field(() => String, { nullable: true })
|
||||
alias?: string | null
|
||||
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
|
||||
@ArgsType()
|
||||
export class SearchUsersArgs {
|
||||
@Field(() => String)
|
||||
searchText: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
currentPage?: number
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
pageSize?: number
|
||||
|
||||
// eslint-disable-next-line type-graphql/wrong-decorator-signature
|
||||
@Field(() => SearchUsersFilters, { nullable: true, defaultValue: null })
|
||||
filters?: SearchUsersFilters | null
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
|
||||
export enum ContributionMessageType {
|
||||
HISTORY = 'HISTORY',
|
||||
DIALOG = 'DIALOG',
|
||||
MODERATOR = 'MODERATOR', // messages for moderator communication, can only be seen by moderators
|
||||
}
|
||||
|
||||
registerEnumType(ContributionMessageType, {
|
||||
@ -15,7 +15,7 @@ export class Contribution {
|
||||
this.confirmedAt = contribution.confirmedAt
|
||||
this.confirmedBy = contribution.confirmedBy
|
||||
this.contributionDate = contribution.contributionDate
|
||||
this.state = contribution.contributionStatus
|
||||
this.status = contribution.contributionStatus
|
||||
this.messagesCount = contribution.messages ? contribution.messages.length : 0
|
||||
this.deniedAt = contribution.deniedAt
|
||||
this.deniedBy = contribution.deniedBy
|
||||
@ -68,7 +68,7 @@ export class Contribution {
|
||||
messagesCount: number
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
moderatorId: number | null
|
||||
|
||||
@ -1,25 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Float, Int } from 'type-graphql'
|
||||
|
||||
import { GdtEntryType } from '@enum/GdtEntryType'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntry {
|
||||
constructor(json: any) {
|
||||
this.id = json.id
|
||||
this.amount = json.amount
|
||||
this.date = json.date
|
||||
this.email = json.email
|
||||
this.comment = json.comment
|
||||
this.couponCode = json.coupon_code
|
||||
this.gdtEntryType = json.gdt_entry_type_id
|
||||
this.factor = json.factor
|
||||
this.amount2 = json.amount2
|
||||
this.factor2 = json.factor2
|
||||
this.gdt = json.gdt
|
||||
constructor({
|
||||
id,
|
||||
amount,
|
||||
date,
|
||||
email,
|
||||
comment,
|
||||
// eslint-disable-next-line camelcase
|
||||
coupon_code,
|
||||
// eslint-disable-next-line camelcase
|
||||
gdt_entry_type_id,
|
||||
factor,
|
||||
amount2,
|
||||
factor2,
|
||||
gdt,
|
||||
}: any) {
|
||||
this.id = id
|
||||
this.amount = amount
|
||||
this.date = date
|
||||
this.email = email
|
||||
this.comment = comment
|
||||
// eslint-disable-next-line camelcase
|
||||
this.couponCode = coupon_code
|
||||
// eslint-disable-next-line camelcase
|
||||
this.gdtEntryType = gdt_entry_type_id
|
||||
this.factor = factor
|
||||
this.amount2 = amount2
|
||||
this.factor2 = factor2
|
||||
this.gdt = gdt
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Int, Float } from 'type-graphql'
|
||||
|
||||
import { GdtEntry } from './GdtEntry'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntryList {
|
||||
constructor(json: any) {
|
||||
this.state = json.state
|
||||
this.count = json.count
|
||||
this.gdtEntries = json.gdtEntries ? json.gdtEntries.map((json: any) => new GdtEntry(json)) : []
|
||||
this.gdtSum = json.gdtSum
|
||||
this.timeUsed = json.timeUsed
|
||||
constructor(status = '', count = 0, gdtEntries = [], gdtSum = 0, timeUsed = 0) {
|
||||
this.status = status
|
||||
this.count = count
|
||||
this.gdtEntries = gdtEntries
|
||||
this.gdtSum = gdtSum
|
||||
this.timeUsed = timeUsed
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class KlickTipp {
|
||||
constructor(json: any) {
|
||||
this.newsletterState = json.status === 'Subscribed'
|
||||
constructor(newsletterState: boolean) {
|
||||
this.newsletterState = newsletterState
|
||||
}
|
||||
|
||||
@Field(() => Boolean)
|
||||
|
||||
@ -16,7 +16,7 @@ export class UnconfirmedContribution {
|
||||
this.email = user ? user.emailContact.email : ''
|
||||
this.moderator = contribution.moderatorId
|
||||
this.creation = creations
|
||||
this.state = contribution.contributionStatus
|
||||
this.status = contribution.contributionStatus
|
||||
this.messageCount = contribution.messages ? contribution.messages.length : 0
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class UnconfirmedContribution {
|
||||
creation: Decimal[]
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int)
|
||||
messageCount: number
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { IsNull } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
|
||||
import { Balance } from '@model/Balance'
|
||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { Context, getUser } from '@/server/context'
|
||||
@ -15,6 +14,7 @@ import { calculateDecay } from '@/util/decay'
|
||||
|
||||
import { GdtResolver } from './GdtResolver'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
@Resolver()
|
||||
export class BalanceResolver {
|
||||
@ -57,7 +57,7 @@ export class BalanceResolver {
|
||||
const linkCount = await dbTransactionLink.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
redeemedAt: null,
|
||||
redeemedAt: IsNull(),
|
||||
// validUntil: MoreThan(new Date()),
|
||||
},
|
||||
})
|
||||
@ -77,10 +77,9 @@ export class BalanceResolver {
|
||||
)
|
||||
|
||||
// The final balance is reduced by the link amount withheld
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount } = context.sumHoldAvailableAmount
|
||||
? { sumHoldAvailableAmount: context.sumHoldAvailableAmount }
|
||||
: await transactionLinkRepository.summary(user.id, now)
|
||||
: await transactionLinkSummary(user.id, now)
|
||||
|
||||
logger.debug(`context.sumHoldAvailableAmount=${context.sumHoldAvailableAmount}`)
|
||||
logger.debug(`sumHoldAvailableAmount=${sumHoldAvailableAmount}`)
|
||||
|
||||
@ -542,7 +542,7 @@ describe('Contribution Links', () => {
|
||||
})
|
||||
|
||||
it('updated the DB record', async () => {
|
||||
await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
|
||||
await expect(DbContributionLink.findOne({ where: { id: linkId } })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: linkId,
|
||||
name: 'Dokumenta 2023',
|
||||
|
||||
@ -103,7 +103,7 @@ export class ContributionLinkResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||
const dbContributionLink = await DbContributionLink.findOne({ where: { id } })
|
||||
if (!dbContributionLink) {
|
||||
throw new LogError('Contribution Link not found', id)
|
||||
}
|
||||
@ -130,7 +130,7 @@ export class ContributionLinkResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<ContributionLink> {
|
||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||
const dbContributionLink = await DbContributionLink.findOne({ where: { id } })
|
||||
if (!dbContributionLink) {
|
||||
throw new LogError('Contribution Link not found', id)
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
createContributionMessage,
|
||||
login,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import { listContributionMessages } from '@/seeds/graphql/queries'
|
||||
import { listContributionMessages, adminListContributionMessages } from '@/seeds/graphql/queries'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
|
||||
@ -217,6 +217,41 @@ describe('ContributionMessageResolver', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('contribution message type MODERATOR', () => {
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('creates ContributionMessage', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
message: 'Internal moderator communication',
|
||||
messageType: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminCreateContributionMessage: expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Internal moderator communication',
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("don't call sendAddedContributionMessageEmail", () => {
|
||||
expect(sendAddedContributionMessageEmail).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -385,7 +420,7 @@ describe('ContributionMessageResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns a list of contributionmessages', async () => {
|
||||
it('returns a list of contributionmessages without type MODERATOR', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: listContributionMessages,
|
||||
@ -419,4 +454,96 @@ describe('ContributionMessageResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('adminListContributionMessages', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as user', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as admin', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns a list of contributionmessages with type MODERATOR', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: result.data.createContribution.id },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminListContributionMessages: {
|
||||
count: 3,
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Admin Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'User Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Internal moderator communication',
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,8 +8,8 @@ import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type
|
||||
|
||||
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
|
||||
|
||||
@ -22,6 +22,8 @@ import {
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { findContributionMessages } from './util/findContributionMessages'
|
||||
|
||||
@Resolver()
|
||||
export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE])
|
||||
@ -36,7 +38,7 @@ export class ContributionMessageResolver {
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
const contributionMessage = DbContributionMessage.create()
|
||||
try {
|
||||
const contribution = await DbContribution.findOne({ id: contributionId })
|
||||
const contribution = await DbContribution.findOne({ where: { id: contributionId } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', contributionId)
|
||||
}
|
||||
@ -82,16 +84,35 @@ export class ContributionMessageResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionMessageListResult> {
|
||||
const [contributionMessages, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('cm')
|
||||
.from(DbContributionMessage, 'cm')
|
||||
.leftJoinAndSelect('cm.user', 'u')
|
||||
.where({ contributionId })
|
||||
.orderBy('cm.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
const [contributionMessages, count] = await findContributionMessages({
|
||||
contributionId,
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
})
|
||||
|
||||
return {
|
||||
count,
|
||||
messages: contributionMessages.map(
|
||||
(message) => new ContributionMessage(message, message.user),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES])
|
||||
@Query(() => ContributionMessageListResult)
|
||||
async adminListContributionMessages(
|
||||
@Arg('contributionId', () => Int) contributionId: number,
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionMessageListResult> {
|
||||
const [contributionMessages, count] = await findContributionMessages({
|
||||
contributionId,
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
showModeratorType: true,
|
||||
})
|
||||
|
||||
return {
|
||||
count,
|
||||
@ -104,7 +125,7 @@ export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE])
|
||||
@Mutation(() => ContributionMessage)
|
||||
async adminCreateContributionMessage(
|
||||
@Args() { contributionId, message }: ContributionMessageArgs,
|
||||
@Args() { contributionId, message, messageType }: ContributionMessageArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<ContributionMessage> {
|
||||
const moderator = getUser(context)
|
||||
@ -124,7 +145,7 @@ export class ContributionMessageResolver {
|
||||
if (contribution.userId === moderator.id) {
|
||||
throw new LogError('Admin can not answer on his own contribution', contributionId)
|
||||
}
|
||||
if (!contribution.user.emailContact) {
|
||||
if (!contribution.user.emailContact && contribution.user.emailId) {
|
||||
contribution.user.emailContact = await DbUserContact.findOneOrFail({
|
||||
where: { id: contribution.user.emailId },
|
||||
})
|
||||
@ -133,7 +154,7 @@ export class ContributionMessageResolver {
|
||||
contributionMessage.createdAt = new Date()
|
||||
contributionMessage.message = message
|
||||
contributionMessage.userId = moderator.id
|
||||
contributionMessage.type = ContributionMessageType.DIALOG
|
||||
contributionMessage.type = messageType
|
||||
contributionMessage.isModerator = true
|
||||
await queryRunner.manager.insert(DbContributionMessage, contributionMessage)
|
||||
|
||||
@ -146,15 +167,17 @@ export class ContributionMessageResolver {
|
||||
await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution)
|
||||
}
|
||||
|
||||
void sendAddedContributionMessageEmail({
|
||||
firstName: contribution.user.firstName,
|
||||
lastName: contribution.user.lastName,
|
||||
email: contribution.user.emailContact.email,
|
||||
language: contribution.user.language,
|
||||
senderFirstName: moderator.firstName,
|
||||
senderLastName: moderator.lastName,
|
||||
contributionMemo: contribution.memo,
|
||||
})
|
||||
if (messageType !== ContributionMessageType.MODERATOR) {
|
||||
void sendAddedContributionMessageEmail({
|
||||
firstName: contribution.user.firstName,
|
||||
lastName: contribution.user.lastName,
|
||||
email: contribution.user.emailContact.email,
|
||||
language: contribution.user.language,
|
||||
senderFirstName: moderator.firstName,
|
||||
senderLastName: moderator.lastName,
|
||||
contributionMemo: contribution.memo,
|
||||
})
|
||||
}
|
||||
await queryRunner.commitTransaction()
|
||||
await EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE(
|
||||
{ id: contribution.userId } as DbUser,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { Connection, Equal } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
@ -457,7 +457,7 @@ describe('ContributionResolver', () => {
|
||||
describe('contribution has wrong status', () => {
|
||||
beforeAll(async () => {
|
||||
const contribution = await Contribution.findOneOrFail({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
where: { id: pendingContribution.data.createContribution.id },
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
await contribution.save()
|
||||
@ -469,7 +469,7 @@ describe('ContributionResolver', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
const contribution = await Contribution.findOneOrFail({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
where: { id: pendingContribution.data.createContribution.id },
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
await contribution.save()
|
||||
@ -1092,29 +1092,29 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 4,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDelete.data.createContribution.id,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
memo: 'Test contribution to delete',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
@ -1223,47 +1223,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1288,47 +1288,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1353,47 +1353,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1419,33 +1419,33 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1468,20 +1468,20 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
@ -1506,20 +1506,20 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
@ -1545,27 +1545,27 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1608,36 +1608,36 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1828,7 +1828,7 @@ describe('ContributionResolver', () => {
|
||||
creation = await Contribution.findOneOrFail({
|
||||
where: {
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: 400,
|
||||
amount: Equal(new Decimal('400')),
|
||||
},
|
||||
})
|
||||
})
|
||||
@ -2691,7 +2691,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(50),
|
||||
@ -2700,7 +2700,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(450),
|
||||
@ -2709,7 +2709,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(400),
|
||||
@ -2718,7 +2718,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2727,7 +2727,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'der Baumeister',
|
||||
memo: 'Confirmed Contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2736,7 +2736,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
@ -2745,7 +2745,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Aktives Grundeinkommen',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
@ -2754,7 +2754,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Das war leider zu Viel!',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2763,7 +2763,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2772,7 +2772,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2781,7 +2781,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2790,7 +2790,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to delete',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2799,7 +2799,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to deny',
|
||||
messagesCount: 0,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2808,7 +2808,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to confirm',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2817,7 +2817,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
messagesCount: 1,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(10),
|
||||
@ -2826,7 +2826,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test PENDING contribution update',
|
||||
messagesCount: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(1000),
|
||||
@ -2835,7 +2835,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -2864,7 +2864,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
@ -2873,23 +2873,114 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
describe('with user query', () => {
|
||||
it('returns only contributions of the queried user', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
variables: {
|
||||
query: 'Peter',
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(3)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 3,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(400),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: 'Das war leider zu Viel!',
|
||||
messagesCount: 0,
|
||||
status: 'DELETED',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
// test for case sensitivity and email
|
||||
it('returns only contributions of the queried user email', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
variables: {
|
||||
query: 'RAEUBER', // only found in lowercase in the email
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(3)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 3,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,9 +11,9 @@ import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs'
|
||||
import { AdminUpdateContributionArgs } from '@arg/AdminUpdateContributionArgs'
|
||||
import { ContributionArgs } from '@arg/ContributionArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
||||
@ -101,7 +101,7 @@ export class ContributionResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const user = getUser(context)
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -138,7 +138,7 @@ export class ContributionResolver {
|
||||
currentPage,
|
||||
pageSize,
|
||||
withDeleted: true,
|
||||
relations: ['messages'],
|
||||
relations: { messages: true },
|
||||
userId: user.id,
|
||||
statusFilter,
|
||||
})
|
||||
@ -160,7 +160,7 @@ export class ContributionResolver {
|
||||
order,
|
||||
currentPage,
|
||||
pageSize,
|
||||
relations: ['user'],
|
||||
relations: { user: true },
|
||||
statusFilter,
|
||||
})
|
||||
|
||||
@ -372,6 +372,8 @@ export class ContributionResolver {
|
||||
statusFilter?: ContributionStatus[] | null,
|
||||
@Arg('userId', () => Int, { nullable: true })
|
||||
userId?: number | null,
|
||||
@Arg('query', () => String, { nullable: true })
|
||||
query?: string | null,
|
||||
): Promise<ContributionListResult> {
|
||||
const [dbContributions, count] = await findContributions({
|
||||
order,
|
||||
@ -379,8 +381,14 @@ export class ContributionResolver {
|
||||
pageSize,
|
||||
withDeleted: true,
|
||||
userId,
|
||||
relations: ['user', 'messages'],
|
||||
relations: {
|
||||
user: {
|
||||
emailContact: true,
|
||||
},
|
||||
messages: true,
|
||||
},
|
||||
statusFilter,
|
||||
query,
|
||||
})
|
||||
|
||||
return new ContributionListResult(
|
||||
@ -395,7 +403,7 @@ export class ContributionResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -409,10 +417,10 @@ export class ContributionResolver {
|
||||
) {
|
||||
throw new LogError('Own contribution can not be deleted as admin')
|
||||
}
|
||||
const user = await DbUser.findOneOrFail(
|
||||
{ id: contribution.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOneOrFail({
|
||||
where: { id: contribution.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
contribution.deletedBy = moderator.id
|
||||
await contribution.save()
|
||||
@ -447,7 +455,7 @@ export class ContributionResolver {
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
try {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -461,10 +469,11 @@ export class ContributionResolver {
|
||||
if (moderatorUser.id === contribution.userId) {
|
||||
throw new LogError('Moderator can not confirm own contribution')
|
||||
}
|
||||
const user = await DbUser.findOneOrFail(
|
||||
{ id: contribution.userId },
|
||||
{ withDeleted: true, relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOneOrFail({
|
||||
where: { id: contribution.userId },
|
||||
withDeleted: true,
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (user.deletedAt) {
|
||||
throw new LogError('Can not confirm contribution since the user was deleted')
|
||||
}
|
||||
@ -565,9 +574,11 @@ export class ContributionResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
id,
|
||||
confirmedAt: IsNull(),
|
||||
deniedBy: IsNull(),
|
||||
where: {
|
||||
id,
|
||||
confirmedAt: IsNull(),
|
||||
deniedBy: IsNull(),
|
||||
},
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
@ -582,10 +593,10 @@ export class ContributionResolver {
|
||||
)
|
||||
}
|
||||
const moderator = getUser(context)
|
||||
const user = await DbUser.findOne(
|
||||
{ id: contributionToUpdate.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOne({
|
||||
where: { id: contributionToUpdate.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('Could not find User of the Contribution', contributionToUpdate.userId)
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { Resolver, Query, Args, Ctx, Authorized, Arg, Int, Float } from 'type-gr
|
||||
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
import { GdtEntry } from '@model/GdtEntry'
|
||||
import { GdtEntryList } from '@model/GdtEntryList'
|
||||
|
||||
import { apiGet, apiPost } from '@/apis/HttpRequest'
|
||||
@ -31,9 +32,17 @@ export class GdtResolver {
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
throw new LogError(resultGDT.data)
|
||||
return new GdtEntryList()
|
||||
}
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
const { state, count, gdtEntries, gdtSum, timeUsed } = resultGDT.data
|
||||
return new GdtEntryList(
|
||||
state,
|
||||
count,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
|
||||
gdtEntries ? gdtEntries.map((data: any) => new GdtEntry(data)) : [],
|
||||
gdtSum,
|
||||
timeUsed,
|
||||
)
|
||||
} catch (err) {
|
||||
throw new LogError('GDT Server is not reachable')
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ describe('KlicktippResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the NEWSLETTER_SUBSCRIBE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.NEWSLETTER_SUBSCRIBE,
|
||||
@ -121,10 +121,10 @@ describe('KlicktippResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the NEWSLETTER_UNSUBSCRIBE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.NEWSLETTER_UNSUBSCRIBE,
|
||||
|
||||
@ -456,10 +456,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the CONTRIBUTION_LINK_REDEEM event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.CONTRIBUTION_LINK_REDEEM,
|
||||
@ -611,10 +611,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_CREATE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_CREATE,
|
||||
@ -664,10 +664,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_DELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_DELETE,
|
||||
@ -719,14 +719,14 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_REDEEM event in the database', async () => {
|
||||
const creator = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const redeemer = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const creator = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const redeemer = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_REDEEM,
|
||||
|
||||
@ -33,6 +33,7 @@ import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
import { TRANSACTION_LINK_LOCK } from '@/util/TRANSACTION_LINK_LOCK'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
import { fullName } from '@/util/utilities'
|
||||
import { calculateBalance } from '@/util/validate'
|
||||
@ -80,6 +81,7 @@ export class TransactionLinkResolver {
|
||||
|
||||
// validate amount
|
||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||
|
||||
if (!sendBalance) {
|
||||
throw new LogError('User has not enough GDD', user.id)
|
||||
}
|
||||
@ -108,7 +110,7 @@ export class TransactionLinkResolver {
|
||||
): Promise<boolean> {
|
||||
const user = getUser(context)
|
||||
|
||||
const transactionLink = await DbTransactionLink.findOne({ id })
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { id } })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', id)
|
||||
}
|
||||
@ -138,17 +140,22 @@ export class TransactionLinkResolver {
|
||||
@Query(() => QueryLinkResult)
|
||||
async queryTransactionLink(@Arg('code') code: string): Promise<typeof QueryLinkResult> {
|
||||
if (code.match(/^CL-/)) {
|
||||
const contributionLink = await DbContributionLink.findOneOrFail(
|
||||
{ code: code.replace('CL-', '') },
|
||||
{ withDeleted: true },
|
||||
)
|
||||
const contributionLink = await DbContributionLink.findOneOrFail({
|
||||
where: { code: code.replace('CL-', '') },
|
||||
withDeleted: true,
|
||||
})
|
||||
return new ContributionLink(contributionLink)
|
||||
} else {
|
||||
const transactionLink = await DbTransactionLink.findOneOrFail({ code }, { withDeleted: true })
|
||||
const user = await DbUser.findOneOrFail({ id: transactionLink.userId })
|
||||
const transactionLink = await DbTransactionLink.findOneOrFail({
|
||||
where: { code },
|
||||
withDeleted: true,
|
||||
})
|
||||
const user = await DbUser.findOneOrFail({ where: { id: transactionLink.userId } })
|
||||
let redeemedBy: User | null = null
|
||||
if (transactionLink?.redeemedBy) {
|
||||
redeemedBy = new User(await DbUser.findOneOrFail({ id: transactionLink.redeemedBy }))
|
||||
redeemedBy = new User(
|
||||
await DbUser.findOneOrFail({ where: { id: transactionLink.redeemedBy } }),
|
||||
)
|
||||
}
|
||||
return new TransactionLink(transactionLink, new User(user), redeemedBy)
|
||||
}
|
||||
@ -191,7 +198,7 @@ export class TransactionLinkResolver {
|
||||
throw new LogError('Contribution link is no longer valid', contributionLink.validTo)
|
||||
}
|
||||
}
|
||||
let alreadyRedeemed: DbContribution | undefined
|
||||
let alreadyRedeemed: DbContribution | null
|
||||
switch (contributionLink.cycle) {
|
||||
case ContributionCycleType.ONCE: {
|
||||
alreadyRedeemed = await queryRunner.manager
|
||||
@ -302,49 +309,51 @@ export class TransactionLinkResolver {
|
||||
return true
|
||||
} else {
|
||||
const now = new Date()
|
||||
const transactionLink = await DbTransactionLink.findOne({ code })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', code)
|
||||
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
|
||||
try {
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { code } })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', code)
|
||||
}
|
||||
|
||||
const linkedUser = await DbUser.findOne({
|
||||
where: {
|
||||
id: transactionLink.userId,
|
||||
},
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
|
||||
if (!linkedUser) {
|
||||
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
||||
}
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
|
||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
await executeTransaction(
|
||||
transactionLink.amount,
|
||||
transactionLink.memo,
|
||||
linkedUser,
|
||||
user,
|
||||
transactionLink,
|
||||
)
|
||||
await EVENT_TRANSACTION_LINK_REDEEM(
|
||||
user,
|
||||
{ id: transactionLink.userId } as DbUser,
|
||||
transactionLink,
|
||||
transactionLink.amount,
|
||||
)
|
||||
} finally {
|
||||
releaseLinkLock()
|
||||
}
|
||||
|
||||
const linkedUser = await DbUser.findOne(
|
||||
{ id: transactionLink.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
|
||||
if (!linkedUser) {
|
||||
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
||||
}
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
|
||||
// TODO: The now check should be done within the semaphore lock,
|
||||
// since the program might wait a while till it is ready to proceed
|
||||
// writing the transaction.
|
||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
|
||||
await executeTransaction(
|
||||
transactionLink.amount,
|
||||
transactionLink.memo,
|
||||
linkedUser,
|
||||
user,
|
||||
transactionLink,
|
||||
)
|
||||
await EVENT_TRANSACTION_LINK_REDEEM(
|
||||
user,
|
||||
{ id: transactionLink.userId } as DbUser,
|
||||
transactionLink,
|
||||
transactionLink.amount,
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -378,7 +387,7 @@ export class TransactionLinkResolver {
|
||||
@Arg('userId', () => Int)
|
||||
userId: number,
|
||||
): Promise<TransactionLinkResult> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
if (!user) {
|
||||
throw new LogError('Could not find requested User', userId)
|
||||
}
|
||||
|
||||
@ -346,8 +346,10 @@ describe('send coins', () => {
|
||||
it('stores the TRANSACTION_SEND event in the database', async () => {
|
||||
// Find the exact transaction (sent one is the one with user[1] as user)
|
||||
const transaction = await Transaction.find({
|
||||
userId: user[1].id,
|
||||
memo: 'unrepeatable memo',
|
||||
where: {
|
||||
userId: user[1].id,
|
||||
memo: 'unrepeatable memo',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
@ -364,8 +366,10 @@ describe('send coins', () => {
|
||||
it('stores the TRANSACTION_RECEIVE event in the database', async () => {
|
||||
// Find the exact transaction (received one is the one with user[0] as user)
|
||||
const transaction = await Transaction.find({
|
||||
userId: user[0].id,
|
||||
memo: 'unrepeatable memo',
|
||||
where: {
|
||||
userId: user[0].id,
|
||||
memo: 'unrepeatable memo',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getCustomRepository, getConnection, In } from '@dbTools/typeorm'
|
||||
import { getConnection, In } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
@ -16,7 +16,6 @@ import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionList } from '@model/TransactionList'
|
||||
import { User } from '@model/User'
|
||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import {
|
||||
@ -38,6 +37,7 @@ import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { getTransactionList } from './util/getTransactionList'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
export const executeTransaction = async (
|
||||
amount: Decimal,
|
||||
@ -245,9 +245,8 @@ export class TransactionResolver {
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, transactionLinkcount } =
|
||||
await transactionLinkRepository.summary(user.id, now)
|
||||
await transactionLinkSummary(user.id, now)
|
||||
context.linkCount = transactionLinkcount
|
||||
logger.debug(`transactionLinkcount=${transactionLinkcount}`)
|
||||
context.sumHoldAvailableAmount = sumHoldAvailableAmount
|
||||
|
||||
@ -195,10 +195,12 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_REGISTER event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: {
|
||||
email: 'peter@lustig.de',
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_REGISTER,
|
||||
@ -271,10 +273,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_ACCOUNT_MULTIREGISTRATION event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_ACCOUNT_MULTIREGISTRATION,
|
||||
@ -292,7 +294,7 @@ describe('UserResolver', () => {
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'it' },
|
||||
})
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'bibi@bloxberg.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'bibi@bloxberg.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
email: 'bibi@bloxberg.de',
|
||||
@ -334,7 +336,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
// make Peter Lustig Admin
|
||||
const peter = await User.findOneOrFail({ id: user[0].id })
|
||||
const peter = await User.findOneOrFail({ where: { id: user[0].id } })
|
||||
peter.isAdmin = new Date()
|
||||
await peter.save()
|
||||
|
||||
@ -365,7 +367,7 @@ describe('UserResolver', () => {
|
||||
|
||||
it('sets the contribution link id', async () => {
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'ein@besucher.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'ein@besucher.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({
|
||||
@ -445,7 +447,7 @@ describe('UserResolver', () => {
|
||||
memo: `testing transaction link`,
|
||||
})
|
||||
|
||||
transactionLink = await TransactionLink.findOneOrFail()
|
||||
transactionLink = await TransactionLink.findOneOrFail({ where: { userId: bob.id } })
|
||||
|
||||
resetToken()
|
||||
|
||||
@ -462,7 +464,7 @@ describe('UserResolver', () => {
|
||||
|
||||
it('sets the referrer id to bob baumeister id', async () => {
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'which@ever.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'which@ever.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({ referrerId: bob.data.login.id }),
|
||||
@ -529,16 +531,18 @@ describe('UserResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUser, variables: createUserVariables })
|
||||
const emailContact = await UserContact.findOneOrFail({ email: createUserVariables.email })
|
||||
const emailContact = await UserContact.findOneOrFail({
|
||||
where: { email: createUserVariables.email },
|
||||
})
|
||||
emailVerificationCode = emailContact.emailVerificationCode.toString()
|
||||
result = await mutate({
|
||||
mutation: setPassword,
|
||||
variables: { code: emailVerificationCode, password: 'Aa12345_' },
|
||||
})
|
||||
newUser = await User.findOneOrFail(
|
||||
{ id: emailContact.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
newUser = await User.findOneOrFail({
|
||||
where: { id: emailContact.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -571,7 +575,9 @@ describe('UserResolver', () => {
|
||||
describe('no valid password', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUser, variables: createUserVariables })
|
||||
const emailContact = await UserContact.findOneOrFail({ email: createUserVariables.email })
|
||||
const emailContact = await UserContact.findOneOrFail({
|
||||
where: { email: createUserVariables.email },
|
||||
})
|
||||
emailVerificationCode = emailContact.emailVerificationCode.toString()
|
||||
})
|
||||
|
||||
@ -697,10 +703,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_LOGIN event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_LOGIN,
|
||||
@ -879,10 +885,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_LOGOUT event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_LOGOUT,
|
||||
@ -1047,10 +1053,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_FORGOT_PASSWORD event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_FORGOT_PASSWORD,
|
||||
@ -1083,7 +1089,7 @@ describe('UserResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
emailContact = await UserContact.findOneOrFail({ email: bibiBloxberg.email })
|
||||
emailContact = await UserContact.findOneOrFail({ where: { email: bibiBloxberg.email } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -1100,7 +1106,9 @@ describe('UserResolver', () => {
|
||||
errors: [
|
||||
// keep Whitspace in error message!
|
||||
new GraphQLError(`Could not find any entity of type "UserContact" matching: {
|
||||
"emailVerificationCode": "not-valid"
|
||||
"where": {
|
||||
"emailVerificationCode": "not-valid"
|
||||
}
|
||||
}`),
|
||||
],
|
||||
}),
|
||||
@ -1175,20 +1183,20 @@ describe('UserResolver', () => {
|
||||
locale: 'en',
|
||||
},
|
||||
})
|
||||
await expect(User.findOne()).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
firstName: 'Benjamin',
|
||||
lastName: 'Blümchen',
|
||||
language: 'en',
|
||||
}),
|
||||
)
|
||||
])
|
||||
})
|
||||
|
||||
it('stores the USER_INFO_UPDATE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_INFO_UPDATE,
|
||||
@ -1212,11 +1220,11 @@ describe('UserResolver', () => {
|
||||
alias: 'bibi_Bloxberg',
|
||||
},
|
||||
})
|
||||
await expect(User.findOne()).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
alias: 'bibi_Bloxberg',
|
||||
}),
|
||||
)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1433,10 +1441,10 @@ describe('UserResolver', () => {
|
||||
|
||||
let bibi: User
|
||||
beforeAll(async () => {
|
||||
const usercontact = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const usercontact = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
bibi = usercontact.user
|
||||
bibi.passwordEncryptionType = PasswordEncryptionType.EMAIL
|
||||
bibi.password = SecretKeyCryptographyCreateKey(
|
||||
@ -1450,10 +1458,10 @@ describe('UserResolver', () => {
|
||||
it('changes to gradidoID on login', async () => {
|
||||
await mutate({ mutation: login, variables })
|
||||
|
||||
const usercontact = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const usercontact = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
bibi = usercontact.user
|
||||
|
||||
expect(bibi).toEqual(
|
||||
@ -1590,14 +1598,14 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_ROLE_SET,
|
||||
@ -1792,14 +1800,15 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_DELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'], withDeleted: true },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
withDeleted: true,
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_DELETE,
|
||||
@ -1943,10 +1952,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('sends an account activation email', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||
/{optin}/g,
|
||||
userConatct.emailVerificationCode.toString(),
|
||||
@ -1965,10 +1974,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_ADMIN_CONFIRMATION event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_ADMIN_CONFIRMATION,
|
||||
@ -2086,14 +2095,14 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_UNDELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_UNDELETE,
|
||||
@ -2110,7 +2119,7 @@ describe('UserResolver', () => {
|
||||
|
||||
describe('search users', () => {
|
||||
const variablesWithoutTextAndFilters = {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
filters: null,
|
||||
|
||||
@ -2,28 +2,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { getConnection, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
import i18n from 'i18n'
|
||||
import {
|
||||
Resolver,
|
||||
Query,
|
||||
Args,
|
||||
Arg,
|
||||
Authorized,
|
||||
Ctx,
|
||||
UseMiddleware,
|
||||
Mutation,
|
||||
Int,
|
||||
} from 'type-graphql'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, Mutation, Int } from 'type-graphql'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { CreateUserArgs } from '@arg/CreateUserArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { SearchUsersArgs } from '@arg/SearchUsersArgs'
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs'
|
||||
import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs'
|
||||
import { OptInType } from '@enum/OptInType'
|
||||
@ -33,7 +23,6 @@ import { UserContactType } from '@enum/UserContactType'
|
||||
import { SearchAdminUsersResult } from '@model/AdminUser'
|
||||
import { User } from '@model/User'
|
||||
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
|
||||
import { UserRepository } from '@repository/User'
|
||||
|
||||
import { subscribe } from '@/apis/KlicktippController'
|
||||
import { encode } from '@/auth/JWT'
|
||||
@ -60,7 +49,6 @@ import {
|
||||
EVENT_ADMIN_USER_DELETE,
|
||||
EVENT_ADMIN_USER_UNDELETE,
|
||||
} from '@/event/Events'
|
||||
import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddleware'
|
||||
import { isValidPassword } from '@/password/EncryptorUtils'
|
||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
||||
@ -71,15 +59,15 @@ import { hasElopageBuys } from '@/util/hasElopageBuys'
|
||||
import { getTimeDurationObject, printTimeDuration } from '@/util/time'
|
||||
|
||||
import random from 'random-bigint'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
|
||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||
import { getUserCreations } from './util/creations'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { findUsers } from './util/findUsers'
|
||||
import { getKlicktippState } from './util/getKlicktippState'
|
||||
import { validateAlias } from './util/validateAlias'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs
|
||||
const sodium = require('sodium-native')
|
||||
|
||||
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
|
||||
const DEFAULT_LANGUAGE = 'de'
|
||||
const isLanguage = (language: string): boolean => {
|
||||
@ -94,13 +82,13 @@ const newEmailContact = (email: string, userId: number): DbUserContact => {
|
||||
emailContact.type = UserContactType.USER_CONTACT_EMAIL
|
||||
emailContact.emailChecked = false
|
||||
emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
|
||||
emailContact.emailVerificationCode = random(64)
|
||||
emailContact.emailVerificationCode = random(64).toString()
|
||||
logger.debug('newEmailContact...successful', emailContact)
|
||||
return emailContact
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const activationLink = (verificationCode: BigInt): string => {
|
||||
export const activationLink = (verificationCode: string): string => {
|
||||
logger.debug(`activationLink(${verificationCode})...`)
|
||||
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, verificationCode.toString())
|
||||
}
|
||||
@ -122,7 +110,6 @@ const newGradidoID = async (): Promise<string> => {
|
||||
export class UserResolver {
|
||||
@Authorized([RIGHTS.VERIFY_LOGIN])
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async verifyLogin(@Ctx() context: Context): Promise<User> {
|
||||
logger.info('verifyLogin...')
|
||||
// TODO refactor and do not have duplicate code with login(see below)
|
||||
@ -132,12 +119,12 @@ export class UserResolver {
|
||||
user.hasElopage = await this.hasElopage(context)
|
||||
|
||||
logger.debug(`verifyLogin... successful: ${user.firstName}.${user.lastName}`)
|
||||
user.klickTipp = await getKlicktippState(userEntity.emailContact.email)
|
||||
return user
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LOGIN])
|
||||
@Mutation(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async login(
|
||||
@Args() { email, password, publisherId }: UnsecureLoginArgs,
|
||||
@Ctx() context: Context,
|
||||
@ -183,6 +170,7 @@ export class UserResolver {
|
||||
dbUser.publisherId = publisherId
|
||||
await DbUser.save(dbUser)
|
||||
}
|
||||
user.klickTipp = await getKlicktippState(dbUser.emailContact.email)
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
@ -207,7 +195,15 @@ export class UserResolver {
|
||||
@Mutation(() => User)
|
||||
async createUser(
|
||||
@Args()
|
||||
{ email, firstName, lastName, language, publisherId = null, redeemCode = null }: CreateUserArgs,
|
||||
{
|
||||
alias = null,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
language,
|
||||
publisherId = null,
|
||||
redeemCode = null,
|
||||
}: CreateUserArgs,
|
||||
): Promise<User> {
|
||||
logger.addContext('user', 'unknown')
|
||||
logger.info(
|
||||
@ -237,12 +233,15 @@ export class UserResolver {
|
||||
// TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent.
|
||||
|
||||
const user = new User(communityDbUser)
|
||||
user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in?
|
||||
user.id = randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in?
|
||||
user.gradidoID = uuidv4()
|
||||
user.firstName = firstName
|
||||
user.lastName = lastName
|
||||
user.language = language
|
||||
user.publisherId = publisherId
|
||||
if (alias && (await validateAlias(alias))) {
|
||||
user.alias = alias
|
||||
}
|
||||
logger.debug('partly faked user', user)
|
||||
|
||||
void sendAccountMultiRegistrationEmail({
|
||||
@ -276,13 +275,16 @@ export class UserResolver {
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
dbUser.language = language
|
||||
if (alias && (await validateAlias(alias))) {
|
||||
dbUser.alias = alias
|
||||
}
|
||||
dbUser.publisherId = publisherId ?? 0
|
||||
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
||||
logger.debug('new dbUser', dbUser)
|
||||
if (redeemCode) {
|
||||
if (redeemCode.match(/^CL-/)) {
|
||||
const contributionLink = await DbContributionLink.findOne({
|
||||
code: redeemCode.replace('CL-', ''),
|
||||
where: { code: redeemCode.replace('CL-', '') },
|
||||
})
|
||||
logger.info('redeemCode found contributionLink', contributionLink)
|
||||
if (contributionLink) {
|
||||
@ -290,7 +292,7 @@ export class UserResolver {
|
||||
eventRegisterRedeem.involvedContributionLink = contributionLink
|
||||
}
|
||||
} else {
|
||||
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { code: redeemCode } })
|
||||
logger.info('redeemCode found transactionLink', transactionLink)
|
||||
if (transactionLink) {
|
||||
dbUser.referrerId = transactionLink.userId
|
||||
@ -364,7 +366,8 @@ export class UserResolver {
|
||||
const user = await findUserByEmail(email).catch(() => {
|
||||
logger.warn(`fail on find UserContact per ${email}`)
|
||||
})
|
||||
if (!user) {
|
||||
|
||||
if (!user || user.deletedAt) {
|
||||
logger.warn(`no user found with ${email}`)
|
||||
return true
|
||||
}
|
||||
@ -377,7 +380,7 @@ export class UserResolver {
|
||||
|
||||
user.emailContact.updatedAt = new Date()
|
||||
user.emailContact.emailResendCount++
|
||||
user.emailContact.emailVerificationCode = random(64)
|
||||
user.emailContact.emailVerificationCode = random(64).toString()
|
||||
user.emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
|
||||
await user.emailContact.save().catch(() => {
|
||||
throw new LogError('Unable to save email verification code', user.emailContact)
|
||||
@ -415,10 +418,10 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
// load code
|
||||
const userContact = await DbUserContact.findOneOrFail(
|
||||
{ emailVerificationCode: code },
|
||||
{ relations: ['user'] },
|
||||
).catch(() => {
|
||||
const userContact = await DbUserContact.findOneOrFail({
|
||||
where: { emailVerificationCode: code },
|
||||
relations: ['user'],
|
||||
}).catch(() => {
|
||||
throw new LogError('Could not login with emailVerificationCode')
|
||||
})
|
||||
logger.debug('userContact loaded...')
|
||||
@ -486,7 +489,9 @@ export class UserResolver {
|
||||
@Query(() => Boolean)
|
||||
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
|
||||
logger.info(`queryOptIn(${optIn})...`)
|
||||
const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: optIn })
|
||||
const userContact = await DbUserContact.findOneOrFail({
|
||||
where: { emailVerificationCode: optIn },
|
||||
})
|
||||
logger.debug('found optInCode', userContact)
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
@ -613,9 +618,7 @@ export class UserResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||
): Promise<SearchAdminUsersResult> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
const [users, count] = await userRepository.findAndCount({
|
||||
const [users, count] = await DbUser.findAndCount({
|
||||
where: {
|
||||
isAdmin: Not(IsNull()),
|
||||
},
|
||||
@ -640,12 +643,14 @@ export class UserResolver {
|
||||
@Authorized([RIGHTS.SEARCH_USERS])
|
||||
@Query(() => SearchUsersResult)
|
||||
async searchUsers(
|
||||
@Arg('query', () => String) query: string,
|
||||
@Arg('filters', () => SearchUsersFilters, { nullable: true })
|
||||
filters: SearchUsersFilters | null | undefined,
|
||||
@Args()
|
||||
{ searchText, currentPage = 1, pageSize = 25, filters }: SearchUsersArgs,
|
||||
{ currentPage = 1, pageSize = 25, order = Order.ASC }: Paginated,
|
||||
@Ctx() context: Context,
|
||||
): Promise<SearchUsersResult> {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userFields = [
|
||||
'id',
|
||||
'firstName',
|
||||
@ -655,19 +660,20 @@ export class UserResolver {
|
||||
'deletedAt',
|
||||
'isAdmin',
|
||||
]
|
||||
const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered(
|
||||
const [users, count] = await findUsers(
|
||||
userFields.map((fieldName) => {
|
||||
return 'user.' + fieldName
|
||||
}),
|
||||
searchText,
|
||||
query,
|
||||
filters ?? null,
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
)
|
||||
|
||||
if (users.length === 0) {
|
||||
return {
|
||||
userCount: 0,
|
||||
userCount: count,
|
||||
userList: [],
|
||||
}
|
||||
}
|
||||
@ -713,7 +719,7 @@ export class UserResolver {
|
||||
@Ctx()
|
||||
context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
@ -742,7 +748,7 @@ export class UserResolver {
|
||||
}
|
||||
await user.save()
|
||||
await EVENT_ADMIN_USER_ROLE_SET(user, moderator)
|
||||
const newUser = await DbUser.findOne({ id: userId })
|
||||
const newUser = await DbUser.findOne({ where: { id: userId } })
|
||||
return newUser ? newUser.isAdmin : null
|
||||
}
|
||||
|
||||
@ -752,7 +758,7 @@ export class UserResolver {
|
||||
@Arg('userId', () => Int) userId: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
@ -765,7 +771,7 @@ export class UserResolver {
|
||||
// soft-delete user
|
||||
await user.softRemove()
|
||||
await EVENT_ADMIN_USER_DELETE(user, moderator)
|
||||
const newUser = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
const newUser = await DbUser.findOne({ where: { id: userId }, withDeleted: true })
|
||||
return newUser ? newUser.deletedAt : null
|
||||
}
|
||||
|
||||
@ -775,7 +781,7 @@ export class UserResolver {
|
||||
@Arg('userId', () => Int) userId: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
const user = await DbUser.findOne({ where: { id: userId }, withDeleted: true })
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
@ -827,10 +833,11 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
const dbUserContact = await DbUserContact.findOneOrFail(
|
||||
{ email },
|
||||
{ withDeleted: true, relations: ['user'] },
|
||||
).catch(() => {
|
||||
const dbUserContact = await DbUserContact.findOneOrFail({
|
||||
where: { email },
|
||||
withDeleted: true,
|
||||
relations: ['user'],
|
||||
}).catch(() => {
|
||||
throw new LogError('No user with this credentials', email)
|
||||
})
|
||||
const dbUser = dbUserContact.user
|
||||
@ -839,7 +846,10 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
}
|
||||
|
||||
async function checkEmailExists(email: string): Promise<boolean> {
|
||||
const userContact = await DbUserContact.findOne({ email }, { withDeleted: true })
|
||||
const userContact = await DbUserContact.findOne({
|
||||
where: { email },
|
||||
withDeleted: true,
|
||||
})
|
||||
if (userContact) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers'
|
||||
|
||||
@ -219,7 +220,7 @@ describe('semaphore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not throw, but should', async () => {
|
||||
it('does throw error on second redeem call', async () => {
|
||||
const redeem1 = mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
@ -236,7 +237,7 @@ describe('semaphore', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
await expect(redeem2).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
errors: [new GraphQLError('Transaction link already redeemed')],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { In } from '@dbTools/typeorm'
|
||||
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
|
||||
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
interface FindContributionMessagesOptions {
|
||||
contributionId: number
|
||||
pageSize: number
|
||||
currentPage: number
|
||||
order: Order
|
||||
showModeratorType?: boolean
|
||||
}
|
||||
|
||||
export const findContributionMessages = async (
|
||||
options: FindContributionMessagesOptions,
|
||||
): Promise<[DbContributionMessage[], number]> => {
|
||||
const { contributionId, pageSize, currentPage, order, showModeratorType } = options
|
||||
|
||||
const messageTypes = [ContributionMessageType.DIALOG, ContributionMessageType.HISTORY]
|
||||
|
||||
if (showModeratorType) messageTypes.push(ContributionMessageType.MODERATOR)
|
||||
|
||||
return DbContributionMessage.findAndCount({
|
||||
where: {
|
||||
contributionId,
|
||||
type: In(messageTypes),
|
||||
},
|
||||
relations: ['user'],
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
}
|
||||
@ -1,38 +1,73 @@
|
||||
import { In } from '@dbTools/typeorm'
|
||||
import { In, Like } from '@dbTools/typeorm'
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
interface Relations {
|
||||
[key: string]: boolean | Relations
|
||||
}
|
||||
|
||||
interface FindContributionsOptions {
|
||||
order: Order
|
||||
currentPage: number
|
||||
pageSize: number
|
||||
withDeleted?: boolean
|
||||
relations?: string[]
|
||||
relations?: Relations | undefined
|
||||
userId?: number | null
|
||||
statusFilter?: ContributionStatus[] | null
|
||||
query?: string | null
|
||||
}
|
||||
|
||||
export const findContributions = async (
|
||||
options: FindContributionsOptions,
|
||||
): Promise<[DbContribution[], number]> => {
|
||||
const { order, currentPage, pageSize, withDeleted, relations, userId, statusFilter } = {
|
||||
const { order, currentPage, pageSize, withDeleted, relations, userId, statusFilter, query } = {
|
||||
withDeleted: false,
|
||||
relations: [],
|
||||
relations: undefined,
|
||||
query: '',
|
||||
...options,
|
||||
}
|
||||
|
||||
const requiredWhere = {
|
||||
...(statusFilter?.length && { contributionStatus: In(statusFilter) }),
|
||||
...(userId && { userId }),
|
||||
}
|
||||
|
||||
const where =
|
||||
query && relations?.user
|
||||
? [
|
||||
{
|
||||
...requiredWhere,
|
||||
user: {
|
||||
firstName: Like(`%${query}%`),
|
||||
},
|
||||
},
|
||||
{
|
||||
...requiredWhere,
|
||||
user: {
|
||||
lastName: Like(`%${query}%`),
|
||||
},
|
||||
},
|
||||
{
|
||||
...requiredWhere,
|
||||
user: {
|
||||
emailContact: {
|
||||
email: Like(`%${query}%`),
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: requiredWhere
|
||||
|
||||
return DbContribution.findAndCount({
|
||||
where: {
|
||||
...(statusFilter?.length && { contributionStatus: In(statusFilter) }),
|
||||
...(userId && { userId }),
|
||||
},
|
||||
relations,
|
||||
where,
|
||||
withDeleted,
|
||||
order: {
|
||||
createdAt: order,
|
||||
id: order,
|
||||
},
|
||||
relations,
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
@ -7,20 +7,20 @@ import { LogError } from '@/server/LogError'
|
||||
import { VALID_ALIAS_REGEX } from './validateAlias'
|
||||
|
||||
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||
let user: DbUser | undefined
|
||||
let user: DbUser | null
|
||||
if (validate(identifier) && version(identifier) === 4) {
|
||||
user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] })
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
}
|
||||
} else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) {
|
||||
const userContact = await DbUserContact.findOne(
|
||||
{
|
||||
const userContact = await DbUserContact.findOne({
|
||||
where: {
|
||||
email: identifier,
|
||||
emailChecked: true,
|
||||
},
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!userContact) {
|
||||
throw new LogError('No user with this credentials', identifier)
|
||||
}
|
||||
|
||||
@ -1,18 +1,24 @@
|
||||
import { Brackets, EntityRepository, IsNull, Not, Repository } from '@dbTools/typeorm'
|
||||
import { getConnection, Brackets, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { SearchUsersFilters } from '@/graphql/arg/SearchUsersFilters'
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
@EntityRepository(DbUser)
|
||||
export class UserRepository extends Repository<DbUser> {
|
||||
async findBySearchCriteriaPagedFiltered(
|
||||
select: string[],
|
||||
searchCriteria: string,
|
||||
filters: SearchUsersFilters | null,
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
): Promise<[DbUser[], number]> {
|
||||
const query = this.createQueryBuilder('user')
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const findUsers = async (
|
||||
select: string[],
|
||||
searchCriteria: string,
|
||||
filters: SearchUsersFilters | null,
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
order = Order.ASC,
|
||||
): Promise<[DbUser[], number]> => {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
try {
|
||||
await queryRunner.connect()
|
||||
const query = queryRunner.manager
|
||||
.createQueryBuilder(DbUser, 'user')
|
||||
.select(select)
|
||||
.withDeleted()
|
||||
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
||||
@ -28,26 +34,24 @@ export class UserRepository extends Repository<DbUser> {
|
||||
)
|
||||
}),
|
||||
)
|
||||
/*
|
||||
filterCriteria.forEach((filter) => {
|
||||
query.andWhere(filter)
|
||||
})
|
||||
*/
|
||||
if (filters) {
|
||||
if (filters.byActivated !== null) {
|
||||
query.andWhere('emailContact.emailChecked = :value', { value: filters.byActivated })
|
||||
// filterCriteria.push({ 'emailContact.emailChecked': filters.byActivated })
|
||||
}
|
||||
|
||||
if (filters.byDeleted !== null) {
|
||||
// filterCriteria.push({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||
query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
return await query
|
||||
.orderBy({ 'user.id': order })
|
||||
.take(pageSize)
|
||||
.skip((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
} catch (err) {
|
||||
throw new LogError('Unable to search users', err)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
}
|
||||
19
backend/src/graphql/resolver/util/getKlicktippState.ts
Normal file
19
backend/src/graphql/resolver/util/getKlicktippState.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { KlickTipp } from '@model/KlickTipp'
|
||||
|
||||
import { getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { klickTippLogger as logger } from '@/server/logger'
|
||||
|
||||
export const getKlicktippState = async (email: string): Promise<KlickTipp> => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const klickTippUser = await getKlickTippUser(email)
|
||||
if (klickTippUser) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
return new KlickTipp(klickTippUser.status === 'Subscribed')
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('There is no klicktipp user for email', email, err)
|
||||
}
|
||||
return new KlickTipp(false)
|
||||
}
|
||||
@ -3,12 +3,10 @@ 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,
|
||||
},
|
||||
)
|
||||
): Promise<DbTransaction | null> => {
|
||||
return DbTransaction.findOne({
|
||||
where: { userId },
|
||||
order: { balanceDate: 'DESC', id: 'DESC' },
|
||||
relations,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MoreThan } from '@dbTools/typeorm'
|
||||
import { MoreThan, IsNull } from '@dbTools/typeorm'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
@ -22,7 +22,7 @@ export async function transactionLinkList(
|
||||
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(!withRedeemed && { redeemedBy: null }),
|
||||
...(!withRedeemed && { redeemedBy: IsNull() }),
|
||||
...(!withExpired && { validUntil: MoreThan(new Date()) }),
|
||||
},
|
||||
withDeleted,
|
||||
|
||||
49
backend/src/graphql/resolver/util/transactionLinkSummary.ts
Normal file
49
backend/src/graphql/resolver/util/transactionLinkSummary.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const transactionLinkSummary = async (
|
||||
userId: number,
|
||||
date: Date,
|
||||
): Promise<{
|
||||
sumHoldAvailableAmount: Decimal
|
||||
sumAmount: Decimal
|
||||
lastDate: Date | null
|
||||
firstDate: Date | null
|
||||
transactionLinkcount: number
|
||||
}> => {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
try {
|
||||
await queryRunner.connect()
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } =
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder(DbTransactionLink, 'transactionLink')
|
||||
.select('SUM(transactionLink.holdAvailableAmount)', 'sumHoldAvailableAmount')
|
||||
.addSelect('SUM(transactionLink.amount)', 'sumAmount')
|
||||
.addSelect('MAX(transactionLink.validUntil)', 'lastDate')
|
||||
.addSelect('MIN(transactionLink.createdAt)', 'firstDate')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.where('transactionLink.userId = :userId', { userId })
|
||||
.andWhere('transactionLink.redeemedAt is NULL')
|
||||
.andWhere('transactionLink.validUntil > :date', { date })
|
||||
.orderBy('transactionLink.createdAt', 'DESC')
|
||||
.getRawOne()
|
||||
return {
|
||||
sumHoldAvailableAmount: sumHoldAvailableAmount
|
||||
? new Decimal(sumHoldAvailableAmount)
|
||||
: new Decimal(0),
|
||||
sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0),
|
||||
lastDate: lastDate || null,
|
||||
firstDate: firstDate || null,
|
||||
transactionLinkcount: count || 0,
|
||||
}
|
||||
} catch (err) {
|
||||
throw new LogError('Unable to get transaction link summary', err)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@ describe('validate alias', () => {
|
||||
describe('test against existing alias in database', () => {
|
||||
beforeAll(async () => {
|
||||
const bibi = await userFactory(testEnv, bibiBloxberg)
|
||||
const user = await User.findOne({ id: bibi.id })
|
||||
const user = await User.findOne({ where: { id: bibi.id } })
|
||||
if (user) {
|
||||
user.alias = 'b-b'
|
||||
await user.save()
|
||||
|
||||
@ -1,69 +1,92 @@
|
||||
{
|
||||
"emails": {
|
||||
"accountActivation": {
|
||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||
"activateAccount": "Konto aktivieren",
|
||||
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
||||
"pleaseClickLink": "Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:",
|
||||
"subject": "Gradido: E-Mail Überprüfung"
|
||||
"pleaseClickLink": "Klicke bitte hier, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren.",
|
||||
"subject": "E-Mail Überprüfung",
|
||||
"title": "E-Mail Überprüfung"
|
||||
},
|
||||
"accountMultiRegistration": {
|
||||
"contactSupport": "Support kontaktieren",
|
||||
"emailExists": "Es existiert jedoch zu deiner E-Mail-Adresse schon ein Konto.",
|
||||
"emailReused": "deine E-Mail-Adresse wurde soeben erneut benutzt, um bei Gradido ein Konto zu registrieren.",
|
||||
"ifYouAreNotTheOne": "Wenn du nicht derjenige bist, der versucht hat sich erneut zu registrieren, wende dich bitte an unseren Support:",
|
||||
"onForgottenPasswordClickLink": "Klicke bitte auf den folgenden Link, falls du dein Passwort vergessen haben solltest:",
|
||||
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
||||
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
||||
"ifYouAreNotTheOne": "Wenn du nicht versucht hast dich erneut zu registrieren, wende dich bitte an unseren Support:",
|
||||
"onForgottenPasswordClickLink": "Solltest du dein Passwort vergessen haben, klicke bitte hier.",
|
||||
"subject": "Erneuter Registrierungsversuch mit deiner E-Mail",
|
||||
"title": "Erneuter Registrierungsversuch mit deiner E-Mail"
|
||||
},
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
|
||||
"subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
"readMessage": "Nachricht lesen und beantworten",
|
||||
"subject": "Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"title": "Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."
|
||||
},
|
||||
"contribution": {
|
||||
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."
|
||||
},
|
||||
"contributionConfirmed": {
|
||||
"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"
|
||||
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt. Es wurden deinem Gradido-Konto {amountGDD} GDD gutgeschrieben.",
|
||||
"subject": "Dein Gemeinwohl-Beitrag wurde bestätigt",
|
||||
"title": "Dein Gemeinwohl-Beitrag wurde bestätigt"
|
||||
},
|
||||
"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ü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
"subject": "Dein Gemeinwohl-Beitrag wurde gelöscht",
|
||||
"title": "Dein Gemeinwohl-Beitrag wurde gelöscht"
|
||||
},
|
||||
"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ü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
"subject": "Dein Gemeinwohl-Beitrag wurde abgelehnt",
|
||||
"title": "Dein Gemeinwohl-Beitrag wurde abgelehnt"
|
||||
},
|
||||
"footer": {
|
||||
"contactOurSupport": "Bei weiteren Fragen kontaktiere bitte unseren Support.",
|
||||
"imprint": "Impressum",
|
||||
"imprintAddress": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||
"privacyPolicy": "Datenschutzerklärung",
|
||||
"supportEmail": "support@gradido.net"
|
||||
},
|
||||
"general": {
|
||||
"amountGDD": "Betrag: {amountGDD} GDD",
|
||||
"detailsYouFindOnLinkToYourAccount": "Details zur Transaktion findest du in deinem Gradido-Konto:",
|
||||
"doNotAnswer": "nicht antworten",
|
||||
"completeRegistration": "Registrierung abschließen",
|
||||
"contribution": "Gemeinwohl-Beitrag: {contributionMemo}",
|
||||
"contributionDetails": "Beitragsdetails",
|
||||
"detailsYouFindOnLinkToYourAccount": "Details zur Transaktion findest du in deinem Gradido-Konto.",
|
||||
"helloName": "Hallo {firstName} {lastName},",
|
||||
"linkToYourAccount": "Link zu deinem Konto:",
|
||||
"orCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
||||
"pleaseDoNotReply": "Bitte antworte nicht auf diese E-Mail!",
|
||||
"linkValidity": "Der Link hat eine Gültigkeit von {hours} Stunden.\nSollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen.",
|
||||
"linkValidityWithMinutes": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten.\nSollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen.",
|
||||
"newLink": "Neuer Link",
|
||||
"orCopyLink": "Oder kopiere den Link in dein Browserfenster.",
|
||||
"pleaseDoNotReply": "Bitte antworte nicht auf diese E-Mail.",
|
||||
"requestNewLink": "Neuen gültigen Link anfordern",
|
||||
"reset": "zurücksetzen",
|
||||
"sincerelyYours": "Liebe Grüße",
|
||||
"toAccount": "Zum Konto",
|
||||
"transactionDetails": "Transaktionsdetails",
|
||||
"yourGradidoTeam": "dein Gradido-Team"
|
||||
},
|
||||
"resetPassword": {
|
||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||
"pleaseClickLink": "Wenn du es warst, klicke bitte auf den Link:",
|
||||
"subject": "Gradido: Passwort zurücksetzen",
|
||||
"youOrSomeoneResetPassword": "du, oder jemand anderes, hast für dieses Konto ein Zurücksetzen des Passworts angefordert."
|
||||
"pleaseClickLink": "Wenn du es warst, klicke bitte hier.",
|
||||
"subject": "Passwort zurücksetzen",
|
||||
"title": "Passwort zurücksetzen",
|
||||
"youOrSomeoneResetPassword": "du oder jemand anderes, hast für dieses Konto ein Zurücksetzen des Passworts angefordert."
|
||||
},
|
||||
"transactionLinkRedeemed": {
|
||||
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) hat soeben deinen Link eingelöst.",
|
||||
"memo": "Nachricht: {transactionMemo}",
|
||||
"subject": "Gradido: {senderFirstName} {senderLastName} hat deinen Gradido-Link eingelöst"
|
||||
"subject": "{senderFirstName} {senderLastName} hat deinen Gradido-Link eingelöst",
|
||||
"title": "{senderFirstName} {senderLastName} hat deinen Gradido-Link eingelöst"
|
||||
},
|
||||
"transactionReceived": {
|
||||
"haveReceivedAmountGDDFrom": "du hast soeben {transactionAmount} GDD von {senderFirstName} {senderLastName} ({senderEmail}) erhalten.",
|
||||
"subject": "Gradido: {senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet"
|
||||
"subject": "{senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet",
|
||||
"title": "{senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"decimalSeparator": ",",
|
||||
"imprint": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||
"imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
|
||||
"decimalSeparator": ","
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,69 +1,92 @@
|
||||
{
|
||||
"emails": {
|
||||
"accountActivation": {
|
||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||
"activateAccount": "Activate account",
|
||||
"emailRegistered": "Your email address has just been registered with Gradido.",
|
||||
"pleaseClickLink": "Please click on this link to complete the registration and activate your Gradido account:",
|
||||
"subject": "Gradido: Email Verification"
|
||||
"pleaseClickLink": "Please click here to complete the registration and activate your Gradido account.",
|
||||
"subject": "Email Verification",
|
||||
"title": "Email Verification"
|
||||
},
|
||||
"accountMultiRegistration": {
|
||||
"contactSupport": "Contact support",
|
||||
"emailExists": "However, an account already exists for your email address.",
|
||||
"emailReused": "Your email address has just been used again to register an account with Gradido.",
|
||||
"ifYouAreNotTheOne": "If you are not the one who tried to register again, please contact our support:",
|
||||
"onForgottenPasswordClickLink": "Please click on the following link if you have forgotten your password:",
|
||||
"onForgottenPasswordCopyLink": "or copy the link above into your browser window.",
|
||||
"subject": "Gradido: Try To Register Again With Your Email"
|
||||
"ifYouAreNotTheOne": "If you did not try to register again, please contact our support:",
|
||||
"onForgottenPasswordClickLink": "If you have forgotten your password, please click here.",
|
||||
"subject": "Try To Register Again With Your Email",
|
||||
"title": "Try To Register Again With Your Email"
|
||||
},
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||
"subject": "Gradido: Message about your common good contribution",
|
||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
"commonGoodContributionMessage": "You have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||
"readMessage": "Read and reply to message",
|
||||
"subject": "Message about your common good contribution",
|
||||
"title": "Message about your common good contribution",
|
||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."
|
||||
},
|
||||
"contribution": {
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."
|
||||
},
|
||||
"contributionConfirmed": {
|
||||
"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"
|
||||
"commonGoodContributionConfirmed": "Your common good contribution “{contributionMemo}” has just been approved by {senderFirstName} {senderLastName}. Your Gradido account has been credited with {amountGDD} GDD.",
|
||||
"subject": "Your contribution to the common good was confirmed",
|
||||
"title": "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 “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
"commonGoodContributionDeleted": "Your common good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Your common good contribution was deleted",
|
||||
"title": "Your common good contribution was deleted"
|
||||
},
|
||||
"contributionDenied": {
|
||||
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Gradido: Your common good contribution was rejected",
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
"commonGoodContributionDenied": "Your common good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Your common good contribution was rejected",
|
||||
"title": "Your common good contribution was rejected"
|
||||
},
|
||||
"footer": {
|
||||
"contactOurSupport": "If you have any further questions, please contact our support.",
|
||||
"imprint": "Impressum",
|
||||
"imprintAddress": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"supportEmail": "support@gradido.net"
|
||||
},
|
||||
"general": {
|
||||
"amountGDD": "Amount: {amountGDD} GDD",
|
||||
"detailsYouFindOnLinkToYourAccount": "You can find transaction details in your Gradido account:",
|
||||
"doNotAnswer": "do not answer",
|
||||
"helloName": "Hello {firstName} {lastName}",
|
||||
"linkToYourAccount": "Link to your account:",
|
||||
"orCopyLink": "or copy the link above into your browser window.",
|
||||
"pleaseDoNotReply": "Please do not reply to this email!",
|
||||
"completeRegistration": "Complete registration",
|
||||
"contribution": "Contribution: : {contributionMemo}",
|
||||
"contributionDetails": "Contribution details",
|
||||
"detailsYouFindOnLinkToYourAccount": "You can find transaction details in your Gradido account.",
|
||||
"helloName": "Hello {firstName} {lastName},",
|
||||
"linkValidity": "The link has a validity of {hours} hours.\nIf the validity of the link has already expired, you can have a new link sent to you here.",
|
||||
"linkValidityWithMinutes": "The link has a validity of {hours} hours and {minutes} minutes.\nIf the validity of the link has already expired, you can have a new link sent to you here.",
|
||||
"newLink": "New link",
|
||||
"orCopyLink": "Or copy the link into your browser window.",
|
||||
"pleaseDoNotReply": "Please do not reply to this email.",
|
||||
"requestNewLink": "Request new valid link",
|
||||
"reset": "reset",
|
||||
"sincerelyYours": "Kind regards,",
|
||||
"toAccount": "To account",
|
||||
"transactionDetails": "Transaction details",
|
||||
"yourGradidoTeam": "your Gradido team"
|
||||
},
|
||||
"resetPassword": {
|
||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||
"pleaseClickLink": "If it was you, please click on the link:",
|
||||
"subject": "Gradido: Reset password",
|
||||
"pleaseClickLink": "If it was you, please click here.",
|
||||
"subject": "Reset password",
|
||||
"title": "Reset password",
|
||||
"youOrSomeoneResetPassword": "You, or someone else, requested a password reset for this account."
|
||||
},
|
||||
"transactionLinkRedeemed": {
|
||||
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) has just redeemed your link.",
|
||||
"memo": "Message: {transactionMemo}",
|
||||
"subject": "Gradido: {senderFirstName} {senderLastName} has redeemed your Gradido link"
|
||||
"subject": "{senderFirstName} {senderLastName} has redeemed your Gradido link",
|
||||
"title": "{senderFirstName} {senderLastName} has redeemed your Gradido link"
|
||||
},
|
||||
"transactionReceived": {
|
||||
"haveReceivedAmountGDDFrom": "You have just received {transactionAmount} GDD from {senderFirstName} {senderLastName} ({senderEmail}).",
|
||||
"subject": "Gradido: {senderFirstName} {senderLastName} has sent you {transactionAmount} Gradido"
|
||||
"subject": "{senderFirstName} {senderLastName} has sent you {transactionAmount} Gradido",
|
||||
"title": "{senderFirstName} {senderLastName} has sent you {transactionAmount} Gradido"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"decimalSeparator": ".",
|
||||
"imprint": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||
"imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
|
||||
"decimalSeparator": "."
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { MiddlewareFn } from 'type-graphql'
|
||||
|
||||
import { KlickTipp } from '@model/KlickTipp'
|
||||
|
||||
import { getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { klickTippLogger as logger } from '@/server/logger'
|
||||
|
||||
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
{ root, args, context, info },
|
||||
next,
|
||||
) => {
|
||||
// eslint-disable-next-line n/callback-return
|
||||
const result = await next()
|
||||
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
|
||||
try {
|
||||
const klickTippUser = await getKlickTippUser(result.email)
|
||||
if (klickTippUser) {
|
||||
klickTipp = new KlickTipp(klickTippUser)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`There is no user for (email='${result.email}') ${err}`)
|
||||
}
|
||||
result.klickTipp = klickTipp
|
||||
return result
|
||||
}
|
||||
@ -10,8 +10,19 @@ import { CONFIG } from '@/config'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs
|
||||
const sodium = require('sodium-native')
|
||||
import {
|
||||
crypto_shorthash_KEYBYTES,
|
||||
crypto_box_SEEDBYTES,
|
||||
crypto_hash_sha512_init,
|
||||
crypto_hash_sha512_update,
|
||||
crypto_hash_sha512_final,
|
||||
crypto_hash_sha512_BYTES,
|
||||
crypto_hash_sha512_STATEBYTES,
|
||||
crypto_shorthash_BYTES,
|
||||
crypto_pwhash_SALTBYTES,
|
||||
crypto_pwhash,
|
||||
crypto_shorthash,
|
||||
} from 'sodium-native'
|
||||
|
||||
// We will reuse this for changePassword
|
||||
export const isValidPassword = (password: string): boolean => {
|
||||
@ -22,36 +33,36 @@ export const SecretKeyCryptographyCreateKey = (salt: string, password: string):
|
||||
logger.trace('SecretKeyCryptographyCreateKey...')
|
||||
const configLoginAppSecret = Buffer.from(CONFIG.LOGIN_APP_SECRET, 'hex')
|
||||
const configLoginServerKey = Buffer.from(CONFIG.LOGIN_SERVER_KEY, 'hex')
|
||||
if (configLoginServerKey.length !== sodium.crypto_shorthash_KEYBYTES) {
|
||||
if (configLoginServerKey.length !== crypto_shorthash_KEYBYTES) {
|
||||
throw new LogError(
|
||||
'ServerKey has an invalid size',
|
||||
configLoginServerKey.length,
|
||||
sodium.crypto_shorthash_KEYBYTES,
|
||||
crypto_shorthash_KEYBYTES,
|
||||
)
|
||||
}
|
||||
|
||||
const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES)
|
||||
sodium.crypto_hash_sha512_init(state)
|
||||
sodium.crypto_hash_sha512_update(state, Buffer.from(salt))
|
||||
sodium.crypto_hash_sha512_update(state, configLoginAppSecret)
|
||||
const hash = Buffer.alloc(sodium.crypto_hash_sha512_BYTES)
|
||||
sodium.crypto_hash_sha512_final(state, hash)
|
||||
const state = Buffer.alloc(crypto_hash_sha512_STATEBYTES)
|
||||
crypto_hash_sha512_init(state)
|
||||
crypto_hash_sha512_update(state, Buffer.from(salt))
|
||||
crypto_hash_sha512_update(state, configLoginAppSecret)
|
||||
const hash = Buffer.alloc(crypto_hash_sha512_BYTES)
|
||||
crypto_hash_sha512_final(state, hash)
|
||||
|
||||
const encryptionKey = Buffer.alloc(sodium.crypto_box_SEEDBYTES)
|
||||
const encryptionKey = Buffer.alloc(crypto_box_SEEDBYTES)
|
||||
const opsLimit = 10
|
||||
const memLimit = 33554432
|
||||
const algo = 2
|
||||
sodium.crypto_pwhash(
|
||||
crypto_pwhash(
|
||||
encryptionKey,
|
||||
Buffer.from(password),
|
||||
hash.slice(0, sodium.crypto_pwhash_SALTBYTES),
|
||||
hash.slice(0, crypto_pwhash_SALTBYTES),
|
||||
opsLimit,
|
||||
memLimit,
|
||||
algo,
|
||||
)
|
||||
|
||||
const encryptionKeyHash = Buffer.alloc(sodium.crypto_shorthash_BYTES)
|
||||
sodium.crypto_shorthash(encryptionKeyHash, encryptionKey, configLoginServerKey)
|
||||
const encryptionKeyHash = Buffer.alloc(crypto_shorthash_BYTES)
|
||||
crypto_shorthash(encryptionKeyHash, encryptionKey, configLoginServerKey)
|
||||
|
||||
return [encryptionKeyHash, encryptionKey]
|
||||
}
|
||||
|
||||
@ -19,7 +19,10 @@ export const creationFactory = async (
|
||||
creation: CreationInterface,
|
||||
): Promise<Contribution> => {
|
||||
const { mutate } = client
|
||||
await mutate({ mutation: login, variables: { email: creation.email, password: 'Aa12345_' } })
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: creation.email, password: 'Aa12345_' },
|
||||
})
|
||||
|
||||
const {
|
||||
data: { createContribution: contribution },
|
||||
@ -30,7 +33,9 @@ export const creationFactory = async (
|
||||
|
||||
await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
|
||||
await mutate({ mutation: confirmContribution, variables: { id: contribution.id } })
|
||||
const confirmedContribution = await Contribution.findOneOrFail({ id: contribution.id })
|
||||
const confirmedContribution = await Contribution.findOneOrFail({
|
||||
where: { id: contribution.id },
|
||||
})
|
||||
|
||||
if (creation.moveCreationDate) {
|
||||
const transaction = await Transaction.findOneOrFail({
|
||||
|
||||
@ -32,7 +32,7 @@ export const transactionLinkFactory = async (
|
||||
} = await mutate({ mutation: createTransactionLink, variables })
|
||||
|
||||
if (transactionLink.createdAt || transactionLink.deletedAt) {
|
||||
const dbTransactionLink = await TransactionLink.findOneOrFail({ id })
|
||||
const dbTransactionLink = await TransactionLink.findOneOrFail({ where: { id } })
|
||||
|
||||
if (transactionLink.createdAt) {
|
||||
dbTransactionLink.createdAt = transactionLink.createdAt
|
||||
|
||||
@ -19,7 +19,7 @@ export const userFactory = async (
|
||||
} = await mutate({ mutation: createUser, variables: user })
|
||||
// console.log('creatUser:', { id }, { user })
|
||||
// get user from database
|
||||
let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact'] })
|
||||
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact'] })
|
||||
// console.log('dbUser:', dbUser)
|
||||
|
||||
const emailContact = dbUser.emailContact
|
||||
@ -33,7 +33,7 @@ export const userFactory = async (
|
||||
}
|
||||
|
||||
// get last changes of user from database
|
||||
dbUser = await User.findOneOrFail({ id })
|
||||
dbUser = await User.findOneOrFail({ where: { id } })
|
||||
|
||||
if (user.createdAt || user.deletedAt || user.isAdmin) {
|
||||
if (user.createdAt) dbUser.createdAt = user.createdAt
|
||||
|
||||
@ -50,6 +50,7 @@ export const updateUserInfos = gql`
|
||||
|
||||
export const createUser = gql`
|
||||
mutation (
|
||||
$alias: String
|
||||
$firstName: String!
|
||||
$lastName: String!
|
||||
$email: String!
|
||||
@ -58,6 +59,7 @@ export const createUser = gql`
|
||||
$redeemCode: String
|
||||
) {
|
||||
createUser(
|
||||
alias: $alias
|
||||
email: $email
|
||||
firstName: $firstName
|
||||
lastName: $lastName
|
||||
@ -284,8 +286,12 @@ export const createContributionMessage = gql`
|
||||
`
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
|
||||
adminCreateContributionMessage(
|
||||
contributionId: $contributionId
|
||||
message: $message
|
||||
messageType: $messageType
|
||||
) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user