mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge remote-tracking branch 'origin/master' into 2501-feature-federation-implement-a-graphql-client-to-request-getpublickey
This commit is contained in:
commit
3282bc6ee9
@ -4,6 +4,7 @@ import path from 'path'
|
||||
import { createTransport } from 'nodemailer'
|
||||
import Email from 'email-templates'
|
||||
import i18n from 'i18n'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
export const sendEmailTranslated = async (params: {
|
||||
receiver: {
|
||||
@ -73,8 +74,7 @@ export const sendEmailTranslated = async (params: {
|
||||
logger.info('Result: ', result)
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
logger.error('Error sending notification email: ', error)
|
||||
throw new Error('Error sending notification email!')
|
||||
throw new LogError('Error sending notification email', error)
|
||||
})
|
||||
|
||||
i18n.setLocale(rememberLocaleToRestore)
|
||||
|
||||
@ -45,6 +45,7 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||
import { logger, i18n as localization } from '@test/testSetup'
|
||||
import { UserInputError } from 'apollo-server-express'
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
// mock account activation email to avoid console spam
|
||||
@ -681,7 +682,7 @@ describe('ContributionResolver', () => {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filterConfirmed: false,
|
||||
statusFilter: null,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -718,7 +719,76 @@ describe('ContributionResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns allCreation', async () => {
|
||||
it('throws an error with "NOT_VALID" in statusFilter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['NOT_VALID'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new UserInputError(
|
||||
'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[0]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('throws an error with a null in statusFilter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: [null],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new UserInputError(
|
||||
'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('throws an error with null and "NOT_VALID" in statusFilter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: [null, 'NOT_VALID'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new UserInputError(
|
||||
'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.',
|
||||
),
|
||||
new UserInputError(
|
||||
'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[1]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all contributions without statusFilter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
@ -726,7 +796,6 @@ describe('ContributionResolver', () => {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filterConfirmed: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -737,11 +806,301 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all contributions for statusFilter = null', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: null,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 2,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all contributions for statusFilter = []', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: [],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 2,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all CONFIRMED contributions', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['CONFIRMED'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all PENDING contributions', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['PENDING'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all IN_PROGRESS Creation', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['IN_PROGRESS'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 0,
|
||||
contributionList: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all DENIED Creation', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['DENIED'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 0,
|
||||
contributionList: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all DELETED Creation', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['DELETED'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 0,
|
||||
contributionList: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns all CONFIRMED and PENDING Creation', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
statusFilter: ['CONFIRMED', 'PENDING'],
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionCount: 2,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'PENDING',
|
||||
memo: 'Test env contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
|
||||
@ -179,12 +179,23 @@ export class ContributionResolver {
|
||||
async listAllContributions(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||
statusFilter?: ContributionStatus[],
|
||||
): Promise<ContributionListResult> {
|
||||
const where: {
|
||||
contributionStatus?: FindOperator<string> | null
|
||||
} = {}
|
||||
|
||||
if (statusFilter && statusFilter.length) {
|
||||
where.contributionStatus = In(statusFilter)
|
||||
}
|
||||
|
||||
const [dbContributions, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('c')
|
||||
.from(DbContribution, 'c')
|
||||
.innerJoinAndSelect('c.user', 'u')
|
||||
.where(where)
|
||||
.orderBy('c.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
|
||||
@ -75,7 +75,7 @@ describe('EmailOptinCodes', () => {
|
||||
query({ query: queryOptIn, variables: { optIn: optinCode } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email was sent more than 24 hours ago')],
|
||||
errors: [new GraphQLError('Email was sent more than 24 hours ago')],
|
||||
})
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ describe('EmailOptinCodes', () => {
|
||||
mutate({ mutation: setPassword, variables: { code: optinCode, password: 'Aa12345_' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email was sent more than 24 hours ago')],
|
||||
errors: [new GraphQLError('Email was sent more than 24 hours ago')],
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -96,7 +96,7 @@ describe('EmailOptinCodes', () => {
|
||||
mutate({ mutation: forgotPassword, variables: { email: 'peter@lustig.de' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email already sent less than 10 minutes ago')],
|
||||
errors: [new GraphQLError('Email already sent less than 10 minutes ago')],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ describe('send coins', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`UserContact with email=wrong@email.com does not exists`)
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', 'wrong@email.com')
|
||||
})
|
||||
|
||||
describe('deleted recipient', () => {
|
||||
|
||||
@ -549,7 +549,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Password entered is lexically invalid')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -606,9 +608,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'UserContact with email=bibi@bloxberg.de does not exists',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
|
||||
})
|
||||
})
|
||||
|
||||
@ -668,7 +668,112 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is in database but deleted', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await userFactory(testEnv, stephenHawking)
|
||||
const variables = {
|
||||
email: stephenHawking.email,
|
||||
password: 'Aa12345_',
|
||||
publisherId: 1234,
|
||||
}
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError('This user was permanently deleted. Contact support for questions'),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'This user was permanently deleted. Contact support for questions',
|
||||
expect.objectContaining({
|
||||
firstName: stephenHawking.firstName,
|
||||
lastName: stephenHawking.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is in database but email not confirmed', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await userFactory(testEnv, garrickOllivander)
|
||||
const variables = {
|
||||
email: garrickOllivander.email,
|
||||
password: 'Aa12345_',
|
||||
publisherId: 1234,
|
||||
}
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('The Users email is not validate yet')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The Users email is not validate yet',
|
||||
expect.objectContaining({
|
||||
firstName: garrickOllivander.firstName,
|
||||
lastName: garrickOllivander.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('user is in database but password is not set', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
// TODO: we need an user without password set
|
||||
const user = await userFactory(testEnv, bibiBloxberg)
|
||||
user.password = BigInt(0)
|
||||
await user.save()
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('The User has not set a password yet')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The User has not set a password yet',
|
||||
expect.objectContaining({
|
||||
firstName: bibiBloxberg.firstName,
|
||||
lastName: bibiBloxberg.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -828,7 +933,7 @@ describe('UserResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
`email already sent less than ${printTimeDuration(
|
||||
`Email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
)} ago`,
|
||||
),
|
||||
@ -870,13 +975,13 @@ describe('UserResolver', () => {
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME = emailCodeRequestTime
|
||||
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('email already sent less than 10 minutes ago')],
|
||||
errors: [new GraphQLError('Email already sent less than 10 minutes ago')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`email already sent less than 10 minutes ago`)
|
||||
expect(logger.error).toBeCalledWith(`Email already sent less than 10 minutes ago`)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1001,13 +1106,13 @@ describe('UserResolver', () => {
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`"not-valid" isn't a valid language`)],
|
||||
errors: [new GraphQLError('Given language is not a valid language')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`"not-valid" isn't a valid language`)
|
||||
expect(logger.error).toBeCalledWith('Given language is not a valid language', 'not-valid')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1058,7 +1163,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith('newPassword does not fullfil the rules')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1116,7 +1223,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1322,13 +1431,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1379,12 +1488,12 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Administrator can not change his own role!')],
|
||||
errors: [new GraphQLError('Administrator can not change his own role')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Administrator can not change his own role!')
|
||||
expect(logger.error).toBeCalledWith('Administrator can not change his own role')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1400,13 +1509,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already admin!')],
|
||||
errors: [new GraphQLError('User is already admin')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already admin!')
|
||||
expect(logger.error).toBeCalledWith('User is already admin')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1421,13 +1530,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already a usual user!')],
|
||||
errors: [new GraphQLError('User is already an usual user')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already a usual user!')
|
||||
expect(logger.error).toBeCalledWith('User is already an usual user')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1494,13 +1603,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1511,13 +1620,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: admin.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Moderator can not delete his own account!')],
|
||||
errors: [new GraphQLError('Moderator can not delete his own account')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Moderator can not delete his own account!')
|
||||
expect(logger.error).toBeCalledWith('Moderator can not delete his own account')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1545,13 +1654,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: user.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${user.id}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${user.id}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', user.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1617,13 +1726,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -63,6 +63,7 @@ import { isValidPassword } from '@/password/EncryptorUtils'
|
||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sodium = require('sodium-native')
|
||||
@ -134,22 +135,19 @@ export class UserResolver {
|
||||
email = email.trim().toLowerCase()
|
||||
const dbUser = await findUserByEmail(email)
|
||||
if (dbUser.deletedAt) {
|
||||
logger.error('The User was permanently deleted in database.')
|
||||
throw new Error('This user was permanently deleted. Contact support for questions.')
|
||||
throw new LogError('This user was permanently deleted. Contact support for questions', dbUser)
|
||||
}
|
||||
if (!dbUser.emailContact.emailChecked) {
|
||||
logger.error('The Users email is not validate yet.')
|
||||
throw new Error('User email not validated')
|
||||
throw new LogError('The Users email is not validate yet', dbUser)
|
||||
}
|
||||
// TODO: at least in test this does not work since `dbUser.password = 0` and `BigInto(0) = 0n`
|
||||
if (dbUser.password === BigInt(0)) {
|
||||
logger.error('The User has not set a password yet.')
|
||||
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||
throw new Error('User has no password set yet')
|
||||
throw new LogError('The User has not set a password yet', dbUser)
|
||||
}
|
||||
|
||||
if (!verifyPassword(dbUser, password)) {
|
||||
logger.error('The User has no valid credentials.')
|
||||
throw new Error('No user with this credentials')
|
||||
throw new LogError('No user with this credentials', dbUser)
|
||||
}
|
||||
|
||||
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
|
||||
@ -309,30 +307,19 @@ export class UserResolver {
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
try {
|
||||
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
logger.error('Error while saving dbUser', error)
|
||||
throw new Error('error saving user')
|
||||
throw new LogError('Error while saving dbUser', error)
|
||||
})
|
||||
let emailContact = newEmailContact(email, dbUser.id)
|
||||
emailContact = await queryRunner.manager.save(emailContact).catch((error) => {
|
||||
logger.error('Error while saving emailContact', error)
|
||||
throw new Error('error saving email user contact')
|
||||
throw new LogError('Error while saving user email contact', error)
|
||||
})
|
||||
|
||||
dbUser.emailContact = emailContact
|
||||
dbUser.emailId = emailContact.id
|
||||
await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
logger.error('Error while updating dbUser', error)
|
||||
throw new Error('error updating user')
|
||||
throw new LogError('Error while updating dbUser', error)
|
||||
})
|
||||
|
||||
/*
|
||||
const emailOptIn = newEmailOptIn(dbUser.id)
|
||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||
logger.error('Error while saving emailOptIn', error)
|
||||
throw new Error('error saving email opt in')
|
||||
})
|
||||
*/
|
||||
|
||||
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||
/{optin}/g,
|
||||
emailContact.emailVerificationCode.toString(),
|
||||
@ -358,9 +345,8 @@ export class UserResolver {
|
||||
await queryRunner.commitTransaction()
|
||||
logger.addContext('user', dbUser.id)
|
||||
} catch (e) {
|
||||
logger.error(`error during create user with ${e}`)
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw e
|
||||
throw new LogError('Error creating user', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -392,11 +378,9 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
if (!canEmailResend(user.emailContact.updatedAt || user.emailContact.createdAt)) {
|
||||
const errorMessage = `email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
)} ago`
|
||||
logger.error(errorMessage)
|
||||
throw new Error(errorMessage)
|
||||
throw new LogError(
|
||||
`Email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
|
||||
user.emailContact.updatedAt = new Date()
|
||||
@ -404,8 +388,7 @@ export class UserResolver {
|
||||
user.emailContact.emailVerificationCode = random(64)
|
||||
user.emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
|
||||
await user.emailContact.save().catch(() => {
|
||||
logger.error('Unable to save email verification code= ' + user.emailContact)
|
||||
throw new Error('Unable to save email verification code.')
|
||||
throw new LogError('Unable to save email verification code', user.emailContact)
|
||||
})
|
||||
|
||||
logger.info(`optInCode for ${email}=${user.emailContact}`)
|
||||
@ -440,34 +423,23 @@ export class UserResolver {
|
||||
logger.info(`setPassword(${code}, ***)...`)
|
||||
// Validate Password
|
||||
if (!isValidPassword(password)) {
|
||||
logger.error('Password entered is lexically invalid')
|
||||
throw new Error(
|
||||
throw new LogError(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
}
|
||||
|
||||
// Load code
|
||||
/*
|
||||
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: code }).catch(() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
})
|
||||
*/
|
||||
// load code
|
||||
const userContact = await DbUserContact.findOneOrFail(
|
||||
{ emailVerificationCode: code },
|
||||
{ relations: ['user'] },
|
||||
).catch(() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
throw new LogError('Could not login with emailVerificationCode')
|
||||
})
|
||||
logger.debug('userContact loaded...')
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
throw new Error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
throw new LogError(
|
||||
`Email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
logger.debug('EmailVerificationCode is valid...')
|
||||
@ -493,13 +465,11 @@ export class UserResolver {
|
||||
try {
|
||||
// Save user
|
||||
await queryRunner.manager.save(user).catch((error) => {
|
||||
logger.error('error saving user: ' + error)
|
||||
throw new Error('error saving user: ' + error)
|
||||
throw new LogError('Error saving user', error)
|
||||
})
|
||||
// Save userContact
|
||||
await queryRunner.manager.save(userContact).catch((error) => {
|
||||
logger.error('error saving userContact: ' + error)
|
||||
throw new Error('error saving userContact: ' + error)
|
||||
throw new LogError('Error saving userContact', error)
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
@ -510,8 +480,7 @@ export class UserResolver {
|
||||
eventProtocol.writeEvent(event.setEventActivateAccount(eventActivateAccount))
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error('Error on writing User and UserContact data:' + e)
|
||||
throw e
|
||||
throw new LogError('Error on writing User and User Contact data', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -525,7 +494,7 @@ export class UserResolver {
|
||||
`klicktippSignIn(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
|
||||
)
|
||||
} catch (e) {
|
||||
logger.error('Error subscribe to klicktipp:' + e)
|
||||
logger.error('Error subscribing to klicktipp', e)
|
||||
// TODO is this a problem?
|
||||
// eslint-disable-next-line no-console
|
||||
/* uncomment this, when you need the activation link on the console
|
||||
@ -545,11 +514,8 @@ export class UserResolver {
|
||||
logger.debug(`found optInCode=${userContact}`)
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
throw new Error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
throw new LogError(
|
||||
`Email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
logger.info(`queryOptIn(${optIn}) successful...`)
|
||||
@ -584,8 +550,7 @@ export class UserResolver {
|
||||
|
||||
if (language) {
|
||||
if (!isLanguage(language)) {
|
||||
logger.error(`"${language}" isn't a valid language`)
|
||||
throw new Error(`"${language}" isn't a valid language`)
|
||||
throw new LogError('Given language is not a valid language', language)
|
||||
}
|
||||
userEntity.language = language
|
||||
i18n.setLocale(language)
|
||||
@ -594,15 +559,13 @@ export class UserResolver {
|
||||
if (password && passwordNew) {
|
||||
// Validate Password
|
||||
if (!isValidPassword(passwordNew)) {
|
||||
logger.error('newPassword does not fullfil the rules')
|
||||
throw new Error(
|
||||
throw new LogError(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
}
|
||||
|
||||
if (!verifyPassword(userEntity, password)) {
|
||||
logger.error(`Old password is invalid`)
|
||||
throw new Error(`Old password is invalid`)
|
||||
throw new LogError(`Old password is invalid`)
|
||||
}
|
||||
|
||||
// Save new password hash and newly encrypted private key
|
||||
@ -625,16 +588,14 @@ export class UserResolver {
|
||||
|
||||
try {
|
||||
await queryRunner.manager.save(userEntity).catch((error) => {
|
||||
logger.error('error saving user: ' + error)
|
||||
throw new Error('error saving user: ' + error)
|
||||
throw new LogError('Error saving user', error)
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.debug('writing User data successful...')
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error(`error on writing updated user data: ${e}`)
|
||||
throw e
|
||||
throw new LogError('Error on writing updated user data', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -761,14 +722,12 @@ export class UserResolver {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
// administrator user changes own role?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Administrator can not change his own role!')
|
||||
throw new Error('Administrator can not change his own role!')
|
||||
throw new LogError('Administrator can not change his own role')
|
||||
}
|
||||
// change isAdmin
|
||||
switch (user.isAdmin) {
|
||||
@ -776,16 +735,14 @@ export class UserResolver {
|
||||
if (isAdmin === true) {
|
||||
user.isAdmin = new Date()
|
||||
} else {
|
||||
logger.error('User is already a usual user!')
|
||||
throw new Error('User is already a usual user!')
|
||||
throw new LogError('User is already an usual user')
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (isAdmin === false) {
|
||||
user.isAdmin = null
|
||||
} else {
|
||||
logger.error('User is already admin!')
|
||||
throw new Error('User is already admin!')
|
||||
throw new LogError('User is already admin')
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -803,14 +760,12 @@ export class UserResolver {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
// moderator user disabled own account?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Moderator can not delete his own account!')
|
||||
throw new Error('Moderator can not delete his own account!')
|
||||
throw new LogError('Moderator can not delete his own account')
|
||||
}
|
||||
// soft-delete user
|
||||
await user.softRemove()
|
||||
@ -823,12 +778,10 @@ export class UserResolver {
|
||||
async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
if (!user.deletedAt) {
|
||||
logger.error('User is not deleted')
|
||||
throw new Error('User is not deleted')
|
||||
throw new LogError('User is not deleted')
|
||||
}
|
||||
await user.recover()
|
||||
return null
|
||||
@ -841,17 +794,14 @@ export class UserResolver {
|
||||
// const user = await dbUser.findOne({ id: emailContact.userId })
|
||||
const user = await findUserByEmail(email)
|
||||
if (!user) {
|
||||
logger.error(`Could not find User to emailContact: ${email}`)
|
||||
throw new Error(`Could not find User to emailContact: ${email}`)
|
||||
throw new LogError('Could not find user to given email contact', email)
|
||||
}
|
||||
if (user.deletedAt) {
|
||||
logger.error(`User with emailContact: ${email} is deleted.`)
|
||||
throw new Error(`User with emailContact: ${email} is deleted.`)
|
||||
throw new LogError('User with given email contact is deleted', email)
|
||||
}
|
||||
const emailContact = user.emailContact
|
||||
if (emailContact.deletedAt) {
|
||||
logger.error(`The emailContact: ${email} of this User is deleted.`)
|
||||
throw new Error(`The emailContact: ${email} of this User is deleted.`)
|
||||
throw new LogError('The given email contact for this user is deleted', email)
|
||||
}
|
||||
|
||||
emailContact.emailResendCount++
|
||||
@ -888,8 +838,7 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
{ email: email },
|
||||
{ withDeleted: true, relations: ['user'] },
|
||||
).catch(() => {
|
||||
logger.error(`UserContact with email=${email} does not exists`)
|
||||
throw new Error('No user with this credentials')
|
||||
throw new LogError('No user with this credentials', email)
|
||||
})
|
||||
const dbUser = dbUserContact.user
|
||||
dbUser.emailContact = dbUserContact
|
||||
@ -904,31 +853,16 @@ async function checkEmailExists(email: string): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
const isTimeExpired = (optIn: LoginEmailOptIn, duration: number): boolean => {
|
||||
const timeElapsed = Date.now() - new Date(optIn.updatedAt).getTime()
|
||||
// time is given in minutes
|
||||
return timeElapsed <= duration * 60 * 1000
|
||||
}
|
||||
*/
|
||||
const isTimeExpired = (updatedAt: Date, duration: number): boolean => {
|
||||
const timeElapsed = Date.now() - new Date(updatedAt).getTime()
|
||||
// time is given in minutes
|
||||
return timeElapsed <= duration * 60 * 1000
|
||||
}
|
||||
/*
|
||||
const isOptInValid = (optIn: LoginEmailOptIn): boolean => {
|
||||
return isTimeExpired(optIn, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
*/
|
||||
|
||||
const isEmailVerificationCodeValid = (updatedAt: Date): boolean => {
|
||||
return isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
/*
|
||||
const canResendOptIn = (optIn: LoginEmailOptIn): boolean => {
|
||||
return !isTimeExpired(optIn, CONFIG.EMAIL_CODE_REQUEST_TIME)
|
||||
}
|
||||
*/
|
||||
|
||||
const canEmailResend = (updatedAt: Date): boolean => {
|
||||
return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME)
|
||||
}
|
||||
|
||||
@ -172,18 +172,23 @@ export const listContributions = gql`
|
||||
`
|
||||
|
||||
export const listAllContributions = `
|
||||
query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC) {
|
||||
listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||
query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusFilter: [ContributionStatus!]) {
|
||||
listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order, statusFilter: $statusFilter) {
|
||||
contributionCount
|
||||
contributionList {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
amount
|
||||
memo
|
||||
createdAt
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
firstName
|
||||
lastName
|
||||
amount
|
||||
memo
|
||||
createdAt
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
contributionDate
|
||||
state
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
backend/src/server/LogError.test.ts
Normal file
26
backend/src/server/LogError.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { logger } from '@test/testSetup'
|
||||
|
||||
import LogError from './LogError'
|
||||
|
||||
describe('LogError', () => {
|
||||
it('logs an Error when created', () => {
|
||||
/* eslint-disable-next-line no-new */
|
||||
new LogError('new LogError')
|
||||
expect(logger.error).toBeCalledWith('new LogError')
|
||||
})
|
||||
|
||||
it('logs an Error including additional data when created', () => {
|
||||
/* eslint-disable-next-line no-new */
|
||||
new LogError('new LogError', { some: 'data' })
|
||||
expect(logger.error).toBeCalledWith('new LogError', { some: 'data' })
|
||||
})
|
||||
|
||||
it('does not contain additional data in Error object when thrown', () => {
|
||||
try {
|
||||
throw new LogError('new LogError', { someWeirdValue123: 'arbitraryData456' })
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
} catch (e: any) {
|
||||
expect(e.stack).not.toMatch(/(someWeirdValue123|arbitraryData456)/i)
|
||||
}
|
||||
})
|
||||
})
|
||||
9
backend/src/server/LogError.ts
Normal file
9
backend/src/server/LogError.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { backendLogger as logger } from './logger'
|
||||
|
||||
export default class LogError extends Error {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(msg: string, ...details: any[]) {
|
||||
super(msg)
|
||||
logger.error(msg, ...details)
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
<b-form
|
||||
ref="form"
|
||||
@submit.prevent="submit"
|
||||
class="p-3 bg-white appBoxShadow gradido-border-radius"
|
||||
class="form-style p-3 bg-white appBoxShadow gradido-border-radius"
|
||||
>
|
||||
<label>{{ $t('contribution.selectDate') }}</label>
|
||||
<b-form-datepicker
|
||||
@ -23,50 +23,70 @@
|
||||
<template #nav-next-year><span></span></template>
|
||||
</b-form-datepicker>
|
||||
|
||||
<input-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
:name="$t('form.message')"
|
||||
:label="$t('contribution.activity')"
|
||||
:placeholder="$t('contribution.yourActivity')"
|
||||
:rules="{ required: true, min: 5, max: 255 }"
|
||||
/>
|
||||
<input-hour
|
||||
v-model="form.hours"
|
||||
:name="$t('form.hours')"
|
||||
:label="$t('form.hours')"
|
||||
placeholder="0.25"
|
||||
:rules="{
|
||||
required: true,
|
||||
min: 0.25,
|
||||
max: validMaxTime,
|
||||
gddCreationTime: [0.25, validMaxTime],
|
||||
}"
|
||||
:validMaxTime="validMaxTime"
|
||||
@updateAmount="updateAmount"
|
||||
></input-hour>
|
||||
<input-amount
|
||||
id="contribution-amount"
|
||||
v-model="form.amount"
|
||||
:name="$t('form.amount')"
|
||||
:label="$t('form.amount')"
|
||||
placeholder="20"
|
||||
:rules="{ required: true, gddSendAmount: [20, validMaxGDD] }"
|
||||
typ="ContributionForm"
|
||||
></input-amount>
|
||||
<div
|
||||
v-if="(isThisMonth && maxGddThisMonth <= 0) || (!isThisMonth && maxGddLastMonth <= 0)"
|
||||
class="p-3"
|
||||
>
|
||||
{{ noOpenCreation }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<input-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
:name="$t('form.message')"
|
||||
:label="$t('contribution.activity')"
|
||||
:placeholder="$t('contribution.yourActivity')"
|
||||
:rules="{ required: true, min: 5, max: 255 }"
|
||||
/>
|
||||
<input-hour
|
||||
v-model="form.hours"
|
||||
:name="$t('form.hours')"
|
||||
:label="$t('form.hours')"
|
||||
placeholder="0.25"
|
||||
:rules="{
|
||||
required: true,
|
||||
min: 0.25,
|
||||
max: validMaxTime,
|
||||
gddCreationTime: [0.25, validMaxTime],
|
||||
}"
|
||||
:validMaxTime="validMaxTime"
|
||||
@updateAmount="updateAmount"
|
||||
></input-hour>
|
||||
<input-amount
|
||||
id="contribution-amount"
|
||||
v-model="form.amount"
|
||||
:name="$t('form.amount')"
|
||||
:label="$t('form.amount')"
|
||||
placeholder="20"
|
||||
:rules="{ required: true, gddSendAmount: [20, validMaxGDD] }"
|
||||
typ="ContributionForm"
|
||||
></input-amount>
|
||||
|
||||
<b-row class="mt-5">
|
||||
<b-col>
|
||||
<b-button type="reset" variant="secondary" @click="reset" data-test="button-cancel">
|
||||
{{ $t('form.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="gradido" :disabled="disabled" data-test="button-submit">
|
||||
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mt-5">
|
||||
<b-col cols="12" lg="6">
|
||||
<b-button
|
||||
block
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
@click="reset"
|
||||
data-test="button-cancel"
|
||||
>
|
||||
{{ $t('form.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col cols="12" lg="6" class="text-right mt-4 mt-lg-0">
|
||||
<b-button
|
||||
block
|
||||
type="submit"
|
||||
variant="gradido"
|
||||
:disabled="disabled"
|
||||
data-test="button-submit"
|
||||
>
|
||||
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-form>
|
||||
</div>
|
||||
</template>
|
||||
@ -133,6 +153,18 @@ export default {
|
||||
validMaxTime() {
|
||||
return Number(this.validMaxGDD / 20)
|
||||
},
|
||||
noOpenCreation() {
|
||||
if (this.maxGddThisMonth <= 0 && this.maxGddLastMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.allMonth')
|
||||
}
|
||||
if (this.isThisMonth && this.maxGddThisMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.thisMonth')
|
||||
}
|
||||
if (!this.isThisMonth && this.maxGddLastMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.lastMonth')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
@ -142,6 +174,9 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.form-style {
|
||||
min-height: 410px;
|
||||
}
|
||||
span.errors {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@ -78,7 +78,24 @@ export default {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
@media screen and (min-width: 1025px) {
|
||||
#side-menu {
|
||||
max-width: 180px;
|
||||
}
|
||||
#component-sidebar {
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1075px) {
|
||||
#side-menu {
|
||||
max-width: 200px;
|
||||
}
|
||||
#component-sidebar {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1108px) {
|
||||
#side-menu {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@ -57,7 +57,16 @@
|
||||
"yourContribution": "Dein Beitrag zum Gemeinwohl"
|
||||
},
|
||||
"lastContribution": "Letzte Beiträge",
|
||||
"noContributions": {
|
||||
"allContributions": "Es wurden noch keine Beiträge eingereicht.",
|
||||
"myContributions": "Du hast noch keine Beiträge eingereicht."
|
||||
},
|
||||
"noDateSelected": "Wähle irgendein Datum im Monat",
|
||||
"noOpenCreation": {
|
||||
"allMonth": "Für alle beiden Monate ist dein Schöpfungslimit erreicht. Den Nächsten Monat kannst du wieder 1000 GDD Schöpfen.",
|
||||
"lastMonth": "Für den ausgewählten Monat ist das Schöpfungslimit erreicht.",
|
||||
"thisMonth": "Für den aktuellen Monat ist das Schöpfungslimit erreicht."
|
||||
},
|
||||
"selectDate": "Wann war dein Beitrag?",
|
||||
"submit": "Einreichen",
|
||||
"submitted": "Der Beitrag wurde eingereicht.",
|
||||
|
||||
@ -57,7 +57,16 @@
|
||||
"yourContribution": "Your Contributions to the Common Good"
|
||||
},
|
||||
"lastContribution": "Last Contributions",
|
||||
"noContributions": {
|
||||
"allContributions": "No contributions have been submitted yet.",
|
||||
"myContributions": "You have not submitted any entries yet."
|
||||
},
|
||||
"noDateSelected": "Choose any date in the month",
|
||||
"noOpenCreation": {
|
||||
"allMonth": "For all two months your creation limit is reached. The next month you can create 1000 GDD again.",
|
||||
"lastMonth": "The creation limit is reached for the selected month.",
|
||||
"thisMonth": "The creation limit has been reached for the current month."
|
||||
},
|
||||
"selectDate": "When was your contribution?",
|
||||
"submit": "Submit",
|
||||
"submitted": "The contribution was submitted.",
|
||||
|
||||
@ -20,28 +20,38 @@
|
||||
/>
|
||||
</b-tab>
|
||||
<b-tab no-body>
|
||||
<contribution-list
|
||||
@closeAllOpenCollapse="closeAllOpenCollapse"
|
||||
:items="items"
|
||||
@update-list-contributions="updateListContributions"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
:contributionCount="contributionCount"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
/>
|
||||
<div v-if="items.length === 0">
|
||||
{{ $t('contribution.noContributions.myContributions') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-list
|
||||
@closeAllOpenCollapse="closeAllOpenCollapse"
|
||||
:items="items"
|
||||
@update-list-contributions="updateListContributions"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
:contributionCount="contributionCount"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
/>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab no-body>
|
||||
<contribution-list
|
||||
:items="itemsAll"
|
||||
@update-list-contributions="updateListAllContributions"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
:contributionCount="contributionCountAll"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSizeAll"
|
||||
:allContribution="true"
|
||||
/>
|
||||
<div v-if="itemsAll.length === 0">
|
||||
{{ $t('contribution.noContributions.allContributions') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-list
|
||||
:items="itemsAll"
|
||||
@update-list-contributions="updateListAllContributions"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
:contributionCount="contributionCountAll"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSizeAll"
|
||||
:allContribution="true"
|
||||
/>
|
||||
</div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user