Merge branch 'master' into send-form-border-radius

This commit is contained in:
Alexander Friedland 2022-09-29 15:09:47 +02:00 committed by GitHub
commit 42cc74e1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 576 additions and 295 deletions

View File

@ -15,7 +15,10 @@
<b-collapse v-model="visible" id="newContribution" class="mt-2"> <b-collapse v-model="visible" id="newContribution" class="mt-2">
<b-card> <b-card>
<p class="h2 ml-5">{{ $t('contributionLink.contributionLinks') }}</p> <p class="h2 ml-5">{{ $t('contributionLink.contributionLinks') }}</p>
<contribution-link-form :contributionLinkData="contributionLinkData" /> <contribution-link-form
:contributionLinkData="contributionLinkData"
@get-contribution-links="$emit('get-contribution-links')"
/>
</b-card> </b-card>
</b-collapse> </b-collapse>
@ -24,6 +27,7 @@
v-if="count > 0" v-if="count > 0"
:items="items" :items="items"
@editContributionLinkData="editContributionLinkData" @editContributionLinkData="editContributionLinkData"
@get-contribution-links="$emit('get-contribution-links')"
/> />
<div v-else>{{ $t('contributionLink.noContributionLinks') }}</div> <div v-else>{{ $t('contributionLink.noContributionLinks') }}</div>
</b-card-text> </b-card-text>

View File

