mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into send-form-border-radius
This commit is contained in:
commit
42cc74e1dd
@ -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>
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -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,28 +70,30 @@ 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
|
||||||
if (value)
|
.msgBoxConfirm(this.$t('contributionLink.deleteNow', { name: name }))
|
||||||
await this.$apollo
|
.then(async (value) => {
|
||||||
.mutate({
|
if (value)
|
||||||
mutation: deleteContributionLink,
|
await this.$apollo
|
||||||
variables: {
|
.mutate({
|
||||||
id: this.id,
|
mutation: deleteContributionLink,
|
||||||
},
|
variables: {
|
||||||
})
|
id: id,
|
||||||
.then(() => {
|
},
|
||||||
this.toastSuccess('TODO: request message deleted ')
|
})
|
||||||
})
|
.then(() => {
|
||||||
.catch((err) => {
|
this.toastSuccess(this.$t('contributionLink.deleted'))
|
||||||
this.toastError(err.message)
|
this.$emit('get-contribution-links')
|
||||||
})
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
|
this.toastError(err.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
editContributionLink(row) {
|
editContributionLink(row) {
|
||||||
this.$emit('editContributionLinkData', row)
|
this.$emit('editContributionLinkData', row)
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -6,30 +6,29 @@ const localVue = global.localVue
|
|||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
creationTransactionList: [
|
creationTransactionList: {
|
||||||
{
|
contributionCount: 2,
|
||||||
id: 1,
|
contributionList: [
|
||||||
amount: 100,
|
{
|
||||||
balanceDate: 0,
|
id: 1,
|
||||||
creationDate: new Date(),
|
amount: 5.8,
|
||||||
memo: 'Testing',
|
createdAt: '2022-09-21T11:09:51.000Z',
|
||||||
linkedUser: {
|
confirmedAt: null,
|
||||||
firstName: 'Gradido',
|
contributionDate: '2022-08-01T00:00:00.000Z',
|
||||||
lastName: 'Akademie',
|
memo: 'für deine Hilfe, Fräulein Rottenmeier',
|
||||||
|
state: 'PENDING',
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
id: 2,
|
||||||
id: 2,
|
amount: '47',
|
||||||
amount: 200,
|
createdAt: '2022-09-21T11:09:28.000Z',
|
||||||
balanceDate: 0,
|
confirmedAt: '2022-09-21T11:09:28.000Z',
|
||||||
creationDate: new Date(),
|
contributionDate: '2022-08-01T00:00:00.000Z',
|
||||||
memo: 'Testing 2',
|
memo: 'für deine Hilfe, Frau Holle',
|
||||||
linkedUser: {
|
state: 'CONFIRMED',
|
||||||
firstName: 'Gradido',
|
|
||||||
lastName: 'Akademie',
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -8,14 +8,15 @@ export const creationTransactionList = gql`
|
|||||||
order: $order
|
order: $order
|
||||||
userId: $userId
|
userId: $userId
|
||||||
) {
|
) {
|
||||||
id
|
contributionCount
|
||||||
amount
|
contributionList {
|
||||||
balanceDate
|
id
|
||||||
creationDate
|
amount
|
||||||
memo
|
createdAt
|
||||||
linkedUser {
|
confirmedAt
|
||||||
firstName
|
contributionDate
|
||||||
lastName
|
memo
|
||||||
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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,
|
||||||
@ -41,47 +41,13 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
|
|
||||||
// merge values from login_email_opt_in table with users.email in new user_contacts table
|
// merge values from login_email_opt_in table with users.email in new user_contacts table
|
||||||
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;')
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
```
|
||||||
|
|||||||
@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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_"
|
||||||
@ -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";
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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");
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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");
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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'))"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user