Merge branch 'master' into more-unit-tests-frontend

This commit is contained in:
Hannes Heine 2023-07-01 14:07:00 +02:00 committed by GitHub
commit e5be42f0f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 3566 additions and 1416 deletions

View File

@ -4,8 +4,64 @@ 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.0](https://github.com/gradido/gradido/compare/1.21.0...1.22.0)
- 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)

View File

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

View File

@ -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')
})
})
})
})

View File

@ -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

View File

@ -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)
})
})
})
})

View File

@ -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>

View File

@ -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', () => {

View File

@ -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>

View File

@ -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,
},
],
},

View File

@ -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',

View File

@ -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()
})
})
})

View File

@ -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>

View 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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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')
})
})

View File

@ -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',

View File

@ -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,

View File

@ -186,7 +186,7 @@ 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: {

View File

@ -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"
@ -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 {

View File

@ -40,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',
@ -197,6 +197,9 @@ module.exports = {
{
files: ['*.test.ts'],
plugins: ['jest'],
env: {
jest: true,
},
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',

View File

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

View File

@ -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',
}

View File

@ -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
}

View File

@ -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

View File

@ -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, {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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}`)

View File

@ -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',

View File

@ -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)
}

View File

@ -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',
}),
]),
},
},
}),
)
})
})
})
})

View File

@ -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,

View File

@ -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',
}),
]),
})
})
})
})
})
})

View File

@ -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)
}

View File

@ -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')
}

View File

@ -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,

View File

@ -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,

View File

@ -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)
}

View File

@ -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(

View File

@ -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

View File

@ -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,

View File

@ -2,7 +2,7 @@
/* 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'
@ -23,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'
@ -65,6 +64,7 @@ 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'
@ -82,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())
}
@ -195,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(
@ -231,6 +239,9 @@ export class UserResolver {
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({
@ -264,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) {
@ -278,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
@ -352,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
}
@ -365,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)
@ -403,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...')
@ -474,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)) {
@ -601,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()),
},
@ -636,7 +651,6 @@ export class UserResolver {
@Ctx() context: Context,
): Promise<SearchUsersResult> {
const clientTimezoneOffset = getClientTimezoneOffset(context)
const userRepository = getCustomRepository(UserRepository)
const userFields = [
'id',
'firstName',
@ -646,7 +660,7 @@ export class UserResolver {
'deletedAt',
'isAdmin',
]
const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered(
const [users, count] = await findUsers(
userFields.map((fieldName) => {
return 'user.' + fieldName
}),
@ -705,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)
@ -734,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
}
@ -744,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)
@ -757,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
}
@ -767,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)
}
@ -819,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
@ -831,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
}

View File

@ -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')],
})
})
})

View File

@ -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,
})
}

View File

@ -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,
})

View File

@ -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)
}

View File

@ -1,20 +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 '@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,
order = Order.ASC,
): 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')
@ -30,27 +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()
}
}

View File

@ -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,
})
}

View File

@ -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,

View 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()
}
}

View File

@ -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()

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -195,7 +195,7 @@ export const listContributions = gql`
confirmedAt
confirmedBy
deletedAt
state
status
messagesCount
deniedAt
deniedBy
@ -218,7 +218,7 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF
confirmedAt
confirmedBy
contributionDate
state
status
messagesCount
deniedAt
deniedBy
@ -235,6 +235,7 @@ export const adminListContributions = gql`
$order: Order = DESC
$statusFilter: [ContributionStatus!]
$userId: Int
$query: String
) {
adminListContributions(
currentPage: $currentPage
@ -242,6 +243,7 @@ export const adminListContributions = gql`
order: $order
statusFilter: $statusFilter
userId: $userId
query: $query
) {
contributionCount
contributionList {
@ -254,7 +256,7 @@ export const adminListContributions = gql`
confirmedAt
confirmedBy
contributionDate
state
status
messagesCount
deniedAt
deniedBy
@ -349,6 +351,29 @@ export const listContributionMessages = gql`
}
`
export const adminListContributionMessages = gql`
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
adminListContributionMessages(
contributionId: $contributionId
pageSize: $pageSize
currentPage: $currentPage
order: $order
) {
count
messages {
id
message
createdAt
updatedAt
type
userFirstName
userLastName
userId
}
}
}
`
export const user = gql`
query ($identifier: String!) {
user(identifier: $identifier) {

View File

@ -1,3 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { entities } from '@entity/index'
import { createTestClient } from 'apollo-server-testing'
import { name, internet, datatype } from 'faker'
@ -36,12 +42,10 @@ export const cleanDB = async () => {
}
}
const [entityTypes] = entities
const resetEntity = async (entity: typeof entityTypes) => {
const resetEntity = async (entity: any) => {
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((i) => i.id)
const ids = items.map((e: any) => e.id)
await entity.delete(ids)
}
}

View File

@ -1,4 +1,5 @@
export interface UserInterface {
alias?: string
email?: string
firstName?: string
lastName?: string

View File

@ -1,6 +1,7 @@
import { UserInterface } from './UserInterface'
export const bobBaumeister: UserInterface = {
alias: 'MeisterBob',
email: 'bob@baumeister.de',
firstName: 'Bob',
lastName: 'der Baumeister',

View File

@ -15,7 +15,7 @@ export interface Context {
clientTimezoneOffset?: number
gradidoID?: string
// hack to use less DB calls for Balance Resolver
lastTransaction?: dbTransaction
lastTransaction?: dbTransaction | null
transactionCount?: number
linkCount?: number
sumHoldAvailableAmount?: Decimal

View File

@ -4,7 +4,7 @@ import { backendLogger as logger } from '@/server/logger'
const getDBVersion = async (): Promise<string | null> => {
try {
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 })
return dbVersion ? dbVersion.fileName : null
} catch (error) {
logger.error(error)

View File

@ -1,41 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Repository, EntityRepository } from '@dbTools/typeorm'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { Decimal } from 'decimal.js-light'
@EntityRepository(dbTransactionLink)
export class TransactionLinkRepository extends Repository<dbTransactionLink> {
async summary(
userId: number,
date: Date,
): Promise<{
sumHoldAvailableAmount: Decimal
sumAmount: Decimal
lastDate: Date | null
firstDate: Date | null
transactionLinkcount: number
}> {
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } =
await this.createQueryBuilder('transactionLinks')
.select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount')
.addSelect('SUM(transactionLinks.amount)', 'sumAmount')
.addSelect('MAX(transactionLinks.validUntil)', 'lastDate')
.addSelect('MIN(transactionLinks.createdAt)', 'firstDate')
.addSelect('COUNT(*)', 'count')
.where('transactionLinks.userId = :userId', { userId })
.andWhere('transactionLinks.redeemedAt is NULL')
.andWhere('transactionLinks.validUntil > :date', { date })
.orderBy('transactionLinks.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,
}
}
}

View File

@ -0,0 +1,4 @@
import { Semaphore } from 'await-semaphore'
const CONCURRENT_TRANSACTIONS = 1
export const TRANSACTION_LINK_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS)

View File

@ -1,6 +1,6 @@
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
export async function hasElopageBuys(email: string): Promise<boolean> {
const elopageBuyCount = await LoginElopageBuys.count({ payerEmail: email })
const elopageBuyCount = await LoginElopageBuys.count({ where: { payerEmail: email } })
return elopageBuyCount > 0
}

View File

@ -1,11 +1,10 @@
import { getCustomRepository } from '@dbTools/typeorm'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { Decimal } from 'decimal.js-light'
import { Decay } from '@model/Decay'
import { TransactionLinkRepository } from '@repository/TransactionLink'
import { getLastTransaction } from '@/graphql/resolver/util/getLastTransaction'
import { transactionLinkSummary } from '@/graphql/resolver/util/transactionLinkSummary'
import { calculateDecay } from './decay'
@ -29,8 +28,7 @@ async function calculateBalance(
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
const balance = decay.balance.add(amount.toString())
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time)
const { sumHoldAvailableAmount } = await transactionLinkSummary(userId, time)
// If we want to redeem a link we need to make sure that the link amount is not considered as blocked
// else we cannot redeem links which are more or equal to half of what an account actually owns

View File

@ -135,7 +135,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
// Do we already have such a user?
// if ((await dbUser.count({ email })) !== 0) {
if ((await dbUserContact.count({ email })) !== 0) {
if ((await dbUserContact.count({ where: { email } })) !== 0) {
// eslint-disable-next-line no-console
console.log(`Did not create User - already exists with email: ${email}`)
return

View File

@ -1,4 +1,10 @@
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { entities } from '@entity/index'
import { createTestClient } from 'apollo-server-testing'
@ -7,7 +13,6 @@ import { createServer } from '@/server/createServer'
import { i18n, logger } from './testSetup'
export const headerPushMock = jest.fn((t) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
context.token = t.value
})
@ -21,7 +26,7 @@ const context = {
}
export const cleanDB = async () => {
// this only works as lond we do not have foreign key constraints
// this only works as long we do not have foreign key constraints
for (const entity of entities) {
await resetEntity(entity)
}
@ -36,12 +41,10 @@ export const testEnvironment = async (testLogger = logger, testI18n = i18n) => {
return { mutate, query, con }
}
const [entityTypes] = entities
export const resetEntity = async (entity: typeof entityTypes) => {
export const resetEntity = async (entity: any) => {
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((i) => i.id)
const ids = items.map((e: any) => e.id)
await entity.delete(ids)
}
}

View File

@ -324,6 +324,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/runtime@^7.21.0":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.15.4", "@babel/template@^7.3.3":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194"
@ -835,7 +842,7 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sqltools/formatter@^1.2.2":
"@sqltools/formatter@^1.2.5":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
@ -1230,11 +1237,6 @@
dependencies:
"@types/yargs-parser" "*"
"@types/zen-observable@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==
"@typescript-eslint/eslint-plugin@^5.57.1":
version "5.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz#d1ab162a3cd2671b8a1c9ddf6e2db73b14439735"
@ -1720,7 +1722,7 @@ apollo-utilities@^1.0.1, apollo-utilities@^1.3.0:
ts-invariant "^0.4.0"
tslib "^1.10.0"
app-root-path@^3.0.0:
app-root-path@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86"
integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==
@ -2010,6 +2012,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -2142,7 +2151,7 @@ chalk@^2.0.0, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.0:
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -2564,6 +2573,13 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.29.3:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"
date-format@^4.0.9:
version "4.0.9"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.9.tgz#4788015ac56dedebe83b03bc361f00c1ddcf1923"
@ -2576,7 +2592,7 @@ debug@2.6.9, debug@^2.2.0:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@ -2818,10 +2834,10 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
dotenv@^16.0.3:
version "16.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
duplexer3@^0.1.4:
version "0.1.4"
@ -3751,6 +3767,17 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^5.0.1"
once "^1.3.0"
global-dirs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
@ -3855,7 +3882,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
mysql2 "^2.3.0"
reflect-metadata "^0.1.13"
ts-mysql-migrate "^1.0.2"
typeorm "^0.2.38"
typeorm "^0.3.16"
uuid "^8.3.2"
grapheme-splitter@^1.0.4:
@ -5062,7 +5089,7 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.0.0, js-yaml@^4.1.0:
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
@ -5487,6 +5514,13 @@ minimatch@^3.0.5, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
@ -5507,10 +5541,10 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mkdirp@^2.1.3:
version "2.1.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
moo@^0.5.0, moo@^0.5.1:
version "0.5.1"
@ -6454,6 +6488,11 @@ reflect-metadata@^0.1.13:
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regexp-tree@~0.1.1:
version "0.1.27"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
@ -6630,11 +6669,6 @@ safe-regex@^2.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
@ -7442,28 +7476,26 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typeorm@^0.2.38:
version "0.2.45"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.45.tgz#e5bbb3af822dc4646bad96cfa48cd22fa4687cea"
integrity sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==
typeorm@^0.3.16:
version "0.3.17"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949"
integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==
dependencies:
"@sqltools/formatter" "^1.2.2"
app-root-path "^3.0.0"
"@sqltools/formatter" "^1.2.5"
app-root-path "^3.1.0"
buffer "^6.0.3"
chalk "^4.1.0"
chalk "^4.1.2"
cli-highlight "^2.1.11"
debug "^4.3.1"
dotenv "^8.2.0"
glob "^7.1.6"
js-yaml "^4.0.0"
mkdirp "^1.0.4"
date-fns "^2.29.3"
debug "^4.3.4"
dotenv "^16.0.3"
glob "^8.1.0"
mkdirp "^2.1.3"
reflect-metadata "^0.1.13"
sha.js "^2.4.11"
tslib "^2.1.0"
uuid "^8.3.2"
xml2js "^0.4.23"
yargs "^17.0.1"
zen-observable-ts "^1.0.0"
tslib "^2.5.0"
uuid "^9.0.0"
yargs "^17.6.2"
typescript@^4.3.4:
version "4.4.3"
@ -7614,6 +7646,11 @@ uuid@^8.0.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v8-to-istanbul@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c"
@ -7853,19 +7890,6 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xml2js@^0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
@ -7917,10 +7941,10 @@ yargs@^16.0.0, yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.0.1:
version "17.7.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967"
integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==
yargs@^17.6.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
@ -7948,15 +7972,7 @@ zen-observable-ts@^0.8.21:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable-ts@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83"
integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==
dependencies:
"@types/zen-observable" "0.8.3"
zen-observable "0.8.15"
zen-observable@0.8.15, zen-observable@^0.8.0:
zen-observable@^0.8.0:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==

View File

@ -32,7 +32,7 @@ export class UserContact extends BaseEntity {
email: string
@Column({ name: 'email_verification_code', type: 'bigint', unsigned: true, unique: true })
emailVerificationCode: BigInt
emailVerificationCode: string
@Column({ name: 'email_opt_in_type_id' })
emailOptInTypeId: number

View File

@ -1,6 +1,6 @@
{
"name": "gradido-database",
"version": "1.21.0",
"version": "1.22.0",
"description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database",
@ -46,7 +46,7 @@
"mysql2": "^2.3.0",
"reflect-metadata": "^0.1.13",
"ts-mysql-migrate": "^1.0.2",
"typeorm": "^0.2.38",
"typeorm": "^0.3.16",
"uuid": "^8.3.2"
},
"engines": {

View File

@ -2,6 +2,13 @@
# yarn lockfile v1
"@babel/runtime@^7.21.0":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
dependencies:
regenerator-runtime "^0.13.11"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
@ -106,10 +113,10 @@
picocolors "^1.0.0"
tslib "^2.5.0"
"@sqltools/formatter@^1.2.2":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
"@sqltools/formatter@^1.2.5":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
@ -173,11 +180,6 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/zen-observable@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==
"@typescript-eslint/eslint-plugin@^5.57.1":
version "5.59.9"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15"
@ -292,11 +294,6 @@ ajv@^6.10.0, ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
@ -307,11 +304,6 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@ -324,10 +316,10 @@ any-promise@^1.0.0:
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
app-root-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad"
integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==
app-root-path@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86"
integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==
arg@^4.1.0:
version "4.1.3"
@ -423,6 +415,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -465,18 +464,7 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chalk@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
dependencies:
ansi-styles "^2.2.1"
escape-string-regexp "^1.0.2"
has-ansi "^2.0.0"
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^4.0.0, chalk@^4.1.0:
chalk@^4.0.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -505,6 +493,15 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -553,6 +550,13 @@ crypto@^1.0.1:
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
date-fns@^2.29.3:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@ -560,7 +564,7 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.1.1, debug@^4.3.1:
debug@^4.1.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@ -658,10 +662,10 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
dotenv@^16.0.3:
version "16.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
emoji-regex@^8.0.0:
version "8.0.0"
@ -746,7 +750,7 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@ -1054,11 +1058,6 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
figlet@^1.1.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.2.tgz#dda34ff233c9a48e36fcff6741aeb5bafe49b634"
integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -1203,17 +1202,16 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.6:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
glob@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
minimatch "^5.0.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^13.19.0:
version "13.20.0"
@ -1274,13 +1272,6 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
dependencies:
ansi-regex "^2.0.0"
has-bigints@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@ -1594,7 +1585,7 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
js-yaml@^4.0.0, js-yaml@^4.1.0:
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
@ -1705,6 +1696,13 @@ minimatch@^3.0.5, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.0:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
@ -1715,10 +1713,10 @@ minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mkdirp@^2.1.3:
version "2.1.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
ms@2.1.2:
version "2.1.2"
@ -1897,11 +1895,6 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parent-require@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977"
integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=
parse5-htmlparser2-tree-adapter@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
@ -2014,6 +2007,11 @@ reflect-metadata@^0.1.13:
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regexp-tree@~0.1.1:
version "0.1.27"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
@ -2114,11 +2112,6 @@ safe-regex@^2.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
@ -2199,6 +2192,15 @@ string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string.prototype.trim@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
@ -2233,13 +2235,6 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@ -2274,11 +2269,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@ -2376,11 +2366,6 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tslib@^2.5.0:
version "2.5.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
@ -2414,28 +2399,26 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
typeorm@^0.2.38:
version "0.2.38"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.38.tgz#2af08079919f6ab04cd17017f9faa2c8d5cd566f"
integrity sha512-M6Y3KQcAREQcphOVJciywf4mv6+A0I/SeR+lWNjKsjnQ+a3XcMwGYMGL0Jonsx3H0Cqlf/3yYqVki1jIXSK/xg==
typeorm@^0.3.16:
version "0.3.17"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949"
integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==
dependencies:
"@sqltools/formatter" "^1.2.2"
app-root-path "^3.0.0"
"@sqltools/formatter" "^1.2.5"
app-root-path "^3.1.0"
buffer "^6.0.3"
chalk "^4.1.0"
chalk "^4.1.2"
cli-highlight "^2.1.11"
debug "^4.3.1"
dotenv "^8.2.0"
glob "^7.1.6"
js-yaml "^4.0.0"
mkdirp "^1.0.4"
date-fns "^2.29.3"
debug "^4.3.4"
dotenv "^16.0.3"
glob "^8.1.0"
mkdirp "^2.1.3"
reflect-metadata "^0.1.13"
sha.js "^2.4.11"
tslib "^2.1.0"
xml2js "^0.4.23"
yargonaut "^1.1.4"
yargs "^17.0.1"
zen-observable-ts "^1.0.0"
tslib "^2.5.0"
uuid "^9.0.0"
yargs "^17.6.2"
typescript@^4.3.5:
version "4.3.5"
@ -2474,6 +2457,11 @@ uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
@ -2523,19 +2511,6 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xml2js@^0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
@ -2551,20 +2526,16 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargonaut@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c"
integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==
dependencies:
chalk "^1.1.1"
figlet "^1.1.1"
parent-require "^1.0.0"
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^16.0.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@ -2578,18 +2549,18 @@ yargs@^16.0.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.0.1:
version "17.2.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
yargs@^17.6.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^7.0.2"
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs-parser "^21.1.1"
yn@3.1.1:
version "3.1.1"
@ -2600,16 +2571,3 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zen-observable-ts@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83"
integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==
dependencies:
"@types/zen-observable" "0.8.3"
zen-observable "0.8.15"
zen-observable@0.8.15:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==

View File

@ -234,7 +234,8 @@ crontab -l
This show all existing entries of the crontab for user `gradido`
To install/add the cronjob for a daily backup at 3:00am please
To install/add the cronjob for a daily backup at 3:00am please,
To install/add the cronjob for a daily klicktipp export at 4:00am please,
Run:
@ -244,4 +245,5 @@ crontab -e
and insert the following line
```bash
0 3 * * * ~/gradido/deployment/bare_metal/backup.sh
0 4 * * * cd ~/gradido/backend/ && yarn klicktipp && cd
```

View File

@ -1,6 +1,6 @@
{
"name": "gradido-dht-node",
"version": "1.21.0",
"version": "1.22.0",
"description": "Gradido dht-node module",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/",
@ -21,9 +21,7 @@
"dotenv": "10.0.0",
"log4js": "^6.7.1",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typescript": "^4.9.4",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -31,9 +29,9 @@
"@types/dotenv": "^8.2.0",
"@types/jest": "^27.0.2",
"@types/node": "^18.11.18",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"@types/uuid": "^8.3.4",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
@ -44,9 +42,11 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-security": "^1.7.1",
"prettier": "^2.8.7",
"jest": "^27.2.4",
"ts-jest": "^27.0.5"
"prettier": "^2.8.7",
"ts-jest": "^27.0.5",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"engines": {
"node": ">=14"

View File

@ -340,7 +340,7 @@ describe('federation', () => {
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbFederatedCommunity.find({ foreign: true })
result = await DbFederatedCommunity.find({ where: { foreign: true } })
})
afterAll(async () => {
@ -601,7 +601,7 @@ describe('federation', () => {
{ api: 'toolong api', url: 'some valid url' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbFederatedCommunity.find({ foreign: true })
result = await DbFederatedCommunity.find({ where: { foreign: true } })
})
afterAll(async () => {
@ -655,7 +655,7 @@ describe('federation', () => {
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbFederatedCommunity.find({ foreign: true })
result = await DbFederatedCommunity.find({ where: { foreign: true } })
})
afterAll(async () => {
@ -791,7 +791,7 @@ describe('federation', () => {
]),
),
)
result = await DbFederatedCommunity.find({ foreign: true })
result = await DbFederatedCommunity.find({ where: { foreign: true } })
})
afterAll(async () => {

View File

@ -217,7 +217,7 @@ async function writeFederatedHomeCommunityEntries(pubKey: string): Promise<Commu
async function writeHomeCommunityEntry(keyPair: KeyPair): Promise<void> {
try {
// check for existing homeCommunity entry
let homeCom = await DbCommunity.findOne({ foreign: false })
let homeCom = await DbCommunity.findOne({ where: { foreign: false } })
if (homeCom) {
// simply update the existing entry, but it MUST keep the ID and UUID because of possible relations
homeCom.publicKey = keyPair.publicKey

View File

@ -3,7 +3,7 @@ import { logger } from '@/server/logger'
const getDBVersion = async (): Promise<string | null> => {
try {
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 })
return dbVersion ? dbVersion.fileName : null
} catch (error) {
logger.error(error)

View File

@ -1 +1,2 @@
node_modules
playwright

View File

@ -2,13 +2,13 @@ module.exports = {
root: true,
env: {
node: true,
cypress: true,
},
parser: '@typescript-eslint/parser',
plugins: ['cypress', 'prettier', '@typescript-eslint' /*, 'jest' */],
extends: [
'standard',
'eslint:recommended',
'plugin:cypress/recommended',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended',
],

View File

@ -6,7 +6,7 @@ let emailLink: string
async function setupNodeEvents(
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
config: Cypress.PluginConfigOptions,
): Promise<Cypress.PluginConfigOptions> {
await addCucumberPreprocessorPlugin(on, config)
@ -14,7 +14,7 @@ async function setupNodeEvents(
'file:preprocessor',
browserify(config, {
typescript: require.resolve('typescript'),
})
}),
)
on('task', {
@ -41,7 +41,6 @@ export default defineConfig({
e2e: {
specPattern: '**/*.feature',
excludeSpecPattern: '*.js',
experimentalSessionAndOrigin: true,
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false,
defaultCommandTimeout: 10000,
@ -49,10 +48,7 @@ export default defineConfig({
viewportHeight: 720,
viewportWidth: 1280,
video: false,
retries: {
runMode: 2,
openMode: 0,
},
retries: 0,
env: {
backendURL: 'http://localhost:4000',
mailserverURL: 'http://localhost:1080',

View File

@ -0,0 +1,38 @@
Feature: Send coins
As a user
I want to send and receive GDD
I want to see transaction details on overview and transactions pages
# Background:
# Given the following "users" are in the database:
# | email | password | name |
# | bob@baumeister.de | Aa12345_ | Bob Baumeister |
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
Scenario: Send GDD to other user
Given the user is logged in as "bob@baumeister.de" "Aa12345_"
And the user navigates to page "/send"
When the user fills the send form with "<receiverEmail>" "<amount>" "<memoText>"
And the user submits the send form
Then the transaction details are presented for confirmation "<receiverEmail>" "<amount>" "<memoText>" "<senderBalance>" "<newSenderBalance>"
When the user submits the transaction by confirming
Then the "<receiverName>" and "<amount>" are displayed on the "send" page
When the user navigates to page "/transactions"
Then the "<receiverName>" and "<amount>" are displayed on the "transactions" page
Examples:
| receiverName | receiverEmail | amount | memoText | senderBalance | newSenderBalance |
| Räuber Hotzenplotz | raeuber@hotzenplotz.de | 120.50 | Some memo text | 515.11 | 394.61 |
Scenario: Receive GDD from other user
Given the user is logged in as "raeuber@hotzenplotz.de" "Aa12345_"
And the user receives the transaction e-mail about "<amount>" GDD from "<senderName>"
When the user opens the "transaction" link in the browser
Then the "<senderName>" and "120.50" are displayed on the "overview" page
When the user navigates to page "/transactions"
Then the "<senderName>" and "120.50" are displayed on the "transactions" page
Examples:
| senderName | amount |
| Bob der Baumeister | 120,50 |

View File

@ -2,13 +2,27 @@ Feature: User Authentication - reset password
As a user
I want to reset my password from the sign in page
# TODO for these pre-conditions utilize seeding or API check, if user exists in test system
# Background:
# Given the following "users" are in the database:
# | email | password | name |
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
Scenario: Reset password as not registered user
Given the user navigates to page "/login"
And the user navigates to the forgot password page
When the user enters the e-mail address "not@registered.com"
And the user submits the e-mail form
Then the user receives no password reset e-mail
Scenario: Reset password as deleted user
# Given the following "users" are in the database:
# | email | password | name |
# | stephen@hawking.uk | Aa12345_ | Stephen Hawking |
Given the user navigates to page "/login"
And the user navigates to the forgot password page
When the user enters the e-mail address "stephen@hawking.uk"
And the user submits the e-mail form
Then the user receives no password reset e-mail
Scenario: Reset password from signin page successfully
# Given the following "users" are in the database:
# | email | password | name |
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
Given the user navigates to page "/login"
And the user navigates to the forgot password page
When the user enters the e-mail address "raeuber@hotzenplotz.de"
@ -23,3 +37,6 @@ Feature: User Authentication - reset password
And the user cannot login
But the user submits the credentials "raeuber@hotzenplotz.de" "12345Aa_"
And the user is logged in with username "Räuber Hotzenplotz"

View File

@ -2,6 +2,7 @@
export class OverviewPage {
navbarName = '[data-test="navbar-item-username"]'
rightLastTransactionsList = '.rightside-last-transactions'
goto() {
cy.visit('/overview')

View File

@ -14,9 +14,7 @@ export class ResetPasswordPage {
}
repeatNewPassword(password: string) {
cy.get(this.newPasswordRepeatInput)
.find('input[type=password]')
.type(password)
cy.get(this.newPasswordRepeatInput).find('input[type=password]').type(password)
return this
}

View File

@ -0,0 +1,25 @@
/// <reference types='cypress' />
export class SendPage {
confirmationBox = '.transaction-confirm-send'
submitBtn = '.btn-gradido'
enterReceiverEmail(email: string) {
cy.get('[data-test="input-identifier"]').find('input').clear().type(email)
return this
}
enterAmount(amount: string) {
cy.get('[data-test="input-amount"]').find('input').clear().type(amount)
return this
}
enterMemoText(text: string) {
cy.get('[data-test="input-textarea"]').find('textarea').clear().type(text)
return this
}
submit() {
cy.get(this.submitBtn).click()
}
}

View File

@ -8,10 +8,7 @@ export class UserEMailSite {
emailSubject = '.subject'
openRecentPasswordResetEMail() {
cy.get(this.emailList)
.find('email-item')
.filter(':contains(asswor)')
.click()
cy.get(this.emailList).find('email-item').filter(':contains(asswor)').click()
expect(cy.get(this.emailSubject)).to('contain', 'asswor')
}
}

View File

@ -7,6 +7,7 @@ import './e2e'
declare global {
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): Chainable<any>
}

View File

@ -1,4 +1,4 @@
import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
import { OverviewPage } from '../../e2e/models/OverviewPage'
import { SideNavMenu } from '../../e2e/models/SideNavMenu'
import { Toasts } from '../../e2e/models/Toasts'
@ -9,12 +9,9 @@ Given('the user navigates to page {string}', (page: string) => {
// login related
Given(
'the user is logged in as {string} {string}',
(email: string, password: string) => {
cy.login(email, password)
}
)
Given('the user is logged in as {string} {string}', (email: string, password: string) => {
cy.login(email, password)
})
Then('the user is logged in with username {string}', (username: string) => {
const overviewPage = new OverviewPage()

View File

@ -1,9 +1,9 @@
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { OverviewPage } from '../../e2e/models/OverviewPage'
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
import { UserEMailSite } from '../../e2e/models/UserEMailSite'
const userEMailSite = new UserEMailSite()
const resetPasswordPage = new ResetPasswordPage()
Then('the user receives an e-mail containing the {string} link', (linkName: string) => {
let emailSubject: string
@ -18,14 +18,20 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
emailSubject = 'asswor'
linkPattern = /\/reset-password\/[0-9]+\d/
break
case 'transaction':
emailSubject = 'Gradido gesendet'
linkPattern = /\/overview/
break
default:
throw new Error(`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`)
throw new Error(
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`,
)
}
cy.origin(
Cypress.env('mailserverURL'),
{ args: { emailSubject, linkPattern, userEMailSite } },
({ emailSubject, linkPattern, userEMailSite }) => {
({ emailSubject, linkPattern, userEMailSite }) => {
cy.visit('/') // navigate to user's e-mail site (on fake mail server)
cy.get(userEMailSite.emailInbox).should('be.visible')
@ -35,11 +41,9 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
.first()
.click()
cy.get(userEMailSite.emailMeta)
.find(userEMailSite.emailSubject)
.contains(emailSubject)
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(emailSubject)
cy.get('.email-content', { timeout: 2000})
cy.get('.email-content', { timeout: 2000 })
.find('.plain-text')
.contains(linkPattern)
.invoke('text')
@ -47,13 +51,79 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
const emailLink = text.match(linkPattern)[0]
cy.task('setEmailLink', emailLink)
})
}
},
)
})
Then('the user receives no password reset e-mail', () => {
cy.origin(Cypress.env('mailserverURL'), { args: { userEMailSite } }, ({ userEMailSite }) => {
cy.visit('/')
cy.wait(300)
cy.get(userEMailSite.emailInbox).should('be.visible')
cy.get(userEMailSite.emailList).then(($emailList) => {
const emailItems = $emailList.find('.email-item')
if (emailItems.length > 0) {
expect(emailItems.filter(`:contains("asswor")`).length).to.equal(0)
}
})
})
})
When(
'the user receives the transaction e-mail about {string} GDD from {string}',
(amount: string, senderName: string) => {
cy.origin(
Cypress.env('mailserverURL'),
{ args: { amount, senderName, userEMailSite } },
({ amount, senderName, userEMailSite }) => {
const subject = `${senderName} hat dir ${amount} Gradido gesendet`
const linkPattern = /\/transactions/
cy.visit('/')
cy.get(userEMailSite.emailInbox).should('be.visible')
cy.get(userEMailSite.emailList)
.find('.email-item')
.filter(`:contains(${subject})`)
.first()
.click()
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(subject)
cy.get('.email-content', { timeout: 2000 })
.find('.plain-text')
.contains(linkPattern)
.invoke('text')
.then((text) => {
const emailLink = text.match(linkPattern)[0]
cy.task('setEmailLink', emailLink)
})
},
)
},
)
When('the user opens the {string} link in the browser', (linkName: string) => {
const resetPasswordPage = new ResetPasswordPage()
cy.task('getEmailLink').then((emailLink) => {
cy.visit(emailLink)
})
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
switch (linkName) {
case 'activation':
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
break
case 'password reset':
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
break
case 'transaction':
// eslint-disable-next-line no-case-declarations
const overviewPage = new OverviewPage()
cy.get(overviewPage.rightLastTransactionsList).should('be.visible')
break
default:
throw new Error(
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect link name string "${linkName}"`,
)
}
})

View File

@ -0,0 +1,90 @@
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
import { SendPage } from '../../e2e/models/SendPage'
const sendPage = new SendPage()
When(
'the user fills the send form with {string} {string} {string}',
(email: string, amount: string, memoText: string) => {
sendPage.enterReceiverEmail(email)
sendPage.enterAmount(amount)
sendPage.enterMemoText(memoText)
},
)
When('the user submits the send form', () => {
sendPage.submit()
cy.get(sendPage.confirmationBox).should('be.visible')
})
Then(
'the transaction details are presented for confirmation {string} {string} {string} {string} {string}',
(
receiverEmail: string,
sendAmount: string,
memoText: string,
senderBalance: string,
newSenderBalance: string,
) => {
cy.get('.transaction-confirm-send').contains(receiverEmail)
cy.get('.transaction-confirm-send').contains(`+ ${sendAmount} GDD`)
cy.get('.transaction-confirm-send').contains(memoText)
cy.get('.transaction-confirm-send').contains(`+ ${senderBalance} GDD`)
cy.get('.transaction-confirm-send').contains(` ${sendAmount} GDD`)
cy.get('.transaction-confirm-send').contains(`+ ${newSenderBalance} GDD`)
},
)
When('the user submits the transaction by confirming', () => {
cy.intercept({
method: 'POST',
url: '/graphql',
hostname: 'localhost',
}).as('sendCoins')
sendPage.submit()
cy.wait('@sendCoins').then((interception) => {
cy.wrap(interception.response?.statusCode).should('eq', 200)
cy.wrap(interception.request.body).should(
'have.property',
'query',
`mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
}
`,
)
cy.wrap(interception.response?.body)
.should('have.nested.property', 'data.sendCoins')
.and('equal', true)
})
cy.get('[data-test="send-transaction-success-text"]').should('be.visible')
})
Then(
'the {string} and {string} are displayed on the {string} page',
(name: string, amount: string, page: string) => {
switch (page) {
case 'overview':
cy.get('.align-items-center').contains(`${name}`)
cy.get('.align-items-center').contains(`${amount} GDD`)
break
case 'send':
cy.get('.align-items-center').contains(`${name}`)
cy.get('.align-items-center').contains(`${amount} GDD`)
break
case 'transactions':
cy.get('div.mt-3 > div > div.test-list-group-item')
.eq(0)
.contains('div.gdd-transaction-list-item-name', `${name}`)
cy.get('div.mt-3 > div > div.test-list-group-item')
.eq(0)
.contains('[data-test="transaction-amount"]', `${amount} GDD`)
break
default:
throw new Error(
`Error in "Then the {string} and {string} are displayed on the {string}} page" step: incorrect page name string "${page}"`,
)
}
},
)

View File

@ -1,4 +1,4 @@
import { When, And } from '@badeball/cypress-cucumber-preprocessor'
import { When } from '@badeball/cypress-cucumber-preprocessor'
import { ForgotPasswordPage } from '../../e2e/models/ForgotPasswordPage'
import { LoginPage } from '../../e2e/models/LoginPage'
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
@ -13,30 +13,25 @@ When('the user submits no credentials', () => {
loginPage.submitLogin()
})
When(
'the user submits the credentials {string} {string}',
(email: string, password: string) => {
cy.intercept('POST', '/graphql', (req) => {
if (
req.body.hasOwnProperty('query') &&
req.body.query.includes('mutation')
) {
req.alias = 'login'
}
})
When('the user submits the credentials {string} {string}', (email: string, password: string) => {
cy.intercept('POST', '/graphql', (req) => {
// eslint-disable-next-line no-prototype-builtins
if (req.body.hasOwnProperty('query') && req.body.query.includes('mutation')) {
req.alias = 'login'
}
})
loginPage.enterEmail(email)
loginPage.enterPassword(password)
loginPage.submitLogin()
cy.wait('@login').then((interception) => {
expect(interception.response.statusCode).equals(200)
})
}
)
loginPage.enterEmail(email)
loginPage.enterPassword(password)
loginPage.submitLogin()
cy.wait('@login').then((interception) => {
expect(interception.response.statusCode).equals(200)
})
})
// password reset related
And('the user navigates to the forgot password page', () => {
When('the user navigates to the forgot password page', () => {
loginPage.openForgotPasswordPage()
cy.url().should('include', '/forgot-password')
})
@ -45,25 +40,25 @@ When('the user enters the e-mail address {string}', (email: string) => {
forgotPasswordPage.enterEmail(email)
})
And('the user submits the e-mail form', () => {
When('the user submits the e-mail form', () => {
forgotPasswordPage.submitEmail()
cy.get(forgotPasswordPage.successComponent).should('be.visible')
})
And('the user enters the password {string}', (password: string) => {
When('the user enters the password {string}', (password: string) => {
resetPasswordPage.enterNewPassword(password)
})
And('the user repeats the password {string}', (password: string) => {
When('the user repeats the password {string}', (password: string) => {
resetPasswordPage.repeatNewPassword(password)
})
And('the user submits the new password', () => {
When('the user submits the new password', () => {
resetPasswordPage.submitNewPassword()
cy.get(resetPasswordPage.resetPasswordMessageBlock).should('be.visible')
})
And('the user clicks the sign in button', () => {
When('the user clicks the sign in button', () => {
resetPasswordPage.openSigninPage()
cy.url().should('contain', '/login')
})

View File

@ -1,28 +1,28 @@
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
import { DataTable, When } from '@badeball/cypress-cucumber-preprocessor'
import { ProfilePage } from '../../e2e/models/ProfilePage'
import { Toasts } from '../../e2e/models/Toasts'
const profilePage = new ProfilePage()
And('the user opens the change password menu', () => {
When('the user opens the change password menu', () => {
cy.get(profilePage.openChangePassword).click()
cy.get(profilePage.newPasswordRepeatInput).should('be.visible')
cy.get(profilePage.submitNewPasswordBtn).should('be.disabled')
cy.get(profilePage.submitNewPasswordBtn).should('have.class','btn-light')
})
When('the user fills the password form with:', (table) => {
let hashedTableRows = table.rowsHash()
When('the user fills the password form with:', (table: DataTable) => {
const hashedTableRows = table.rowsHash()
profilePage.enterOldPassword(hashedTableRows['Old password'])
profilePage.enterNewPassword(hashedTableRows['New password'])
profilePage.enterRepeatPassword(hashedTableRows['Repeat new password'])
cy.get(profilePage.submitNewPasswordBtn).should('be.enabled')
})
And('the user submits the password form', () => {
When('the user submits the password form', () => {
profilePage.submitPasswordForm()
})
When('the user is presented a {string} message', (type: string) => {
When('the user is presented a {string} message', () => {
const toast = new Toasts()
cy.get(toast.toastSlot).within(() => {
cy.get(toast.toastTypeSuccess)

View File

@ -1,4 +1,4 @@
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
import { When } from '@badeball/cypress-cucumber-preprocessor'
import { RegistrationPage } from '../../e2e/models/RegistrationPage'
const registrationPage = new RegistrationPage()
@ -10,14 +10,14 @@ When(
registrationPage.enterFirstname(firstname)
registrationPage.enterLastname(lastname)
registrationPage.enterEmail(email)
}
},
)
And('the user agrees to the privacy policy', () => {
When('the user agrees to the privacy policy', () => {
registrationPage.checkPrivacyCheckbox()
})
And('the user submits the registration form', () => {
When('the user submits the registration form', () => {
registrationPage.submitRegistrationForm()
cy.get(registrationPage.RegistrationThanxHeadline).should('be.visible')
cy.get(registrationPage.RegistrationThanxText).should('be.visible')

View File

@ -18,20 +18,20 @@
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
},
"dependencies": {
"@badeball/cypress-cucumber-preprocessor": "^12.0.0",
"@badeball/cypress-cucumber-preprocessor": "^18.0.1",
"@cypress/browserify-preprocessor": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"cypress": "^12.7.0",
"cypress": "^12.16.0",
"eslint": "^8.23.1",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
"eslint-loader": "^4.0.2",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-promise": "^6.1.1",
"jwt-decode": "^3.1.2",
"prettier": "^2.7.1",
"typescript": "^4.7.4"

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More