@ -163,7 +163,6 @@ export default {
if (this.form.validFrom === null) if (this.form.validFrom === null)
return this.toastError(this.$t('contributionLink.noStartDate')) return this.toastError(this.$t('contributionLink.noStartDate'))
if (this.form.validTo === null) return this.toastError(this.$t('contributionLink.noEndDate')) if (this.form.validTo === null) return this.toastError(this.$t('contributionLink.noEndDate'))
// alert(JSON.stringify(this.form))
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: createContributionLink, mutation: createContributionLink,
@ -182,6 +181,8 @@ export default {
this.link = result.data.createContributionLink.link this.link = result.data.createContributionLink.link
this.toastSuccess(this.link) this.toastSuccess(this.link)
this.onReset() this.onReset()
this.$root.$emit('bv::toggle::collapse', 'newContribution')
this.$emit('get-contribution-links')
}) })
.catch((error) => { .catch((error) => {
this.toastError(error.message) this.toastError(error.message)

View File

@ -95,7 +95,7 @@ describe('ContributionLinkList', () => {
}) })
it('toasts a success message', () => { it('toasts a success message', () => {
expect(toastSuccessSpy).toBeCalledWith('TODO: request message deleted ') expect(toastSuccessSpy).toBeCalledWith('contributionLink.deleted')
}) })
}) })

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="contribution-link-list"> <div class="contribution-link-list">
<b-table striped hover :items="items" :fields="fields"> <b-table striped hover :items="items" :fields="fields">
<template #cell(delete)> <template #cell(delete)="data">
<b-button <b-button
variant="danger" variant="danger"
size="md" size="md"
class="mr-2 test-delete-link" class="mr-2 test-delete-link"
@click="deleteContributionLink" @click="deleteContributionLink(data.item.id, data.item.name)"
> >
<b-icon icon="trash" variant="light"></b-icon> <b-icon icon="trash" variant="light"></b-icon>
</b-button> </b-button>
@ -34,7 +34,7 @@
<h6 class="mb-0">{{ modalData ? modalData.name : '' }}</h6> <h6 class="mb-0">{{ modalData ? modalData.name : '' }}</h6>
</template> </template>
<b-card-text> <b-card-text>
{{ modalData }} {{ modalData.memo ? modalData.memo : '' }}
<figure-qr-code :link="modalData ? modalData.link : ''" /> <figure-qr-code :link="modalData ? modalData.link : ''" />
</b-card-text> </b-card-text>
<template #footer> <template #footer>
@ -70,23 +70,25 @@ export default {
'edit', 'edit',
'show', 'show',
], ],
modalData: null, modalData: {},
modalDataLink: null,
} }
}, },
methods: { methods: {
deleteContributionLink() { deleteContributionLink(id, name) {
this.$bvModal.msgBoxConfirm(this.$t('contributionLink.deleteNow')).then(async (value) => { this.$bvModal
.msgBoxConfirm(this.$t('contributionLink.deleteNow', { name: name }))
.then(async (value) => {
if (value) if (value)
await this.$apollo await this.$apollo
.mutate({ .mutate({
mutation: deleteContributionLink, mutation: deleteContributionLink,
variables: { variables: {
id: this.id, id: id,
}, },
}) })
.then(() => { .then(() => {
this.toastSuccess('TODO: request message deleted ') this.toastSuccess(this.$t('contributionLink.deleted'))
this.$emit('get-contribution-links')
}) })
.catch((err) => { .catch((err) => {
this.toastError(err.message) this.toastError(err.message)

View File

@ -20,7 +20,7 @@ describe('ContributionMessagesListItem', () => {
const propsData = { const propsData = {
contributionId: 42, contributionId: 42,
state: 'PENDING0', state: 'PENDING',
message: { message: {
id: 111, id: 111,
message: 'asd asda sda sda', message: 'asd asda sda sda',

View File

@ -6,31 +6,30 @@ const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({ const apolloQueryMock = jest.fn().mockResolvedValue({
data: { data: {
creationTransactionList: [ creationTransactionList: {
contributionCount: 2,
contributionList: [
{ {
id: 1, id: 1,
amount: 100, amount: 5.8,
balanceDate: 0, createdAt: '2022-09-21T11:09:51.000Z',
creationDate: new Date(), confirmedAt: null,
memo: 'Testing', contributionDate: '2022-08-01T00:00:00.000Z',
linkedUser: { memo: 'für deine Hilfe, Fräulein Rottenmeier',
firstName: 'Gradido', state: 'PENDING',
lastName: 'Akademie',
},
}, },
{ {
id: 2, id: 2,
amount: 200, amount: '47',
balanceDate: 0, createdAt: '2022-09-21T11:09:28.000Z',
creationDate: new Date(), confirmedAt: '2022-09-21T11:09:28.000Z',
memo: 'Testing 2', contributionDate: '2022-08-01T00:00:00.000Z',
linkedUser: { memo: 'für deine Hilfe, Frau Holle',
firstName: 'Gradido', state: 'CONFIRMED',
lastName: 'Akademie',
},
}, },
], ],
}, },
},
}) })
const mocks = { const mocks = {
@ -43,7 +42,7 @@ const mocks = {
const propsData = { const propsData = {
userId: 1, userId: 1,
fields: ['date', 'balance', 'name', 'memo', 'decay'], fields: ['createdAt', 'contributionDate', 'confirmedAt', 'amount', 'memo'],
} }
describe('CreationTransactionList', () => { describe('CreationTransactionList', () => {
@ -63,7 +62,7 @@ describe('CreationTransactionList', () => {
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
currentPage: 1, currentPage: 1,
pageSize: 25, pageSize: 10,
order: 'DESC', order: 'DESC',
userId: 1, userId: 1,
}, },

View File

@ -1,7 +1,44 @@
<template> <template>
<div class="component-creation-transaction-list"> <div class="component-creation-transaction-list">
<div class="h3">{{ $t('transactionlist.title') }}</div> <div class="h3">{{ $t('transactionlist.title') }}</div>
<b-table striped hover :fields="fields" :items="items"></b-table> <b-table striped hover :fields="fields" :items="items">
<template #cell(contributionDate)="data">
<div class="font-weight-bold">
{{ $d(new Date(data.item.contributionDate), 'month') }}
</div>
<div>{{ $d(new Date(data.item.contributionDate)) }}</div>
</template>
</b-table>
<div>
<b-pagination
pills
size="lg"
v-model="currentPage"
:per-page="perPage"
:total-rows="rows"
align="center"
:hide-ellipsis="true"
></b-pagination>
<b-button v-b-toggle.collapse-1 variant="light" size="sm">{{ $t('help.help') }}</b-button>
<b-collapse id="collapse-1" class="mt-2">
<div>
{{ $t('transactionlist.submitted') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.submitted') }}
</div>
<div>
{{ $t('transactionlist.period') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.periods') }}
</div>
<div>
{{ $t('transactionlist.confirmed') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.confirmed') }}
</div>
<div>
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
{{ $t('help.transactionlist.state') }}
</div>
</b-collapse>
</div>
</div> </div>
</template> </template>
<script> <script>
@ -13,14 +50,37 @@ export default {
}, },
data() { data() {
return { return {
items: [],
rows: 0,
currentPage: 1,
perPage: 10,
fields: [ fields: [
{ {
key: 'creationDate', key: 'createdAt',
label: this.$t('transactionlist.date'), label: this.$t('transactionlist.submitted'),
formatter: (value, key, item) => { formatter: (value, key, item) => {
return this.$d(new Date(value)) return this.$d(new Date(value))
}, },
}, },
{
key: 'contributionDate',
label: this.$t('transactionlist.period'),
},
{
key: 'confirmedAt',
label: this.$t('transactionlist.confirmed'),
formatter: (value, key, item) => {
if (value) {
return this.$d(new Date(value))
} else {
return null
}
},
},
{
key: 'state',
label: this.$t('transactionlist.state'),
},
{ {
key: 'amount', key: 'amount',
label: this.$t('transactionlist.amount'), label: this.$t('transactionlist.amount'),
@ -28,23 +88,8 @@ export default {
return `${value} GDD` return `${value} GDD`
}, },
}, },
{
key: 'linkedUser',
label: this.$t('transactionlist.community'),
formatter: (value, key, item) => {
return `${value.firstName} ${value.lastName}`
},
},
{ key: 'memo', label: this.$t('transactionlist.memo') }, { key: 'memo', label: this.$t('transactionlist.memo') },
{
key: 'balanceDate',
label: this.$t('transactionlist.balanceDate'),
formatter: (value, key, item) => {
return this.$d(new Date(value))
},
},
], ],
items: [],
} }
}, },
methods: { methods: {
@ -53,14 +98,15 @@ export default {
.query({ .query({
query: creationTransactionList, query: creationTransactionList,
variables: { variables: {
currentPage: 1, currentPage: this.currentPage,
pageSize: 25, pageSize: this.perPage,
order: 'DESC', order: 'DESC',
userId: parseInt(this.userId), userId: parseInt(this.userId),
}, },
}) })
.then((result) => { .then((result) => {
this.items = result.data.creationTransactionList this.rows = result.data.creationTransactionList.contributionCount
this.items = result.data.creationTransactionList.contributionList
}) })
.catch((error) => { .catch((error) => {
this.toastError(error.message) this.toastError(error.message)
@ -70,5 +116,10 @@ export default {
created() { created() {
this.getTransactions() this.getTransactions()
}, },
watch: {
currentPage() {
this.getTransactions()
},
},
} }
</script> </script>

View File

@ -8,14 +8,15 @@ export const creationTransactionList = gql`
order: $order order: $order
userId: $userId userId: $userId
) { ) {
contributionCount
contributionList {
id id
amount amount
balanceDate createdAt
creationDate confirmedAt
contributionDate
memo memo
linkedUser { state
firstName
lastName
} }
} }
} }

View File

@ -1,18 +0,0 @@
import gql from 'graphql-tag'
export const showContributionLink = gql`
query ($id: Int!) {
showContributionLink {
id
validFrom
validTo
name
memo
amount
cycle
maxPerCycle
maxAmountPerMonth
code
}
}
`

View File

@ -7,7 +7,8 @@
"contributionLinks": "Beitragslinks", "contributionLinks": "Beitragslinks",
"create": "Anlegen", "create": "Anlegen",
"cycle": "Zyklus", "cycle": "Zyklus",
"deleteNow": "Automatische Creations wirklich löschen?", "deleted": "Automatische Schöpfung gelöscht!",
"deleteNow": "Automatische Creations '{name}' wirklich löschen?",
"maximumAmount": "maximaler Betrag", "maximumAmount": "maximaler Betrag",
"maxPerCycle": "Wiederholungen", "maxPerCycle": "Wiederholungen",
"memo": "Nachricht", "memo": "Nachricht",
@ -74,10 +75,20 @@
"submit": "Senden" "submit": "Senden"
}, },
"GDD": "GDD", "GDD": "GDD",
"help": {
"help": "Hilfe",
"transactionlist": {
"confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.",
"periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.",
"state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
"submitted": "Wann wurde es vom Mitglied eingereicht"
}
},
"hide_details": "Details verbergen", "hide_details": "Details verbergen",
"lastname": "Nachname", "lastname": "Nachname",
"math": { "math": {
"colon": ":", "colon": ":",
"equals": "=",
"exclaim": "!", "exclaim": "!",
"pipe": "|", "pipe": "|",
"plus": "+" "plus": "+"
@ -133,10 +144,11 @@
}, },
"transactionlist": { "transactionlist": {
"amount": "Betrag", "amount": "Betrag",
"balanceDate": "Schöpfungsdatum", "confirmed": "Bestätigt",
"community": "Gemeinschaft",
"date": "Datum",
"memo": "Nachricht", "memo": "Nachricht",
"period": "Zeitraum",
"state": "Status",
"submitted": "Eingereicht",
"title": "Alle geschöpften Transaktionen für den Nutzer" "title": "Alle geschöpften Transaktionen für den Nutzer"
}, },
"undelete_user": "Nutzer wiederherstellen", "undelete_user": "Nutzer wiederherstellen",

View File

@ -7,7 +7,8 @@
"contributionLinks": "Contribution Links", "contributionLinks": "Contribution Links",
"create": "Create", "create": "Create",
"cycle": "Cycle", "cycle": "Cycle",
"deleteNow": "Do you really delete automatic creations?", "deleted": "Automatic creation deleted!",
"deleteNow": "Do you really delete automatic creations '{name}'?",
"maximumAmount": "Maximum amount", "maximumAmount": "Maximum amount",
"maxPerCycle": "Repetition", "maxPerCycle": "Repetition",
"memo": "Memo", "memo": "Memo",
@ -74,10 +75,20 @@
"submit": "Send" "submit": "Send"
}, },
"GDD": "GDD", "GDD": "GDD",
"help": {
"help": "Help",
"transactionlist": {
"confirmed": "When was it confirmed by a moderator / admin.",
"periods": "For what period was it submitted by the member.",
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = denied, CONFIRMED = confirmed]",
"submitted": "When was it submitted by the member"
}
},
"hide_details": "Hide details", "hide_details": "Hide details",
"lastname": "Lastname", "lastname": "Lastname",
"math": { "math": {
"colon": ":", "colon": ":",
"equals": "=",
"exclaim": "!", "exclaim": "!",
"pipe": "|", "pipe": "|",
"plus": "+" "plus": "+"
@ -133,10 +144,11 @@
}, },
"transactionlist": { "transactionlist": {
"amount": "Amount", "amount": "Amount",
"balanceDate": "Creation date", "confirmed": "Confirmed",
"community": "Community",
"date": "Date",
"memo": "Message", "memo": "Message",
"period": "Period",
"state": "State",
"submitted": "Submitted",
"title": "All creation-transactions for the user" "title": "All creation-transactions for the user"
}, },
"undelete_user": "Undelete User", "undelete_user": "Undelete User",

View File

@ -28,7 +28,11 @@
</b-link> </b-link>
</b-card-text> </b-card-text>
</b-card> </b-card>
<contribution-link :items="items" :count="count" /> <contribution-link
:items="items"
:count="count"
@get-contribution-links="getContributionLinks"
/>
<community-statistic class="mt-5" v-model="statistics" /> <community-statistic class="mt-5" v-model="statistics" />
</div> </div>
</template> </template>

View File

@ -5,7 +5,7 @@ import { User } from '@entity/User'
@ObjectType() @ObjectType()
export class Contribution { export class Contribution {
constructor(contribution: dbContribution, user: User) { constructor(contribution: dbContribution, user?: User | null) {
this.id = contribution.id this.id = contribution.id
this.firstName = user ? user.firstName : null this.firstName = user ? user.firstName : null
this.lastName = user ? user.lastName : null this.lastName = user ? user.lastName : null

View File

@ -15,6 +15,7 @@ import { AdminCreateContributions } from '@model/AdminCreateContributions'
import { AdminUpdateContribution } from '@model/AdminUpdateContribution' import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { ContributionLinkList } from '@model/ContributionLinkList' import { ContributionLinkList } from '@model/ContributionLinkList'
import { Contribution } from '@model/Contribution'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { UserRepository } from '@repository/User' import { UserRepository } from '@repository/User'
import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs' import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs'
@ -23,12 +24,10 @@ import SearchUsersArgs from '@arg/SearchUsersArgs'
import ContributionLinkArgs from '@arg/ContributionLinkArgs' import ContributionLinkArgs from '@arg/ContributionLinkArgs'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Transaction } from '@model/Transaction'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { TransactionRepository } from '@repository/Transaction'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
import { Contribution } from '@entity/Contribution' import { Contribution as DbContribution } from '@entity/Contribution'
import { hasElopageBuys } from '@/util/hasElopageBuys' import { hasElopageBuys } from '@/util/hasElopageBuys'
import { User as dbUser } from '@entity/User' import { User as dbUser } from '@entity/User'
import { User } from '@model/User' import { User } from '@model/User'
@ -40,7 +39,6 @@ import { Decay } from '@model/Decay'
import Paginated from '@arg/Paginated' import Paginated from '@arg/Paginated'
import TransactionLinkFilters from '@arg/TransactionLinkFilters' import TransactionLinkFilters from '@arg/TransactionLinkFilters'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
import { communityUser } from '@/util/communityUser'
import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver' import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
@ -66,6 +64,7 @@ import { ContributionMessageType } from '@enum/MessageType'
import { ContributionMessage } from '@model/ContributionMessage' import { ContributionMessage } from '@model/ContributionMessage'
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail' import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail' import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
import { ContributionListResult } from '../model/Contribution'
// const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_IN_REGISTER = 1
// const EMAIL_OPT_UNKNOWN = 3 // elopage? // const EMAIL_OPT_UNKNOWN = 3 // elopage?
@ -248,7 +247,7 @@ export class AdminResolver {
const creationDateObj = new Date(creationDate) const creationDateObj = new Date(creationDate)
logger.trace('creationDateObj:', creationDateObj) logger.trace('creationDateObj:', creationDateObj)
validateContribution(creations, amount, creationDateObj) validateContribution(creations, amount, creationDateObj)
const contribution = Contribution.create() const contribution = DbContribution.create()
contribution.userId = emailContact.userId contribution.userId = emailContact.userId
contribution.amount = amount contribution.amount = amount
contribution.createdAt = new Date() contribution.createdAt = new Date()
@ -259,7 +258,7 @@ export class AdminResolver {
contribution.contributionStatus = ContributionStatus.PENDING contribution.contributionStatus = ContributionStatus.PENDING
logger.trace('contribution to save', contribution) logger.trace('contribution to save', contribution)
await Contribution.save(contribution) await DbContribution.save(contribution)
return getUserCreation(emailContact.userId) return getUserCreation(emailContact.userId)
} }
@ -317,7 +316,7 @@ export class AdminResolver {
const moderator = getUser(context) const moderator = getUser(context)
const contributionToUpdate = await Contribution.findOne({ const contributionToUpdate = await DbContribution.findOne({
where: { id, confirmedAt: IsNull() }, where: { id, confirmedAt: IsNull() },
}) })
@ -350,7 +349,7 @@ export class AdminResolver {
contributionToUpdate.moderatorId = moderator.id contributionToUpdate.moderatorId = moderator.id
contributionToUpdate.contributionStatus = ContributionStatus.PENDING contributionToUpdate.contributionStatus = ContributionStatus.PENDING
await Contribution.save(contributionToUpdate) await DbContribution.save(contributionToUpdate)
const result = new AdminUpdateContribution() const result = new AdminUpdateContribution()
result.amount = amount result.amount = amount
result.memo = contributionToUpdate.memo result.memo = contributionToUpdate.memo
@ -367,7 +366,7 @@ export class AdminResolver {
const contributions = await getConnection() const contributions = await getConnection()
.createQueryBuilder() .createQueryBuilder()
.select('c') .select('c')
.from(Contribution, 'c') .from(DbContribution, 'c')
.leftJoinAndSelect('c.messages', 'm') .leftJoinAndSelect('c.messages', 'm')
.where({ confirmedAt: IsNull() }) .where({ confirmedAt: IsNull() })
.getMany() .getMany()
@ -399,7 +398,7 @@ export class AdminResolver {
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise<boolean> { async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise<boolean> {
const contribution = await Contribution.findOne(id) const contribution = await DbContribution.findOne(id)
if (!contribution) { if (!contribution) {
logger.error(`Contribution not found for given id: ${id}`) logger.error(`Contribution not found for given id: ${id}`)
throw new Error('Contribution not found for given id.') throw new Error('Contribution not found for given id.')
@ -416,7 +415,7 @@ export class AdminResolver {
@Arg('id', () => Int) id: number, @Arg('id', () => Int) id: number,
@Ctx() context: Context, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const contribution = await Contribution.findOne(id) const contribution = await DbContribution.findOne(id)
if (!contribution) { if (!contribution) {
logger.error(`Contribution not found for given id: ${id}`) logger.error(`Contribution not found for given id: ${id}`)
throw new Error('Contribution not found to given id.') throw new Error('Contribution not found to given id.')
@ -481,7 +480,7 @@ export class AdminResolver {
contribution.confirmedBy = moderatorUser.id contribution.confirmedBy = moderatorUser.id
contribution.transactionId = transaction.id contribution.transactionId = transaction.id
contribution.contributionStatus = ContributionStatus.CONFIRMED contribution.contributionStatus = ContributionStatus.CONFIRMED
await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution) await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction() await queryRunner.commitTransaction()
logger.info('creation commited successfuly.') logger.info('creation commited successfuly.')
@ -506,24 +505,29 @@ export class AdminResolver {
} }
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST]) @Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
@Query(() => [Transaction]) @Query(() => ContributionListResult)
async creationTransactionList( async creationTransactionList(
@Args() @Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Arg('userId', () => Int) userId: number, @Arg('userId', () => Int) userId: number,
): Promise<Transaction[]> { ): Promise<ContributionListResult> {
const offset = (currentPage - 1) * pageSize const offset = (currentPage - 1) * pageSize
const transactionRepository = getCustomRepository(TransactionRepository) const [contributionResult, count] = await getConnection()
const [userTransactions] = await transactionRepository.findByUserPaged( .createQueryBuilder()
userId, .select('c')
pageSize, .from(DbContribution, 'c')
offset, .leftJoinAndSelect('c.user', 'u')
order, .where(`user_id = ${userId}`)
true, .limit(pageSize)
) .offset(offset)
.orderBy('c.created_at', order)
.getManyAndCount()
const user = await dbUser.findOneOrFail({ id: userId }) return new ContributionListResult(
return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) count,
contributionResult.map((contribution) => new Contribution(contribution, contribution.user)),
)
// return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
} }
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL]) @Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@ -744,7 +748,7 @@ export class AdminResolver {
await queryRunner.startTransaction('REPEATABLE READ') await queryRunner.startTransaction('REPEATABLE READ')
const contributionMessage = DbContributionMessage.create() const contributionMessage = DbContributionMessage.create()
try { try {
const contribution = await Contribution.findOne({ const contribution = await DbContribution.findOne({
where: { id: contributionId }, where: { id: contributionId },
relations: ['user'], relations: ['user'],
}) })
@ -773,7 +777,7 @@ export class AdminResolver {
contribution.contributionStatus === ContributionStatus.PENDING contribution.contributionStatus === ContributionStatus.PENDING
) { ) {
contribution.contributionStatus = ContributionStatus.IN_PROGRESS contribution.contributionStatus = ContributionStatus.IN_PROGRESS
await queryRunner.manager.update(Contribution, { id: contributionId }, contribution) await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution)
} }
await sendAddedContributionMessageEmail({ await sendAddedContributionMessageEmail({

View File

@ -14,8 +14,8 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
\`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, \`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
\`user_id\` int(10) unsigned NOT NULL, \`user_id\` int(10) unsigned NOT NULL,
\`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE, \`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE,
\`email_verification_code\` bigint(20) unsigned NOT NULL UNIQUE, \`email_verification_code\` bigint(20) unsigned DEFAULT NULL UNIQUE,
\`email_opt_in_type_id\` int NOT NULL, \`email_opt_in_type_id\` int DEFAULT NULL,
\`email_resend_count\` int DEFAULT '0', \`email_resend_count\` int DEFAULT '0',
\`email_checked\` tinyint(4) NOT NULL DEFAULT 0, \`email_checked\` tinyint(4) NOT NULL DEFAULT 0,
\`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, \`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
@ -43,45 +43,11 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
await queryFn(` await queryFn(`
INSERT INTO user_contacts INSERT INTO user_contacts
(type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at) (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at)
SELECT SELECT 'EMAIL', users.id, users.email, optin.verification_code, optin.email_opt_in_type_id, optin.resend_count, users.email_checked, users.created, null, users.deletedAt
'EMAIL', FROM users LEFT JOIN
u.id as user_id, (SELECT le.id, le.user_id, le.verification_code, le.email_opt_in_type_id, le.resend_count, le.created, le.updated,
u.email, ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
e.verification_code as email_verification_code, FROM login_email_opt_in as le) AS optin ON users.id = optin.user_id AND row_num = 1;`)
e.email_opt_in_type_id,
e.resend_count as email_resend_count,
u.email_checked,
e.created as created_at,
e.updated as updated_at,
u.deletedAt as deleted_at\
FROM
users as u,
login_email_opt_in as e
WHERE
u.id = e.user_id AND
e.id in (
WITH opt_in AS (
SELECT
le.id, le.user_id, le.created, le.updated, ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
FROM
login_email_opt_in as le
)
SELECT
opt_in.id
FROM
opt_in
WHERE
row_num = 1);`)
/*
// SELECT
// le.id
// FROM
// login_email_opt_in as le
// WHERE
// le.user_id = u.id
// ORDER BY
// le.updated DESC, le.created DESC LIMIT 1);`)
*/
// insert in users table the email_id of the new created email-contacts // insert in users table the email_id of the new created email-contacts
const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`) const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`)
@ -113,11 +79,13 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom
) )
// reconstruct the previous email back from contacts to users table // reconstruct the previous email back from contacts to users table
const contacts = await queryFn(`SELECT c.id, c.email, c.user_id FROM user_contacts as c`) const contacts = await queryFn(
`SELECT c.id, c.email, c.user_id, c.email_checked FROM user_contacts as c`,
)
for (const id in contacts) { for (const id in contacts) {
const contact = contacts[id] const contact = contacts[id]
await queryFn( await queryFn(
`UPDATE users SET email = "${contact.email}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`, `UPDATE users SET email = "${contact.email}", email_checked="${contact.email_checked}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`,
) )
} }
await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;') await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;')

View File

@ -4,6 +4,12 @@
# How to do this is described in detail in [setup.md](./setup.md) # How to do this is described in detail in [setup.md](./setup.md)
# Find current directory & configure paths # Find current directory & configure paths
## For manualy use in terminal
## set -o allexport
## SCRIPT_DIR=$(pwd)
## PROJECT_ROOT=$SCRIPT_DIR/../..
## set +o allexport
# Use here in script
set -o allexport set -o allexport
SCRIPT_PATH=$(realpath $0) SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH) SCRIPT_DIR=$(dirname $SCRIPT_PATH)
@ -90,7 +96,7 @@ sudo certbot
# Install logrotate # Install logrotate
sudo apt-get install -y logrotate sudo apt-get install -y logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
sudo mv $SCRIPT_DIR/logrotate/gradido.conf /etc/logrotate.d/gradido.conf sudo cp $SCRIPT_DIR/logrotate/gradido.conf.template /etc/logrotate.d/gradido.conf
sudo chown root:root /etc/logrotate.d/gradido.conf sudo chown root:root /etc/logrotate.d/gradido.conf
# Install mysql autobackup # Install mysql autobackup

View File

@ -1,107 +1,233 @@
# Setup script to setup the server be ready to run gradido
# This assums you have root access via ssh to your cleanly setup server
# Furthermore this assumes you have debian (11 64bit) running
# Check your (Sub-)Domain with your Provider. # Instructions To Run `Gradido` On Your Server
# In this document gddhost.tld refers to your chosen domain
> ssh root@gddhost.tld We split setting up `Gradido` on your server into three steps:
# change root default shell - [Preparing your server](#command-list-to-setup-your-server-be-ready-to-install-gradido)
> chsh -s /bin/bash - [Installing `Gradido`](#use-commands-in-installsh-manually-in-your-shell-for-now)
# Create user `gradido` - [Crone-Job for `Gradido`](#define-cronjob-to-compensate-yarn-output-in-tmp)
> useradd -d /home/gradido -m gradido
> passwd gradido
>> enter new password twice
# Gives the user priviledges - this might be omitted in order to harden security ## Command List To Setup Your Server Be Ready To Install `Gradido`
# Care: This will require another administering user if you don't want root access.
# Since this setup expects the user running the software be the same as the administering user,
# you have to adjust the instructions according to that scenario.
# You might lock yourself out, if done wrong.
> usermod -a -G sudo gradido
# change gradido default shell We assume you have root access via ssh to your cleanly setup server.
> chsh -s /bin/bash gradido Furthermore we assume you have debian (11 64bit) running.
# Install sudo
> apt-get install sudo
# switch to the new user
> su gradido
# Register first ssh key for user `gradido` Check your (Sub-)Domain with your Provider.
> mkdir ~/.ssh In this document `gddhost.tld` refers to your chosen domain.
> chmod 700 ~/.ssh
> nano ~/.ssh/authorized_keys
>> insert public key
>> ctrl + x
>> save
# Test authentication via SSH ### SSH into your server
> ssh -i /path/to/privKey gradido@gddhost.tld
>> This should log you in and allow you to use sudo commands, which will require the user's password
# Disable password authentication & root login ```bash
> cd /etc/ssh ssh root@gddhost.tld
> sudo cp sshd_config sshd_config.org ```
> sudo nano sshd_config
>> change `PermitRootLogin yes` to `PermitRootLogin no`
>> change `#PasswordAuthentication yes` to `PasswordAuthentication no`
>> change `UsePAM yes` to `UsePAM no`
>> ctrl + x
>> save
> sudo /etc/init.d/ssh restart
# Test SSH Access only, no root ssh access ### Change root default shell
> ssh gradido@gddhost.tld
>> Will result in in either a password request for your key or the message `Permission denied (publickey)`
> ssh -i /path/to/privKey root@gddhost.tld
>> Will result in `Permission denied (publickey)`
> ssh -i /path/to/privKey gradido@gddhost.tld
>> Will succeed after entering the correct keys password (if any)
# update system ```bash
> sudo apt-get update chsh -s /bin/bash
> sudo apt-get upgrade ```
# Install security tools ### Create user `gradido`
## ufw
> sudo apt-get install ufw
> sudo ufw allow http
> sudo ufw allow https
> sudo ufw allow ssh
> sudo ufw enable
## fail2ban ```bash
> sudo apt-get install -y fail2ban $ useradd -d /home/gradido -m gradido
> sudo /etc/init.d/fail2ban restart $ passwd gradido
# enter new password twice
```
# Install gradido ### Give the user priviledges
> sudo apt-get install -y git
> cd ~
> git clone https://github.com/gradido/gradido.git
# Timezone This might be omitted in order to harden security.
# Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data
> sudo timedatectl set-timezone UTC
# > sudo timedatectl set-ntp on
# > sudo apt purge ntp
# > sudo systemctl start systemd-timesyncd
# >> timedatectl to verify
# Adjust .env ***!!! Attention !!!***
# NOTE ';' can not be part of any value
# The Github Secret is Created on Github in Settimgs -> Webhooks - Care: This will require another administering user if you don't want root access.
> cd gradido/deployment/bare_metal - Since this setup expects the user running the software be the same as the administering user,
> cp .env.dist .env - you have to adjust the instructions according to that scenario.
> nano .env - you might lock yourself out, if done wrong.
>> Adjust values accordingly
# Define cronjob to compensate yarn output in /tmp #### Add the new user `gradido` to `sudo` group
> yarn creates output in /tmp directory, which must be deleted regularly and will be done per cronjob
> on stage1 a hourly job is necessary by setting the following job in the crontab for the gradido user ```bash
> crontab -e opens the crontab in edit-mode and insert the following entry: usermod -a -G sudo gradido
> "0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null" ```
> on stage2 a daily job is necessary by setting the following job in the crontab for the gradido user
> crontab -e opens the crontab in edit-mode and insert the following entry: ### Change gradido default shell
> "0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null"
# TODO the install.sh is not yet ready to run directly - consider to use it as pattern to do it manually ```bash
> ./install.sh chsh -s /bin/bash gradido
```
### Install sudo
```bash
apt-get install sudo
```
### Switch to the new user
```bash
su gradido
```
### Register first ssh key for user `gradido`
```bash
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ nano ~/.ssh/authorized_keys
# insert public key
# ctrl + x
# save
```
### Test authentication via SSH
If you logout from the server you can test authentication:
```bash
$ ssh -i /path/to/privKey gradido@gddhost.tld
# This should log you in and allow you to use sudo commands, which will require the user's password
```
### Disable password authentication and root login
```bash
$ cd /etc/ssh
$ sudo cp sshd_config sshd_config.org
$ sudo nano sshd_config
# change 'PermitRootLogin yes' to `PermitRootLogin no`
# change 'PasswordAuthentication yes' to 'PasswordAuthentication no'
# change 'UsePAM yes' to 'UsePAM no'
# ctrl + x
# save
$ sudo /etc/init.d/ssh restart
```
### Test SSH Access only, no root ssh access
```bash
$ ssh gradido@gddhost.tld
# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
$ ssh -i /path/to/privKey root@gddhost.tld
# Will result in 'Permission denied (publickey)'
$ ssh -i /path/to/privKey gradido@gddhost.tld
# Will succeed after entering the correct keys passphrase (if any)
```
### Update system
```bash
sudo apt-get update
sudo apt-get upgrade
```
### Install security tools
#### Install: `ufw`
```bash
sudo apt-get install ufw
sudo ufw allow http
sudo ufw allow https
sudo ufw allow ssh
sudo ufw enable
```
#### Install: `fail2ban`
```bash
sudo apt-get install -y fail2ban
sudo /etc/init.d/fail2ban restart
```
### Install `Gradido` code
```bash
sudo apt-get install -y git
cd ~
git clone https://github.com/gradido/gradido.git
```
### Timezone
*Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data.*
```bash
sudo timedatectl set-timezone UTC
sudo timedatectl set-ntp on
sudo apt purge ntp
sudo systemctl start systemd-timesyncd
# timedatectl to verify
```
### Adjust the values in `.env`
***!!! Attention !!!***
*Don't forget this step!
All your following installations in `install.sh` will fail!*
*Notes:*
- *`;` cannot be part of any value!*
- *The GitHub secret is created on GitHub in Settings -> Webhooks.*
#### Create `.env` and set values
```bash
$ cd gradido/deployment/bare_metal
$ cp .env.dist .env
$ nano .env
# adjust values accordingly
```
## Use Commands In `install.sh` Manually In Your Shell For Now
The script `install.sh` is not yet ready to run directly.
Use it as pattern to do all steps manually in your terminal shell.
*TODO: Bring the `install.sh` script to run in the shell.*
***!!! Attention !!!***
- *Commands in `install.sh`:*
- *The commands for setting the paths in the used env variables are not working directly in the terminal, consider the out commented commands for this purpose.*
Follow the commands in `./install.sh` as installation pattern.
## Define Cronjob To Compensate Yarn Output In `/tmp`
`yarn` creates output in `/tmp` directory, which must be deleted regularly and will be done per Cron-Job.
### On `stage1`
An hourly job is necessary on `stage1` by setting the following job in the `crontab` for the `gradido` user.
Run:
```bash
crontab -e
```
This opens the crontab in edit-mode and insert the following entry:
```bash
0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null
```
### On `stage2`
A daily job is necessary on `stage2` by setting the following job in the `crontab` for the `gradido` user.
Run:
```bash
crontab -e
```
This opens the `crontab` in edit-mode and insert the following entry:
```bash
0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
```

View File

@ -1,24 +1,73 @@
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker) # Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
A setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
The tests are organized in feature files written in Gherkin syntax.
## Features under test
So far these features are initially tested
- [User authentication](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature)
- [User profile - change password](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature)
- [User registration]((https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature)) (WIP)
A sample setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
Here we have a simple UI-based happy path login test running against the DEV system.
## Precondition ## Precondition
Since dependencies and configurations for Github Actions integration is not set up yet, please run in root directory
Before running the tests, change to the repo's root directory (gradido).
### Boot up the system under test
```bash ```bash
docker-compose up docker-compose up
``` ```
to boot up the DEV system, before running the test. ### Seed the database
The database has to be seeded upfront to every test run.
```bash
# change to the backend directory
cd /path/to/gradido/gradido/backend
# install all dependencies
yarn
# seed the database (everytime before running the tests)
yarn seed
```
## Execute the test ## Execute the test
This setup will be integrated in the Gradido Github Actions to automatically support the CI/CD process.
For now the test setup can only be used locally in two modes.
### Run Cypress directly from the code
```bash ```bash
# change to the tests directory
cd /path/to/gradido/e2e-tests/cypress/tests
# install all dependencies
yarn install
# a) run the tests on command line
yarn cypress run
# b) open the Cypress GUI to run the tests in interactive mode
yarn cypress open
```
### Run Cyprss from a separate Docker container
```bash
# change to the cypress directory
cd /path/to/gradido/e2e-tests/cypress/
# build a Docker image from the Dockerfile # build a Docker image from the Dockerfile
docker build -t gradido_e2e-tests-cypress . docker build -t gradido_e2e-tests-cypress .
# run the Docker container and execute the given tests # run the Docker image and execute the given tests
docker run -it --network=host gradido_e2e-tests-cypress yarn run cypress-e2e-tests docker run -it --network=host gradido_e2e-tests-cypress yarn cypress-e2e
``` ```

View File

@ -32,6 +32,7 @@ export default defineConfig({
excludeSpecPattern: "*.js", excludeSpecPattern: "*.js",
baseUrl: "http://localhost:3000", baseUrl: "http://localhost:3000",
chromeWebSecurity: false, chromeWebSecurity: false,
defaultCommandTimeout: 10000,
supportFile: "cypress/support/index.ts", supportFile: "cypress/support/index.ts",
viewportHeight: 720, viewportHeight: 720,
viewportWidth: 1280, viewportWidth: 1280,

View File

@ -0,0 +1,13 @@
Feature: User registration
As a user
I want to register to create an account
@skip
Scenario: Register successfully
Given the browser navigates to page "/register"
When the user fills name and email "Regina" "Register" "regina@register.com"
And the user agrees to the privacy policy
And the user submits the registration form
Then the user can use a provided activation link
And the user can set a password "Aa12345_"
And the user can login with the credentials "regina@register.com" "Aa12345_"

View File

@ -2,8 +2,8 @@
export class LoginPage { export class LoginPage {
// selectors // selectors
emailInput = "#Email-input-field"; emailInput = "input[type=email]";
passwordInput = "#Password-input-field"; passwordInput = "input[type=password]";
submitBtn = "[type=submit]"; submitBtn = "[type=submit]";
emailHint = "#vee_Email"; emailHint = "#vee_Email";
passwordHint = "#vee_Password"; passwordHint = "#vee_Password";

View File

@ -4,8 +4,8 @@ export class ProfilePage {
// selectors // selectors
openChangePassword = "[data-test=open-password-change-form]"; openChangePassword = "[data-test=open-password-change-form]";
oldPasswordInput = "#password-input-field"; oldPasswordInput = "#password-input-field";
newPasswordInput = "#New-password-input-field"; newPasswordInput = "#new-password-input-field";
newPasswordRepeatInput = "#Repeat-new-password-input-field"; newPasswordRepeatInput = "#repeat-new-password-input-field";
submitNewPasswordBtn = "[data-test=submit-new-password-btn]"; submitNewPasswordBtn = "[data-test=submit-new-password-btn]";
goto() { goto() {
@ -19,12 +19,12 @@ export class ProfilePage {
} }
enterNewPassword(password: string) { enterNewPassword(password: string) {
cy.get(this.newPasswordInput).clear().type(password); cy.get(this.newPasswordInput).find("input").clear().type(password);
return this; return this;
} }
enterRepeatPassword(password: string) { enterRepeatPassword(password: string) {
cy.get(this.newPasswordRepeatInput).clear().type(password); cy.get(this.newPasswordRepeatInput).find("input").clear().type(password);
return this; return this;
} }

View File

@ -0,0 +1,42 @@
/// <reference types="cypress" />
export class RegistrationPage {
// selectors
firstnameInput = "#registerFirstname";
lastnameInput = "#registerLastname";
emailInput = "#Email-input-field";
checkbox = "#registerCheckbox";
submitBtn = "[type=submit]";
RegistrationThanxHeadline = ".test-message-headline";
RegistrationThanxText = ".test-message-subtitle";
goto() {
cy.visit("/register");
return this;
}
enterFirstname(firstname: string) {
cy.get(this.firstnameInput).clear().type(firstname);
return this;
}
enterLastname(lastname: string) {
cy.get(this.lastnameInput).clear().type(lastname);
return this;
}
enterEmail(email: string) {
cy.get(this.emailInput).clear().type(email);
return this;
}
checkPrivacyCheckbox() {
cy.get(this.checkbox).click({ force: true });
}
submitRegistrationPage() {
cy.get(this.submitBtn).should("be.enabled");
cy.get(this.submitBtn).click();
}
}

View File

@ -2,6 +2,9 @@
export class Toasts { export class Toasts {
// selectors // selectors
toastSlot = ".b-toaster-slot";
toastTypeSuccess = ".b-toast-success";
toastTypeError = ".b-toast-danger";
toastTitle = ".gdd-toaster-title"; toastTitle = ".gdd-toaster-title";
toastMessage = ".gdd-toaster-body"; toastMessage = ".gdd-toaster-body";
} }

View File

@ -25,11 +25,11 @@ Then("the user is logged in with username {string}", (username: string) => {
Then("the user cannot login", () => { Then("the user cannot login", () => {
const toast = new Toasts(); const toast = new Toasts();
cy.get(toast.toastTitle).should("contain.text", "Error!"); cy.get(toast.toastSlot).within(() => {
cy.get(toast.toastMessage).should( cy.get(toast.toastTypeError);
"contain.text", cy.get(toast.toastTitle).should("be.visible");
"No user with this credentials." cy.get(toast.toastMessage).should("be.visible");
); });
}); });
// //

View File

@ -24,9 +24,9 @@ And("the user submits the password form", () => {
When("the user is presented a {string} message", (type: string) => { When("the user is presented a {string} message", (type: string) => {
const toast = new Toasts(); const toast = new Toasts();
cy.get(toast.toastTitle).should("contain.text", "Success"); cy.get(toast.toastSlot).within(() => {
cy.get(toast.toastMessage).should( cy.get(toast.toastTypeSuccess);
"contain.text", cy.get(toast.toastTitle).should("be.visible");
"Your password has been changed." cy.get(toast.toastMessage).should("be.visible");
); });
}); });

View File

@ -14,7 +14,7 @@
} }
}, },
"scripts": { "scripts": {
"cypress": "cypress run", "cypress-e2e": "cypress run",
"lint": "eslint --max-warnings=0 --ext .js,.ts ." "lint": "eslint --max-warnings=0 --ext .js,.ts ."
}, },
"dependencies": { "dependencies": {

View File

@ -29,6 +29,7 @@
required: true, required: true,
samePassword: value.password, samePassword: value.password,
}" }"
id="repeat-new-password-input-field"
:label="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')" :label="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')"
:immediate="true" :immediate="true"
:name="createId(register ? $t('form.passwordRepeat') : $t('form.password_new_repeat'))" :name="createId(register ? $t('form.passwordRepeat') : $t('form.password_new_repeat'))"