Merge branch 'master' into 2682-Desktop-Sidebar-Style

This commit is contained in:
Alexander Friedland 2023-03-01 14:43:41 +01:00 committed by GitHub
commit 5df9aecef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 271 additions and 606 deletions

View File

@ -1,4 +1,4 @@
name: gradido test_dht-node CI
name: Gradido DHT Node Test CI
on: push
@ -7,7 +7,7 @@ jobs:
# JOB: DOCKER BUILD TEST #####################################################
##############################################################################
build:
name: Docker Build Test
name: Docker Build Test - DHT Node
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -28,7 +28,7 @@ jobs:
# JOB: LINT ##################################################################
##############################################################################
lint:
name: Lint
name: Lint - DHT Node
runs-on: ubuntu-latest
needs: [build]
steps:
@ -50,7 +50,7 @@ jobs:
# JOB: UNIT TEST #############################################################
##############################################################################
unit_test:
name: Unit tests
name: Unit Tests - DHT Node
runs-on: ubuntu-latest
needs: [build]
steps:
@ -90,7 +90,7 @@ jobs:
- name: Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage dht-node
report_name: Coverage DHT Node
type: lcov
#result_path: ./dht-node/coverage/lcov.info
result_path: ./coverage/lcov.info

View File

@ -1,4 +1,4 @@
name: gradido test_federation CI
name: Gradido Federation Test CI
on: push
@ -7,7 +7,7 @@ jobs:
# JOB: DOCKER BUILD TEST #####################################################
##############################################################################
build:
name: Docker Build Test
name: Docker Build Test - Federation
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -28,7 +28,7 @@ jobs:
# JOB: LINT ##################################################################
##############################################################################
lint:
name: Lint
name: Lint - Federation
runs-on: ubuntu-latest
needs: [build]
steps:
@ -50,7 +50,7 @@ jobs:
# JOB: UNIT TEST #############################################################
##############################################################################
unit_test:
name: Unit tests
name: Unit Tests - Federation
runs-on: ubuntu-latest
needs: [build]
steps:
@ -90,7 +90,7 @@ jobs:
- name: Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage federation
report_name: Coverage Federation
type: lcov
#result_path: ./federation/coverage/lcov.info
result_path: ./coverage/lcov.info

View File

@ -1,7 +1,6 @@
import { mount } from '@vue/test-utils'
import CreationFormular from './CreationFormular'
import { adminCreateContribution } from '../graphql/adminCreateContribution'
import { adminCreateContributions } from '../graphql/adminCreateContributions'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
const localVue = global.localVue
@ -328,122 +327,6 @@ describe('CreationFormular', () => {
})
})
})
describe('mass creation with success', () => {
beforeEach(async () => {
jest.clearAllMocks()
apolloMutateMock.mockResolvedValue({
data: {
adminCreateContributions: {
success: true,
successfulContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'],
failedContribution: [],
},
},
})
await wrapper.setProps({
type: 'massCreation',
creation: [200, 400, 600],
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
})
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
await wrapper.find('textarea').setValue('Test mass create coins')
await wrapper.find('input[type="number"]').setValue(200)
await wrapper.find('.test-submit').trigger('click')
})
it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
mutation: adminCreateContributions,
variables: {
pendingCreations: [
{
email: 'bob@baumeister.de',
creationDate: getCreationDate(1),
amount: 200,
memo: 'Test mass create coins',
},
{
email: 'bibi@bloxberg.de',
creationDate: getCreationDate(1),
amount: 200,
memo: 'Test mass create coins',
},
],
},
}),
)
})
it('updates open creations in store', () => {
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 2)
})
it('emits remove-all-bookmark', () => {
expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy()
})
})
describe('mass creation with success but all failed', () => {
beforeEach(async () => {
jest.clearAllMocks()
apolloMutateMock.mockResolvedValue({
data: {
adminCreateContributions: {
success: true,
successfulContribution: [],
failedContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'],
},
},
})
await wrapper.setProps({
type: 'massCreation',
creation: [200, 400, 600],
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
})
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
await wrapper.find('textarea').setValue('Test mass create coins')
await wrapper.find('input[type="number"]').setValue(200)
await wrapper.find('.test-submit').trigger('click')
})
it('updates open creations in store', () => {
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 0)
})
it('emits remove all bookmarks', () => {
expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy()
})
it('emits toast failed creations with two emails', () => {
expect(wrapper.emitted('toast-failed-creations')).toEqual([
[['bob@baumeister.de', 'bibi@bloxberg.de']],
])
})
})
describe('mass creation with error', () => {
beforeEach(async () => {
jest.clearAllMocks()
apolloMutateMock.mockRejectedValue({
message: 'Oh no!',
})
await wrapper.setProps({
type: 'massCreation',
creation: [200, 400, 600],
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
})
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
await wrapper.find('textarea').setValue('Test mass create coins')
await wrapper.find('input[type="number"]').setValue(200)
await wrapper.find('.test-submit').trigger('click')
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
})
})
})

