Merge branch '1697-refactor-thx-page-#1' of github.com:gradido/gradido into 1697-refactor-thx-page-#2

This commit is contained in:
Wolfgang Huß 2022-04-29 13:32:39 +02:00
commit d938424d2b
41 changed files with 1300 additions and 320 deletions

View File

@ -528,7 +528,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
min_coverage: 58
min_coverage: 65
token: ${{ github.token }}
##########################################################################

View File

@ -4,8 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.8.1](https://github.com/gradido/gradido/compare/1.8.0...1.8.1)
- 1851 integrate and test the behaviour of clipboard polyfill [`#1853`](https://github.com/gradido/gradido/pull/1853)
- fix: Deprecated Warning from Faker on Seeding [`#1854`](https://github.com/gradido/gradido/pull/1854)
- feat: Test Admin Resolver [`#1848`](https://github.com/gradido/gradido/pull/1848)
- devops: Disable DB Reset on Stage 1 [`#1852`](https://github.com/gradido/gradido/pull/1852)
- 🍰 Refactor notActivated and isDeleted [`#1791`](https://github.com/gradido/gradido/pull/1791)
- devops: Disable Send Email on Seeding [`#1849`](https://github.com/gradido/gradido/pull/1849)
- fix: Confirm Creation with Decimal [`#1838`](https://github.com/gradido/gradido/pull/1838)
- error message by no navigator.clipbord function [`#1841`](https://github.com/gradido/gradido/pull/1841)
#### [1.8.0](https://github.com/gradido/gradido/compare/1.7.1...1.8.0)
> 25 April 2022
- v1.8.0 [`#1836`](https://github.com/gradido/gradido/pull/1836)
- Fix: database version requirement for backend corrected [`#1835`](https://github.com/gradido/gradido/pull/1835)
- feat: More User Resolver Tests [`#1827`](https://github.com/gradido/gradido/pull/1827)
- fix: Round Decay with Tranasction Links [`#1834`](https://github.com/gradido/gradido/pull/1834)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido",
"main": "index.js",
"author": "Moriz Wahl",
"version": "1.8.0",
"version": "1.8.1",
"license": "MIT",
"private": false,
"scripts": {

View File

@ -24,12 +24,6 @@ const mocks = {
},
$store: {
commit: stateCommitMock,
state: {
moderator: {
id: 0,
name: 'test moderator',
},
},
},
}
@ -122,7 +116,6 @@ describe('CreationFormular', () => {
creationDate: getCreationDate(2),
amount: 90,
memo: 'Test create coins',
moderator: 0,
},
}),
)
@ -370,14 +363,12 @@ describe('CreationFormular', () => {
creationDate: getCreationDate(1),
amount: 200,
memo: 'Test mass create coins',
moderator: 0,
},
{
email: 'bibi@bloxberg.de',
creationDate: getCreationDate(1),
amount: 200,
memo: 'Test mass create coins',
moderator: 0,
},
],
},

View File

@ -154,7 +154,6 @@ export default {
creationDate: this.selected.date,
amount: Number(this.value),
memo: this.text,
moderator: Number(this.$store.state.moderator.id),
})
})
this.$apollo
@ -188,7 +187,6 @@ export default {
creationDate: this.selected.date,
amount: Number(this.value),
memo: this.text,
moderator: Number(this.$store.state.moderator.id),
}
this.$apollo
.mutate({

View File

@ -11,7 +11,6 @@ const apolloMutateMock = jest.fn().mockResolvedValue({
amount: 500,
date: new Date(),
memo: 'Test Schöpfung 2',
moderator: 0,
},
},
})
@ -28,12 +27,6 @@ const mocks = {
mutate: apolloMutateMock,
},
$store: {
state: {
moderator: {
id: 0,
name: 'test moderator',
},
},
commit: stateCommitMock,
},
}
@ -104,7 +97,6 @@ describe('EditCreationFormular', () => {
creationDate: getCreationDate(0),
amount: 500,
memo: 'Test Schöpfung 2',
moderator: 0,
},
}),
)
@ -129,7 +121,6 @@ describe('EditCreationFormular', () => {
amount: 500,
date: expect.any(Date),
memo: 'Test Schöpfung 2',
moderator: 0,
row: expect.any(Object),
},
],

View File

@ -120,7 +120,6 @@ export default {
creationDate: this.selected.date,
amount: Number(this.value),
memo: this.text,
moderator: Number(this.$store.state.moderator.id),
},
})
.then((result) => {
@ -129,7 +128,6 @@ export default {
amount: Number(result.data.updatePendingCreation.amount),
date: result.data.updatePendingCreation.date,
memo: result.data.updatePendingCreation.memo,
moderator: Number(result.data.updatePendingCreation.moderator),
row: this.row,
})
this.toastSuccess(

View File

@ -1,19 +1,7 @@
import gql from 'graphql-tag'
export const createPendingCreation = gql`
mutation (
$email: String!
$amount: Decimal!
$memo: String!
$creationDate: String!
$moderator: Int!
) {
createPendingCreation(
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
moderator: $moderator
)
mutation ($email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
createPendingCreation(email: $email, amount: $amount, memo: $memo, creationDate: $creationDate)
}
`

View File

@ -5,15 +5,15 @@ export const searchUsers = gql`
$searchText: String!
$currentPage: Int
$pageSize: Int
$notActivated: Boolean
$isDeleted: Boolean
$filterByActivated: Boolean
$filterByDeleted: Boolean
) {
searchUsers(
searchText: $searchText
currentPage: $currentPage
pageSize: $pageSize
notActivated: $notActivated
isDeleted: $isDeleted
filterByActivated: $filterByActivated
filterByDeleted: $filterByDeleted
) {
userCount
userList {

View File

@ -1,27 +1,18 @@
import gql from 'graphql-tag'
export const updatePendingCreation = gql`
mutation (
$id: Int!
$email: String!
$amount: Decimal!
$memo: String!
$creationDate: String!
$moderator: Int!
) {
mutation ($id: Int!, $email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
updatePendingCreation(
id: $id
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
moderator: $moderator
) {
amount
date
memo
creation
moderator
}
}
`

View File

@ -71,8 +71,8 @@ describe('Creation', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
isDeleted: false,
notActivated: false,
filterByActivated: true,
filterByDeleted: false,
},
}),
)
@ -271,8 +271,8 @@ describe('Creation', () => {
searchText: 'XX',
currentPage: 1,
pageSize: 25,
isDeleted: false,
notActivated: false,
filterByActivated: true,
filterByDeleted: false,
},
}),
)
@ -288,8 +288,8 @@ describe('Creation', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
isDeleted: false,
notActivated: false,
filterByActivated: true,
filterByDeleted: false,
},
}),
)
@ -305,8 +305,8 @@ describe('Creation', () => {
searchText: '',
currentPage: 2,
pageSize: 25,
isDeleted: false,
notActivated: false,
filterByActivated: true,
filterByDeleted: false,
},
}),
)

View File

@ -102,8 +102,8 @@ export default {
searchText: this.criteria,
currentPage: this.currentPage,
pageSize: this.perPage,
notActivated: false,
isDeleted: false,
filterByActivated: true,
filterByDeleted: false,
},
fetchPolicy: 'network-only',
})

View File

@ -82,8 +82,8 @@ describe('UserSearch', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: null,
isDeleted: null,
filterByActivated: null,
filterByDeleted: null,
},
}),
)
@ -101,8 +101,8 @@ describe('UserSearch', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: true,
isDeleted: null,
filterByActivated: false,
filterByDeleted: null,
},
}),
)
@ -121,8 +121,8 @@ describe('UserSearch', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: null,
isDeleted: true,
filterByActivated: null,
filterByDeleted: true,
},
}),
)
@ -141,8 +141,8 @@ describe('UserSearch', () => {
searchText: '',
currentPage: 2,
pageSize: 25,
notActivated: null,
isDeleted: null,
filterByActivated: null,
filterByDeleted: null,
},
}),
)
@ -161,8 +161,8 @@ describe('UserSearch', () => {
searchText: 'search string',
currentPage: 1,
pageSize: 25,
notActivated: null,
isDeleted: null,
filterByActivated: null,
filterByDeleted: null,
},
}),
)
@ -178,8 +178,8 @@ describe('UserSearch', () => {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: null,
isDeleted: null,
filterByActivated: null,
filterByDeleted: null,
},
}),
)

View File

@ -3,11 +3,23 @@
<div class="user-search-first-div">
<b-button class="unconfirmedRegisterMails" variant="light" @click="unconfirmedRegisterMails">
<b-icon icon="envelope" variant="danger"></b-icon>
{{ filterCheckedEmails ? $t('unregistered_emails') : $t('all_emails') }}
{{
filterByActivated === null
? $t('all_emails')
: filterByActivated === false
? $t('unregistered_emails')
: ''
}}
</b-button>
<b-button class="deletedUserSearch" variant="light" @click="deletedUserSearch">
<b-icon icon="x-circle" variant="danger"></b-icon>
{{ filterDeletedUser ? $t('deleted_user') : $t('all_emails') }}
{{
filterByDeleted === null
? $t('all_emails')
: filterByDeleted === true
? $t('deleted_user')
: ''
}}
</b-button>
</div>
<label>{{ $t('user_search') }}</label>
@ -60,8 +72,8 @@ export default {
searchResult: [],
massCreation: [],
criteria: '',
filterCheckedEmails: null,
filterDeletedUser: null,
filterByActivated: null,
filterByDeleted: null,
rows: 0,
currentPage: 1,
perPage: 25,
@ -70,11 +82,11 @@ export default {
},
methods: {
unconfirmedRegisterMails() {
this.filterCheckedEmails = this.filterCheckedEmails ? null : true
this.filterByActivated = this.filterByActivated === null ? false : null
this.getUsers()
},
deletedUserSearch() {
this.filterDeletedUser = this.filterDeletedUser ? null : true
this.filterByDeleted = this.filterByDeleted === null ? true : null
this.getUsers()
},
getUsers() {
@ -85,8 +97,8 @@ export default {
searchText: this.criteria,
currentPage: this.currentPage,
pageSize: this.perPage,
notActivated: this.filterCheckedEmails,
isDeleted: this.filterDeletedUser,
filterByActivated: this.filterByActivated,
filterByDeleted: this.filterByDeleted,
},
fetchPolicy: 'no-cache',
})

View File

@ -1,6 +1,6 @@
{
"name": "gradido-backend",
"version": "1.8.0",
"version": "1.8.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -1,4 +1,4 @@
import { ArgsType, Field, InputType, Int } from 'type-graphql'
import { ArgsType, Field, InputType } from 'type-graphql'
import Decimal from 'decimal.js-light'
@InputType()
@ -15,7 +15,4 @@ export default class CreatePendingCreationArgs {
@Field(() => String)
creationDate: string
@Field(() => Int)
moderator: number
}

View File

@ -12,8 +12,8 @@ export default class SearchUsersArgs {
pageSize?: number
@Field(() => Boolean, { nullable: true })
notActivated?: boolean | null
filterByActivated?: boolean | null
@Field(() => Boolean, { nullable: true })
isDeleted?: boolean | null
filterByDeleted?: boolean | null
}

View File

@ -17,7 +17,4 @@ export default class UpdatePendingCreationArgs {
@Field(() => String)
creationDate: string
@Field(() => Int)
moderator: number
}

View File

@ -12,9 +12,6 @@ export class UpdatePendingCreation {
@Field(() => Decimal)
amount: Decimal
@Field(() => Number)
moderator: number
@Field(() => [Decimal])
creation: Decimal[]
}

File diff suppressed because it is too large Load Diff

View File

@ -56,19 +56,19 @@ export class AdminResolver {
searchText,
currentPage = 1,
pageSize = 25,
notActivated = null,
isDeleted = null,
filterByActivated = null,
filterByDeleted = null,
}: SearchUsersArgs,
): Promise<SearchUsersResult> {
const userRepository = getCustomRepository(UserRepository)
const filterCriteria: ObjectLiteral[] = []
if (notActivated !== null) {
filterCriteria.push({ emailChecked: !notActivated })
if (filterByActivated !== null) {
filterCriteria.push({ emailChecked: filterByActivated })
}
if (isDeleted !== null) {
filterCriteria.push({ deletedAt: isDeleted ? Not(IsNull()) : IsNull() })
if (filterByDeleted !== null) {
filterCriteria.push({ deletedAt: filterByDeleted ? Not(IsNull()) : IsNull() })
}
const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked', 'deletedAt']
@ -157,11 +157,12 @@ export class AdminResolver {
@Mutation(() => Date, { nullable: true })
async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise<Date | null> {
const user = await dbUser.findOne({ id: userId }, { withDeleted: true })
// user exists ?
if (!user) {
throw new Error(`Could not find user with userId: ${userId}`)
}
// recover user account
if (!user.deletedAt) {
throw new Error('User is not deleted')
}
await user.recover()
return null
}
@ -169,7 +170,8 @@ export class AdminResolver {
@Authorized([RIGHTS.CREATE_PENDING_CREATION])
@Mutation(() => [Number])
async createPendingCreation(
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
@Args() { email, amount, memo, creationDate }: CreatePendingCreationArgs,
@Ctx() context: Context,
): Promise<Decimal[]> {
const user = await dbUser.findOne({ email }, { withDeleted: true })
if (!user) {
@ -181,6 +183,7 @@ export class AdminResolver {
if (!user.emailChecked) {
throw new Error('Creation could not be saved, Email is not activated')
}
const moderator = getUser(context)
const creations = await getUserCreation(user.id)
const creationDateObj = new Date(creationDate)
if (isCreationValid(creations, amount, creationDateObj)) {
@ -190,7 +193,7 @@ export class AdminResolver {
adminPendingCreation.created = new Date()
adminPendingCreation.date = creationDateObj
adminPendingCreation.memo = memo
adminPendingCreation.moderator = moderator
adminPendingCreation.moderator = moderator.id
await AdminPendingCreation.save(adminPendingCreation)
}
@ -202,12 +205,13 @@ export class AdminResolver {
async createPendingCreations(
@Arg('pendingCreations', () => [CreatePendingCreationArgs])
pendingCreations: CreatePendingCreationArgs[],
@Ctx() context: Context,
): Promise<CreatePendingCreations> {
let success = false
const successfulCreation: string[] = []
const failedCreation: string[] = []
for (const pendingCreation of pendingCreations) {
await this.createPendingCreation(pendingCreation)
await this.createPendingCreation(pendingCreation, context)
.then(() => {
successfulCreation.push(pendingCreation.email)
success = true
@ -226,7 +230,8 @@ export class AdminResolver {
@Authorized([RIGHTS.UPDATE_PENDING_CREATION])
@Mutation(() => UpdatePendingCreation)
async updatePendingCreation(
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
@Args() { id, email, amount, memo, creationDate }: UpdatePendingCreationArgs,
@Ctx() context: Context,
): Promise<UpdatePendingCreation> {
const user = await dbUser.findOne({ email }, { withDeleted: true })
if (!user) {
@ -236,7 +241,13 @@ export class AdminResolver {
throw new Error(`User was deleted (${email})`)
}
const pendingCreationToUpdate = await AdminPendingCreation.findOneOrFail({ id })
const moderator = getUser(context)
const pendingCreationToUpdate = await AdminPendingCreation.findOne({ id })
if (!pendingCreationToUpdate) {
throw new Error('No creation found to given id.')
}
if (pendingCreationToUpdate.userId !== user.id) {
throw new Error('user of the pending creation and send user does not correspond')
@ -248,20 +259,18 @@ export class AdminResolver {
creations = updateCreations(creations, pendingCreationToUpdate)
}
if (!isCreationValid(creations, amount, creationDateObj)) {
throw new Error('Creation is not valid')
}
// all possible cases not to be true are thrown in this function
isCreationValid(creations, amount, creationDateObj)
pendingCreationToUpdate.amount = amount
pendingCreationToUpdate.memo = memo
pendingCreationToUpdate.date = new Date(creationDate)
pendingCreationToUpdate.moderator = moderator
pendingCreationToUpdate.moderator = moderator.id
await AdminPendingCreation.save(pendingCreationToUpdate)
const result = new UpdatePendingCreation()
result.amount = amount
result.memo = pendingCreationToUpdate.memo
result.date = pendingCreationToUpdate.date
result.moderator = pendingCreationToUpdate.moderator
result.creation = await getUserCreation(user.id)
@ -298,8 +307,11 @@ export class AdminResolver {
@Authorized([RIGHTS.DELETE_PENDING_CREATION])
@Mutation(() => Boolean)
async deletePendingCreation(@Arg('id', () => Int) id: number): Promise<boolean> {
const entity = await AdminPendingCreation.findOneOrFail(id)
const res = await AdminPendingCreation.delete(entity)
const pendingCreation = await AdminPendingCreation.findOne(id)
if (!pendingCreation) {
throw new Error('Creation not found for given id.')
}
const res = await AdminPendingCreation.delete(pendingCreation)
return !!res
}
@ -309,7 +321,10 @@ export class AdminResolver {
@Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise<boolean> {
const pendingCreation = await AdminPendingCreation.findOneOrFail(id)
const pendingCreation = await AdminPendingCreation.findOne(id)
if (!pendingCreation) {
throw new Error('Creation not found to given id.')
}
const moderatorUser = getUser(context)
if (moderatorUser.id === pendingCreation.userId)
throw new Error('Moderator can not confirm own pending creation')
@ -340,8 +355,7 @@ export class AdminResolver {
transaction.memo = pendingCreation.memo
transaction.userId = pendingCreation.userId
transaction.previous = lastTransaction ? lastTransaction.id : null
// TODO pending creations decimal
transaction.amount = new Decimal(Number(pendingCreation.amount))
transaction.amount = pendingCreation.amount
transaction.creationDate = pendingCreation.date
transaction.balance = newBalance
transaction.balanceDate = receivedCallDate
@ -504,7 +518,7 @@ function updateCreations(creations: Decimal[], pendingCreation: AdminPendingCrea
if (index < 0) {
throw new Error('You cannot create GDD for a month older than the last three months.')
}
creations[index] = creations[index].plus(pendingCreation.amount)
creations[index] = creations[index].plus(pendingCreation.amount.toString())
return creations
}
@ -512,12 +526,12 @@ function isCreationValid(creations: Decimal[], amount: Decimal, creationDate: Da
const index = getCreationIndex(creationDate.getMonth())
if (index < 0) {
throw new Error(`No Creation found!`)
throw new Error('No information for available creations for the given date')
}
if (amount.greaterThan(creations[index].toString())) {
throw new Error(
`The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`,
`The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`,
)
}

View File

@ -17,27 +17,22 @@ export const nMonthsBefore = (date: Date, months = 1): string => {
export const creationFactory = async (
client: ApolloServerTestClient,
creation: CreationInterface,
): Promise<void> => {
): Promise<AdminPendingCreation | void> => {
const { mutate, query } = client
// login as Peter Lustig (admin) and get his user ID
const {
data: {
login: { id },
},
} = await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
await mutate({ mutation: createPendingCreation, variables: { ...creation, moderator: id } })
// TODO it would be nice to have this mutation return the id
await mutate({ mutation: createPendingCreation, variables: { ...creation } })
// get User
const user = await User.findOneOrFail({ where: { email: creation.email } })
if (creation.confirmed) {
const pendingCreation = await AdminPendingCreation.findOneOrFail({
where: { userId: user.id },
order: { created: 'DESC' },
})
const pendingCreation = await AdminPendingCreation.findOneOrFail({
where: { userId: user.id, amount: creation.amount },
order: { created: 'DESC' },
})
if (creation.confirmed) {
await mutate({ mutation: confirmPendingCreation, variables: { id: pendingCreation.id } })
if (creation.moveCreationDate) {
@ -55,5 +50,7 @@ export const creationFactory = async (
await transaction.save()
}
}
} else {
return pendingCreation
}
}

View File

@ -7,7 +7,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
export const userFactory = async (
client: ApolloServerTestClient,
user: UserInterface,
): Promise<void> => {
): Promise<User> => {
const { mutate } = client
const {
@ -24,13 +24,15 @@ export const userFactory = async (
})
}
if (user.createdAt || user.deletedAt || user.isAdmin) {
// get user from database
const dbUser = await User.findOneOrFail({ id })
// get user from database
const dbUser = await User.findOneOrFail({ id })
if (user.createdAt || user.deletedAt || user.isAdmin) {
if (user.createdAt) dbUser.createdAt = user.createdAt
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
if (user.isAdmin) dbUser.isAdmin = new Date()
await dbUser.save()
}
return dbUser
}

View File

@ -84,20 +84,8 @@ export const createTransactionLink = gql`
// from admin interface
export const createPendingCreation = gql`
mutation (
$email: String!
$amount: Decimal!
$memo: String!
$creationDate: String!
$moderator: Int!
) {
createPendingCreation(
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
moderator: $moderator
)
mutation ($email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
createPendingCreation(email: $email, amount: $amount, memo: $memo, creationDate: $creationDate)
}
`
@ -106,3 +94,48 @@ export const confirmPendingCreation = gql`
confirmPendingCreation(id: $id)
}
`
export const deleteUser = gql`
mutation ($userId: Int!) {
deleteUser(userId: $userId)
}
`
export const unDeleteUser = gql`
mutation ($userId: Int!) {
unDeleteUser(userId: $userId)
}
`
export const createPendingCreations = gql`
mutation ($pendingCreations: [CreatePendingCreationArgs!]!) {
createPendingCreations(pendingCreations: $pendingCreations) {
success
successfulCreation
failedCreation
}
}
`
export const updatePendingCreation = gql`
mutation ($id: Int!, $email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
updatePendingCreation(
id: $id
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
) {
amount
date
memo
creation
}
}
`
export const deletePendingCreation = gql`
mutation ($id: Int!) {
deletePendingCreation(id: $id)
}
`

View File

@ -148,3 +148,21 @@ export const queryTransactionLink = gql`
}
}
`
// from admin interface
export const getPendingCreations = gql`
query {
getPendingCreations {
id
firstName
lastName
email
amount
memo
date
moderator
creation
}
}
`

View File

@ -4,7 +4,7 @@
import createServer from '../server/createServer'
import { createTestClient } from 'apollo-server-testing'
import { name, internet, random } from 'faker'
import { name, internet, datatype } from 'faker'
import { users } from './users/index'
import { creations } from './creation/index'
@ -13,6 +13,9 @@ import { userFactory } from './factory/user'
import { creationFactory } from './factory/creation'
import { transactionLinkFactory } from './factory/transactionLink'
import { entities } from '@entity/index'
import CONFIG from '@/config'
CONFIG.EMAIL = false
const context = {
token: '',
@ -57,7 +60,7 @@ const run = async () => {
firstName: name.firstName(),
lastName: name.lastName(),
email: internet.email(),
language: random.boolean() ? 'en' : 'de',
language: datatype.boolean() ? 'en' : 'de',
})
}

View File

@ -1,6 +1,6 @@
{
"name": "gradido-database",
"version": "1.8.0",
"version": "1.8.1",
"description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database",

View File

@ -38,7 +38,7 @@ KLICKTIPP_PASSWORD=
KLICKTIPP_APIKEY_DE=
KLICKTIPP_APIKEY_EN=
EMAIL=false
EMAIL=true
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234

View File

@ -105,7 +105,8 @@ yarn install
yarn build
if [ "$DEPLOY_SEED_DATA" = "true" ]; then
yarn dev_up
yarn dev_reset
# As dev_reset is not running properly (0019-replace_login_user_id_with_state_user_id)
# yarn dev_reset
else
yarn up
fi

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "1.8.0",
"version": "1.8.1",
"private": true,
"scripts": {
"start": "node run/server.js",
@ -25,6 +25,7 @@
"babel-preset-vue": "^2.0.2",
"bootstrap": "^4.5.3",
"bootstrap-vue": "^2.21.2",
"clipboard-polyfill": "^4.0.0-rc1",
"es6-promise": "^4.1.1",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.1.0",

View File

@ -37,74 +37,7 @@ describe('Message', () => {
})
it('button link redirects to /login', () => {
// Wolle console.log(wrapper.html())
expect(wrapper.find('a.btn').attributes('href')).toBe('/login')
})
})
// Wolle: test 'code' prop and have a look if there is other important stuff uncommented below
// Wolle describe('coming from /forgot-password', () => {
// beforeEach(() => {
// wrapper = Wrapper(createMockObject('forgotPassword'))
// })
// it('renders the thanks text', () => {
// expect(wrapper.find('p.h4').text()).toBe('site.thx.email')
// })
// it('renders the thanks redirect button', () => {
// expect(wrapper.find('a.btn').text()).toBe('login')
// })
// it('links the redirect button to /login', () => {
// expect(wrapper.find('a.btn').attributes('href')).toBe('/login')
// })
// })
// describe('coming from /reset-password', () => {
// beforeEach(() => {
// wrapper = Wrapper(createMockObject('resetPassword'))
// })
// it('renders the thanks text', () => {
// expect(wrapper.find('p.h4').text()).toBe('site.thx.reset')
// })
// it('renders the thanks redirect button', () => {
// expect(wrapper.find('a.btn').text()).toBe('login')
// })
// it('links the redirect button to /login', () => {
// expect(wrapper.find('a.btn').attributes('href')).toBe('/login')
// })
// })
// describe('coming from /register', () => {
// beforeEach(() => {
// wrapper = Wrapper(createMockObject('register'))
// })
// it('renders the thanks text', () => {
// expect(wrapper.find('p.h4').text()).toBe('site.thx.register')
// })
// })
// describe('coming from /login', () => {
// beforeEach(() => {
// wrapper = Wrapper(createMockObject('login'))
// })
// it('renders the thanks text', () => {
// expect(wrapper.find('p.h4').text()).toBe('site.thx.activateEmail')
// })
// it('renders the thanks redirect button', () => {
// expect(wrapper.find('a.btn').text()).toBe('settings.password.reset')
// })
// it('links the redirect button to /forgot-password', () => {
// expect(wrapper.find('a.btn').attributes('href')).toBe('/forgot-password')
// })
// })
})

View File

@ -1,12 +1,9 @@
<template>
<div>
<!-- Header -->
<div class="header py-lg-6">
<b-container class="w-50">
<div class="header-body text-center mb-7">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
<p class="h1 test-message-headline">{{ headline }}</p>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
<p class="h4 test-message-subtitle">{{ subtitle }}</p>
<hr />
<b-button
@ -15,13 +12,11 @@
:to="linkTo ? linkTo + (code ? `/${code}` : '') : null"
@click="optionalCallback()"
>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
{{ buttonText }}
</b-button>
</div>
</b-container>
</div>
<!-- Page content -->
</div>
</template>
@ -34,7 +29,6 @@ export default {
buttonText: { type: String, required: false, default: null },
linkTo: { type: String, required: false, default: null },
callback: { type: Function, required: false, default: null },
code: { type: String, required: false, default: null }, // Wolle: to be removed by adding it directly to the "linkTo"
},
methods: {
optionalCallback() {

View File

@ -16,6 +16,8 @@ import router from './routes/router'
import { apolloProvider } from './plugins/apolloProvider'
import 'clipboard-polyfill/overwrite-globals'
// plugin setup
Vue.use(DashboardPlugin)
Vue.config.productionTip = false

View File

@ -1,5 +1,6 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import { toastErrorSpy } from '@test/testSetup'
import ForgotPassword from './ForgotPassword'
const mockAPIcall = jest.fn()
@ -127,12 +128,15 @@ describe('ForgotPassword', () => {
expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login')
})
// Wolle
it.skip('click redirects to "/login"', async () => {
// wrapper.find('.test-message-button').trigger('click')
// await wrapper.vm.$nextTick()
expect(mockRouterPush).toBeCalledWith('/login')
})
it('toasts a standard error message', () => {
expect(toastErrorSpy).toBeCalledWith('error.email-already-sent')
})
})
describe('success', () => {
@ -159,7 +163,6 @@ describe('ForgotPassword', () => {
expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login')
})
// Wolle
it.skip('click redirects to "/login"', () => {
// expect(mockRouterPush).toBeCalledWith('/login')
})

View File

@ -37,7 +37,6 @@
</div>
</b-container>
<b-container v-else class="mt--8 p-1">
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys-->
<message
v-if="success"
:headline="$t('site.thx.title')"
@ -52,7 +51,6 @@
:buttonText="$t('login')"
linkTo="/login"
/>
<!-- eslint-enable @intlify/vue-i18n/no-dynamic-keys-->
</b-container>
</div>
</template>
@ -91,9 +89,9 @@ export default {
this.success = true
})
.catch(() => {
this.toastError(this.$t('error.email-already-sent'))
this.showPageMessage = true
this.success = false
this.toastError(this.$t('error.email-already-sent'))
})
},
},

View File

@ -1,8 +1,7 @@
import { RouterLinkStub, mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Login from './Login'
import { toastErrorSpy } from '@test/testSetup'
import Login from './Login'
const localVue = global.localVue
@ -205,32 +204,10 @@ describe('Login', () => {
})
describe('login fails', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.find('input[placeholder="Email"]').setValue('user@example.org')
await wrapper.find('input[placeholder="form.password"]').setValue('1234')
await flushPromises()
apolloQueryMock.mockRejectedValue({
message: '...No user with this credentials',
})
await wrapper.find('form').trigger('submit')
await flushPromises()
})
it('hides the spinner', () => {
expect(spinnerHideMock).toBeCalled()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith(
'error.unknown-error...No user with this credentials',
)
})
describe('login fails with "User email not validated"', () => {
beforeEach(async () => {
apolloQueryMock.mockRejectedValue({
message: 'User email not validated',
message: 'GraphQL error: User email not validated',
})
wrapper = Wrapper()
jest.clearAllMocks()
@ -241,6 +218,10 @@ describe('Login', () => {
await flushPromises()
})
it('hides the spinner', () => {
expect(spinnerHideMock).toBeCalled()
})
it('shows error title, subtitle, login button', () => {
expect(wrapper.vm.showPageMessage).toBeTruthy()
expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.errorTitle')
@ -252,16 +233,19 @@ describe('Login', () => {
expect(wrapper.find('.test-message-button').attributes('href')).toBe('/forgot-password')
})
// Wolle
it.skip('click redirects to "/forgot-password"', () => {
// expect(mockRouterPush).toBeCalledWith('/thx/login')
})
it('toasts the error message', () => {
expect(toastErrorSpy).toBeCalledWith('error.no-account')
})
})
describe('login fails with "User has no password set yet"', () => {
beforeEach(async () => {
apolloQueryMock.mockRejectedValue({
message: 'User has no password set yet',
message: 'GraphQL error: User has no password set yet',
})
wrapper = Wrapper()
jest.clearAllMocks()
@ -285,10 +269,13 @@ describe('Login', () => {
)
})
// Wolle
it.skip('click redirects to "/reset-password/login"', () => {
// expect(mockRouterPush).toBeCalledWith('/reset-password/login')
})
it('toasts the error message', () => {
expect(toastErrorSpy).toBeCalledWith('error.no-account')
})
})
})
})

View File

@ -58,14 +58,12 @@
</b-row>
</b-container>
<b-container v-else class="mt--8 p-1">
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys-->
<message
:headline="$t('site.thx.errorTitle')"
:subtitle="errorSubtitle"
:buttonText="$t('settings.password.reset')"
:linkTo="errorLinkTo"
/>
<!-- eslint-enable @intlify/vue-i18n/no-dynamic-keys-->
</b-container>
</div>
</template>
@ -127,22 +125,22 @@ export default {
})
.catch((error) => {
if (error.message.includes('User email not validated')) {
this.toastError(this.$t('error.no-account'))
this.showPageMessage = true
this.errorSubtitle = this.$t('site.thx.activateEmail')
this.errorLinkTo = '/forgot-password'
} else if (error.message.includes('User has no password set yet')) {
this.toastError(this.$t('error.no-account'))
} else if (error.message.includes('User has no password set yet')) {
this.showPageMessage = true
this.errorSubtitle = this.$t('site.thx.unsetPassword')
this.errorLinkTo = '/reset-password/login'
this.toastError(this.$t('error.no-account'))
} else {
// appeared errors: 'GraphQL error: No user with this credentials'
const errorMessage = this.$t('error.unknown-error') + error.message
this.toastError(errorMessage)
this.showPageMessage = true
this.errorSubtitle = errorMessage
this.errorLinkTo = '/forgot-password'
this.toastError(errorMessage)
}
loader.hide()
})

View File

@ -1,5 +1,6 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import { toastErrorSpy } from '@test/testSetup'
import Register from './Register'
const localVue = global.localVue
@ -228,24 +229,6 @@ describe('Register', () => {
await flushPromises()
})
// Wolle: remove?
// it('shows error message', () => {
// expect(wrapper.find('span.alert-text').exists()).toBeTruthy()
// expect(wrapper.find('span.alert-text').text().length !== 0).toBeTruthy()
// expect(wrapper.find('span.alert-text').text()).toContain('error.error')
// expect(wrapper.find('span.alert-text').text()).toContain('Ouch!')
// })
// it('button to dismisses error message is present', () => {
// expect(wrapper.find('button.close').exists()).toBeTruthy()
// })
// it('dismisses error message', async () => {
// await wrapper.find('button.close').trigger('click')
// await flushPromises()
// expect(wrapper.find('span.alert-text').exists()).not.toBeTruthy()
// })
it('shows success title, subtitle, login button', () => {
expect(wrapper.vm.showPageMessage).toBeTruthy()
expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.errorTitle')
@ -255,6 +238,10 @@ describe('Register', () => {
)
})
it('toasts the error message', () => {
expect(toastErrorSpy).toBeCalledWith('error.user-already-exists')
})
it('click calls "solveError"', async () => {
wrapper.find('.test-message-button').trigger('click')
await wrapper.vm.$nextTick()

View File

@ -105,20 +105,6 @@
</b-form-checkbox>
</b-col>
</b-row>
<!-- Wolle: remove this? or shall the alert or a toaster be shown? -->
<b-alert
v-if="showError"
show
dismissible
variant="danger"
@dismissed="closeAlert"
>
<span class="alert-icon"><i class="ni ni-point"></i></span>
<span class="alert-text">
<strong>{{ $t('error.error') }}</strong>
{{ messageError }}
</span>
</b-alert>
<b-row v-b-toggle:my-collapse class="text-muted shadow-sm p-3 publisherCollaps">
<b-col>{{ $t('publisher.publisherId') }} {{ $store.state.publisherId }}</b-col>
@ -179,7 +165,6 @@
</b-row>
</b-container>
<b-container v-else class="mt--8 p-1">
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys-->
<message
v-if="success"
:headline="$t('site.thx.title')"
@ -192,7 +177,6 @@
:buttonText="$t('site.register.message-button-text')"
:callback="solveError"
/>
<!-- eslint-enable @intlify/vue-i18n/no-dynamic-keys-->
</b-container>
<!--
<div class="text-center pt-4">
@ -230,9 +214,7 @@ export default {
language: '',
showPageMessage: false,
submitted: false,
showError: false,
messageError: '',
register: true,
publisherId: this.$store.state.publisherId,
redeemCode: this.$route.params.code,
CONFIG,
@ -263,14 +245,12 @@ export default {
},
})
.then(() => {
// Wolle: this.$router.push('/thx/register')
this.showPageMessage = true
this.success = true
})
.catch((error) => {
this.showPageMessage = true
this.success = false
this.showError = true
switch (error.message) {
case 'GraphQL error: User already exists.':
this.messageError = this.$t('error.user-already-exists')
@ -279,21 +259,11 @@ export default {
this.messageError = this.$t('error.unknown-error') + error.message
break
}
// Wolle: this.toastError(this.$t('error.email-already-sent'))
// Wolle: shall the alert be replaced by a toaster or shall only the page message be shown?
this.toastError(this.messageError)
})
},
// Wolle: remove this?
closeAlert() {
this.showError = false
this.messageError = ''
this.form.email = ''
this.form.firstname = ''
this.form.lastname = ''
},
solveError() {
this.showPageMessage = false
this.showError = false
this.messageError = ''
this.form.email = ''
this.form.firstname = ''

View File

@ -4856,6 +4856,11 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
clipboard-polyfill@^4.0.0-rc1:
version "4.0.0-rc1"
resolved "https://registry.yarnpkg.com/clipboard-polyfill/-/clipboard-polyfill-4.0.0-rc1.tgz#a000ab25b1f582bca03667dc572854f1c8d92b04"
integrity sha512-Cel03Es9ZgP6pYA2JT9cZ2VgvOH2/EHgB7jji84FpINBJWqfMEwiI1Y3LstVL+E43cm3CnCrLL2vwb9DMbr28A==
clipboardy@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "1.8.0",
"version": "1.8.1",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",