mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
Merge remote-tracking branch 'origin/master' into 3184-feature-x-send-coins-2-add-pending_transactions-table-for-x-community-tx
This commit is contained in:
commit
7e13bf8db8
@ -89,7 +89,6 @@
|
||||
"submit": "Senden"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hashtag_symbol": "#",
|
||||
"help": {
|
||||
"help": "Hilfe",
|
||||
"transactionlist": {
|
||||
@ -125,10 +124,8 @@
|
||||
"user_search": "Nutzersuche"
|
||||
},
|
||||
"not_open_creations": "Keine offenen Schöpfungen",
|
||||
"no_filter": "Keine Filterung",
|
||||
"no_filter_tooltip": "Es wird nicht nach Hashtags gefiltert",
|
||||
"no_hashtag": "Ohne Hashtag",
|
||||
"no_hashtag_tooltip": "Zeigt nur Schöpfungen ohne Hashtag im Kommentar an",
|
||||
"no_hashtag": "#Hashtags verbergen",
|
||||
"no_hashtag_tooltip": "Zeigt nur Beiträge ohne Hashtag im Text",
|
||||
"open": "offen",
|
||||
"open_creations": "Offene Schöpfungen",
|
||||
"overlay": {
|
||||
@ -221,7 +218,7 @@
|
||||
"tabTitle": "Nutzer-Rolle"
|
||||
},
|
||||
"user_deleted": "Nutzer ist gelöscht.",
|
||||
"user_memo_search": "Nutzer-Kommentar-Suche",
|
||||
"user_memo_search": "Benutzer- und Text-Suche",
|
||||
"user_recovered": "Nutzer ist wiederhergestellt.",
|
||||
"user_search": "Nutzer-Suche"
|
||||
}
|
||||
|
||||
@ -89,7 +89,6 @@
|
||||
"submit": "Send"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hashtag_symbol": "#",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"transactionlist": {
|
||||
@ -125,10 +124,8 @@
|
||||
"user_search": "User search"
|
||||
},
|
||||
"not_open_creations": "No open creations",
|
||||
"no_filter": "No Filter",
|
||||
"no_filter_tooltip": "It is not filtered by hashtags",
|
||||
"no_hashtag": "No Hashtag",
|
||||
"no_hashtag_tooltip": "Displays only contributions without hashtag in comment",
|
||||
"no_hashtag": "Hide #hashtags",
|
||||
"no_hashtag_tooltip": "Shows only contributions without hashtag in text",
|
||||
"open": "open",
|
||||
"open_creations": "Open creations",
|
||||
"overlay": {
|
||||
@ -221,7 +218,7 @@
|
||||
"tabTitle": "User Role"
|
||||
},
|
||||
"user_deleted": "User is deleted.",
|
||||
"user_memo_search": "User and Memo search",
|
||||
"user_memo_search": "User and text search",
|
||||
"user_recovered": "User is recovered.",
|
||||
"user_search": "User search"
|
||||
}
|
||||
|
||||
@ -2,12 +2,10 @@
|
||||
<template>
|
||||
<div class="creation-confirm">
|
||||
<user-query class="mb-2 mt-2" v-model="query" :placeholder="$t('user_memo_search')" />
|
||||
<div class="mb-4">
|
||||
<b-button class="noHashtag" variant="light" @click="swapNoHashtag" v-b-tooltip="tooltipText">
|
||||
<span :style="hashtagColor">{{ $t('hashtag_symbol') }}</span>
|
||||
{{ noHashtag ? $t('no_hashtag') : $t('no_filter') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<label class="mb-4">
|
||||
<input type="checkbox" class="noHashtag" v-model="noHashtag" @change="swapNoHashtag" />
|
||||
<span class="ml-2" v-b-tooltip="$t('no_hashtag_tooltip')">{{ $t('no_hashtag') }}</span>
|
||||
</label>
|
||||
<div>
|
||||
<b-tabs v-model="tabIndex" content-class="mt-3" fill>
|
||||
<b-tab active :title-link-attributes="{ 'data-test': 'open' }">
|
||||
@ -134,7 +132,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
swapNoHashtag() {
|
||||
this.noHashtag = !!(this.noHashtag === null || this.noHashtag === false)
|
||||
this.query()
|
||||
},
|
||||
deleteCreation() {
|
||||
@ -209,12 +206,6 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hashtagColor() {
|
||||
return this.noHashtag ? 'color: red' : 'color: black'
|
||||
},
|
||||
tooltipText() {
|
||||
return this.noHashtag ? this.$t('no_hashtag_tooltip') : this.$t('no_filter_tooltip')
|
||||
},
|
||||
fields() {
|
||||
return [
|
||||
[
|
||||
|
||||
@ -20,6 +20,7 @@ module.exports = {
|
||||
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
|
||||
'@union/(.*)': '<rootDir>/src/graphql/union/$1',
|
||||
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
||||
'@typeorm/(.*)': '<rootDir>/src/typeorm/$1',
|
||||
'@test/(.*)': '<rootDir>/test/$1',
|
||||
'@entity/(.*)':
|
||||
// eslint-disable-next-line n/no-process-env
|
||||
|
||||
@ -2718,22 +2718,40 @@ describe('ContributionResolver', () => {
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
amount: 100.0,
|
||||
memo: '#firefighters',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns 17 creations in total', async () => {
|
||||
it('returns 18 creations in total', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(17)
|
||||
// console.log('17 contributions: %s', JSON.stringify(contributionListObject, null, 2))
|
||||
expect(contributionListObject.contributionList).toHaveLength(18)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 17,
|
||||
contributionCount: 18,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: '#firefighters',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(50),
|
||||
firstName: 'Bibi',
|
||||
@ -2905,8 +2923,17 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(2)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 4,
|
||||
contributionCount: 5,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: '#firefighters',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '400',
|
||||
firstName: 'Peter',
|
||||
@ -2916,15 +2943,6 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
status: 'DENIED',
|
||||
}),
|
||||
@ -2951,6 +2969,60 @@ describe('ContributionResolver', () => {
|
||||
query: 'Peter',
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(4)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 4,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: '#firefighters',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
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',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
it('returns only contributions of the queried user without hashtags', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
variables: {
|
||||
query: 'Peter',
|
||||
noHashtag: true,
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(3)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 3,
|
||||
@ -2986,6 +3058,48 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('returns only contributions with #firefighter', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
variables: {
|
||||
query: '#firefighter',
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(1)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Lustig',
|
||||
memo: '#firefighters',
|
||||
messagesCount: 0,
|
||||
status: 'PENDING',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
})
|
||||
|
||||
it('returns no contributions with #firefighter and no hashtag', async () => {
|
||||
const {
|
||||
data: { adminListContributions: contributionListObject },
|
||||
} = await query({
|
||||
query: adminListContributions,
|
||||
variables: {
|
||||
query: '#firefighter',
|
||||
noHashtag: true,
|
||||
},
|
||||
})
|
||||
expect(contributionListObject.contributionList).toHaveLength(0)
|
||||
expect(contributionListObject).toMatchObject({
|
||||
contributionCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
// test for case sensitivity and email
|
||||
it('returns only contributions of the queried user email', async () => {
|
||||
const {
|
||||
|
||||
@ -1,68 +1,69 @@
|
||||
import { In, Like, Not } from '@dbTools/typeorm'
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
import { Brackets, In, Like, Not, SelectQueryBuilder } from '@dbTools/typeorm'
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { SearchContributionsFilterArgs } from '@arg/SearchContributionsFilterArgs'
|
||||
import { Connection } from '@typeorm/connection'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
interface Relations {
|
||||
[key: string]: boolean | Relations
|
||||
}
|
||||
|
||||
function joinRelationsRecursive(
|
||||
relations: Relations,
|
||||
queryBuilder: SelectQueryBuilder<DbContribution>,
|
||||
currentPath: string,
|
||||
): void {
|
||||
for (const key in relations) {
|
||||
// console.log('leftJoin: %s, %s', `${currentPath}.${key}`, key)
|
||||
queryBuilder.leftJoinAndSelect(`${currentPath}.${key}`, key)
|
||||
if (typeof relations[key] === 'object') {
|
||||
// If it's a nested relation
|
||||
joinRelationsRecursive(relations[key] as Relations, queryBuilder, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const findContributions = async (
|
||||
paginate: Paginated,
|
||||
filter: SearchContributionsFilterArgs,
|
||||
withDeleted = false,
|
||||
relations: Relations | undefined = undefined,
|
||||
): Promise<[DbContribution[], number]> => {
|
||||
const requiredWhere = {
|
||||
const connection = await Connection.getInstance()
|
||||
if (!connection) {
|
||||
throw new LogError('Cannot connect to db')
|
||||
}
|
||||
const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution')
|
||||
if (relations) joinRelationsRecursive(relations, queryBuilder, 'Contribution')
|
||||
if (withDeleted) queryBuilder.withDeleted()
|
||||
queryBuilder.where({
|
||||
...(filter.statusFilter?.length && { contributionStatus: In(filter.statusFilter) }),
|
||||
...(filter.userId && { userId: filter.userId }),
|
||||
...(filter.noHashtag && { memo: Not(Like(`%#%`)) }),
|
||||
}
|
||||
|
||||
let where =
|
||||
filter.query && relations?.user
|
||||
? [
|
||||
{
|
||||
...requiredWhere, // And
|
||||
user: {
|
||||
firstName: Like(`%${filter.query}%`),
|
||||
},
|
||||
}, // Or
|
||||
{
|
||||
...requiredWhere,
|
||||
user: {
|
||||
lastName: Like(`%${filter.query}%`),
|
||||
},
|
||||
}, // Or
|
||||
{
|
||||
...requiredWhere, // And
|
||||
user: {
|
||||
emailContact: {
|
||||
email: Like(`%${filter.query}%`),
|
||||
},
|
||||
},
|
||||
}, // Or
|
||||
{
|
||||
...requiredWhere, // And
|
||||
memo: Like(`%${filter.query}%`),
|
||||
},
|
||||
]
|
||||
: requiredWhere
|
||||
|
||||
if (!relations?.user && filter.query) {
|
||||
where = [{ ...requiredWhere, memo: Like(`%${filter.query}%`) }]
|
||||
}
|
||||
|
||||
return DbContribution.findAndCount({
|
||||
relations,
|
||||
where,
|
||||
withDeleted,
|
||||
order: {
|
||||
createdAt: paginate.order,
|
||||
id: paginate.order,
|
||||
},
|
||||
skip: (paginate.currentPage - 1) * paginate.pageSize,
|
||||
take: paginate.pageSize,
|
||||
})
|
||||
queryBuilder.printSql()
|
||||
if (filter.query) {
|
||||
const queryString = '%' + filter.query + '%'
|
||||
queryBuilder.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where({ memo: Like(queryString) })
|
||||
if (relations?.user) {
|
||||
qb.orWhere('user.first_name LIKE :firstName', { firstName: queryString })
|
||||
.orWhere('user.last_name LIKE :lastName', { lastName: queryString })
|
||||
.orWhere('emailContact.email LIKE :emailContact', { emailContact: queryString })
|
||||
.orWhere({ memo: Like(queryString) })
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
return queryBuilder
|
||||
.orderBy('Contribution.createdAt', paginate.order)
|
||||
.addOrderBy('Contribution.id', paginate.order)
|
||||
.skip((paginate.currentPage - 1) * paginate.pageSize)
|
||||
.take(paginate.pageSize)
|
||||
.getManyAndCount()
|
||||
}
|
||||
|
||||
@ -136,6 +136,14 @@ export const creations: CreationInterface[] = [
|
||||
confirmed: true,
|
||||
moveCreationDate: 12,
|
||||
},
|
||||
{
|
||||
email: 'bibi@bloxberg.de',
|
||||
amount: 1000,
|
||||
memo: '#Hexen',
|
||||
creationDate: nMonthsBefore(new Date()),
|
||||
confirmed: true,
|
||||
moveCreationDate: 12,
|
||||
},
|
||||
...bobsTransactions,
|
||||
{
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
|
||||
@ -251,6 +251,7 @@ export const adminListContributions = gql`
|
||||
$statusFilter: [ContributionStatus!]
|
||||
$userId: Int
|
||||
$query: String
|
||||
$noHashtag: Boolean
|
||||
) {
|
||||
adminListContributions(
|
||||
currentPage: $currentPage
|
||||
@ -259,6 +260,7 @@ export const adminListContributions = gql`
|
||||
statusFilter: $statusFilter
|
||||
userId: $userId
|
||||
query: $query
|
||||
noHashtag: $noHashtag
|
||||
) {
|
||||
contributionCount
|
||||
contributionList {
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
"@model/*": ["src/graphql/model/*"],
|
||||
"@union/*": ["src/graphql/union/*"],
|
||||
"@repository/*": ["src/typeorm/repository/*"],
|
||||
"@typeorm/*": ["src/typeorm/*"],
|
||||
"@test/*": ["test/*"],
|
||||
/* external */
|
||||
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user