View File

@ -86,16 +86,11 @@
</template>
<script>
import { adminCreateContribution } from '../graphql/adminCreateContribution'
import { adminCreateContributions } from '../graphql/adminCreateContributions'
import { creationMonths } from '../mixins/creationMonths'
export default {
name: 'CreationFormular',
mixins: [creationMonths],
props: {
type: {
type: String,
required: false,
},
pagetype: {
type: String,
required: false,
@ -140,78 +135,38 @@ export default {
updateRadioSelected(name) {
// do we want to reset the memo everytime the month changes?
this.text = this.$t('creation_form.creation_for') + ' ' + name.short + ' ' + name.year
if (this.type === 'singleCreation') {
this.rangeMin = 0
this.rangeMax = name.creation
}
this.rangeMin = 0
this.rangeMax = name.creation
},
submitCreation() {
let submitObj = []
if (this.type === 'massCreation') {
this.items.forEach((item) => {
submitObj.push({
email: item.email,
this.$apollo
.mutate({
mutation: adminCreateContribution,
variables: {
email: this.item.email,
creationDate: this.selected.date,
amount: Number(this.value),
memo: this.text,
})
},
})
.then((result) => {
this.$emit('update-user-data', this.item, result.data.adminCreateContribution)
this.$store.commit('openCreationsPlus', 1)
this.toastSuccess(
this.$t('creation_form.toasted', {
value: this.value,
email: this.item.email,
}),
)
// what is this? Tests says that this.text is not reseted
this.$refs.creationForm.reset()
this.value = 0
})
.catch((error) => {
this.toastError(error.message)
this.$refs.creationForm.reset()
this.value = 0
})
this.$apollo
.mutate({
mutation: adminCreateContributions,
variables: {
pendingCreations: submitObj,
},
fetchPolicy: 'no-cache',
})
.then((result) => {
const failedContributions = []
this.$store.commit(
'openCreationsPlus',
result.data.adminCreateContributions.successfulContribution.length,
)
if (result.data.adminCreateContributions.failedContribution.length > 0) {
result.data.adminCreateContributions.failedContribution.forEach((email) => {
failedContributions.push(email)
})
}
this.$emit('remove-all-bookmark')
this.$emit('toast-failed-creations', failedContributions)
})
.catch((error) => {
this.toastError(error.message)
})
} else if (this.type === 'singleCreation') {
submitObj = {
email: this.item.email,
creationDate: this.selected.date,
amount: Number(this.value),
memo: this.text,
}
this.$apollo
.mutate({
mutation: adminCreateContribution,
variables: submitObj,
})
.then((result) => {
this.$emit('update-user-data', this.item, result.data.adminCreateContribution)
this.$store.commit('openCreationsPlus', 1)
this.toastSuccess(
this.$t('creation_form.toasted', {
value: this.value,
email: this.item.email,
}),
)
// what is this? Tests says that this.text is not reseted
this.$refs.creationForm.reset()
this.value = 0
})
.catch((error) => {
this.toastError(error.message)
this.$refs.creationForm.reset()
this.value = 0
})
}
},
},
watch: {

View File

@ -68,14 +68,11 @@ describe('NavBar', () => {
})
describe('wallet', () => {
const windowLocationMock = jest.fn()
const windowLocation = window.location
beforeEach(async () => {
delete window.location
window.location = {
assign: windowLocationMock,
}
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
window.location = ''
await wrapper.findAll('.nav-item').at(4).find('a').trigger('click')
})
afterEach(() => {
@ -83,8 +80,8 @@ describe('NavBar', () => {
window.location = windowLocation
})
it.skip('changes window location to wallet', () => {
expect(windowLocationMock()).toBe('valid-token')
it('changes window location to wallet', () => {
expect(window.location).toBe('http://localhost/authenticate?token=valid-token')
})
it('dispatches logout to store', () => {

View File

@ -53,7 +53,6 @@
<b-tab :title="$t('creation')" active :disabled="row.item.deletedAt !== null">
<creation-formular
v-if="!row.item.deletedAt"
type="singleCreation"
pagetype="singleCreation"
:creation="row.item.creation"
:item="row.item"

View File

@ -9,8 +9,8 @@ const apolloQueryMock = jest.fn()
apolloQueryMock.mockResolvedValue({
data: {
listTransactionLinksAdmin: {
linkCount: 8,
linkList: [
count: 8,
links: [
{
amount: '19.99',
code: '62ef8236ace7217fbd066c5a',

View File

@ -42,8 +42,8 @@ export default {
},
})
.then((result) => {
this.rows = result.data.listTransactionLinksAdmin.linkCount
this.items = result.data.listTransactionLinksAdmin.linkList
this.rows = result.data.listTransactionLinksAdmin.count
this.items = result.data.listTransactionLinksAdmin.links
})
.catch((error) => {
this.toastError(error.message)

View File

@ -1,11 +0,0 @@
import gql from 'graphql-tag'
export const adminCreateContributions = gql`
mutation ($pendingCreations: [AdminCreateContributionArgs!]!) {
adminCreateContributions(pendingCreations: $pendingCreations) {
success
successfulContribution
failedContribution
}
}
`

View File

@ -8,8 +8,8 @@ export const listTransactionLinksAdmin = gql`
userId: $userId
filters: { withRedeemed: true, withExpired: true, withDeleted: true }
) {
linkCount
linkList {
count
links {
id
amount
holdAvailableAmount

View File

@ -72,7 +72,6 @@ export default {
return {
showArrays: false,
searchResult: [],
massCreation: [],
criteria: '',
filters: {
byActivated: null,

View File

@ -24,9 +24,6 @@ export const mutations = {
moderator: (state, moderator) => {
state.moderator = moderator
},
setUserSelectedInMassCreation: (state, userSelectedInMassCreation) => {
state.userSelectedInMassCreation = userSelectedInMassCreation
},
}
export const actions = {

View File

@ -10,7 +10,6 @@ const {
resetOpenCreations,
setOpenCreations,
moderator,
setUserSelectedInMassCreation,
} = mutations
const { logout } = actions
@ -65,14 +64,6 @@ describe('Vuex store', () => {
expect(state.openCreations).toEqual(12)
})
})
describe('setUserSelectedInMassCreation', () => {
it('sets userSelectedInMassCreation to given value', () => {
const state = { userSelectedInMassCreation: [] }
setUserSelectedInMassCreation(state, [0, 1, 2])
expect(state.userSelectedInMassCreation).toEqual([0, 1, 2])
})
})
})
describe('actions', () => {

View File

@ -42,7 +42,6 @@ export enum RIGHTS {
DELETE_USER = 'DELETE_USER',
UNDELETE_USER = 'UNDELETE_USER',
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
ADMIN_CREATE_CONTRIBUTIONS = 'ADMIN_CREATE_CONTRIBUTIONS',
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS',

View File

@ -106,7 +106,7 @@ describe('sendEmailVariants', () => {
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
)
expect(result.originalMessage.html).toContain(
'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
'To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
)
expect(result.originalMessage.html).toContain(
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
@ -424,7 +424,7 @@ describe('sendEmailVariants', () => {
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
)
expect(result.originalMessage.html).toContain(
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
)
expect(result.originalMessage.html).toContain(
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
@ -502,7 +502,7 @@ describe('sendEmailVariants', () => {
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
)
expect(result.originalMessage.html).toContain(
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
)
expect(result.originalMessage.html).toContain(
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,

View File

@ -1,19 +0,0 @@
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class AdminCreateContributions {
constructor() {
this.success = false
this.successfulContribution = []
this.failedContribution = []
}
@Field(() => Boolean)
success: boolean
@Field(() => [String])
successfulContribution: string[]
@Field(() => [String])
failedContribution: string[]
}

View File

@ -61,8 +61,8 @@ export class TransactionLink {
@ObjectType()
export class TransactionLinkResult {
@Field(() => Int)
linkCount: number
count: number
@Field(() => [TransactionLink])
linkList: TransactionLink[]
links: TransactionLink[]
}

View File

@ -13,7 +13,6 @@ import {
denyContribution,
confirmContribution,
adminCreateContribution,
adminCreateContributions,
adminUpdateContribution,
adminDeleteContribution,
login,
@ -1655,21 +1654,6 @@ describe('ContributionResolver', () => {
})
})
describe('adminCreateContributions', () => {
it('returns an error', async () => {
await expect(
mutate({
mutation: adminCreateContributions,
variables: { pendingCreations: [variables] },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('adminUpdateContribution', () => {
it('returns an error', async () => {
await expect(
@ -1749,21 +1733,6 @@ describe('ContributionResolver', () => {
})
})
describe('adminCreateContributions', () => {
it('returns an error', async () => {
await expect(
mutate({
mutation: adminCreateContributions,
variables: { pendingCreations: [variables] },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('adminUpdateContribution', () => {
it('returns an error', async () => {
await expect(
@ -2094,59 +2063,13 @@ describe('ContributionResolver', () => {
})
})
describe('adminCreateContributions', () => {
describe('adminUpdateContribution', () => {
// at this point we have this data in DB:
// bibi@bloxberg.de: [1000, 1000, 800]
// peter@lustig.de: [1000, 600, 1000]
// stephen@hawking.uk: [1000, 1000, 1000] - deleted
// garrick@ollivander.com: [1000, 1000, 1000] - not activated
const massCreationVariables = [
'bibi@bloxberg.de',
'peter@lustig.de',
'stephen@hawking.uk',
'garrick@ollivander.com',
'bob@baumeister.de',
].map((email) => {
return {
email,
amount: new Decimal(500),
memo: 'Grundeinkommen',
creationDate: contributionDateFormatter(new Date()),
}
})
it('returns success, two successful creation and three failed creations', async () => {
await expect(
mutate({
mutation: adminCreateContributions,
variables: { pendingCreations: massCreationVariables },
}),
).resolves.toEqual(
expect.objectContaining({
data: {
adminCreateContributions: {
success: true,
successfulContribution: ['bibi@bloxberg.de', 'peter@lustig.de'],
failedContribution: [
'stephen@hawking.uk',
'garrick@ollivander.com',
'bob@baumeister.de',
],
},
},
}),
)
})
})
describe('adminUpdateContribution', () => {
// at this I expect to have this data in DB:
// bibi@bloxberg.de: [1000, 1000, 300]
// peter@lustig.de: [1000, 600, 500]
// stephen@hawking.uk: [1000, 1000, 1000] - deleted
// garrick@ollivander.com: [1000, 1000, 1000] - not activated
describe('user for creation to update does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
@ -2360,7 +2283,7 @@ describe('ContributionResolver', () => {
date: expect.any(String),
memo: 'Das war leider zu Viel!',
amount: '200',
creation: ['1000', '800', '500'],
creation: ['1000', '800', '1000'],
},
},
}),
@ -2772,15 +2695,15 @@ describe('ContributionResolver', () => {
resetToken()
})
it('returns 19 creations in total', async () => {
it('returns 17 creations in total', async () => {
const {
data: { adminListAllContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({
query: adminListAllContributions,
})
expect(contributionListObject.contributionList).toHaveLength(19)
expect(contributionListObject.contributionList).toHaveLength(17)
expect(contributionListObject).toMatchObject({
contributionCount: 19,
contributionCount: 17,
contributionList: expect.arrayContaining([
expect.objectContaining({
amount: expect.decimalEqual(50),
@ -2845,24 +2768,6 @@ describe('ContributionResolver', () => {
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(500),
firstName: 'Bibi',
id: expect.any(Number),
lastName: 'Bloxberg',
memo: 'Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(500),
firstName: 'Peter',
id: expect.any(Number),
lastName: 'Lustig',
memo: 'Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: expect.decimalEqual(10),
firstName: 'Bibi',
@ -2957,21 +2862,21 @@ describe('ContributionResolver', () => {
})
})
it('returns five pending creations with page size set to 5', async () => {
it('returns two pending creations with page size set to 2', async () => {
const {
data: { adminListAllContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({
query: adminListAllContributions,
variables: {
currentPage: 1,
pageSize: 5,
pageSize: 2,
order: Order.DESC,
statusFilter: ['PENDING'],
},
})
expect(contributionListObject.contributionList).toHaveLength(5)
expect(contributionListObject.contributionList).toHaveLength(2)
expect(contributionListObject).toMatchObject({
contributionCount: 6,
contributionCount: 4,
contributionList: expect.arrayContaining([
expect.objectContaining({
amount: '400',
@ -2982,33 +2887,6 @@ describe('ContributionResolver', () => {
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: '200',
firstName: 'Bibi',
id: expect.any(Number),
lastName: 'Bloxberg',
memo: 'Aktives Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: '500',
firstName: 'Bibi',
id: expect.any(Number),
lastName: 'Bloxberg',
memo: 'Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: '500',
firstName: 'Peter',
id: expect.any(Number),
lastName: 'Lustig',
memo: 'Grundeinkommen',
messagesCount: 0,
state: 'PENDING',
}),
expect.objectContaining({
amount: '100',
firstName: 'Peter',

View File

@ -8,7 +8,6 @@ import { UserContact } from '@entity/UserContact'
import { User as DbUser } from '@entity/User'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { AdminCreateContributions } from '@model/AdminCreateContributions'
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { Contribution, ContributionListResult } from '@model/Contribution'
import { Decay } from '@model/Decay'
@ -318,33 +317,6 @@ export class ContributionResolver {
return getUserCreation(emailContact.userId, clientTimezoneOffset)
}
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS])
@Mutation(() => AdminCreateContributions)
async adminCreateContributions(
@Arg('pendingCreations', () => [AdminCreateContributionArgs])
contributions: AdminCreateContributionArgs[],
@Ctx() context: Context,
): Promise<AdminCreateContributions> {
let success = false
const successfulContribution: string[] = []
const failedContribution: string[] = []
for (const contribution of contributions) {
await this.adminCreateContribution(contribution, context)
.then(() => {
successfulContribution.push(contribution.email)
success = true
})
.catch(() => {
failedContribution.push(contribution.email)
})
}
return {
success,
successfulContribution,
failedContribution,
}
}
@Authorized([RIGHTS.ADMIN_UPDATE_CONTRIBUTION])
@Mutation(() => AdminUpdateContribution)
async adminUpdateContribution(

View File

@ -600,6 +600,26 @@ describe('TransactionLinkResolver', () => {
resetToken()
})
describe('', () => {
it('throws error when user does not exists', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: listTransactionLinksAdmin,
variables: {
userId: -1,
},
}),
).resolves.toMatchObject({
errors: [new GraphQLError('Could not find requested User')],
})
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find requested User', -1)
})
})
describe('without any filters', () => {
it('finds 6 open transaction links and no deleted or redeemed', async () => {
await expect(
@ -611,8 +631,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({
data: {
listTransactionLinksAdmin: {
linkCount: 6,
linkList: expect.not.arrayContaining([
count: 6,
links: expect.not.arrayContaining([
expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String),
@ -647,8 +667,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({
data: {
listTransactionLinksAdmin: {
linkCount: 6,
linkList: expect.not.arrayContaining([
count: 6,
links: expect.not.arrayContaining([
expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String),
@ -681,8 +701,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({
data: {
listTransactionLinksAdmin: {
linkCount: 7,
linkList: expect.arrayContaining([
count: 7,
links: expect.arrayContaining([
expect.not.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String),
@ -715,8 +735,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({
data: {
listTransactionLinksAdmin: {
linkCount: 7,
linkList: expect.arrayContaining([
count: 7,
links: expect.arrayContaining([
expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String),
@ -752,8 +772,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({
data: {
listTransactionLinksAdmin: {
linkCount: 6,
linkList: expect.arrayContaining([
count: 6,
links: expect.arrayContaining([
expect.not.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String),

View File

@ -1,7 +1,7 @@
import { randomBytes } from 'crypto'
import Decimal from 'decimal.js-light'
import { getConnection, MoreThan, FindOperator } from '@dbTools/typeorm'
import { getConnection } from '@dbTools/typeorm'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
@ -13,7 +13,6 @@ import { User } from '@model/User'
import { ContributionLink } from '@model/ContributionLink'
import { Decay } from '@model/Decay'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { Order } from '@enum/Order'
import { ContributionType } from '@enum/ContributionType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { TransactionTypeId } from '@enum/TransactionTypeId'
@ -35,6 +34,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import LogError from '@/server/LogError'
import { getLastTransaction } from './util/getLastTransaction'
import transactionLinkList from './util/transactionLinkList'
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
@ -145,30 +145,6 @@ export class TransactionLinkResolver {
}
}
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
@Query(() => [TransactionLink])
async listTransactionLinks(
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Ctx() context: Context,
): Promise<TransactionLink[]> {
const user = getUser(context)
// const now = new Date()
const transactionLinks = await DbTransactionLink.find({
where: {
userId: user.id,
redeemedBy: null,
// validUntil: MoreThan(now),
},
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return transactionLinks.map((tl) => new TransactionLink(tl, new User(user)))
}
@Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
@Mutation(() => Boolean)
async redeemTransactionLink(
@ -342,43 +318,38 @@ export class TransactionLinkResolver {
}
}
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
@Query(() => TransactionLinkResult)
async listTransactionLinks(
@Args()
paginated: Paginated,
@Ctx() context: Context,
): Promise<TransactionLinkResult> {
return transactionLinkList(
paginated,
{
withDeleted: false,
withExpired: true,
withRedeemed: false,
},
getUser(context),
)
}
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
@Query(() => TransactionLinkResult)
async listTransactionLinksAdmin(
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
paginated: Paginated,
@Arg('filters', () => TransactionLinkFilters, { nullable: true })
filters: TransactionLinkFilters,
filters: TransactionLinkFilters | null,
@Arg('userId', () => Int)
userId: number,
): Promise<TransactionLinkResult> {
const user = await DbUser.findOneOrFail({ id: userId })
const where: {
userId: number
redeemedBy?: number | null
validUntil?: FindOperator<Date> | null
} = {
userId,
redeemedBy: null,
validUntil: MoreThan(new Date()),
}
if (filters) {
if (filters.withRedeemed) delete where.redeemedBy
if (filters.withExpired) delete where.validUntil
}
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
where,
withDeleted: filters ? filters.withDeleted : false,
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return {
linkCount: count,
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
const user = await DbUser.findOne({ id: userId })
if (!user) {
throw new LogError('Could not find requested User', userId)
}
return transactionLinkList(paginated, filters, user)
}
}

View File

@ -17,6 +17,7 @@ export const findContributions = async (
withDeleted: withDeleted,
order: {
createdAt: order,
id: order,
},
relations: ['user'],
skip: (currentPage - 1) * pageSize,

View File

@ -0,0 +1,38 @@
import { MoreThan } from '@dbTools/typeorm'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Order } from '@enum/Order'
import Paginated from '@arg/Paginated'
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@/graphql/model/User'
export default async function transactionLinkList(
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
filters: TransactionLinkFilters | null,
user: DbUser,
): Promise<TransactionLinkResult> {
const { withDeleted, withExpired, withRedeemed } = filters || {
withDeleted: false,
withExpired: false,
withRedeemed: false,
}
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
where: {
userId: user.id,
...(!withRedeemed && { redeemedBy: null }),
...(!withExpired && { validUntil: MoreThan(new Date()) }),
},
withDeleted,
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return {
count,
links: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
}
}

View File

@ -17,7 +17,7 @@
"addedContributionMessage": {
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
"subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag",
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
},
"contributionConfirmed": {
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
@ -26,12 +26,12 @@
"contributionDeleted": {
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
},
"contributionDenied": {
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
},
"general": {
"amountGDD": "Betrag: {amountGDD} GDD",

View File

@ -17,7 +17,7 @@
"addedContributionMessage": {
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
"subject": "Gradido: Message about your common good contribution",
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
},
"contributionConfirmed": {
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
@ -26,12 +26,12 @@
"contributionDeleted": {
"commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was deleted",
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
},
"contributionDenied": {
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was rejected",
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
},
"general": {
"amountGDD": "Amount: {amountGDD} GDD",

View File

@ -126,16 +126,6 @@ export const unDeleteUser = gql`
}
`
export const adminCreateContributions = gql`
mutation ($pendingCreations: [AdminCreateContributionArgs!]!) {
adminCreateContributions(pendingCreations: $pendingCreations) {
success
successfulContribution
failedContribution
}
}
`
export const adminUpdateContribution = gql`
mutation ($id: Int!, $email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
adminUpdateContribution(

View File

@ -250,8 +250,8 @@ export const listTransactionLinksAdmin = gql`
currentPage: $currentPage
pageSize: $pageSize
) {
linkCount
linkList {
count
links {
id
amount
holdAvailableAmount

View File

@ -123,10 +123,6 @@ Zusätzlich wird als Parameter ein *creationDate* vom User mitgeliefert, das dem
nothing to do
#### + adminCreateContributions
Hier wird eine Liste von übergebenen Contributions über den internen Aufruf von *adminCreateContribution()* verarbeitet. Da dort eine Berücksichtigung des User-TimeOffsets notwendig ist, muss hier die UserTime entsprechen im Context weitergereicht werden.
#### - adminDeleteContribution
nothing to do

View File

@ -47,51 +47,53 @@ describe('TransactionLinkSummary', () => {
beforeEach(() => {
apolloQueryMock.mockResolvedValue({
data: {
listTransactionLinks: [
{
amount: '75',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 86,
memo:
'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '85',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 107,
memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '95',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 92,
memo:
'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '150',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 16,
memo:
'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
],
listTransactionLinks: {
links: [
{
amount: '75',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 86,
memo:
'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '85',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 107,
memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '95',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 92,
memo:
'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '150',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 16,
memo:
'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
],
},
},
})
@ -166,51 +168,53 @@ describe('TransactionLinkSummary', () => {
jest.clearAllMocks()
apolloQueryMock.mockResolvedValue({
data: {
listTransactionLinks: [
{
amount: '76',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 87,
memo:
'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '86',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 108,
memo:
'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '96',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 93,
memo:
'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an Walpurgisnacht vorüber!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '150',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 17,
memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
],
listTransactionLinks: {
links: [
{
amount: '76',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 87,
memo:
'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '86',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 108,
memo:
'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '96',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 93,
memo:
'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an Walpurgisnacht vorüber!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '150',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 17,
memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
],
},
},
})
await wrapper.setData({

View File

@ -90,7 +90,10 @@ export default {
fetchPolicy: 'network-only',
})
.then((result) => {
this.transactionLinks = [...this.transactionLinks, ...result.data.listTransactionLinks]
this.transactionLinks = [
...this.transactionLinks,
...result.data.listTransactionLinks.links,
]
this.$emit('update-transactions')
this.pending = false
})

View File

@ -126,14 +126,16 @@ export const queryTransactionLink = gql`
export const listTransactionLinks = gql`
query($currentPage: Int = 1, $pageSize: Int = 5) {
listTransactionLinks(currentPage: $currentPage, pageSize: $pageSize) {
id
amount
holdAvailableAmount
memo
link
createdAt
validUntil
redeemedAt
links {
id
amount
holdAvailableAmount
memo
link
createdAt
validUntil
redeemedAt
}
}
}
`