mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch 'master' into update-cypress
This commit is contained in:
commit
938cfcc89a
@ -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',
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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, {
|
||||
@ -1,25 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Float, Int } from 'type-graphql'
|
||||
|
||||
import { GdtEntryType } from '@enum/GdtEntryType'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntry {
|
||||
constructor(json: any) {
|
||||
this.id = json.id
|
||||
this.amount = json.amount
|
||||
this.date = json.date
|
||||
this.email = json.email
|
||||
this.comment = json.comment
|
||||
this.couponCode = json.coupon_code
|
||||
this.gdtEntryType = json.gdt_entry_type_id
|
||||
this.factor = json.factor
|
||||
this.amount2 = json.amount2
|
||||
this.factor2 = json.factor2
|
||||
this.gdt = json.gdt
|
||||
constructor({
|
||||
id,
|
||||
amount,
|
||||
date,
|
||||
email,
|
||||
comment,
|
||||
// eslint-disable-next-line camelcase
|
||||
coupon_code,
|
||||
// eslint-disable-next-line camelcase
|
||||
gdt_entry_type_id,
|
||||
factor,
|
||||
amount2,
|
||||
factor2,
|
||||
gdt,
|
||||
}: any) {
|
||||
this.id = id
|
||||
this.amount = amount
|
||||
this.date = date
|
||||
this.email = email
|
||||
this.comment = comment
|
||||
// eslint-disable-next-line camelcase
|
||||
this.couponCode = coupon_code
|
||||
// eslint-disable-next-line camelcase
|
||||
this.gdtEntryType = gdt_entry_type_id
|
||||
this.factor = factor
|
||||
this.amount2 = amount2
|
||||
this.factor2 = factor2
|
||||
this.gdt = gdt
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
/* 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.status = 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)
|
||||
|
||||
@ -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,33 @@ describe('ContributionMessageResolver', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('contribution message type MODERATOR', () => {
|
||||
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',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -385,7 +412,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 +446,96 @@ describe('ContributionMessageResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('adminListContributionMessages', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as user', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as admin', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns a list of contributionmessages with type MODERATOR', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: result.data.createContribution.id },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminListContributionMessages: {
|
||||
count: 3,
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Admin Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'User Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Internal moderator communication',
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,8 +8,8 @@ import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type
|
||||
|
||||
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
|
||||
|
||||
@ -22,6 +22,8 @@ import {
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { findContributionMessages } from './util/findContributionMessages'
|
||||
|
||||
@Resolver()
|
||||
export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE])
|
||||
@ -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)
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -352,7 +352,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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
@ -284,8 +284,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
|
||||
|
||||
@ -349,6 +349,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) {
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -276,7 +276,7 @@ export default {
|
||||
} = result
|
||||
this.GdtBalance =
|
||||
transactionList.balance.balanceGDT === null
|
||||
? null
|
||||
? 0
|
||||
: Number(transactionList.balance.balanceGDT)
|
||||
this.transactions = transactionList.transactions
|
||||
this.balance = Number(transactionList.balance.balance)
|
||||
|
||||
@ -142,6 +142,7 @@ describe('Transactions', () => {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
})
|
||||
|
||||
@ -170,6 +171,7 @@ describe('Transactions', () => {
|
||||
currentPage: 2,
|
||||
pageSize: 25,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -59,6 +59,7 @@ export default {
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((result) => {
|
||||
const {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user