Merge branch 'master' into 2594-contributions-list-frontend

This commit is contained in:
Alexander Friedland 2023-02-02 16:57:30 +01:00 committed by GitHub
commit 0f449de4a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 352 additions and 211 deletions

View File

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

View File

@ -180,14 +180,14 @@ export class ContributionResolver {
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilters?: ContributionStatus[],
statusFilter?: ContributionStatus[],
): Promise<ContributionListResult> {
const where: {
contributionStatus?: FindOperator<string> | null
} = {}
if (statusFilters && statusFilters.length) {
where.contributionStatus = In(statusFilters)
if (statusFilter && statusFilter.length) {
where.contributionStatus = In(statusFilter)
}
const [dbContributions, count] = await getConnection()

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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