Merge branch 'master' into 1676-feature-federation-technical-concept

This commit is contained in:
Ulf Gebhardt 2022-04-22 13:22:15 +02:00 committed by GitHub
commit c0836d03e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
179 changed files with 2899 additions and 2491 deletions

View File

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

View File

@ -4,8 +4,102 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.7.1](https://github.com/gradido/gradido/compare/1.7.0...1.7.1)
- fix: Localize Dates on Redeem Transaction Link Page [`#1720`](https://github.com/gradido/gradido/pull/1720)
- fix: Round Virtual Transaction Link Transaction [`#1718`](https://github.com/gradido/gradido/pull/1718)
- larger icon and deacy information if center [`#1719`](https://github.com/gradido/gradido/pull/1719)
- Fix: restore script load correct .env [`#1717`](https://github.com/gradido/gradido/pull/1717)
- fix-disbled-button-if-totalBalance [`#1716`](https://github.com/gradido/gradido/pull/1716)
- icon droplet-halflarger and correctly positioned [`#1713`](https://github.com/gradido/gradido/pull/1713)
- fix: Clean up Registration Flow [`#1709`](https://github.com/gradido/gradido/pull/1709)
- 1703 submit button disabled when total amount to submit is minus [`#1705`](https://github.com/gradido/gradido/pull/1705)
- add extra disabled variable for send emit, disabled send by emit [`#1704`](https://github.com/gradido/gradido/pull/1704)
- Fix: Correct calculation of decay [`#1699`](https://github.com/gradido/gradido/pull/1699)
- Fix: Allow sending of more then half of my wealth via link [`#1700`](https://github.com/gradido/gradido/pull/1700)
- feat: Seed Creations Months Ago From Now [`#1702`](https://github.com/gradido/gradido/pull/1702)
- Fix: Frontend show proper error message on failed send [`#1701`](https://github.com/gradido/gradido/pull/1701)
#### [1.7.0](https://github.com/gradido/gradido/compare/1.6.6...1.7.0)
> 30 March 2022
- v1.7.0 [`#1698`](https://github.com/gradido/gradido/pull/1698)
- folder for new style images [`#1694`](https://github.com/gradido/gradido/pull/1694)
- fix: No Email Exposed on Forgot Password [`#1696`](https://github.com/gradido/gradido/pull/1696)
- fix: No Decay Calculation in Frontend [`#1692`](https://github.com/gradido/gradido/pull/1692)
- fix: Wrong Balance on Decay Transaction [`#1691`](https://github.com/gradido/gradido/pull/1691)
- fix: No Plus Before Zero Decay [`#1689`](https://github.com/gradido/gradido/pull/1689)
- fix: Update Deployment env.dist [`#1688`](https://github.com/gradido/gradido/pull/1688)
- 1684 when generating a link form does not reset [`#1687`](https://github.com/gradido/gradido/pull/1687)
- Refactor: Multicreation - do not show unactivated emails [`#1679`](https://github.com/gradido/gradido/pull/1679)
- feat: Show Link Duration in Emails [`#1663`](https://github.com/gradido/gradido/pull/1663)
- refactor: Balance Resolver [`#1665`](https://github.com/gradido/gradido/pull/1665)
- refactor: Set Email Optin Valid Time to 24 hours [`#1662`](https://github.com/gradido/gradido/pull/1662)
- Fix: Fixes found on Stage1 [`#1683`](https://github.com/gradido/gradido/pull/1683)
- 1555 admin see user generated link [`#1656`](https://github.com/gradido/gradido/pull/1656)
- 1594 show transaction was created by link [`#1680`](https://github.com/gradido/gradido/pull/1680)
- refactor: Memo Text Length to 255 Characters [`#1675`](https://github.com/gradido/gradido/pull/1675)
- adminarea: fetchPolicy on searchUser deleted User [`#1678`](https://github.com/gradido/gradido/pull/1678)
- 1223 community communication concept [`#1313`](https://github.com/gradido/gradido/pull/1313)
- clear form.email if click send per link, tests if clicked [`#1660`](https://github.com/gradido/gradido/pull/1660)
- feat: User in Transaction Clickable to Send Directly [`#1658`](https://github.com/gradido/gradido/pull/1658)
- feat: Add Sender Email to Transaction Received Mail [`#1664`](https://github.com/gradido/gradido/pull/1664)
- Feature: Enforce config versions [`#1627`](https://github.com/gradido/gradido/pull/1627)
- 1559 frontend transport redeem link through register [`#1647`](https://github.com/gradido/gradido/pull/1647)
- update-balance if link succesfully generated [`#1655`](https://github.com/gradido/gradido/pull/1655)
- feat: Add Referrer ID to Users [`#1654`](https://github.com/gradido/gradido/pull/1654)
- 1558 - show tranaction link information page [`#1625`](https://github.com/gradido/gradido/pull/1625)
- refactor: No Float Ids [`#1624`](https://github.com/gradido/gradido/pull/1624)
- Change the text if the account is not activated yet and changed the b… [`#1336`](https://github.com/gradido/gradido/pull/1336)
- Refactor: Corrected name of transaction link summary [`#1628`](https://github.com/gradido/gradido/pull/1628)
- fix: Query for Only Creations Transaction List [`#1623`](https://github.com/gradido/gradido/pull/1623)
- Fix: build for development and production links external modules properly [`#1626`](https://github.com/gradido/gradido/pull/1626)
- feat: Seed Transaction Links [`#1622`](https://github.com/gradido/gradido/pull/1622)
- 1588 frontend expendable paginated link list [`#1620`](https://github.com/gradido/gradido/pull/1620)
- feat: Seed Creation Transactions in Backend [`#1621`](https://github.com/gradido/gradido/pull/1621)
- Feature: Eslint i18n validation [`#1618`](https://github.com/gradido/gradido/pull/1618)
- refactor: Seed in Backend [`#1619`](https://github.com/gradido/gradido/pull/1619)
- 1554 frontend transaction link summary [`#1613`](https://github.com/gradido/gradido/pull/1613)
- Frontend generate link for send gdd [`#1579`](https://github.com/gradido/gradido/pull/1579)
- feat: Test Logout in User Resolver [`#1617`](https://github.com/gradido/gradido/pull/1617)
- Fix: Logrotate & Log Dates & Save Update Log & Correct tag Checkout [`#1612`](https://github.com/gradido/gradido/pull/1612)
- refactor: No Reset DB in Backend Unit Tests [`#1616`](https://github.com/gradido/gradido/pull/1616)
- Test: Require 53% backend coverage [`#1611`](https://github.com/gradido/gradido/pull/1611)
- 1599 components for transactionlist types [`#1600`](https://github.com/gradido/gradido/pull/1600)
- feat: Link Transaction to Transaction Link on Redeem [`#1610`](https://github.com/gradido/gradido/pull/1610)
- feat: Redeem Transaction Link Mutation [`#1607`](https://github.com/gradido/gradido/pull/1607)
- feat: List Transaction Links Query [`#1606`](https://github.com/gradido/gradido/pull/1606)
- feat: Virtual Transaction for Transaction Links [`#1603`](https://github.com/gradido/gradido/pull/1603)
- refactor: Transaction Link Query [`#1605`](https://github.com/gradido/gradido/pull/1605)
- 1216 seo vorschau links [`#1426`](https://github.com/gradido/gradido/pull/1426)
- Feature: Eslint style rules & Stylelint for SCSS [`#1598`](https://github.com/gradido/gradido/pull/1598)
- refactor: Remove showEmail from Transaction Links [`#1602`](https://github.com/gradido/gradido/pull/1602)
- feat: Delete Transaction Link Mutation [`#1597`](https://github.com/gradido/gradido/pull/1597)
- Query-transaction-link [`#1586`](https://github.com/gradido/gradido/pull/1586)
- feat: Create Transaction Link Mutation [`#1585`](https://github.com/gradido/gradido/pull/1585)
- feat: Model Transaction Link [`#1584`](https://github.com/gradido/gradido/pull/1584)
- feat: Test Login in User Resolver [`#1538`](https://github.com/gradido/gradido/pull/1538)
- add style in App.vue, set class .pointer on transaction-list-item [`#1583`](https://github.com/gradido/gradido/pull/1583)
- feat: Use Vue Filter to Display Gradido Amounts [`#1576`](https://github.com/gradido/gradido/pull/1576)
- refactor: Resolve Relative Paths in Backend [`#1572`](https://github.com/gradido/gradido/pull/1572)
- refactor: Frontend Directory Structure and Routes [`#1571`](https://github.com/gradido/gradido/pull/1571)
- community name in creation transaction is displayed cleanly [`#1578`](https://github.com/gradido/gradido/pull/1578)
- Planning: send new users gradido [`#1567`](https://github.com/gradido/gradido/pull/1567)
- Refactor arithmetic merge [`#1548`](https://github.com/gradido/gradido/pull/1548)
- Adminarea creation transactionlist show [`#1550`](https://github.com/gradido/gradido/pull/1550)
- Fix: Validate password on UpdateUserInfos [`#1568`](https://github.com/gradido/gradido/pull/1568)
- Fix: Allow sending to user without transactions [`#1549`](https://github.com/gradido/gradido/pull/1549)
- Fix: Balance type [`#1569`](https://github.com/gradido/gradido/pull/1569)
- Refactor: arithmetic and data types [`#1539`](https://github.com/gradido/gradido/pull/1539)
- refactor: Bootstrap Vue Toast in Admin interface [`#1547`](https://github.com/gradido/gradido/pull/1547)
- Refactor: Combine transaction tables and restructure transaction design [`#1531`](https://github.com/gradido/gradido/pull/1531)
#### [1.6.6](https://github.com/gradido/gradido/compare/1.6.5...1.6.6) #### [1.6.6](https://github.com/gradido/gradido/compare/1.6.5...1.6.6)
> 28 February 2022
- v1.6.6 [`#1541`](https://github.com/gradido/gradido/pull/1541)
- Fix: Upper case email on register breaks account [`#1542`](https://github.com/gradido/gradido/pull/1542) - Fix: Upper case email on register breaks account [`#1542`](https://github.com/gradido/gradido/pull/1542)
- 1106 first transaction cannot be expanded [`#1432`](https://github.com/gradido/gradido/pull/1432) - 1106 first transaction cannot be expanded [`#1432`](https://github.com/gradido/gradido/pull/1432)
- added missing bootstrap scss. bootstrap/scss/bootstrap, plus more mis… [`#1540`](https://github.com/gradido/gradido/pull/1540) - added missing bootstrap scss. bootstrap/scss/bootstrap, plus more mis… [`#1540`](https://github.com/gradido/gradido/pull/1540)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido", "description": "Administraion Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Moriz Wahl", "author": "Moriz Wahl",
"version": "1.6.6", "version": "1.7.1",
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"scripts": { "scripts": {
@ -14,7 +14,7 @@
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "TZ=UTC jest --coverage", "test": "cross-env TZ=UTC jest --coverage",
"locales": "scripts/sort.sh" "locales": "scripts/sort.sh"
}, },
"dependencies": { "dependencies": {
@ -57,6 +57,7 @@
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-plugin-transform-require-context": "^0.1.1", "babel-plugin-transform-require-context": "^0.1.1",
"cross-env": "^7.0.3",
"eslint": "7.25.0", "eslint": "7.25.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import CreationTransactionListFormular from './CreationTransactionListFormular.vue' import CreationTransactionList from './CreationTransactionList.vue'
import { toastErrorSpy } from '../../test/testSetup' import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue const localVue = global.localVue
@ -46,11 +46,11 @@ const propsData = {
fields: ['date', 'balance', 'name', 'memo', 'decay'], fields: ['date', 'balance', 'name', 'memo', 'decay'],
} }
describe('CreationTransactionListFormular', () => { describe('CreationTransactionList', () => {
let wrapper let wrapper
const Wrapper = () => { const Wrapper = () => {
return mount(CreationTransactionListFormular, { localVue, mocks, propsData }) return mount(CreationTransactionList, { localVue, mocks, propsData })
} }
describe('mount', () => { describe('mount', () => {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="component-creation-transaction-list"> <div class="component-creation-transaction-list">
{{ $t('transactionlist.title') }} <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"></b-table>
</div> </div>
</template> </template>

View File

@ -0,0 +1,129 @@
import { mount } from '@vue/test-utils'
import OpenCreationsTable from './OpenCreationsTable.vue'
const localVue = global.localVue
const apolloMutateMock = jest.fn().mockResolvedValue({})
const apolloQueryMock = jest.fn().mockResolvedValue({})
const propsData = {
items: [
{
id: 4,
firstName: 'Bob',
lastName: 'der Baumeister',
email: 'bob@baumeister.de',
amount: 300,
memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z',
moderator: 1,
creation: [700, 1000, 1000],
__typename: 'PendingCreation',
},
{
id: 5,
firstName: 'Räuber',
lastName: 'Hotzenplotz',
email: 'raeuber@hotzenplotz.de',
amount: 210,
memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z',
moderator: 1,
creation: [790, 1000, 1000],
__typename: 'PendingCreation',
},
{
id: 6,
firstName: 'Stephen',
lastName: 'Hawking',
email: 'stephen@hawking.uk',
amount: 330,
memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z',
moderator: 1,
creation: [670, 1000, 1000],
__typename: 'PendingCreation',
},
],
fields: [
{ key: 'bookmark', label: 'delete' },
{ key: 'email', label: 'e_mail' },
{ key: 'firstName', label: 'firstname' },
{ key: 'lastName', label: 'lastname' },
{
key: 'amount',
label: 'creation',
formatter: (value) => {
return value + ' GDD'
},
},
{ key: 'memo', label: 'text' },
{
key: 'date',
label: 'date',
formatter: (value) => {
return value
},
},
{ key: 'moderator', label: 'moderator' },
{ key: 'edit_creation', label: 'edit' },
{ key: 'confirm', label: 'save' },
],
}
const mocks = {
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
$apollo: {
mutate: apolloMutateMock,
query: apolloQueryMock,
},
$store: {
state: {
moderator: {
id: 0,
name: 'test moderator',
},
},
},
}
describe('OpenCreationsTable', () => {
let wrapper
const Wrapper = () => {
return mount(OpenCreationsTable, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('has a DIV element with the class .open-creations-table', () => {
expect(wrapper.find('div.open-creations-table').exists()).toBeTruthy()
})
it('has a table with three rows', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(3)
})
it('find first button.bi-pencil-square for open EditCreationFormular ', () => {
expect(wrapper.findAll('tr').at(1).find('.bi-pencil-square').exists()).toBeTruthy()
})
describe('show edit details', () => {
beforeEach(async () => {
await wrapper.findAll('tr').at(1).find('.bi-pencil-square').trigger('click')
})
it.skip('has a component element with name EditCreationFormular', () => {
expect(wrapper.findComponent({ name: 'EditCreationFormular' }).exists()).toBe(true)
})
it.skip('renders the component component-edit-creation-formular', () => {
expect(wrapper.find('div.component-edit-creation-formular').exists()).toBeTruthy()
})
})
})
})

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="component-open-creations-table"> <div class="open-creations-table">
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md"> <b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
<template #cell(bookmark)="row"> <template #cell(bookmark)="row">
<b-button <b-button

View File

@ -49,31 +49,40 @@
<template #row-details="row"> <template #row-details="row">
<b-card ref="rowDetails" class="shadow-lg pl-3 pr-3 mb-5 bg-white rounded"> <b-card ref="rowDetails" class="shadow-lg pl-3 pr-3 mb-5 bg-white rounded">
<creation-formular <b-tabs content-class="mt-3">
v-if="!row.item.deletedAt" <b-tab :title="$t('creation')" active :disabled="row.item.deletedAt !== null">
type="singleCreation" <creation-formular
pagetype="singleCreation" v-if="!row.item.deletedAt"
:creation="row.item.creation" type="singleCreation"
:item="row.item" pagetype="singleCreation"
:creationUserData="creationUserData" :creation="row.item.creation"
@update-user-data="updateUserData" :item="row.item"
/> :creationUserData="creationUserData"
<div v-else>{{ $t('userIsDeleted') }}</div> @update-user-data="updateUserData"
<confirm-register-mail-formular />
v-if="!row.item.deletedAt" </b-tab>
:checked="row.item.emailChecked" <b-tab :title="$t('e_mail')" :disabled="row.item.deletedAt !== null">
:email="row.item.email" <confirm-register-mail-formular
:dateLastSend=" v-if="!row.item.deletedAt"
row.item.emailConfirmationSend :checked="row.item.emailChecked"
? $d(new Date(row.item.emailConfirmationSend), 'long') :email="row.item.email"
: '' :dateLastSend="
" row.item.emailConfirmationSend
/> ? $d(new Date(row.item.emailConfirmationSend), 'long')
<creation-transaction-list-formular : ''
v-if="!row.item.deletedAt" "
:userId="row.item.userId" />
/> </b-tab>
<deleted-user-formular :item="row.item" @updateDeletedAt="updateDeletedAt" /> <b-tab :title="$t('creationList')" :disabled="row.item.deletedAt !== null">
<creation-transaction-list v-if="!row.item.deletedAt" :userId="row.item.userId" />
</b-tab>
<b-tab :title="$t('transactionlink.name')" :disabled="row.item.deletedAt !== null">
<transaction-link-list v-if="!row.item.deletedAt" :userId="row.item.userId" />
</b-tab>
<b-tab :title="$t('delete_user')">
<deleted-user-formular :item="row.item" @updateDeletedAt="updateDeletedAt" />
</b-tab>
</b-tabs>
</b-card> </b-card>
</template> </template>
</b-table> </b-table>
@ -82,7 +91,8 @@
<script> <script>
import CreationFormular from '../CreationFormular.vue' import CreationFormular from '../CreationFormular.vue'
import ConfirmRegisterMailFormular from '../ConfirmRegisterMailFormular.vue' import ConfirmRegisterMailFormular from '../ConfirmRegisterMailFormular.vue'
import CreationTransactionListFormular from '../CreationTransactionListFormular.vue' import CreationTransactionList from '../CreationTransactionList.vue'
import TransactionLinkList from '../TransactionLinkList.vue'
import DeletedUserFormular from '../DeletedUserFormular.vue' import DeletedUserFormular from '../DeletedUserFormular.vue'
export default { export default {
@ -90,7 +100,8 @@ export default {
components: { components: {
CreationFormular, CreationFormular,
ConfirmRegisterMailFormular, ConfirmRegisterMailFormular,
CreationTransactionListFormular, CreationTransactionList,
TransactionLinkList,
DeletedUserFormular, DeletedUserFormular,
}, },
props: { props: {

View File

@ -0,0 +1,140 @@
import { mount } from '@vue/test-utils'
import TransactionLinkList from './TransactionLinkList.vue'
import { listTransactionLinksAdmin } from '../graphql/listTransactionLinksAdmin.js'
import { toastErrorSpy } from '../../test/testSetup'
const localVue = global.localVue
const apolloQueryMock = jest.fn()
apolloQueryMock.mockResolvedValue({
data: {
listTransactionLinksAdmin: {
linkCount: 8,
linkList: [
{
amount: '19.99',
code: '62ef8236ace7217fbd066c5a',
createdAt: '2022-03-24T17:43:09.000Z',
deletedAt: null,
holdAvailableAmount: '20.51411720068412022949',
id: 36,
memo: 'Kein Trick, keine Zauberrei,\nbei Gradidio sei dabei!',
redeemedAt: null,
validUntil: '2022-04-07T17:43:09.000Z',
},
{
amount: '19.99',
code: '2b603f36521c617fbd066cef',
createdAt: '2022-03-24T17:43:09.000Z',
deletedAt: null,
holdAvailableAmount: '20.51411720068412022949',
id: 37,
memo: 'Kein Trick, keine Zauberrei,\nbei Gradidio sei dabei!',
redeemedAt: null,
validUntil: '2022-04-07T17:43:09.000Z',
},
{
amount: '19.99',
code: '0bb789b5bd5b717fbd066eb5',
createdAt: '2022-03-24T17:43:09.000Z',
deletedAt: '2022-03-24T17:43:09.000Z',
holdAvailableAmount: '20.51411720068412022949',
id: 40,
memo: 'Da habe ich mich wohl etwas übernommen.',
redeemedAt: '2022-04-07T14:43:09.000Z',
validUntil: '2022-04-07T17:43:09.000Z',
},
{
amount: '19.99',
code: '2d4a763e516b317fbd066a85',
createdAt: '2022-01-01T00:00:00.000Z',
deletedAt: null,
holdAvailableAmount: '20.51411720068412022949',
id: 33,
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
redeemedAt: null,
validUntil: '2022-01-15T00:00:00.000Z',
},
],
},
},
})
const propsData = {
userId: 42,
}
const mocks = {
$apollo: {
query: apolloQueryMock,
},
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}
describe('TransactionLinkList', () => {
let wrapper
const Wrapper = () => {
return mount(TransactionLinkList, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper()
})
it('calls the API', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: listTransactionLinksAdmin,
variables: {
currentPage: 1,
pageSize: 5,
userId: 42,
},
}),
)
})
it('has 4 items in the table', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(4)
})
it('has pagination buttons', () => {
expect(wrapper.findComponent({ name: 'BPagination' }).exists()).toBe(true)
})
describe('next page', () => {
beforeAll(async () => {
jest.clearAllMocks()
await wrapper.findComponent({ name: 'BPagination' }).vm.$emit('input', 2)
})
it('calls the API again', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
query: listTransactionLinksAdmin,
variables: {
currentPage: 1,
pageSize: 5,
userId: 42,
},
}),
)
})
})
describe('server response with error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({ message: 'Oh no!' })
wrapper = Wrapper()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
})
})

View File

@ -0,0 +1,105 @@
<template>
<div class="transaction-link-list">
<div v-if="items.length > 0">
<div class="h3">{{ $t('transactionlink.name') }}</div>
<b-table striped hover :fields="fields" :items="items"></b-table>
</div>
<b-pagination
pills
size="lg"
v-model="currentPage"
:per-page="perPage"
:total-rows="rows"
align="center"
></b-pagination>
</div>
</template>
<script>
import { listTransactionLinksAdmin } from '../graphql/listTransactionLinksAdmin.js'
export default {
name: 'TransactionLinkList',
props: {
userId: { type: Number, required: true },
},
data() {
return {
items: [],
rows: 0,
currentPage: 1,
perPage: 5,
}
},
methods: {
getListTransactionLinks() {
this.$apollo
.query({
query: listTransactionLinksAdmin,
variables: {
currentPage: this.currentPage,
pageSize: this.perPage,
userId: this.userId,
},
})
.then((result) => {
this.rows = result.data.listTransactionLinksAdmin.linkCount
this.items = result.data.listTransactionLinksAdmin.linkList
})
.catch((error) => {
this.toastError(error.message)
})
},
},
computed: {
fields() {
return [
{
key: 'createdAt',
label: this.$t('transactionlink.created'),
formatter: (value, key, item) => {
return this.$d(new Date(value))
},
},
{
key: 'amount',
label: this.$t('transactionlist.amount'),
formatter: (value, key, item) => {
return `${value} GDD`
},
},
{ key: 'memo', label: this.$t('transactionlist.memo') },
{
key: 'validUntil',
label: this.$t('transactionlink.valid_until'),
formatter: (value, key, item) => {
return this.$d(new Date(value))
},
},
{
key: 'status',
label: 'status',
formatter: (value, key, item) => {
// deleted
if (item.deletedAt) return this.$t('deleted') + ': ' + this.$d(new Date(item.deletedAt))
// redeemed
if (item.redeemedAt)
return this.$t('redeemed') + ': ' + this.$d(new Date(item.redeemedAt))
// expired
if (new Date() > new Date(item.validUntil))
return this.$t('expired') + ': ' + this.$d(new Date(item.validUntil))
// open
return this.$t('open')
},
},
]
},
},
created() {
this.getListTransactionLinks()
},
watch: {
currentPage() {
this.getListTransactionLinks()
},
},
}
</script>

View File

@ -0,0 +1,20 @@
import gql from 'graphql-tag'
export const listTransactionLinksAdmin = gql`
query ($currentPage: Int = 1, $pageSize: Int = 5, $userId: Int!) {
listTransactionLinksAdmin(currentPage: $currentPage, pageSize: $pageSize, userId: $userId) {
linkCount
linkList {
id
amount
holdAvailableAmount
memo
code
createdAt
validUntil
redeemedAt
deletedAt
}
}
}
`

View File

@ -2,6 +2,7 @@
"all_emails": "Alle Nutzer", "all_emails": "Alle Nutzer",
"back": "zurück", "back": "zurück",
"creation": "Schöpfung", "creation": "Schöpfung",
"creationList": "Schöpfungsliste",
"creation_form": { "creation_form": {
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.", "creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
"creation_for": "Aktives Grundeinkommen für", "creation_for": "Aktives Grundeinkommen für",
@ -27,6 +28,7 @@
"edit": "Bearbeiten", "edit": "Bearbeiten",
"enabled": "aktiviert", "enabled": "aktiviert",
"error": "Fehler", "error": "Fehler",
"expired": "abgelaufen",
"e_mail": "E-Mail", "e_mail": "E-Mail",
"firstname": "Vorname", "firstname": "Vorname",
"footer": { "footer": {
@ -56,6 +58,7 @@
"user_search": "Nutzersuche" "user_search": "Nutzersuche"
}, },
"not_open_creations": "Keine offenen Schöpfungen", "not_open_creations": "Keine offenen Schöpfungen",
"open": "offen",
"open_creations": "Offene Schöpfungen", "open_creations": "Offene Schöpfungen",
"overlay": { "overlay": {
"confirm": { "confirm": {
@ -66,6 +69,7 @@
"yes": "Ja, Schöpfung bestätigen und speichern!" "yes": "Ja, Schöpfung bestätigen und speichern!"
} }
}, },
"redeemed": "eingelöst",
"remove": "Entfernen", "remove": "Entfernen",
"removeNotSelf": "Als Admin / Moderator kannst du dich nicht selber löschen.", "removeNotSelf": "Als Admin / Moderator kannst du dich nicht selber löschen.",
"remove_all": "alle Nutzer entfernen", "remove_all": "alle Nutzer entfernen",
@ -73,6 +77,11 @@
"status": "Status", "status": "Status",
"success": "Erfolg", "success": "Erfolg",
"text": "Text", "text": "Text",
"transactionlink": {
"created": "Erstellt",
"name": "Transaktion-Links",
"valid_until": "Gültig bis"
},
"transactionlist": { "transactionlist": {
"amount": "Betrag", "amount": "Betrag",
"balanceDate": "Schöpfungsdatum", "balanceDate": "Schöpfungsdatum",
@ -92,7 +101,6 @@
"text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({email}) gesendet.", "text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({email}) gesendet.",
"text_true": " Die Email wurde bestätigt." "text_true": " Die Email wurde bestätigt."
}, },
"userIsDeleted": "Der Nutzer ist gelöscht. Es können keine GDD mehr geschöpft werden.",
"user_deleted": "Nutzer ist gelöscht.", "user_deleted": "Nutzer ist gelöscht.",
"user_recovered": "Nutzer ist wiederhergestellt.", "user_recovered": "Nutzer ist wiederhergestellt.",
"user_search": "Nutzer-Suche" "user_search": "Nutzer-Suche"

View File

@ -2,6 +2,7 @@
"all_emails": "All users", "all_emails": "All users",
"back": "back", "back": "back",
"creation": "Creation", "creation": "Creation",
"creationList": "Creation list",
"creation_form": { "creation_form": {
"creation_failed": "Could not create pending creation for {email}", "creation_failed": "Could not create pending creation for {email}",
"creation_for": "Active Basic Income for", "creation_for": "Active Basic Income for",
@ -27,6 +28,7 @@
"edit": "Edit", "edit": "Edit",
"enabled": "enabled", "enabled": "enabled",
"error": "Error", "error": "Error",
"expired": "expired",
"e_mail": "E-mail", "e_mail": "E-mail",
"firstname": "Firstname", "firstname": "Firstname",
"footer": { "footer": {
@ -56,6 +58,7 @@
"user_search": "User search" "user_search": "User search"
}, },
"not_open_creations": "No open creations", "not_open_creations": "No open creations",
"open": "open",
"open_creations": "Open creations", "open_creations": "Open creations",
"overlay": { "overlay": {
"confirm": { "confirm": {
@ -66,6 +69,7 @@
"yes": "Yes, confirm and save creation!" "yes": "Yes, confirm and save creation!"
} }
}, },
"redeemed": "redeemed",
"remove": "Remove", "remove": "Remove",
"removeNotSelf": "As admin / moderator you cannot delete yourself.", "removeNotSelf": "As admin / moderator you cannot delete yourself.",
"remove_all": "Remove all users", "remove_all": "Remove all users",
@ -73,6 +77,11 @@
"status": "Status", "status": "Status",
"success": "Success", "success": "Success",
"text": "Text", "text": "Text",
"transactionlink": {
"created": "Created",
"name": "Transaction links",
"valid_until": "Valid until"
},
"transactionlist": { "transactionlist": {
"amount": "Amount", "amount": "Amount",
"balanceDate": "Creation date", "balanceDate": "Creation date",
@ -92,7 +101,6 @@
"text_false": "The last email was sent to the member ({email}) on {date}.", "text_false": "The last email was sent to the member ({email}) on {date}.",
"text_true": "The email was confirmed." "text_true": "The email was confirmed."
}, },
"userIsDeleted": "The user is deleted. No more GDD can be created.",
"user_deleted": "User is deleted.", "user_deleted": "User is deleted.",
"user_recovered": "User is recovered.", "user_recovered": "User is recovered.",
"user_search": "User search" "user_search": "User search"

View File

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

View File

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

View File

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

View File

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

View File

@ -4688,6 +4688,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^5.0.1: cross-spawn@^5.0.1:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@ -4708,7 +4715,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0" shebang-command "^1.2.0"
which "^1.2.9" which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.2: cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==

View File

@ -1,9 +1,9 @@
CONFIG_VERSION=v1.2022-03-18 CONFIG_VERSION=v6.2022-04-21
# Server # Server
PORT=4000 PORT=4000
JWT_SECRET=secret123 JWT_SECRET=secret123
JWT_EXPIRES_IN=10m JWT_EXPIRES_IN=30m
GRAPHIQL=false GRAPHIQL=false
GDT_API_URL=https://gdt.gradido.net GDT_API_URL=https://gdt.gradido.net
@ -27,6 +27,7 @@ KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
COMMUNITY_NAME=Gradido Entwicklung COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost/ COMMUNITY_URL=http://localhost/
COMMUNITY_REGISTER_URL=http://localhost/register COMMUNITY_REGISTER_URL=http://localhost/register
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido. COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
# Login Server # Login Server
@ -41,8 +42,11 @@ EMAIL_PASSWORD=xxx
EMAIL_SMTP_URL=gmail.com EMAIL_SMTP_URL=gmail.com
EMAIL_SMTP_PORT=587 EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION=http://localhost/checkEmail/{optin}{code} EMAIL_LINK_VERIFICATION=http://localhost/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD=http://localhost/reset/{optin} EMAIL_LINK_SETPASSWORD=http://localhost/reset-password/{optin}
EMAIL_CODE_VALID_TIME=10 EMAIL_LINK_FORGOTPASSWORD=http://localhost/forgot-password
EMAIL_LINK_OVERVIEW=http://localhost/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
# Webhook # Webhook
WEBHOOK_ELOPAGE_SECRET=secret WEBHOOK_ELOPAGE_SECRET=secret

View File

@ -2,7 +2,7 @@ CONFIG_VERSION=$BACKEND_CONFIG_VERSION
# Server # Server
JWT_SECRET=$JWT_SECRET JWT_SECRET=$JWT_SECRET
JWT_EXPIRES_IN=10m JWT_EXPIRES_IN=$JWT_EXPIRES_IN
GRAPHIQL=false GRAPHIQL=false
GDT_API_URL=$GDT_API_URL GDT_API_URL=$GDT_API_URL
@ -26,6 +26,7 @@ KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
COMMUNITY_NAME=$COMMUNITY_NAME COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_URL=$COMMUNITY_URL COMMUNITY_URL=$COMMUNITY_URL
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
# Login Server # Login Server
@ -33,7 +34,6 @@ LOGIN_APP_SECRET=21ffbbc616fe
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
# EMail # EMail
RESEND_TIME=10
EMAIL=$EMAIL EMAIL=$EMAIL
EMAIL_USERNAME=$EMAIL_USERNAME EMAIL_USERNAME=$EMAIL_USERNAME
EMAIL_SENDER=$EMAIL_SENDER EMAIL_SENDER=$EMAIL_SENDER
@ -42,7 +42,9 @@ EMAIL_SMTP_URL=$EMAIL_SMTP_URL
EMAIL_SMTP_PORT=587 EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION=$EMAIL_LINK_VERIFICATION EMAIL_LINK_VERIFICATION=$EMAIL_LINK_VERIFICATION
EMAIL_LINK_SETPASSWORD=$EMAIL_LINK_SETPASSWORD EMAIL_LINK_SETPASSWORD=$EMAIL_LINK_SETPASSWORD
RESEND_TIME=10 EMAIL_LINK_OVERVIEW=$EMAIL_LINK_OVERVIEW
EMAIL_CODE_VALID_TIME=$EMAIL_CODE_VALID_TIME
EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME
# Webhook # Webhook
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET

24
backend/README.md Normal file
View File

@ -0,0 +1,24 @@
# backend
## Project setup
```
yarn install
```
## Seed DB
```
yarn seed
```
Deletes all data in database. Then seeds data in database.
## Seeded Users
| email | password | admin | email checked | deleted |
|------------------------|------------|---------|---------------|---------|
| peter@lustig.de | `Aa12345_` | `true` | `true` | `false` |
| bibi@bloxberg.de | `Aa12345_` | `false` | `true` | `false` |
| raeuber@hotzenplotz.de | `Aa12345_` | `false` | `true` | `false` |
| bob@baumeister.de | `Aa12345_` | `false` | `true` | `false` |
| garrick@ollivander.com | | `false` | `false` | `false` |
| stephen@hawking.uk | `Aa12345_` | `false` | `true` | `true` |

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-backend", "name": "gradido-backend",
"version": "1.6.6", "version": "1.7.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",
@ -10,11 +10,11 @@
"scripts": { "scripts": {
"build": "tsc --build", "build": "tsc --build",
"clean": "tsc --build --clean", "clean": "tsc --build --clean",
"start": "TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"dev": "TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts", "dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .", "lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
"seed": "TZ=UTC ts-node -r tsconfig-paths/register src/seeds/index.ts" "seed": "cross-env TZ=UTC ts-node -r tsconfig-paths/register src/seeds/index.ts"
}, },
"dependencies": { "dependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
@ -25,6 +25,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"class-validator": "^0.13.1", "class-validator": "^0.13.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "^7.0.3",
"decimal.js-light": "^2.5.1", "decimal.js-light": "^2.5.1",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "^4.17.1", "express": "^4.17.1",

View File

@ -24,6 +24,7 @@ export enum RIGHTS {
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK', QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
REDEEM_TRANSACTION_LINK = 'REDEEM_TRANSACTION_LINK', REDEEM_TRANSACTION_LINK = 'REDEEM_TRANSACTION_LINK',
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS', LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
GDT_BALANCE = 'GDT_BALANCE',
// Admin // Admin
SEARCH_USERS = 'SEARCH_USERS', SEARCH_USERS = 'SEARCH_USERS',
CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION', CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION',
@ -35,4 +36,5 @@ export enum RIGHTS {
DELETE_USER = 'DELETE_USER', DELETE_USER = 'DELETE_USER',
UNDELETE_USER = 'UNDELETE_USER', UNDELETE_USER = 'UNDELETE_USER',
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST', CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
} }

View File

@ -22,6 +22,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.DELETE_TRANSACTION_LINK, RIGHTS.DELETE_TRANSACTION_LINK,
RIGHTS.REDEEM_TRANSACTION_LINK, RIGHTS.REDEEM_TRANSACTION_LINK,
RIGHTS.LIST_TRANSACTION_LINKS, RIGHTS.LIST_TRANSACTION_LINKS,
RIGHTS.GDT_BALANCE,
]) ])
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights

View File

@ -14,7 +14,7 @@ const constants = {
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
CONFIG_VERSION: { CONFIG_VERSION: {
DEFAULT: 'DEFAULT', DEFAULT: 'DEFAULT',
EXPECTED: 'v1.2022-03-18', EXPECTED: 'v6.2022-04-21',
CURRENT: '', CURRENT: '',
}, },
} }
@ -22,7 +22,7 @@ const constants = {
const server = { const server = {
PORT: process.env.PORT || 4000, PORT: process.env.PORT || 4000,
JWT_SECRET: process.env.JWT_SECRET || 'secret123', JWT_SECRET: process.env.JWT_SECRET || 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m', JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '30m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false, GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net', GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
PRODUCTION: process.env.NODE_ENV === 'production' || false, PRODUCTION: process.env.NODE_ENV === 'production' || false,
@ -50,6 +50,7 @@ const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung', COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/', COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register', COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL || 'http://localhost/redeem/{code}',
COMMUNITY_DESCRIPTION: COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.', process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
} }
@ -70,8 +71,16 @@ const email = {
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{optin}{code}', process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{optin}{code}',
EMAIL_LINK_SETPASSWORD: EMAIL_LINK_SETPASSWORD:
process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset-password/{optin}', process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset-password/{optin}',
EMAIL_LINK_FORGOTPASSWORD:
process.env.EMAIL_LINK_FORGOTPASSWORD || 'http://localhost/forgot-password',
EMAIL_LINK_OVERVIEW: process.env.EMAIL_LINK_OVERVIEW || 'http://localhost/overview',
// time in minutes a optin code is valid
EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME
? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 10 ? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 1440
: 1440,
// time in minutes that must pass to request a new optin code
EMAIL_CODE_REQUEST_TIME: process.env.EMAIL_CODE_REQUEST_TIME
? parseInt(process.env.EMAIL_CODE_REQUEST_TIME) || 10
: 10, : 10,
} }

View File

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

View File

@ -0,0 +1,13 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class TransactionLinkFilters {
@Field(() => Boolean, { nullable: true, defaultValue: true })
withDeleted?: boolean
@Field(() => Boolean, { nullable: true, defaultValue: true })
withExpired?: boolean
@Field(() => Boolean, { nullable: true, defaultValue: true })
withRedeemed?: boolean
}

View File

@ -0,0 +1,11 @@
import { registerEnumType } from 'type-graphql'
export enum OptInType {
EMAIL_OPT_IN_REGISTER = 1,
EMAIL_OPT_IN_RESET_PASSWORD = 2,
}
registerEnumType(OptInType, {
name: 'OptInType', // this one is mandatory
description: 'Type of the email optin', // this one is optional
})

View File

@ -1,22 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql' import { ObjectType, Field } from 'type-graphql'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
@ObjectType() @ObjectType()
export class Balance { export class Balance {
constructor(json: any) { constructor(data: {
this.balance = json.balance balance: Decimal
this.decay = json.decay balanceGDT: number | null
this.decayDate = json.decay_date count: number
linkCount: number
}) {
this.balance = data.balance
this.balanceGDT = data.balanceGDT || null
this.count = data.count
this.linkCount = data.linkCount
} }
// the actual balance, decay included
@Field(() => Decimal) @Field(() => Decimal)
balance: Decimal balance: Decimal
@Field(() => Decimal) @Field(() => Number, { nullable: true })
decay: Decimal balanceGDT: number | null
@Field(() => Date) // the count of all transactions
decayDate: Date @Field(() => Number)
count: number
// the count of transaction links
@Field(() => Number)
linkCount: number
} }

View File

@ -1,17 +1,21 @@
import { ObjectType, Field, Int } from 'type-graphql' import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
interface DecayInterface {
balance: Decimal
decay: Decimal
roundedDecay: Decimal
start: Date | null
end: Date | null
duration: number | null
}
@ObjectType() @ObjectType()
export class Decay { export class Decay {
constructor( constructor({ balance, decay, roundedDecay, start, end, duration }: DecayInterface) {
balance: Decimal,
decay: Decimal,
start: Date | null,
end: Date | null,
duration: number | null,
) {
this.balance = balance this.balance = balance
this.decay = decay this.decay = decay
this.roundedDecay = roundedDecay
this.start = start this.start = start
this.end = end this.end = end
this.duration = duration this.duration = duration
@ -23,6 +27,9 @@ export class Decay {
@Field(() => Decimal) @Field(() => Decimal)
decay: Decimal decay: Decimal
@Field(() => Decimal)
roundedDecay: Decimal
@Field(() => Date, { nullable: true }) @Field(() => Date, { nullable: true })
start: Date | null start: Date | null

View File

@ -12,19 +12,31 @@ export class Transaction {
this.user = user this.user = user
this.previous = transaction.previous this.previous = transaction.previous
this.typeId = transaction.typeId this.typeId = transaction.typeId
this.amount = transaction.amount this.amount = transaction.amount.toDecimalPlaces(2, Decimal.ROUND_DOWN)
this.balance = transaction.balance this.balance = transaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN)
this.balanceDate = transaction.balanceDate this.balanceDate = transaction.balanceDate
if (!transaction.decayStart) { if (!transaction.decayStart) {
this.decay = new Decay(transaction.balance, new Decimal(0), null, null, null) // TODO: hot fix, we should separate decay calculation from decay graphql model
this.decay = new Decay({
balance: transaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN),
decay: new Decimal(0),
roundedDecay: new Decimal(0),
start: null,
end: null,
duration: null,
})
} else { } else {
this.decay = new Decay( this.decay = new Decay({
transaction.balance, balance: transaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN),
transaction.decay, decay: transaction.decay.toDecimalPlaces(2, Decimal.ROUND_FLOOR),
transaction.decayStart, // TODO: add correct value when decay must be rounded in transaction context
transaction.balanceDate, roundedDecay: new Decimal(0),
Math.round((transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000), start: transaction.decayStart,
) end: transaction.balanceDate,
duration: Math.round(
(transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000,
),
})
} }
this.memo = transaction.memo this.memo = transaction.memo
this.creationDate = transaction.creationDate this.creationDate = transaction.creationDate

View File

@ -1,7 +1,8 @@
import { ObjectType, Field } from 'type-graphql' import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { User } from './User' import { User } from './User'
import CONFIG from '@/config'
@ObjectType() @ObjectType()
export class TransactionLink { export class TransactionLink {
@ -17,6 +18,7 @@ export class TransactionLink {
this.deletedAt = transactionLink.deletedAt this.deletedAt = transactionLink.deletedAt
this.redeemedAt = transactionLink.redeemedAt this.redeemedAt = transactionLink.redeemedAt
this.redeemedBy = redeemedBy this.redeemedBy = redeemedBy
this.link = CONFIG.COMMUNITY_REDEEM_URL.replace(/{code}/g, this.code)
} }
@Field(() => Number) @Field(() => Number)
@ -51,4 +53,16 @@ export class TransactionLink {
@Field(() => User, { nullable: true }) @Field(() => User, { nullable: true })
redeemedBy: User | null redeemedBy: User | null
@Field(() => String)
link: string
}
@ObjectType()
export class TransactionLinkResult {
@Field(() => Int)
linkCount: number
@Field(() => [TransactionLink])
linkList: TransactionLink[]
} }

View File

@ -1,40 +1,16 @@
import { ObjectType, Field } from 'type-graphql' import { ObjectType, Field } from 'type-graphql'
import CONFIG from '@/config'
import Decimal from 'decimal.js-light'
import { Transaction } from './Transaction' import { Transaction } from './Transaction'
import { Balance } from './Balance'
@ObjectType() @ObjectType()
export class TransactionList { export class TransactionList {
constructor( constructor(balance: Balance, transactions: Transaction[]) {
balance: Decimal,
transactions: Transaction[],
count: number,
linkCount: number,
balanceGDT?: number | null,
decayStartBlock: Date = CONFIG.DECAY_START_TIME,
) {
this.balance = balance this.balance = balance
this.transactions = transactions this.transactions = transactions
this.count = count
this.linkCount = linkCount
this.balanceGDT = balanceGDT || null
this.decayStartBlock = decayStartBlock
} }
@Field(() => Number, { nullable: true }) @Field(() => Balance)
balanceGDT: number | null balance: Balance
@Field(() => Number)
count: number
@Field(() => Number)
linkCount: number
@Field(() => Decimal)
balance: Decimal
@Field(() => Date)
decayStartBlock: Date
@Field(() => [Transaction]) @Field(() => [Transaction])
transactions: Transaction[] transactions: Transaction[]

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, getUser } from '@/server/context'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql' import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql'
import { import {
getCustomRepository, getCustomRepository,
@ -9,6 +7,8 @@ import {
ObjectLiteral, ObjectLiteral,
getConnection, getConnection,
In, In,
MoreThan,
FindOperator,
} from '@dbTools/typeorm' } from '@dbTools/typeorm'
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin' import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
import { PendingCreation } from '@model/PendingCreation' import { PendingCreation } from '@model/PendingCreation'
@ -21,6 +21,8 @@ import UpdatePendingCreationArgs from '@arg/UpdatePendingCreationArgs'
import SearchUsersArgs from '@arg/SearchUsersArgs' import SearchUsersArgs from '@arg/SearchUsersArgs'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { Transaction } from '@model/Transaction' import { Transaction } from '@model/Transaction'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { TransactionRepository } from '@repository/Transaction' import { TransactionRepository } from '@repository/Transaction'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
import { AdminPendingCreation } from '@entity/AdminPendingCreation' import { AdminPendingCreation } from '@entity/AdminPendingCreation'
@ -32,8 +34,12 @@ import { TransactionTypeId } from '@enum/TransactionTypeId'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { Decay } from '@model/Decay' import { Decay } from '@model/Decay'
import Paginated from '@arg/Paginated' import Paginated from '@arg/Paginated'
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
import { communityUser } from '@/util/communityUser' import { communityUser } from '@/util/communityUser'
import { checkOptInCode, activationLink, printTimeDuration } from './UserResolver'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import CONFIG from '@/config'
// const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_IN_REGISTER = 1
// const EMAIL_OPT_UNKNOWN = 3 // elopage? // const EMAIL_OPT_UNKNOWN = 3 // elopage?
@ -50,19 +56,19 @@ export class AdminResolver {
searchText, searchText,
currentPage = 1, currentPage = 1,
pageSize = 25, pageSize = 25,
notActivated = false, notActivated = null,
isDeleted = false, isDeleted = null,
}: SearchUsersArgs, }: SearchUsersArgs,
): Promise<SearchUsersResult> { ): Promise<SearchUsersResult> {
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const filterCriteria: ObjectLiteral[] = [] const filterCriteria: ObjectLiteral[] = []
if (notActivated) { if (notActivated !== null) {
filterCriteria.push({ emailChecked: false }) filterCriteria.push({ emailChecked: !notActivated })
} }
if (isDeleted) { if (isDeleted !== null) {
filterCriteria.push({ deletedAt: Not(IsNull()) }) filterCriteria.push({ deletedAt: isDeleted ? Not(IsNull()) : IsNull() })
} }
const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked', 'deletedAt'] const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked', 'deletedAt']
@ -129,7 +135,7 @@ export class AdminResolver {
@Mutation(() => Date, { nullable: true }) @Mutation(() => Date, { nullable: true })
async deleteUser( async deleteUser(
@Arg('userId', () => Int) userId: number, @Arg('userId', () => Int) userId: number,
@Ctx() context: any, @Ctx() context: Context,
): Promise<Date | null> { ): Promise<Date | null> {
const user = await dbUser.findOne({ id: userId }) const user = await dbUser.findOne({ id: userId })
// user exists ? // user exists ?
@ -137,7 +143,7 @@ export class AdminResolver {
throw new Error(`Could not find user with userId: ${userId}`) throw new Error(`Could not find user with userId: ${userId}`)
} }
// moderator user disabled own account? // moderator user disabled own account?
const moderatorUser = context.user const moderatorUser = getUser(context)
if (moderatorUser.id === userId) { if (moderatorUser.id === userId) {
throw new Error('Moderator can not delete his own account!') throw new Error('Moderator can not delete his own account!')
} }
@ -301,10 +307,10 @@ export class AdminResolver {
@Mutation(() => Boolean) @Mutation(() => Boolean)
async confirmPendingCreation( async confirmPendingCreation(
@Arg('id', () => Int) id: number, @Arg('id', () => Int) id: number,
@Ctx() context: any, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const pendingCreation = await AdminPendingCreation.findOneOrFail(id) const pendingCreation = await AdminPendingCreation.findOneOrFail(id)
const moderatorUser = context.user const moderatorUser = getUser(context)
if (moderatorUser.id === pendingCreation.userId) if (moderatorUser.id === pendingCreation.userId)
throw new Error('Moderator can not confirm own pending creation') throw new Error('Moderator can not confirm own pending creation')
@ -369,6 +375,75 @@ export class AdminResolver {
const user = await dbUser.findOneOrFail({ id: userId }) const user = await dbUser.findOneOrFail({ id: userId })
return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
} }
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
email = email.trim().toLowerCase()
const user = await dbUser.findOneOrFail({ email: email })
// can be both types: REGISTER and RESET_PASSWORD
let optInCode = await LoginEmailOptIn.findOne({
where: { userId: user.id },
order: { updatedAt: 'DESC' },
})
optInCode = await checkOptInCode(optInCode, user.id)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink(optInCode),
firstName: user.firstName,
lastName: user.lastName,
email,
duration: printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME),
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
return true
}
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
@Query(() => TransactionLinkResult)
async listTransactionLinksAdmin(
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Args()
filters: TransactionLinkFilters,
@Arg('userId', () => Int) userId: number,
): Promise<TransactionLinkResult> {
const user = await dbUser.findOneOrFail({ id: userId })
const where: {
userId: number
redeemedBy?: number | null
validUntil?: FindOperator<Date> | null
} = {
userId,
}
if (!filters.withRedeemed) where.redeemedBy = null
if (!filters.withExpired) where.validUntil = MoreThan(new Date())
const [transactionLinks, count] = await dbTransactionLink.findAndCount({
where,
withDeleted: filters.withDeleted,
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return {
linkCount: count,
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
}
}
} }
interface CreationMap { interface CreationMap {

View File

@ -1,40 +1,75 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, getUser } from '@/server/context'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Ctx, Authorized } from 'type-graphql' import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import { Balance } from '@model/Balance' import { Balance } from '@model/Balance'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { Transaction } from '@entity/Transaction' import { Transaction as dbTransaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { GdtResolver } from './GdtResolver'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { MoreThan, getCustomRepository } from '@dbTools/typeorm'
import { TransactionLinkRepository } from '@repository/TransactionLink'
@Resolver() @Resolver()
export class BalanceResolver { export class BalanceResolver {
@Authorized([RIGHTS.BALANCE]) @Authorized([RIGHTS.BALANCE])
@Query(() => Balance) @Query(() => Balance)
async balance(@Ctx() context: any): Promise<Balance> { async balance(@Ctx() context: Context): Promise<Balance> {
// load user and balance const user = getUser(context)
const { user } = context
const now = new Date() const now = new Date()
const lastTransaction = await Transaction.findOne( const gdtResolver = new GdtResolver()
{ userId: user.id }, const balanceGDT = await gdtResolver.gdtBalance(context)
{ order: { balanceDate: 'DESC' } },
) const lastTransaction = context.lastTransaction
? context.lastTransaction
: await dbTransaction.findOne({ userId: user.id }, { order: { balanceDate: 'DESC' } })
// No balance found // No balance found
if (!lastTransaction) { if (!lastTransaction) {
return new Balance({ return new Balance({
balance: new Decimal(0), balance: new Decimal(0),
decay: new Decimal(0), balanceGDT,
decay_date: now.toString(), count: 0,
linkCount: 0,
}) })
} }
const count =
context.transactionCount || context.transactionCount === 0
? context.transactionCount
: await dbTransaction.count({ where: { userId: user.id } })
const linkCount =
context.linkCount || context.linkCount === 0
? context.linkCount
: await dbTransactionLink.count({
where: {
userId: user.id,
redeemedAt: null,
validUntil: MoreThan(new Date()),
},
})
// The decay is always calculated on the last booked transaction
const calculatedDecay = calculateDecay(
lastTransaction.balance,
lastTransaction.balanceDate,
now,
)
// The final balance is reduced by the link amount withheld
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
const { sumHoldAvailableAmount } = context.sumHoldAvailableAmount
? { sumHoldAvailableAmount: context.sumHoldAvailableAmount }
: await transactionLinkRepository.summary(user.id, now)
return new Balance({ return new Balance({
balance: lastTransaction.balance, balance: calculatedDecay.balance
decay: calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance, .minus(sumHoldAvailableAmount.toString())
decay_date: now.toString(), .toDecimalPlaces(2, Decimal.ROUND_DOWN), // round towards zero
balanceGDT,
count,
linkCount,
}) })
} }
} }

View File

@ -1,6 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Authorized } from 'type-graphql' import { Resolver, Query, Authorized } from 'type-graphql'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import CONFIG from '@/config' import CONFIG from '@/config'

View File

@ -1,11 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, getUser } from '@/server/context'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Ctx, Authorized, Arg } from 'type-graphql' import { Resolver, Query, Args, Ctx, Authorized, Arg } from 'type-graphql'
import CONFIG from '@/config' import CONFIG from '@/config'
import { GdtEntryList } from '@model/GdtEntryList' import { GdtEntryList } from '@model/GdtEntryList'
import Paginated from '@arg/Paginated' import Paginated from '@arg/Paginated'
import { apiGet } from '@/apis/HttpRequest' import { apiGet, apiPost } from '@/apis/HttpRequest'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
@ -13,14 +11,12 @@ import { RIGHTS } from '@/auth/RIGHTS'
export class GdtResolver { export class GdtResolver {
@Authorized([RIGHTS.LIST_GDT_ENTRIES]) @Authorized([RIGHTS.LIST_GDT_ENTRIES])
@Query(() => GdtEntryList) @Query(() => GdtEntryList)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async listGDTEntries( async listGDTEntries(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Ctx() context: any, @Ctx() context: Context,
): Promise<GdtEntryList> { ): Promise<GdtEntryList> {
// load user const userEntity = getUser(context)
const userEntity = context.user
try { try {
const resultGDT = await apiGet( const resultGDT = await apiGet(
@ -30,11 +26,30 @@ export class GdtResolver {
throw new Error(resultGDT.data) throw new Error(resultGDT.data)
} }
return new GdtEntryList(resultGDT.data) return new GdtEntryList(resultGDT.data)
} catch (err: any) { } catch (err) {
throw new Error('GDT Server is not reachable.') throw new Error('GDT Server is not reachable.')
} }
} }
@Authorized([RIGHTS.GDT_BALANCE])
@Query(() => Number)
async gdtBalance(@Ctx() context: Context): Promise<number | null> {
const user = getUser(context)
try {
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
email: user.email,
})
if (!resultGDTSum.success) {
throw new Error('Call not successful')
}
return Number(resultGDTSum.data.sum) || 0
} catch (err) {
// eslint-disable-next-line no-console
console.log('Could not query GDT Server')
return null
}
}
@Authorized([RIGHTS.EXIST_PID]) @Authorized([RIGHTS.EXIST_PID])
@Query(() => Number) @Query(() => Number)
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -1,6 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql' import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql'
import { import {
getKlickTippUser, getKlickTippUser,

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, getUser } from '@/server/context'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query, Int } from 'type-graphql' import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query, Int } from 'type-graphql'
import { TransactionLink } from '@model/TransactionLink' import { TransactionLink } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
@ -38,9 +36,9 @@ export class TransactionLinkResolver {
@Mutation(() => TransactionLink) @Mutation(() => TransactionLink)
async createTransactionLink( async createTransactionLink(
@Args() { amount, memo }: TransactionLinkArgs, @Args() { amount, memo }: TransactionLinkArgs,
@Ctx() context: any, @Ctx() context: Context,
): Promise<TransactionLink> { ): Promise<TransactionLink> {
const { user } = context const user = getUser(context)
const createdDate = new Date() const createdDate = new Date()
const validUntil = transactionLinkExpireDate(createdDate) const validUntil = transactionLinkExpireDate(createdDate)
@ -72,9 +70,9 @@ export class TransactionLinkResolver {
@Mutation(() => Boolean) @Mutation(() => Boolean)
async deleteTransactionLink( async deleteTransactionLink(
@Arg('id', () => Int) id: number, @Arg('id', () => Int) id: number,
@Ctx() context: any, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const { user } = context const user = getUser(context)
const transactionLink = await dbTransactionLink.findOne({ id }) const transactionLink = await dbTransactionLink.findOne({ id })
if (!transactionLink) { if (!transactionLink) {
@ -113,9 +111,9 @@ export class TransactionLinkResolver {
async listTransactionLinks( async listTransactionLinks(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Ctx() context: any, @Ctx() context: Context,
): Promise<TransactionLink[]> { ): Promise<TransactionLink[]> {
const { user } = context const user = getUser(context)
// const now = new Date() // const now = new Date()
const transactionLinks = await dbTransactionLink.find({ const transactionLinks = await dbTransactionLink.find({
where: { where: {
@ -136,9 +134,9 @@ export class TransactionLinkResolver {
@Mutation(() => Boolean) @Mutation(() => Boolean)
async redeemTransactionLink( async redeemTransactionLink(
@Arg('code', () => String) code: string, @Arg('code', () => String) code: string,
@Ctx() context: any, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const { user } = context const user = getUser(context)
const transactionLink = await dbTransactionLink.findOneOrFail({ code }) const transactionLink = await dbTransactionLink.findOneOrFail({ code })
const linkedUser = await dbUser.findOneOrFail({ id: transactionLink.userId }) const linkedUser = await dbUser.findOneOrFail({ id: transactionLink.userId })

View File

@ -1,12 +1,12 @@
/* eslint-disable new-cap */ /* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import CONFIG from '@/config'
import { Context, getUser } from '@/server/context'
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository, getConnection } from '@dbTools/typeorm' import { getCustomRepository, getConnection } from '@dbTools/typeorm'
import CONFIG from '@/config'
import { sendTransactionReceivedEmail } from '@/mailer/sendTransactionReceivedEmail' import { sendTransactionReceivedEmail } from '@/mailer/sendTransactionReceivedEmail'
import { Transaction } from '@model/Transaction' import { Transaction } from '@model/Transaction'
@ -24,7 +24,6 @@ import { User as dbUser } from '@entity/User'
import { Transaction as dbTransaction } from '@entity/Transaction' import { Transaction as dbTransaction } from '@entity/Transaction'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { apiPost } from '@/apis/HttpRequest'
import { TransactionTypeId } from '@enum/TransactionTypeId' import { TransactionTypeId } from '@enum/TransactionTypeId'
import { calculateBalance, isHexPublicKey } from '@/util/validate' import { calculateBalance, isHexPublicKey } from '@/util/validate'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
@ -32,7 +31,11 @@ import { User } from '@model/User'
import { communityUser } from '@/util/communityUser' import { communityUser } from '@/util/communityUser'
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { calculateDecay } from '@/util/decay'
import { BalanceResolver } from './BalanceResolver'
const MEMO_MAX_CHARS = 255
const MEMO_MIN_CHARS = 5
export const executeTransaction = async ( export const executeTransaction = async (
amount: Decimal, amount: Decimal,
@ -45,9 +48,22 @@ export const executeTransaction = async (
throw new Error('Sender and Recipient are the same.') throw new Error('Sender and Recipient are the same.')
} }
if (memo.length > MEMO_MAX_CHARS) {
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
}
if (memo.length < MEMO_MIN_CHARS) {
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
}
// validate amount // validate amount
const receivedCallDate = new Date() const receivedCallDate = new Date()
const sendBalance = await calculateBalance(sender.id, amount.mul(-1), receivedCallDate) const sendBalance = await calculateBalance(
sender.id,
amount.mul(-1),
receivedCallDate,
transactionLink,
)
if (!sendBalance) { if (!sendBalance) {
throw new Error("user hasn't enough GDD or amount is < 0") throw new Error("user hasn't enough GDD or amount is < 0")
} }
@ -120,6 +136,7 @@ export const executeTransaction = async (
senderEmail: sender.email, senderEmail: sender.email,
amount, amount,
memo, memo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
}) })
return true return true
@ -132,10 +149,10 @@ export class TransactionResolver {
async transactionList( async transactionList(
@Args() @Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Ctx() context: any, @Ctx() context: Context,
): Promise<TransactionList> { ): Promise<TransactionList> {
const now = new Date() const now = new Date()
const user = context.user const user = getUser(context)
// find current balance // find current balance
const lastTransaction = await dbTransaction.findOne( const lastTransaction = await dbTransaction.findOne(
@ -143,23 +160,11 @@ export class TransactionResolver {
{ order: { balanceDate: 'DESC' } }, { order: { balanceDate: 'DESC' } },
) )
// get GDT const balanceResolver = new BalanceResolver()
let balanceGDT = null context.lastTransaction = lastTransaction
try {
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
email: user.email,
})
if (!resultGDTSum.success) {
throw new Error('Call not successful')
}
balanceGDT = Number(resultGDTSum.data.sum) || 0
} catch (err: any) {
// eslint-disable-next-line no-console
console.log('Could not query GDT Server', err)
}
if (!lastTransaction) { if (!lastTransaction) {
return new TransactionList(new Decimal(0), [], 0, 0, balanceGDT) return new TransactionList(await balanceResolver.balance(context), [])
} }
// find transactions // find transactions
@ -172,6 +177,7 @@ export class TransactionResolver {
offset, offset,
order, order,
) )
context.transactionCount = userTransactionsCount
// find involved users; I am involved // find involved users; I am involved
const involvedUserIds: number[] = [user.id] const involvedUserIds: number[] = [user.id]
@ -194,11 +200,21 @@ export class TransactionResolver {
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, transactionLinkcount } = const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, transactionLinkcount } =
await transactionLinkRepository.summary(user.id, now) await transactionLinkRepository.summary(user.id, now)
context.linkCount = transactionLinkcount
context.sumHoldAvailableAmount = sumHoldAvailableAmount
// decay & link transactions // decay & link transactions
if (currentPage === 1 && order === Order.DESC) { if (currentPage === 1 && order === Order.DESC) {
// The virtual decay is always on the booked amount, not including the generated, not yet booked links,
// since the decay is substantially different when the amount is less
transactions.push( transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self), virtualDecayTransaction(
lastTransaction.balance,
lastTransaction.balanceDate,
now,
self,
sumHoldAvailableAmount,
),
) )
// virtual transaction for pending transaction-links sum // virtual transaction for pending transaction-links sum
if (sumHoldAvailableAmount.greaterThan(0)) { if (sumHoldAvailableAmount.greaterThan(0)) {
@ -226,25 +242,17 @@ export class TransactionResolver {
}) })
// Construct Result // Construct Result
return new TransactionList( return new TransactionList(await balanceResolver.balance(context), transactions)
calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance.minus(
sumHoldAvailableAmount.toString(),
),
transactions,
userTransactionsCount,
transactionLinkcount,
balanceGDT,
)
} }
@Authorized([RIGHTS.SEND_COINS]) @Authorized([RIGHTS.SEND_COINS])
@Mutation(() => String) @Mutation(() => String)
async sendCoins( async sendCoins(
@Args() { email, amount, memo }: TransactionSendArgs, @Args() { email, amount, memo }: TransactionSendArgs,
@Ctx() context: any, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
// TODO this is subject to replay attacks // TODO this is subject to replay attacks
const senderUser = context.user const senderUser = getUser(context)
if (senderUser.pubKey.length !== 32) { if (senderUser.pubKey.length !== 32) {
throw new Error('invalid sender public key') throw new Error('invalid sender public key')
} }
@ -257,6 +265,9 @@ export class TransactionResolver {
if (recipientUser.deletedAt) { if (recipientUser.deletedAt) {
throw new Error('The recipient account was deleted') throw new Error('The recipient account was deleted')
} }
if (!recipientUser.emailChecked) {
throw new Error('The recipient account is not activated')
}
if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) { if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) {
throw new Error('invalid recipient public key') throw new Error('invalid recipient public key')
} }

View File

@ -11,6 +11,7 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User' import { User } from '@entity/User'
import CONFIG from '@/config' import CONFIG from '@/config'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import { printTimeDuration } from './UserResolver'
// import { klicktippSignIn } from '@/apis/KlicktippController' // import { klicktippSignIn } from '@/apis/KlicktippController'
@ -133,6 +134,7 @@ describe('UserResolver', () => {
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
email: 'peter@lustig.de', email: 'peter@lustig.de',
duration: expect.any(String),
}) })
}) })
}) })
@ -220,10 +222,6 @@ describe('UserResolver', () => {
expect(newUser[0].password).toEqual('3917921995996627700') expect(newUser[0].password).toEqual('3917921995996627700')
}) })
it('removes the optin', async () => {
await expect(LoginEmailOptIn.find()).resolves.toHaveLength(0)
})
/* /*
it('calls the klicktipp API', () => { it('calls the klicktipp API', () => {
expect(klicktippSignIn).toBeCalledWith( expect(klicktippSignIn).toBeCalledWith(
@ -415,3 +413,17 @@ describe('UserResolver', () => {
}) })
}) })
}) })
describe('printTimeDuration', () => {
it('works with 10 minutes', () => {
expect(printTimeDuration(10)).toBe('10 minutes')
})
it('works with 1440 minutes', () => {
expect(printTimeDuration(1440)).toBe('24 hours')
})
it('works with 1410 minutes', () => {
expect(printTimeDuration(1410)).toBe('23 hours and 30 minutes')
})
})

View File

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import fs from 'fs' import fs from 'fs'
import { Context, getUser } from '@/server/context'
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql' import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
import { getConnection, getCustomRepository, QueryRunner } from '@dbTools/typeorm' import { getConnection, getCustomRepository } from '@dbTools/typeorm'
import CONFIG from '@/config' import CONFIG from '@/config'
import { User } from '@model/User' import { User } from '@model/User'
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
@ -15,8 +13,9 @@ import UpdateUserInfosArgs from '@arg/UpdateUserInfosArgs'
import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddleware' import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddleware'
import { UserSettingRepository } from '@repository/UserSettingRepository' import { UserSettingRepository } from '@repository/UserSettingRepository'
import { Setting } from '@enum/Setting' import { Setting } from '@enum/Setting'
import { OptInType } from '@enum/OptInType'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail' import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer/sendResetPasswordEmail'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import { klicktippSignIn } from '@/apis/KlicktippController' import { klicktippSignIn } from '@/apis/KlicktippController'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
@ -24,9 +23,6 @@ import { ROLE_ADMIN } from '@/auth/ROLES'
import { hasElopageBuys } from '@/util/hasElopageBuys' import { hasElopageBuys } from '@/util/hasElopageBuys'
import { ServerUser } from '@entity/ServerUser' import { ServerUser } from '@entity/ServerUser'
const EMAIL_OPT_IN_RESET_PASSWORD = 2
const EMAIL_OPT_IN_REGISTER = 1
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const sodium = require('sodium-native') const sodium = require('sodium-native')
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
@ -148,65 +144,55 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
return message return message
} }
const createEmailOptIn = async (
loginUserId: number, const newEmailOptIn = (userId: number): LoginEmailOptIn => {
queryRunner: QueryRunner, const emailOptIn = new LoginEmailOptIn()
): Promise<LoginEmailOptIn> => { emailOptIn.verificationCode = random(64)
let emailOptIn = await LoginEmailOptIn.findOne({ emailOptIn.userId = userId
userId: loginUserId, emailOptIn.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
emailOptInTypeId: EMAIL_OPT_IN_REGISTER,
})
if (emailOptIn) {
if (isOptInCodeValid(emailOptIn)) {
throw new Error(`email already sent less than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`)
}
emailOptIn.updatedAt = new Date()
emailOptIn.resendCount++
} else {
emailOptIn = new LoginEmailOptIn()
emailOptIn.verificationCode = random(64)
emailOptIn.userId = loginUserId
emailOptIn.emailOptInTypeId = EMAIL_OPT_IN_REGISTER
}
await queryRunner.manager.save(emailOptIn).catch((error) => {
// eslint-disable-next-line no-console
console.log('Error while saving emailOptIn', error)
throw new Error('error saving email opt in')
})
return emailOptIn return emailOptIn
} }
const getOptInCode = async (loginUserId: number): Promise<LoginEmailOptIn> => { // needed by AdminResolver
let optInCode = await LoginEmailOptIn.findOne({ // checks if given code exists and can be resent
userId: loginUserId, // if optIn does not exits, it is created
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD, export const checkOptInCode = async (
}) optInCode: LoginEmailOptIn | undefined,
userId: number,
// Check for `CONFIG.EMAIL_CODE_VALID_TIME` minute delay optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER,
): Promise<LoginEmailOptIn> => {
if (optInCode) { if (optInCode) {
if (isOptInCodeValid(optInCode)) { if (!canResendOptIn(optInCode)) {
throw new Error(`email already sent less than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) throw new Error(
`email already sent less than ${printTimeDuration(
CONFIG.EMAIL_CODE_REQUEST_TIME,
)} minutes ago`,
)
} }
optInCode.updatedAt = new Date() optInCode.updatedAt = new Date()
optInCode.resendCount++ optInCode.resendCount++
} else { } else {
optInCode = new LoginEmailOptIn() optInCode = newEmailOptIn(userId)
optInCode.verificationCode = random(64)
optInCode.userId = loginUserId
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
} }
await LoginEmailOptIn.save(optInCode) optInCode.emailOptInTypeId = optInType
await LoginEmailOptIn.save(optInCode).catch(() => {
throw new Error('Unable to save optin code.')
})
return optInCode return optInCode
} }
export const activationLink = (optInCode: LoginEmailOptIn): string => {
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, optInCode.verificationCode.toString())
}
@Resolver() @Resolver()
export class UserResolver { export class UserResolver {
@Authorized([RIGHTS.VERIFY_LOGIN]) @Authorized([RIGHTS.VERIFY_LOGIN])
@Query(() => User) @Query(() => User)
@UseMiddleware(klicktippNewsletterStateMiddleware) @UseMiddleware(klicktippNewsletterStateMiddleware)
async verifyLogin(@Ctx() context: any): Promise<User> { async verifyLogin(@Ctx() context: Context): Promise<User> {
// TODO refactor and do not have duplicate code with login(see below) // TODO refactor and do not have duplicate code with login(see below)
const userEntity = context.user const userEntity = getUser(context)
const user = new User(userEntity) const user = new User(userEntity)
// user.pubkey = userEntity.pubKey.toString('hex') // user.pubkey = userEntity.pubKey.toString('hex')
// Elopage Status & Stored PublisherId // Elopage Status & Stored PublisherId
@ -230,7 +216,7 @@ export class UserResolver {
@UseMiddleware(klicktippNewsletterStateMiddleware) @UseMiddleware(klicktippNewsletterStateMiddleware)
async login( async login(
@Args() { email, password, publisherId }: UnsecureLoginArgs, @Args() { email, password, publisherId }: UnsecureLoginArgs,
@Ctx() context: any, @Ctx() context: Context,
): Promise<User> { ): Promise<User> {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
const dbUser = await DbUser.findOneOrFail({ email }, { withDeleted: true }).catch(() => { const dbUser = await DbUser.findOneOrFail({ email }, { withDeleted: true }).catch(() => {
@ -262,7 +248,7 @@ export class UserResolver {
user.language = dbUser.language user.language = dbUser.language
// Elopage Status & Stored PublisherId // Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage({ pubKey: dbUser.pubKey.toString('hex') }) user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
if (!user.hasElopage && publisherId) { if (!user.hasElopage && publisherId) {
user.publisherId = publisherId user.publisherId = publisherId
// TODO: Check if we can use updateUserInfos // TODO: Check if we can use updateUserInfos
@ -363,9 +349,12 @@ export class UserResolver {
throw new Error('error saving user') throw new Error('error saving user')
}) })
// Store EmailOptIn in DB const emailOptIn = newEmailOptIn(dbUser.id)
// TODO: this has duplicate code with sendResetPasswordEmail await queryRunner.manager.save(emailOptIn).catch((error) => {
const emailOptIn = await createEmailOptIn(dbUser.id, queryRunner) // eslint-disable-next-line no-console
console.log('Error while saving emailOptIn', error)
throw new Error('error saving email opt in')
})
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace( const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/{optin}/g, /{optin}/g,
@ -378,6 +367,7 @@ export class UserResolver {
firstName, firstName,
lastName, lastName,
email, email,
duration: printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME),
}) })
/* uncomment this, when you need the activation link on the console /* uncomment this, when you need the activation link on the console
@ -398,70 +388,27 @@ export class UserResolver {
return new User(dbUser) return new User(dbUser)
} }
// THis is used by the admin only - should we move it to the admin resolver?
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
email = email.trim().toLowerCase()
const user = await DbUser.findOneOrFail({ email: email })
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED')
try {
const emailOptIn = await createEmailOptIn(user.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/{optin}/g,
emailOptIn.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName: user.firstName,
lastName: user.lastName,
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
throw e
} finally {
await queryRunner.release()
}
return true
}
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL]) @Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
@Query(() => Boolean) @Mutation(() => Boolean)
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> { async forgotPassword(@Arg('email') email: string): Promise<boolean> {
// TODO: this has duplicate code with createUser
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
const user = await DbUser.findOneOrFail({ email }) const user = await DbUser.findOne({ email })
if (!user) return true
const optInCode = await getOptInCode(user.id) // can be both types: REGISTER and RESET_PASSWORD
let optInCode = await LoginEmailOptIn.findOne({
userId: user.id,
})
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace( optInCode = await checkOptInCode(optInCode, user.id, OptInType.EMAIL_OPT_IN_RESET_PASSWORD)
/{optin}/g,
optInCode.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendResetPasswordEmail({ const emailSent = await sendResetPasswordEmailMailer({
link, link: activationLink(optInCode),
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
email, email,
duration: printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME),
}) })
/* uncomment this, when you need the activation link on the console /* uncomment this, when you need the activation link on the console
@ -494,8 +441,10 @@ export class UserResolver {
}) })
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes // Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
if (!isOptInCodeValid(optInCode)) { if (!isOptInValid(optInCode)) {
throw new Error(`email already more than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) throw new Error(
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
)
} }
// load user // load user
@ -538,11 +487,6 @@ export class UserResolver {
throw new Error('error saving user: ' + error) throw new Error('error saving user: ' + error)
}) })
// Delete Code
await queryRunner.manager.remove(optInCode).catch((error) => {
throw new Error('error deleting code: ' + error)
})
await queryRunner.commitTransaction() await queryRunner.commitTransaction()
} catch (e) { } catch (e) {
await queryRunner.rollbackTransaction() await queryRunner.rollbackTransaction()
@ -553,7 +497,7 @@ export class UserResolver {
// Sign into Klicktipp // Sign into Klicktipp
// TODO do we always signUp the user? How to handle things with old users? // TODO do we always signUp the user? How to handle things with old users?
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) { if (optInCode.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) {
try { try {
await klicktippSignIn(user.email, user.language, user.firstName, user.lastName) await klicktippSignIn(user.email, user.language, user.firstName, user.lastName)
} catch { } catch {
@ -573,8 +517,10 @@ export class UserResolver {
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> { async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: optIn }) const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: optIn })
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes // Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
if (!isOptInCodeValid(optInCode)) { if (!isOptInValid(optInCode)) {
throw new Error(`email was sent more than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) throw new Error(
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
)
} }
return true return true
} }
@ -592,9 +538,9 @@ export class UserResolver {
passwordNew, passwordNew,
coinanimation, coinanimation,
}: UpdateUserInfosArgs, }: UpdateUserInfosArgs,
@Ctx() context: any, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
const userEntity = context.user const userEntity = getUser(context)
if (firstName) { if (firstName) {
userEntity.firstName = firstName userEntity.firstName = firstName
@ -671,7 +617,7 @@ export class UserResolver {
@Authorized([RIGHTS.HAS_ELOPAGE]) @Authorized([RIGHTS.HAS_ELOPAGE])
@Query(() => Boolean) @Query(() => Boolean)
async hasElopage(@Ctx() context: any): Promise<boolean> { async hasElopage(@Ctx() context: Context): Promise<boolean> {
const userEntity = context.user const userEntity = context.user
if (!userEntity) { if (!userEntity) {
return false return false
@ -680,7 +626,34 @@ export class UserResolver {
return hasElopageBuys(userEntity.email) return hasElopageBuys(userEntity.email)
} }
} }
function isOptInCodeValid(optInCode: LoginEmailOptIn) {
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime() const isTimeExpired = (optIn: LoginEmailOptIn, duration: number): boolean => {
return timeElapsed <= CONFIG.EMAIL_CODE_VALID_TIME * 60 * 1000 const timeElapsed = Date.now() - new Date(optIn.updatedAt).getTime()
// time is given in minutes
return timeElapsed <= duration * 60 * 1000
}
const isOptInValid = (optIn: LoginEmailOptIn): boolean => {
return isTimeExpired(optIn, CONFIG.EMAIL_CODE_VALID_TIME)
}
const canResendOptIn = (optIn: LoginEmailOptIn): boolean => {
return !isTimeExpired(optIn, CONFIG.EMAIL_CODE_REQUEST_TIME)
}
const getTimeDurationObject = (time: number): { hours?: number; minutes: number } => {
if (time > 60) {
return {
hours: Math.floor(time / 60),
minutes: time % 60,
}
}
return { minutes: time }
}
export const printTimeDuration = (duration: number): string => {
const time = getTimeDurationObject(duration)
const result = time.minutes > 0 ? `${time.minutes} minutes` : ''
if (time.hours) return `${time.hours} hours` + (result !== '' ? ` and ${result}` : '')
return result
} }

View File

@ -15,6 +15,7 @@ describe('sendAccountActivationEmail', () => {
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
email: 'peter@lustig.de', email: 'peter@lustig.de',
duration: '23 hours and 30 minutes',
}) })
}) })
@ -23,7 +24,9 @@ describe('sendAccountActivationEmail', () => {
to: `Peter Lustig <peter@lustig.de>`, to: `Peter Lustig <peter@lustig.de>`,
subject: 'Gradido: E-Mail Überprüfung', subject: 'Gradido: E-Mail Überprüfung',
text: text:
expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('activationLink'), expect.stringContaining('Hallo Peter Lustig') &&
expect.stringContaining('activationLink') &&
expect.stringContaining('23 Stunden und 30 Minuten'),
}) })
}) })
}) })

View File

@ -1,15 +1,17 @@
import { sendEMail } from './sendEMail' import { sendEMail } from './sendEMail'
import { accountActivation } from './text/accountActivation' import { accountActivation } from './text/accountActivation'
import CONFIG from '@/config'
export const sendAccountActivationEmail = (data: { export const sendAccountActivationEmail = (data: {
link: string link: string
firstName: string firstName: string
lastName: string lastName: string
email: string email: string
duration: string
}): Promise<boolean> => { }): Promise<boolean> => {
return sendEMail({ return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`, to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: accountActivation.de.subject, subject: accountActivation.de.subject,
text: accountActivation.de.text(data), text: accountActivation.de.text({ ...data, resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD }),
}) })
} }

View File

@ -15,6 +15,7 @@ describe('sendResetPasswordEmail', () => {
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
email: 'peter@lustig.de', email: 'peter@lustig.de',
duration: '23 hours and 30 minutes',
}) })
}) })
@ -22,7 +23,10 @@ describe('sendResetPasswordEmail', () => {
expect(sendEMail).toBeCalledWith({ expect(sendEMail).toBeCalledWith({
to: `Peter Lustig <peter@lustig.de>`, to: `Peter Lustig <peter@lustig.de>`,
subject: 'Gradido: Passwort zurücksetzen', subject: 'Gradido: Passwort zurücksetzen',
text: expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('resetLink'), text:
expect.stringContaining('Hallo Peter Lustig') &&
expect.stringContaining('resetLink') &&
expect.stringContaining('23 Stunden und 30 Minuten'),
}) })
}) })
}) })

View File

@ -1,15 +1,17 @@
import { sendEMail } from './sendEMail' import { sendEMail } from './sendEMail'
import { resetPassword } from './text/resetPassword' import { resetPassword } from './text/resetPassword'
import CONFIG from '@/config'
export const sendResetPasswordEmail = (data: { export const sendResetPasswordEmail = (data: {
link: string link: string
firstName: string firstName: string
lastName: string lastName: string
email: string email: string
duration: string
}): Promise<boolean> => { }): Promise<boolean> => {
return sendEMail({ return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`, to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: resetPassword.de.subject, subject: resetPassword.de.subject,
text: resetPassword.de.text(data), text: resetPassword.de.text({ ...data, resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD }),
}) })
} }

View File

@ -20,6 +20,7 @@ describe('sendTransactionReceivedEmail', () => {
senderEmail: 'bibi@bloxberg.de', senderEmail: 'bibi@bloxberg.de',
amount: new Decimal(42.0), amount: new Decimal(42.0),
memo: 'Vielen herzlichen Dank für den neuen Hexenbesen!', memo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
overviewURL: 'http://localhost/overview',
}) })
}) })
@ -32,7 +33,8 @@ describe('sendTransactionReceivedEmail', () => {
expect.stringContaining('42,00 GDD') && expect.stringContaining('42,00 GDD') &&
expect.stringContaining('Bibi Bloxberg') && expect.stringContaining('Bibi Bloxberg') &&
expect.stringContaining('(bibi@bloxberg.de)') && expect.stringContaining('(bibi@bloxberg.de)') &&
expect.stringContaining('Vielen herzlichen Dank für den neuen Hexenbesen!'), expect.stringContaining('Vielen herzlichen Dank für den neuen Hexenbesen!') &&
expect.stringContaining('http://localhost/overview'),
}) })
}) })
}) })

View File

@ -11,6 +11,7 @@ export const sendTransactionReceivedEmail = (data: {
senderEmail: string senderEmail: string
amount: Decimal amount: Decimal
memo: string memo: string
overviewURL: string
}): Promise<boolean> => { }): Promise<boolean> => {
return sendEMail({ return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`, to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,

View File

@ -1,7 +1,14 @@
export const accountActivation = { export const accountActivation = {
de: { de: {
subject: 'Gradido: E-Mail Überprüfung', subject: 'Gradido: E-Mail Überprüfung',
text: (data: { link: string; firstName: string; lastName: string; email: string }): string => text: (data: {
link: string
firstName: string
lastName: string
email: string
duration: string
resendLink: string
}): string =>
`Hallo ${data.firstName} ${data.lastName}, `Hallo ${data.firstName} ${data.lastName},
Deine E-Mail-Adresse wurde soeben bei Gradido registriert. Deine E-Mail-Adresse wurde soeben bei Gradido registriert.
@ -10,6 +17,15 @@ Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradi
${data.link} ${data.link}
oder kopiere den obigen Link in dein Browserfenster. oder kopiere den obigen Link in dein Browserfenster.
Der Link hat eine Gültigkeit von ${data.duration
.replace('hours', 'Stunden')
.replace('minutes', 'Minuten')
.replace(
' and ',
' und ',
)}. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:
${data.resendLink}
Mit freundlichen Grüßen, Mit freundlichen Grüßen,
dein Gradido-Team`, dein Gradido-Team`,
}, },

View File

@ -1,13 +1,29 @@
export const resetPassword = { export const resetPassword = {
de: { de: {
subject: 'Gradido: Passwort zurücksetzen', subject: 'Gradido: Passwort zurücksetzen',
text: (data: { link: string; firstName: string; lastName: string; email: string }): string => text: (data: {
link: string
firstName: string
lastName: string
email: string
duration: string
resendLink: string
}): string =>
`Hallo ${data.firstName} ${data.lastName}, `Hallo ${data.firstName} ${data.lastName},
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert. Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
Wenn du es warst, klicke bitte auf den Link: ${data.link} Wenn du es warst, klicke bitte auf den Link: ${data.link}
oder kopiere den obigen Link in Dein Browserfenster. oder kopiere den obigen Link in Dein Browserfenster.
Der Link hat eine Gültigkeit von ${data.duration
.replace('hours', 'Stunden')
.replace('minutes', 'Minuten')
.replace(
' and ',
' und ',
)}. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:
${data.resendLink}
Mit freundlichen Grüßen, Mit freundlichen Grüßen,
dein Gradido-Team`, dein Gradido-Team`,
}, },

View File

@ -12,6 +12,7 @@ export const transactionReceived = {
senderEmail: string senderEmail: string
amount: Decimal amount: Decimal
memo: string memo: string
overviewURL: string
}): string => }): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName} `Hallo ${data.recipientFirstName} ${data.recipientLastName}
@ -25,6 +26,9 @@ ${data.memo}
Bitte antworte nicht auf diese E-Mail! Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen, Mit freundlichen Grüßen,
dein Gradido-Team`, dein Gradido-Team
Link zu deinem Konto: ${data.overviewURL}`,
}, },
} }

View File

@ -4,4 +4,6 @@ export interface CreationInterface {
memo: string memo: string
creationDate: string creationDate: string
confirmed?: boolean confirmed?: boolean
// number of months to move the confirmed creation to the past
moveCreationDate?: number
} }

View File

@ -1,29 +1,28 @@
import { CreationInterface } from './CreationInterface' import { CreationInterface } from './CreationInterface'
import { nMonthsBefore } from '../factory/creation'
const lastMonth = (date: Date): string => {
return new Date(date.getFullYear(), date.getMonth() - 1, 1).toISOString()
}
export const creations: CreationInterface[] = [ export const creations: CreationInterface[] = [
{ {
email: 'bibi@bloxberg.de', email: 'bibi@bloxberg.de',
amount: 1000, amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!', memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()), creationDate: nMonthsBefore(new Date()),
confirmed: true, confirmed: true,
moveCreationDate: 12,
}, },
{ {
email: 'bob@baumeister.de', email: 'bob@baumeister.de',
amount: 1000, amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!', memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()), creationDate: nMonthsBefore(new Date()),
confirmed: true, confirmed: true,
moveCreationDate: 8,
}, },
{ {
email: 'raeuber@hotzenplotz.de', email: 'raeuber@hotzenplotz.de',
amount: 1000, amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!', memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()), creationDate: nMonthsBefore(new Date()),
confirmed: true, confirmed: true,
}, },
] ]

View File

@ -6,9 +6,14 @@ import { login } from '@/seeds/graphql/queries'
import { CreationInterface } from '@/seeds/creation/CreationInterface' import { CreationInterface } from '@/seeds/creation/CreationInterface'
import { ApolloServerTestClient } from 'apollo-server-testing' import { ApolloServerTestClient } from 'apollo-server-testing'
import { User } from '@entity/User' import { User } from '@entity/User'
import { Transaction } from '@entity/Transaction'
import { AdminPendingCreation } from '@entity/AdminPendingCreation' import { AdminPendingCreation } from '@entity/AdminPendingCreation'
// import CONFIG from '@/config/index' // import CONFIG from '@/config/index'
export const nMonthsBefore = (date: Date, months = 1): string => {
return new Date(date.getFullYear(), date.getMonth() - months, 1).toISOString()
}
export const creationFactory = async ( export const creationFactory = async (
client: ApolloServerTestClient, client: ApolloServerTestClient,
creation: CreationInterface, creation: CreationInterface,
@ -34,5 +39,21 @@ export const creationFactory = async (
}) })
await mutate({ mutation: confirmPendingCreation, variables: { id: pendingCreation.id } }) await mutate({ mutation: confirmPendingCreation, variables: { id: pendingCreation.id } })
if (creation.moveCreationDate) {
const transaction = await Transaction.findOneOrFail({
where: { userId: user.id, creationDate: new Date(creation.creationDate) },
order: { balanceDate: 'DESC' },
})
if (transaction.decay.equals(0) && transaction.creationDate) {
transaction.creationDate = new Date(
nMonthsBefore(transaction.creationDate, creation.moveCreationDate),
)
transaction.balanceDate = new Date(
nMonthsBefore(transaction.balanceDate, creation.moveCreationDate),
)
await transaction.save()
}
}
} }
} }

View File

@ -59,7 +59,6 @@ export const transactionsQuery = gql`
balanceGDT balanceGDT
count count
balance balance
decayStartBlock
transactions { transactions {
id id
typeId typeId

View File

@ -1,9 +1,24 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Role } from '@/auth/Role'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { User as dbUser } from '@entity/User'
import { Transaction as dbTransaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light'
import { ExpressContext } from 'apollo-server-express'
const context = (args: any) => { export interface Context {
token: string | null
setHeaders: { key: string; value: string }[]
role?: Role
user?: dbUser
// hack to use less DB calls for Balance Resolver
lastTransaction?: dbTransaction
transactionCount?: number
linkCount?: number
sumHoldAvailableAmount?: Decimal
}
const context = (args: ExpressContext): Context => {
const authorization = args.req.headers.authorization const authorization = args.req.headers.authorization
let token = null let token: string | null = null
if (authorization) { if (authorization) {
token = authorization.replace(/^Bearer /, '') token = authorization.replace(/^Bearer /, '')
} }
@ -14,4 +29,9 @@ const context = (args: any) => {
return context return context
} }
export const getUser = (context: Context): dbUser => {
if (context.user) return context.user
throw new Error('No user given in context!')
}
export default context export default context

View File

@ -29,6 +29,7 @@ function calculateDecay(
const decay: Decay = { const decay: Decay = {
balance: amount, balance: amount,
decay: new Decimal(0), decay: new Decimal(0),
roundedDecay: new Decimal(0),
start: null, start: null,
end: null, end: null,
duration: null, duration: null,
@ -52,6 +53,10 @@ function calculateDecay(
decay.end = to decay.end = to
decay.balance = decayFormula(amount, decay.duration) decay.balance = decayFormula(amount, decay.duration)
decay.decay = decay.balance.minus(amount) decay.decay = decay.balance.minus(amount)
decay.roundedDecay = amount
.toDecimalPlaces(2, Decimal.ROUND_DOWN)
.minus(decay.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN).toString())
.mul(-1)
return decay return decay
} }

View File

@ -4,6 +4,7 @@ import { Transaction } from '@entity/Transaction'
import { Decay } from '@model/Decay' import { Decay } from '@model/Decay'
import { getCustomRepository } from '@dbTools/typeorm' import { getCustomRepository } from '@dbTools/typeorm'
import { TransactionLinkRepository } from '@repository/TransactionLink' import { TransactionLinkRepository } from '@repository/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
function isStringBoolean(value: string): boolean { function isStringBoolean(value: string): boolean {
const lowerValue = value.toLowerCase() const lowerValue = value.toLowerCase()
@ -21,6 +22,7 @@ async function calculateBalance(
userId: number, userId: number,
amount: Decimal, amount: Decimal,
time: Date, time: Date,
transactionLink?: dbTransactionLink | null,
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> { ): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } }) const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } })
if (!lastTransaction) return null if (!lastTransaction) return null
@ -32,7 +34,13 @@ async function calculateBalance(
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time) const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time)
if (balance.minus(sumHoldAvailableAmount.toString()).lessThan(0)) { // If we want to redeem a link we need to make sure that the link amount is not considered as blocked
// else we cannot redeem links which are more or equal to half of what an account actually owns
const releasedLinkAmount = transactionLink ? transactionLink.holdAvailableAmount : new Decimal(0)
if (
balance.minus(sumHoldAvailableAmount.toString()).plus(releasedLinkAmount.toString()).lessThan(0)
) {
return null return null
} }
return { balance, lastTransactionId: lastTransaction.id, decay } return { balance, lastTransactionId: lastTransaction.id, decay }

View File

@ -42,11 +42,11 @@ const virtualLinkTransaction = (
userId: -1, userId: -1,
previous: -1, previous: -1,
typeId: TransactionTypeId.LINK_SUMMARY, typeId: TransactionTypeId.LINK_SUMMARY,
amount: amount, amount: amount.toDecimalPlaces(2, Decimal.ROUND_FLOOR),
balance: balance, balance: balance.toDecimalPlaces(2, Decimal.ROUND_DOWN),
balanceDate: validUntil, balanceDate: validUntil,
decayStart: createdAt, decayStart: createdAt,
decay: decay, decay: decay.toDecimalPlaces(2, Decimal.ROUND_FLOOR),
memo: '', memo: '',
creationDate: null, creationDate: null,
...defaultModelFunctions, ...defaultModelFunctions,
@ -59,6 +59,7 @@ const virtualDecayTransaction = (
balanceDate: Date, balanceDate: Date,
time: Date = new Date(), time: Date = new Date(),
user: User, user: User,
holdAvailabeAmount: Decimal,
): Transaction => { ): Transaction => {
const decay = calculateDecay(balance, balanceDate, time) const decay = calculateDecay(balance, balanceDate, time)
// const balance = decay.balance.minus(lastTransaction.balance) // const balance = decay.balance.minus(lastTransaction.balance)
@ -67,10 +68,12 @@ const virtualDecayTransaction = (
userId: -1, userId: -1,
previous: -1, previous: -1,
typeId: TransactionTypeId.DECAY, typeId: TransactionTypeId.DECAY,
amount: decay.decay ? decay.decay : new Decimal(0), // new Decimal(0), // this kinda is wrong, but helps with the frontend query amount: decay.decay ? decay.roundedDecay : new Decimal(0),
balance: decay.balance, balance: decay.balance
.minus(holdAvailabeAmount.toString())
.toDecimalPlaces(2, Decimal.ROUND_DOWN),
balanceDate: time, balanceDate: time,
decay: decay.decay ? decay.decay : new Decimal(0), decay: decay.decay ? decay.roundedDecay : new Decimal(0),
decayStart: decay.start, decayStart: decay.start,
memo: '', memo: '',
creationDate: null, creationDate: null,

View File

@ -1704,9 +1704,9 @@ camelcase@^6.2.0:
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
caniuse-lite@^1.0.30001264: caniuse-lite@^1.0.30001264:
version "1.0.30001265" version "1.0.30001325"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz"
integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
chalk@^2.0.0: chalk@^2.0.0:
version "2.4.2" version "2.4.2"
@ -1900,7 +1900,14 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==

View File

@ -25,23 +25,9 @@ yarn down
yarn dev_down yarn dev_down
``` ```
## Reset database
## Reset DB
``` ```
yarn dev_reset yarn dev_reset
``` ```
Runs all down migrations and after this all up migrations.
## Seed DB
```
yarn seed
```
## Seeded Users
| email | password | admin |
|------------------------|------------|---------|
| peter@lustig.de | `Aa12345_` | `true` |
| bibi@bloxberg.de | `Aa12345_` | `false` |
| raeuber@hotzenplotz.de | `Aa12345_` | `false` |
| bob@baumeister.de | `Aa12345_` | `false` |

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-database", "name": "gradido-database",
"version": "1.6.6", "version": "1.7.1",
"description": "Gradido Database Tool to execute database migrations", "description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database", "repository": "https://github.com/gradido/gradido/database",
@ -10,15 +10,15 @@
"scripts": { "scripts": {
"build": "mkdir -p build/src/config/ && cp src/config/*.txt build/src/config/ && tsc --build", "build": "mkdir -p build/src/config/ && cp src/config/*.txt build/src/config/ && tsc --build",
"clean": "tsc --build --clean", "clean": "tsc --build --clean",
"up": "TZ=UTC node build/src/index.js up", "up": "cross-env TZ=UTC node build/src/index.js up",
"down": "TZ=UTC node build/src/index.js down", "down": "cross-env TZ=UTC node build/src/index.js down",
"reset": "TZ=UTC node build/src/index.js reset", "reset": "cross-env TZ=UTC node build/src/index.js reset",
"dev_up": "TZ=UTC ts-node src/index.ts up", "dev_up": "cross-env TZ=UTC ts-node src/index.ts up",
"dev_down": "TZ=UTC ts-node src/index.ts down", "dev_down": "cross-env TZ=UTC ts-node src/index.ts down",
"dev_reset": "TZ=UTC ts-node src/index.ts reset", "dev_reset": "cross-env TZ=UTC ts-node src/index.ts reset",
"lint": "eslint --max-warnings=0 --ext .js,.ts .", "lint": "eslint --max-warnings=0 --ext .js,.ts .",
"seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config", "seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config",
"seed": "TZ=UTC ts-node src/index.ts seed" "seed": "cross-env TZ=UTC ts-node src/index.ts seed"
}, },
"devDependencies": { "devDependencies": {
"@types/faker": "^5.5.9", "@types/faker": "^5.5.9",
@ -37,14 +37,13 @@
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },
"dependencies": { "dependencies": {
"cross-env": "^7.0.3",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"decimal.js-light": "^2.5.1", "decimal.js-light": "^2.5.1",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"faker": "^5.5.3",
"mysql2": "^2.3.0", "mysql2": "^2.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"ts-mysql-migrate": "^1.0.2", "ts-mysql-migrate": "^1.0.2",
"typeorm": "^0.2.38", "typeorm": "^0.2.38"
"typeorm-seeding": "^1.6.1"
} }
} }

View File

@ -1,20 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { ServerUser } from '../../entity/ServerUser'
import { ServerUserContext } from '../interface/UserContext'
define(ServerUser, (faker: typeof Faker, context?: ServerUserContext) => {
if (!context) context = {}
const user = new ServerUser()
user.username = context.username ? context.username : faker.internet.userName()
user.password = context.password ? context.password : faker.internet.password()
user.email = context.email ? context.email : faker.internet.email()
user.role = context.role ? context.role : 'admin'
user.activated = context.activated ? context.activated : 0
user.lastLogin = context.lastLogin ? context.lastLogin : faker.date.recent()
user.created = context.created ? context.created : faker.date.recent()
user.modified = context.modified ? context.modified : faker.date.recent()
return user
})

View File

@ -1,24 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { Transaction } from '../../entity/Transaction'
import { TransactionContext } from '../interface/TransactionContext'
import Decimal from 'decimal.js-light'
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
if (!context) {
throw new Error('TransactionContext not well defined.')
}
const transaction = new Transaction()
transaction.typeId = context.typeId // || 2
transaction.userId = context.userId
transaction.amount = context.amount
transaction.balance = context.balance
transaction.decay = new Decimal(0) // context.decay
transaction.memo = context.memo
transaction.creationDate = context.creationDate || new Date()
// transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null
transaction.linkedUserId = context.sendReceiverUserId || null
return transaction
})

View File

@ -1,27 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { User } from '../../entity/User'
import { randomBytes } from 'crypto'
import { UserContext } from '../interface/UserContext'
define(User, (faker: typeof Faker, context?: UserContext) => {
if (!context) context = {}
const user = new User()
user.pubKey = context.pubKey ? context.pubKey : randomBytes(32)
user.email = context.email ? context.email : faker.internet.email()
user.firstName = context.firstName ? context.firstName : faker.name.firstName()
user.lastName = context.lastName ? context.lastName : faker.name.lastName()
user.deletedAt = context.deletedAt ? context.deletedAt : null
// TODO Create real password and keys/hash
user.password = context.password ? context.password : BigInt(0)
user.privKey = context.privKey ? context.privKey : randomBytes(80)
user.emailHash = context.emailHash ? context.emailHash : randomBytes(32)
user.createdAt = context.createdAt ? context.createdAt : faker.date.recent()
user.emailChecked = context.emailChecked === undefined ? false : context.emailChecked
user.language = context.language ? context.language : 'en'
user.publisherId = context.publisherId ? context.publisherId : 0
user.passphrase = context.passphrase ? context.passphrase : faker.random.words(24)
return user
})

View File

@ -1,6 +1,5 @@
import CONFIG from './config' import CONFIG from './config'
import { createPool, PoolConfig } from 'mysql' import { createPool, PoolConfig } from 'mysql'
import { useSeeding, runSeeder } from 'typeorm-seeding'
import { Migration } from 'ts-mysql-migrate' import { Migration } from 'ts-mysql-migrate'
import path from 'path' import path from 'path'
@ -32,17 +31,4 @@ const resetDB = async (closePool = false): Promise<void> => {
if (closePool) pool.end() if (closePool) pool.end()
} }
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export { resetDB, pool, migration, initialize }
const runSeeds = async (seeds: any[]): Promise<void> => {
if (seeds.length > 0) {
await useSeeding({
root: process.cwd(),
configName: 'ormconfig.js',
})
for (let i = 0; i < seeds.length; i++) {
await runSeeder(seeds[i])
}
}
}
export { resetDB, pool, migration, initialize, runSeeds }

View File

@ -1,14 +1,7 @@
import 'reflect-metadata' import 'reflect-metadata'
import prepare from './prepare' import prepare from './prepare'
import connection from './typeorm/connection' import connection from './typeorm/connection'
import { CreatePeterLustigSeed } from './seeds/users/peter-lustig.admin.seed' import { resetDB, pool, migration } from './helpers'
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
import { CreateStephenHawkingSeed } from './seeds/users/stephen-hawking.seed'
import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed'
import { CreateUserSeed } from './seeds/create-user.seed'
import { resetDB, pool, migration, runSeeds } from './helpers'
const run = async (command: string) => { const run = async (command: string) => {
// Database actions not supported by our migration library // Database actions not supported by our migration library
@ -34,19 +27,6 @@ const run = async (command: string) => {
// TODO protect from production // TODO protect from production
await resetDB() // use for resetting database await resetDB() // use for resetting database
break break
case 'seed':
// TODO protect from production
// await runSeeder(CreatePeterLustigSeed)
await runSeeds([
CreatePeterLustigSeed,
CreateBibiBloxbergSeed,
CreateRaeuberHotzenplotzSeed,
CreateBobBaumeisterSeed,
CreateStephenHawkingSeed,
CreateGarrickOllivanderSeed,
...Array(96).fill(CreateUserSeed),
])
break
default: default:
throw new Error(`Unsupported command ${command}`) throw new Error(`Unsupported command ${command}`)
} }

View File

@ -1,8 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { userSeeder } from './helpers/user-helpers'
export class CreateUserSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, {})
}
}

View File

@ -1,69 +0,0 @@
import { UserContext, ServerUserContext } from '../../interface/UserContext'
import { TransactionContext } from '../../interface/TransactionContext'
import { UserInterface } from '../../interface/UserInterface'
import { User } from '../../../entity/User'
import { ServerUser } from '../../../entity/ServerUser'
import { Transaction } from '../../../entity/Transaction'
import { Factory } from 'typeorm-seeding'
import Decimal from 'decimal.js-light'
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
const user = await factory(User)(createUserContext(userData)).create()
if (userData.isAdmin) {
await factory(ServerUser)(createServerUserContext(userData)).create()
}
if (userData.addBalance) {
// create some GDD for the user
await factory(Transaction)(
createTransactionContext(userData, user, 1, 'Herzlich Willkommen bei Gradido!'),
).create()
}
}
const createUserContext = (context: UserInterface): UserContext => {
return {
pubKey: context.pubKey,
email: context.email,
firstName: context.firstName,
lastName: context.lastName,
deletedAt: context.deletedAt,
password: context.password,
privKey: context.privKey,
emailHash: context.emailHash,
createdAt: context.createdAt,
emailChecked: context.emailChecked,
language: context.language,
publisherId: context.publisherId,
}
}
const createServerUserContext = (context: UserInterface): ServerUserContext => {
return {
role: context.role,
password: context.serverUserPassword,
email: context.email,
activated: context.activated,
created: context.createdAt,
lastLogin: context.lastLogin,
modified: context.modified,
}
}
const createTransactionContext = (
context: UserInterface,
user: User,
type: number,
memo: string,
): TransactionContext => {
return {
typeId: type,
userId: user.id,
amount: context.amount || new Decimal(1000),
balance: context.amount || new Decimal(1000),
balanceDate: new Date(context.recordDate || Date.now()),
memo,
creationDate: context.creationDate,
}
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { bibiBloxberg } from './bibi-bloxberg'
import { userSeeder } from '../helpers/user-helpers'
export class CreateBibiBloxbergSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, bibiBloxberg)
}
}

View File

@ -1,26 +0,0 @@
import Decimal from 'decimal.js-light'
import { UserInterface } from '../../interface/UserInterface'
export const bibiBloxberg: UserInterface = {
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
// description: 'Hex Hex',
password: BigInt('12825419584724616625'),
pubKey: Buffer.from('42de7e4754625b730018c3b4ea745a4d043d9d867af352d0f08871793dfa6743', 'hex'),
privKey: Buffer.from(
'60681365b6ad6fd500eae09ac8df0de6beb7554226e0ca1049e957cc6f202205b86e258bbbe98561a86bd9b986ea8b2a6c60abdff8a745f73c8932d4b6545a8da09bbcd6e18ec61a2ef30bac85f83c5d',
'hex',
),
emailHash: Buffer.from('38a0d8c8658a5681cc1180c5d9e2b2a18e4f611db8ab3ca61de4aa91ae94219b', 'hex'),
createdAt: new Date('2021-11-26T11:32:16'),
emailChecked: true,
language: 'de',
passphrase:
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
isAdmin: false,
addBalance: true,
recordDate: new Date('2021-11-30T10:37:11'),
creationDate: new Date('2021-08-01 00:00:00'),
amount: new Decimal(1000),
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { bobBaumeister } from './bob-baumeister'
import { userSeeder } from '../helpers/user-helpers'
export class CreateBobBaumeisterSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, bobBaumeister)
}
}

View File

@ -1,26 +0,0 @@
import Decimal from 'decimal.js-light'
import { UserInterface } from '../../interface/UserInterface'
export const bobBaumeister: UserInterface = {
email: 'bob@baumeister.de',
firstName: 'Bob',
lastName: 'der Baumeister',
// description: 'Können wir das schaffen? Ja, wir schaffen das!',
password: BigInt('3296644341468822636'),
pubKey: Buffer.from('a509d9a146374fc975e3677db801ae8a4a83bff9dea96da64053ff6de6b2dd7e', 'hex'),
privKey: Buffer.from(
'd30606ac59c29058896180bebd6dcd1714dbdd697cc14b65eb4de9ef5241a5d5fc789eaab48957a887c45b7e71ab75c47fd132c14b99007891b5bdfb1026575009f0802b0126930803c113ab3f44e1be',
'hex',
),
emailHash: Buffer.from('4b8ce4e175587aaf33da19e272719da1a547daff557820191fab0c65c5a3b7f1', 'hex'),
createdAt: new Date('2021-11-26T11:36:31'),
emailChecked: true,
language: 'de',
passphrase:
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
isAdmin: false,
addBalance: true,
recordDate: new Date('2021-11-30T10:37:14'),
creationDate: new Date('2021-08-01 00:00:00'),
amount: new Decimal(1000),
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { garrickOllivander } from './garrick-ollivander'
import { userSeeder } from '../helpers/user-helpers'
export class CreateGarrickOllivanderSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, garrickOllivander)
}
}

View File

@ -1,18 +0,0 @@
import { UserInterface } from '../../interface/UserInterface'
export const garrickOllivander: UserInterface = {
email: 'garrick@ollivander.com',
firstName: 'Garrick',
lastName: 'Ollivander',
// description: `Curious ... curious ...
// Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`,
password: BigInt('0'),
emailHash: Buffer.from('91e358000e908146342789979d62a7255b2b88a71dad0c6a10e32af44be57886', 'hex'),
createdAt: new Date('2022-01-10T10:23:17'),
emailChecked: false,
language: 'en',
passphrase:
'human glide theory clump wish history other duty door fringe neck industry ostrich equal plate diesel tornado neck people antenna door category moon hen ',
isAdmin: false,
addBalance: false,
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { peterLustig } from './peter-lustig'
import { userSeeder } from '../helpers/user-helpers'
export class CreatePeterLustigSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, peterLustig)
}
}

View File

@ -1,26 +0,0 @@
import { UserInterface } from '../../interface/UserInterface'
export const peterLustig: UserInterface = {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
// description: 'Latzhose und Nickelbrille',
password: BigInt('3917921995996627700'),
pubKey: Buffer.from('7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', 'hex'),
privKey: Buffer.from(
'3c7c0253033212ed983f6bb10ce73362a99f0bd01d4d1b21ca702d532ca32710ba36abf72a22a963b9026e764e954f441f4905b87a66861bd6b9d9689b7f8aefea66cc493e21da4244e85be81660b9c4',
'hex',
),
emailHash: Buffer.from('9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036', 'hex'),
createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true,
language: 'de',
passphrase:
'okay property choice naive calm present weird increase stuff royal vibrant frame attend wood one else tribe pull hedgehog woman kitchen hawk snack smart ',
role: 'admin',
serverUserPassword: '$2y$10$TzIWLeZoKs251gwrhSQmHeKhKI/EQ4EV5ClfAT8Ufnb4lcUXPa5X.',
activated: 1,
lastLogin: new Date('2021-10-27T12:25:57'),
modified: new Date('2021-09-27T12:25:57'),
isAdmin: true,
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { raeuberHotzenplotz } from './raeuber-hotzenplotz'
import { userSeeder } from '../helpers/user-helpers'
export class CreateRaeuberHotzenplotzSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, raeuberHotzenplotz)
}
}

View File

@ -1,26 +0,0 @@
import Decimal from 'decimal.js-light'
import { UserInterface } from '../../interface/UserInterface'
export const raeuberHotzenplotz: UserInterface = {
email: 'raeuber@hotzenplotz.de',
firstName: 'Räuber',
lastName: 'Hotzenplotz',
// description: 'Pfefferpistole',
password: BigInt('12123692783243004812'),
pubKey: Buffer.from('d7c70f94234dff071d982aa8f41583876c356599773b5911b39080da2b8c2d2b', 'hex'),
privKey: Buffer.from(
'c4ede7e7e65acd4cc0a2d91136ee8f753c6903b3594798afde341092b21a4c1589f296d43c6e7adcd7602fcc2a2bcbf74c9f42453ad49cc5186eadf654bbd2c5fa9aa027f152592819246da896ebfcd2',
'hex',
),
emailHash: Buffer.from('ec8d34112adb40ff2f6538b05660b03440372690f034cd7d6322d17020233c77', 'hex'),
createdAt: new Date('2021-11-26T11:32:16'),
emailChecked: true,
language: 'de',
passphrase:
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
isAdmin: false,
addBalance: true,
recordDate: new Date('2021-11-30T10:37:13'),
creationDate: new Date('2021-08-01 00:00:00'),
amount: new Decimal(1000),
}

View File

@ -1,9 +0,0 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { stephenHawking } from './stephen-hawking'
import { userSeeder } from '../helpers/user-helpers'
export class CreateStephenHawkingSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, stephenHawking)
}
}

View File

@ -1,23 +0,0 @@
import { UserInterface } from '../../interface/UserInterface'
export const stephenHawking: UserInterface = {
email: 'stephen@hawking.uk',
firstName: 'Stephen',
lastName: 'Hawking',
// description: 'A Brief History of Time',
password: BigInt('18075098469449931746'),
pubKey: Buffer.from('19576a7aab8cd4ce683ed6735bba937d6bdbd08016568f730a385b6481241213', 'hex'),
privKey: Buffer.from(
'1d8ce9b5df086a713fee9eb562adc127073f3211a6214a54e53eb22c1249d49e1e94580ab00f25fd4b38808c1e31b41624ef627f277d21ef5d5717d4b81958f13dc2b257759caba07c6fdbc72f86ab0f',
'hex',
),
emailHash: Buffer.from('71d4ed7a25d2130d445d6451135eefbbdd96c3105dd297783590ced0bf3116fd', 'hex'),
createdAt: new Date('1942-01-08T09:17:52'),
deletedAt: new Date('2018-03-14T09:17:52'),
emailChecked: true,
language: 'en',
passphrase:
'demise tree praise funny ignore despair vessel shop sorry try day peanut tongue toddler bone december inch chicken clump sheriff weasel rally check suggest ',
isAdmin: false,
addBalance: false,
}

View File

@ -393,11 +393,6 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chalk@^1.1.1: chalk@^1.1.1:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@ -409,7 +404,7 @@ chalk@^1.1.1:
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
supports-color "^2.0.0" supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.4.2: chalk@^2.0.0:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -418,14 +413,6 @@ chalk@^2.0.0, chalk@^2.4.2:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0: chalk@^4.0.0, chalk@^4.1.0:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@ -434,13 +421,6 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
restore-cursor "^3.1.0"
cli-highlight@^2.1.11: cli-highlight@^2.1.11:
version "2.1.11" version "2.1.11"
resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf"
@ -453,20 +433,6 @@ cli-highlight@^2.1.11:
parse5-htmlparser2-tree-adapter "^6.0.0" parse5-htmlparser2-tree-adapter "^6.0.0"
yargs "^16.0.0" yargs "^16.0.0"
cli-spinners@^2.2.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^7.0.2: cliui@^7.0.2:
version "7.0.4" version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
@ -476,11 +442,6 @@ cliui@^7.0.2:
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi "^7.0.0" wrap-ansi "^7.0.0"
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
color-convert@^1.9.0: color-convert@^1.9.0:
version "1.9.3" version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -520,7 +481,14 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-spawn@^7.0.2: cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^7.0.1, cross-spawn@^7.0.2:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -555,11 +523,6 @@ debug@^4.0.1, debug@^4.1.1, debug@^4.3.1:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decimal.js-light@^2.5.1: decimal.js-light@^2.5.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
@ -570,13 +533,6 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
defaults@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
dependencies:
clone "^1.0.2"
define-properties@^1.1.3: define-properties@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -891,16 +847,6 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
faker@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
faker@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -965,14 +911,6 @@ find-up@^2.0.0, find-up@^2.1.0:
dependencies: dependencies:
locate-path "^2.0.0" locate-path "^2.0.0"
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
flat-cache@^3.0.4: flat-cache@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@ -1008,7 +946,7 @@ generate-function@^2.3.1:
dependencies: dependencies:
is-property "^1.0.2" is-property "^1.0.2"
get-caller-file@^2.0.1, get-caller-file@^2.0.5: get-caller-file@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@ -1029,18 +967,6 @@ glob-parent@^5.1.2:
dependencies: dependencies:
is-glob "^4.0.1" is-glob "^4.0.1"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3: glob@^7.1.3:
version "7.1.7" version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
@ -1253,11 +1179,6 @@ is-glob@^4.0.0, is-glob@^4.0.1:
dependencies: dependencies:
is-extglob "^2.1.1" is-extglob "^2.1.1"
is-interactive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
is-negative-zero@^2.0.1: is-negative-zero@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
@ -1385,13 +1306,6 @@ locate-path@^2.0.0:
p-locate "^2.0.0" p-locate "^2.0.0"
path-exists "^3.0.0" path-exists "^3.0.0"
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
lodash.clonedeep@^4.5.0: lodash.clonedeep@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@ -1407,13 +1321,6 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
log-symbols@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
dependencies:
chalk "^2.4.2"
long@^4.0.0: long@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@ -1452,11 +1359,6 @@ micromatch@^4.0.4:
braces "^3.0.1" braces "^3.0.1"
picomatch "^2.2.3" picomatch "^2.2.3"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@^3.0.4: minimatch@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -1489,11 +1391,6 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mute-stream@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mysql2@^2.3.0: mysql2@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.0.tgz#600f5cc27e397dfb77b59eac93666434f88e8079" resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.0.tgz#600f5cc27e397dfb77b59eac93666434f88e8079"
@ -1590,13 +1487,6 @@ once@^1.3.0:
dependencies: dependencies:
wrappy "1" wrappy "1"
onetime@^5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
dependencies:
mimic-fn "^2.1.0"
optionator@^0.9.1: optionator@^0.9.1:
version "0.9.1" version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@ -1609,20 +1499,6 @@ optionator@^0.9.1:
type-check "^0.4.0" type-check "^0.4.0"
word-wrap "^1.2.3" word-wrap "^1.2.3"
ora@4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz#752a1b7b4be4825546a7a3d59256fa523b6b6d05"
integrity sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==
dependencies:
chalk "^3.0.0"
cli-cursor "^3.1.0"
cli-spinners "^2.2.0"
is-interactive "^1.0.0"
log-symbols "^3.0.0"
mute-stream "0.0.8"
strip-ansi "^6.0.0"
wcwidth "^1.0.1"
p-limit@^1.1.0: p-limit@^1.1.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@ -1630,13 +1506,6 @@ p-limit@^1.1.0:
dependencies: dependencies:
p-try "^1.0.0" p-try "^1.0.0"
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^2.0.0: p-locate@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
@ -1644,23 +1513,11 @@ p-locate@^2.0.0:
dependencies: dependencies:
p-limit "^1.1.0" p-limit "^1.1.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^1.0.0: p-try@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parent-module@^1.0.0: parent-module@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@ -1703,11 +1560,6 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0: path-is-absolute@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@ -1831,7 +1683,7 @@ readable-stream@2.3.7:
string_decoder "~1.1.1" string_decoder "~1.1.1"
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
reflect-metadata@0.1.13, reflect-metadata@^0.1.13: reflect-metadata@^0.1.13:
version "0.1.13" version "0.1.13"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
@ -1851,11 +1703,6 @@ require-from-string@^2.0.2:
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
resolve-from@^4.0.0: resolve-from@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -1869,14 +1716,6 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0:
is-core-module "^2.2.0" is-core-module "^2.2.0"
path-parse "^1.0.6" path-parse "^1.0.6"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"
reusify@^1.0.4: reusify@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@ -1938,11 +1777,6 @@ seq-queue@^0.0.5:
resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
sha.js@^2.4.11: sha.js@^2.4.11:
version "2.4.11" version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@ -1972,11 +1806,6 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2" get-intrinsic "^1.0.2"
object-inspect "^1.9.0" object-inspect "^1.9.0"
signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
slash@^3.0.0: slash@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@ -2209,18 +2038,6 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typeorm-seeding@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/typeorm-seeding/-/typeorm-seeding-1.6.1.tgz#4fe3a1aec9a611007d1135419cde286cced8defd"
integrity sha512-xJIW1pp72hv6npPqbQ7xDvawcDmS60EDUjK++UCfiqT0WE4xTzCn+QK1ZijLkD3GYCqFPuFt4nmeyRJn6VO2Vw==
dependencies:
chalk "^4.0.0"
faker "4.1.0"
glob "7.1.6"
ora "4.0.3"
reflect-metadata "0.1.13"
yargs "15.3.1"
typeorm@^0.2.38: typeorm@^0.2.38:
version "0.2.38" version "0.2.38"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.38.tgz#2af08079919f6ab04cd17017f9faa2c8d5cd566f" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.38.tgz#2af08079919f6ab04cd17017f9faa2c8d5cd566f"
@ -2284,13 +2101,6 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0" spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0" spdx-expression-parse "^3.0.0"
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
dependencies:
defaults "^1.0.3"
which-boxed-primitive@^1.0.2: which-boxed-primitive@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
@ -2302,11 +2112,6 @@ which-boxed-primitive@^1.0.2:
is-string "^1.0.5" is-string "^1.0.5"
is-symbol "^1.0.3" is-symbol "^1.0.3"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which@^2.0.1: which@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@ -2319,15 +2124,6 @@ word-wrap@^1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0: wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@ -2355,11 +2151,6 @@ xmlbuilder@~11.0.0:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
y18n@^5.0.5: y18n@^5.0.5:
version "5.0.8" version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
@ -2384,36 +2175,11 @@ yargonaut@^1.1.4:
figlet "^1.1.1" figlet "^1.1.1"
parent-require "^1.0.0" parent-require "^1.0.0"
yargs-parser@^18.1.1:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^20.2.2: yargs-parser@^20.2.2:
version "20.2.9" version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs@15.3.1:
version "15.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.1"
yargs@^16.0.0: yargs@^16.0.0:
version "16.2.0" version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"

View File

@ -17,41 +17,51 @@ NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-
WEBHOOK_GITHUB_SECRET=secret WEBHOOK_GITHUB_SECRET=secret
WEBHOOK_GITHUB_BRANCH=master WEBHOOK_GITHUB_BRANCH=master
# backend # community
BACKEND_CONFIG_VERSION=v1.2022-03-18
EMAIL=true
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_URL=smtp.lustig.de
EMAIL_LINK_VERIFICATION=https://stage1.gradido.net/checkEmail/{code}
EMAIL_LINK_SETPASSWORD=https://stage1.gradido.net/reset/{code}
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
WEBHOOK_ELOPAGE_SECRET=secret
GDT_API_URL=https://gdt.gradido.net
COMMUNITY_NAME="Gradido Development Stage1" COMMUNITY_NAME="Gradido Development Stage1"
COMMUNITY_URL=https://stage1.gradido.net/ COMMUNITY_URL=https://stage1.gradido.net/
COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community" COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
# backend
BACKEND_CONFIG_VERSION=v6.2022-04-21
JWT_EXPIRES_IN=30m
GDT_API_URL=https://gdt.gradido.net
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
KLICKTIPP=false KLICKTIPP=false
KLICKTIPP_USER= KLICKTIPP_USER=
KLICKTIPP_PASSWORD= KLICKTIPP_PASSWORD=
KLICKTIPP_APIKEY_DE= KLICKTIPP_APIKEY_DE=
KLICKTIPP_APIKEY_EN= KLICKTIPP_APIKEY_EN=
EMAIL=true
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_URL=smtp.lustig.de
EMAIL_LINK_VERIFICATION=https://stage1.gradido.net/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD=https://stage1.gradido.net/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD=https://stage1.gradido.net/forgot-password
EMAIL_LINK_OVERVIEW=https://stage1.gradido.net/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
WEBHOOK_ELOPAGE_SECRET=secret
# database # database
DATABASE_CONFIG_VERSION=v1.2022-03-18 DATABASE_CONFIG_VERSION=v1.2022-03-18
# frontend # frontend
FRONTEND_CONFIG_VERSION=v2.2022-04-07
GRAPHQL_URI=https://stage1.gradido.net/graphql GRAPHQL_URI=https://stage1.gradido.net/graphql
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token} ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
DEFAULT_PUBLISHER_ID=2896
META_URL=http://localhost META_URL=http://localhost
META_TITLE_DE="Gradido Dein Dankbarkeitskonto" META_TITLE_DE="Gradido Dein Dankbarkeitskonto"
META_TITLE_EN="Gradido - Your gratitude account" META_TITLE_EN="Gradido - Your gratitude account"
@ -62,5 +72,7 @@ META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Econo
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie" META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
# admin # admin
ADMIN_CONFIG_VERSION=v1.2022-03-18
WALLET_AUTH_URL=https://stage1.gradido.net/authenticate?token={token} WALLET_AUTH_URL=https://stage1.gradido.net/authenticate?token={token}
WALLET_URL=https://stage1.gradido.net/login WALLET_URL=https://stage1.gradido.net/login

View File

@ -81,6 +81,11 @@ sudo certbot
> No names were found in your configuration files. Please enter in your domain > stage1.gradido.net > No names were found in your configuration files. Please enter in your domain > stage1.gradido.net
# Note: this will throw an error regarding not beeing able to identify the nginx corresponding # Note: this will throw an error regarding not beeing able to identify the nginx corresponding
# config but produce the required certificate - thats perfectly fine this way # config but produce the required certificate - thats perfectly fine this way
# Troubleshoot: to manually renew a certificate with running nginx use the following command:
# (this might be required once to properly have things setup for the cron to autorenew)
# sudo certbot --nginx -d example.com -d www.example.com
# Troubleshoot: to check ut if things working you can use
# sudo certbot renew --dry-run
# Install logrotate # Install logrotate
sudo apt-get install -y logrotate sudo apt-get install -y logrotate

View File

@ -15,13 +15,13 @@ if [ ! -f "$BACKUP_FILE" ]; then
return "File '$BACKUP_FILE' does not exist" 2>/dev/null || exit 1 return "File '$BACKUP_FILE' does not exist" 2>/dev/null || exit 1
fi fi
# Load backend .env for DB_USERNAME, DB_PASSWORD & DB_DATABASE # Load database .env for DB_USERNAME, DB_PASSWORD & DB_DATABASE
# NOTE: all config values will be in process.env when starting # NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env # the services and will therefore take precedence over the .env
if [ -f "$PROJECT_ROOT/backend/.env" ]; then if [ -f "$PROJECT_ROOT/database/.env" ]; then
export $(cat $PROJECT_ROOT/backend/.env | sed 's/#.*//g' | xargs) export $(cat $PROJECT_ROOT/database/.env | sed 's/#.*//g' | xargs)
else else
export $(cat $PROJECT_ROOT/backend/.env.dist | sed 's/#.*//g' | xargs) export $(cat $PROJECT_ROOT/database/.env.dist | sed 's/#.*//g' | xargs)
fi fi
# Stop gradido-backend service # Stop gradido-backend service
@ -30,6 +30,11 @@ pm2 stop gradido-backend
# Backup data # Backup data
mysqldump --databases --single-transaction --quick --hex-blob --lock-tables=false > ${SCRIPT_DIR}/backup/mariadb-restore-backup-$(date +%d-%m-%Y_%H-%M-%S).sql -u ${DB_USER} -p${DB_PASSWORD} ${DB_DATABASE} mysqldump --databases --single-transaction --quick --hex-blob --lock-tables=false > ${SCRIPT_DIR}/backup/mariadb-restore-backup-$(date +%d-%m-%Y_%H-%M-%S).sql -u ${DB_USER} -p${DB_PASSWORD} ${DB_DATABASE}
# Drop Database
mysql -u ${DB_USER} -p${DB_PASSWORD} <<EOFMYSQL
DROP DATABASE $DB_DATABASE
EOFMYSQL
# Restore Data # Restore Data
mysql -u ${DB_USER} -p${DB_PASSWORD} <<EOFMYSQL mysql -u ${DB_USER} -p${DB_PASSWORD} <<EOFMYSQL
source $BACKUP_FILE source $BACKUP_FILE

View File

@ -78,8 +78,8 @@
> git clone https://github.com/gradido/gradido.git > git clone https://github.com/gradido/gradido.git
# Timezone # Timezone
# Note: This is not needed - UTC(default) is REQUIRED for production data # 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-timezone UTC
# > sudo timedatectl set-ntp on # > sudo timedatectl set-ntp on
# > sudo apt purge ntp # > sudo apt purge ntp
# > sudo systemctl start systemd-timesyncd # > sudo systemctl start systemd-timesyncd

View File

@ -67,7 +67,7 @@ BRANCH=${1:-master}
echo "Starting with git pull - branch:$BRANCH" >> $UPDATE_HTML echo "Starting with git pull - branch:$BRANCH" >> $UPDATE_HTML
cd $PROJECT_ROOT cd $PROJECT_ROOT
# TODO: this overfetches alot, but ensures we can use start.sh with tags # TODO: this overfetches alot, but ensures we can use start.sh with tags
git fetch origin --all git fetch --all
git checkout $BRANCH git checkout $BRANCH
git pull git pull
export BUILD_COMMIT="$(git rev-parse HEAD)" export BUILD_COMMIT="$(git rev-parse HEAD)"
@ -106,7 +106,6 @@ yarn build
if [ "$DEPLOY_SEED_DATA" = "true" ]; then if [ "$DEPLOY_SEED_DATA" = "true" ]; then
yarn dev_up yarn dev_up
yarn dev_reset yarn dev_reset
yarn seed
else else
yarn up yarn up
fi fi
@ -118,6 +117,9 @@ cd $PROJECT_ROOT/backend
unset NODE_ENV unset NODE_ENV
yarn install yarn install
yarn build yarn build
if [ "$DEPLOY_SEED_DATA" = "true" ]; then
yarn seed
fi
# TODO maybe handle this differently? # TODO maybe handle this differently?
export NODE_ENV=production export NODE_ENV=production
pm2 delete gradido-backend pm2 delete gradido-backend
@ -160,4 +162,4 @@ sudo /etc/init.d/nginx restart
cat $UPDATE_HTML >> $GRADIDO_LOG_PATH/update.$TODAY.log cat $UPDATE_HTML >> $GRADIDO_LOG_PATH/update.$TODAY.log
# release lock # release lock
rm $LOCK_FILE rm $LOCK_FILE

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,132 @@
<mxfile host="65bd71144e">
<diagram id="qH1decoyufLOWHgfqT4g" name="Page-1">
<mxGraphModel dx="888" dy="633" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="310" y="230" width="220" height="200" as="geometry"/>
</mxCell>
<mxCell id="3" value="Communities" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="330" y="330" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="4" value="" style="shape=flexArrow;endArrow=classic;startArrow=classic;html=1;" parent="1" edge="1">
<mxGeometry width="100" height="100" relative="1" as="geometry">
<mxPoint x="450" y="390" as="sourcePoint"/>
<mxPoint x="485" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="5" value="Relationships" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="4" vertex="1" connectable="0">
<mxGeometry x="0.2283" y="-3" relative="1" as="geometry">
<mxPoint x="11" y="33" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="6" value="Money&lt;br&gt;Creation" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="430" y="240" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="7" value="&lt;br&gt;Money&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;br&gt;Transfer&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;" style="triangle;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="240" width="60" height="80" as="geometry"/>
</mxCell>
<mxCell id="11" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="485" y="330" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="14" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="431" y="390" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="17" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="530" y="230" width="120" height="200" as="geometry"/>
</mxCell>
<mxCell id="18" value="Abilities &amp;amp;&lt;br&gt;Talents&amp;nbsp;" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="543" y="250" width="100" height="60" as="geometry"/>
</mxCell>
<mxCell id="20" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="6" target="18">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="24" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;dashed=1;dashPattern=1 2;" edge="1" parent="1" source="22" target="18">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="22" value="User Management" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="543" y="342" width="100" height="60" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;dashPattern=1 2;" edge="1" parent="1" target="22">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="500" y="372" as="sourcePoint"/>
<mxPoint x="530" y="310" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" value="" style="shape=or;whiteSpace=wrap;html=1;direction=south;" vertex="1" parent="1">
<mxGeometry x="438" y="430" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="26" value="" style="shape=or;whiteSpace=wrap;html=1;direction=south;" vertex="1" parent="1">
<mxGeometry x="559" y="430" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="27" value="" style="shape=or;whiteSpace=wrap;html=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="559" y="190" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="28" value="" style="shape=or;whiteSpace=wrap;html=1;direction=east;" vertex="1" parent="1">
<mxGeometry x="650" y="340" width="40" height="70" as="geometry"/>
</mxCell>
<mxCell id="29" value="" style="shape=or;whiteSpace=wrap;html=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="439" y="190" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="30" value="" style="shape=or;whiteSpace=wrap;html=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="333" y="190" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="31" value="" style="shape=or;whiteSpace=wrap;html=1;direction=east;" vertex="1" parent="1">
<mxGeometry x="650" y="248" width="40" height="70" as="geometry"/>
</mxCell>
<mxCell id="32" value="" style="shape=or;whiteSpace=wrap;html=1;direction=south;" vertex="1" parent="1">
<mxGeometry x="336" y="430" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="33" value="" style="shape=or;whiteSpace=wrap;html=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="270" y="245" width="40" height="70" as="geometry"/>
</mxCell>
<mxCell id="34" value="" style="shape=or;whiteSpace=wrap;html=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="270" y="335" width="40" height="70" as="geometry"/>
</mxCell>
<mxCell id="36" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="677" y="337" width="83" height="75" as="geometry"/>
</mxCell>
<mxCell id="37" value="Dating App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="740" y="330" width="100" height="100" as="geometry"/>
</mxCell>
<mxCell id="38" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="677" y="244" width="83" height="75" as="geometry"/>
</mxCell>
<mxCell id="39" value="Local Area Search for Abilities &amp;amp; Talents" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="740" y="227" width="100" height="103" as="geometry"/>
</mxCell>
<mxCell id="40" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=south;" vertex="1" parent="1">
<mxGeometry x="437" y="120" width="75" height="83" as="geometry"/>
</mxCell>
<mxCell id="41" value="Micro Creations to gamify Applications" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="424" y="47" width="100" height="103" as="geometry"/>
</mxCell>
<mxCell id="42" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=south;" vertex="1" parent="1">
<mxGeometry x="330" y="120" width="75" height="83" as="geometry"/>
</mxCell>
<mxCell id="43" value="Marketplace" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="317" y="47" width="100" height="103" as="geometry"/>
</mxCell>
<mxCell id="44" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="436" y="457" width="75" height="83" as="geometry"/>
</mxCell>
<mxCell id="45" value="Search Engine" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="424" y="507" width="100" height="103" as="geometry"/>
</mxCell>
<mxCell id="46" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="333" y="456" width="75" height="83" as="geometry"/>
</mxCell>
<mxCell id="47" value="Chat" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="319" y="506" width="100" height="103" as="geometry"/>
</mxCell>
<mxCell id="48" value="" style="shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;direction=north;" vertex="1" parent="1">
<mxGeometry x="556" y="457" width="75" height="83" as="geometry"/>
</mxCell>
<mxCell id="49" value="Newsletter" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="544" y="507" width="100" height="103" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,19 @@
CONFIG_VERSION=v1.2022-03-18 CONFIG_VERSION=v2.2022-04-07
# Environment
DEFAULT_PUBLISHER_ID=2896
# Endpoints
GRAPHQL_URI=http://localhost/graphql
ADMIN_AUTH_URL=http://localhost/admin/authenticate?token={token}
# Community
COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost/
COMMUNITY_REGISTER_URL=http://localhost/register
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
# Meta
META_URL=http://localhost META_URL=http://localhost
META_TITLE_DE="Gradido Dein Dankbarkeitskonto" META_TITLE_DE="Gradido Dein Dankbarkeitskonto"
META_TITLE_EN="Gradido - Your gratitude account" META_TITLE_EN="Gradido - Your gratitude account"
@ -7,7 +21,4 @@ META_DESCRIPTION_DE="Dankbarkeit ist die Währung der neuen Zeit. Immer mehr Men
META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more people are unleashing their potential and shaping a good future for all." META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more people are unleashing their potential and shaping a good future for all."
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem" META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System" META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie" META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
GRAPHQL_URI=http://localhost/graphql
DEFAULT_PUBLISHER_ID=2896
ADMIN_AUTH_URL=http://localhost/admin/authenticate?token={token}

View File

@ -1,5 +1,19 @@
CONFIG_VERSION=$FRONTEND_CONFIG_VERSION CONFIG_VERSION=$FRONTEND_CONFIG_VERSION
# Environment
DEFAULT_PUBLISHER_ID=$DEFAULT_PUBLISHER_ID
# Endpoints
GRAPHQL_URI=$GRAPHQL_URI
ADMIN_AUTH_URL=$ADMIN_AUTH_URL
# Community
COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_URL=$COMMUNITY_URL
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
# Meta
META_URL=$META_URL META_URL=$META_URL
META_TITLE_DE=$META_TITLE_DE META_TITLE_DE=$META_TITLE_DE
META_TITLE_EN=$META_TITLE_EN META_TITLE_EN=$META_TITLE_EN
@ -7,7 +21,4 @@ META_DESCRIPTION_DE=$META_DESCRIPTION_DE
META_DESCRIPTION_EN=$META_DESCRIPTION_EN META_DESCRIPTION_EN=$META_DESCRIPTION_EN
META_KEYWORDS_DE=$META_KEYWORDS_DE META_KEYWORDS_DE=$META_KEYWORDS_DE
META_KEYWORDS_EN=$META_KEYWORDS_EN META_KEYWORDS_EN=$META_KEYWORDS_EN
META_AUTHOR=$META_AUTHOR META_AUTHOR=$META_AUTHOR
GRAPHQL_URI=$GRAPHQL_URI
DEFAULT_PUBLISHER_ID=2896
ADMIN_AUTH_URL=$ADMIN_AUTH_URL

View File

@ -1,6 +1,6 @@
{ {
"name": "bootstrap-vue-gradido-wallet", "name": "bootstrap-vue-gradido-wallet",
"version": "1.6.6", "version": "1.7.1",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node run/server.js", "start": "node run/server.js",
@ -10,7 +10,7 @@
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "TZ=UTC jest --coverage", "test": "cross-env TZ=UTC jest --coverage",
"locales": "scripts/sort.sh" "locales": "scripts/sort.sh"
}, },
"dependencies": { "dependencies": {
@ -47,7 +47,7 @@
"particles-bg-vue": "1.2.3", "particles-bg-vue": "1.2.3",
"portal-vue": "^2.1.7", "portal-vue": "^2.1.7",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"qrcode": "^1.4.4", "qrcanvas-vue": "2.1.1",
"regenerator-runtime": "^0.13.7", "regenerator-runtime": "^0.13.7",
"vee-validate": "^3.4.5", "vee-validate": "^3.4.5",
"vue": "2.6.12", "vue": "2.6.12",
@ -58,8 +58,6 @@
"vue-jest": "^3.0.7", "vue-jest": "^3.0.7",
"vue-loading-overlay": "^3.4.2", "vue-loading-overlay": "^3.4.2",
"vue-moment": "^4.1.0", "vue-moment": "^4.1.0",
"vue-qrcode": "^0.3.5",
"vue-qrcode-reader": "^2.3.16",
"vue-router": "^3.0.6", "vue-router": "^3.0.6",
"vue2-transitions": "^0.2.3", "vue2-transitions": "^0.2.3",
"vuex": "^3.6.0", "vuex": "^3.6.0",
@ -74,6 +72,7 @@
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.0", "babel-plugin-component": "^1.1.0",
"babel-plugin-transform-require-context": "^0.1.1", "babel-plugin-transform-require-context": "^0.1.1",
"cross-env": "^7.0.3",
"dotenv-webpack": "^7.0.3", "dotenv-webpack": "^7.0.3",
"postcss": "^8.4.8", "postcss": "^8.4.8",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-qr-code" viewBox="0 0 16 16">
<path d="M2 2h2v2H2V2Z"/>
<path d="M6 0v6H0V0h6ZM5 1H1v4h4V1ZM4 12H2v2h2v-2Z"/>
<path d="M6 10v6H0v-6h6Zm-5 1v4h4v-4H1Zm11-9h2v2h-2V2Z"/>
<path d="M10 0v6h6V0h-6Zm5 1v4h-4V1h4ZM8 1V0h1v2H8v2H7V1h1Zm0 5V4h1v2H8ZM6 8V7h1V6h1v2h1V7h5v1h-4v1H7V8H6Zm0 0v1H2V8H1v1H0V7h3v1h3Zm10 1h-1V7h1v2Zm-1 0h-1v2h2v-1h-1V9Zm-4 0h2v1h-1v1h-1V9Zm2 3v-1h-1v1h-1v1H9v1h3v-2h1Zm0 0h3v1h-2v1h-1v-2Zm-4-1v1h1v-2H7v1h2Z"/>
<path d="M7 12h1v3h4v1H7v-4Zm9 2v2h-3v-1h2v-1h1Z"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@ -168,6 +168,10 @@ a,
background-color: #ebebeba3 !important; background-color: #ebebeba3 !important;
} }
.gradido-shadow-inset {
box-shadow: inset 0.3em rgba(241 187 187 / 100%);
}
.gradido-max-width { .gradido-max-width {
width: 100%; width: 100%;
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="clipboard-copy"> <div class="clipboard-copy">
<b-input-group size="lg" class="mb-3" prepend="Link"> <b-input-group size="lg" class="mb-3" prepend="Link">
<b-form-input v-model="url" type="text" readonly></b-form-input> <b-form-input :value="link" type="text" readonly></b-form-input>
<b-input-group-append> <b-input-group-append>
<b-button size="sm" text="Button" variant="success" @click="CopyLink"> <b-button size="sm" text="Button" variant="success" @click="CopyLink">
{{ $t('gdd_per_link.copy') }} {{ $t('gdd_per_link.copy') }}
@ -14,17 +14,12 @@
export default { export default {
name: 'ClipboardCopy', name: 'ClipboardCopy',
props: { props: {
code: { type: String, required: true }, link: { type: String, required: true },
},
data() {
return {
url: `${window.location.origin}/redeem/${this.code}`,
}
}, },
methods: { methods: {
CopyLink() { CopyLink() {
navigator.clipboard navigator.clipboard
.writeText(this.url) .writeText(this.link)
.then(() => { .then(() => {
this.toastSuccess(this.$t('gdd_per_link.link-copied')) this.toastSuccess(this.$t('gdd_per_link.link-copied'))
}) })

View File

@ -9,6 +9,7 @@ const mocks = {
}, },
$tc: jest.fn((tc) => tc), $tc: jest.fn((tc) => tc),
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$d: jest.fn((d) => d),
} }
const propsData = { const propsData = {
@ -16,6 +17,7 @@ const propsData = {
{ {
amount: '5', amount: '5',
code: 'ce28664b5308c17f931c0367', code: 'ce28664b5308c17f931c0367',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z', createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111', holdAvailableAmount: '5.13109484759482747111',
id: 87, id: 87,
@ -26,6 +28,7 @@ const propsData = {
{ {
amount: '6', amount: '6',
code: 'ce28664b5308c17f931c0367', code: 'ce28664b5308c17f931c0367',
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z', createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111', holdAvailableAmount: '5.13109484759482747111',
id: 86, id: 86,

View File

@ -1,24 +1,30 @@
<template> <template>
<div class="decayinformation-decay"> <div class="decayinformation-decay">
<div class="d-flex">
<div class="text-center pb-3 gradido-max-width">
<b-icon icon="droplet-half" height="12" class="mb-2" />
<b>{{ $t('decay.calculation_decay') }}</b>
</div>
</div>
<b-row> <b-row>
<b-col cols="6" class="text-right"> <b-col>
<div>{{ $t('decay.decay') }}</div> <div class="text-center pb-3">
</b-col> <b-icon icon="droplet-half" class="mr-2" />
<b-col cols="6"> <b>{{ $t('decay.calculation_decay') }}</b>
<div>
{{ (Number(balance) - Number(decay)) | GDD }}
{{ decay | GDD }} {{ $t('math.equal') }}
<b>{{ balance | GDD }}</b>
</div> </div>
</b-col> </b-col>
</b-row> </b-row>
<b-row>
<b-col offset="1" cols="11">
<b-row>
<b-col cols="5" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="7">
<div>
{{ previousBookedBalance | GDD }}
{{ decay === '0' ? $t('math.minus') : '' }}
{{ decay | GDD }} {{ $t('math.equal') }}
<b>{{ balance | GDD }}</b>
</div>
</b-col>
</b-row>
</b-col>
</b-row>
</div> </div>
</template> </template>
<script> <script>
@ -33,6 +39,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
previousBookedBalance: {
type: String,
required: true,
},
}, },
} }
</script> </script>

Some files were not shown because too many files have changed in this diff Show More