mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' of github.com:gradido/gradido into 1685-refactor-notActivated-isDeleted
This commit is contained in:
commit
ee18ae41b9
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -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: 55
|
min_coverage: 58
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|||||||
46
CHANGELOG.md
46
CHANGELOG.md
@ -4,8 +4,54 @@ 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.8.0](https://github.com/gradido/gradido/compare/1.7.1...1.8.0)
|
||||||
|
|
||||||
|
- Fix: database version requirement for backend corrected [`#1835`](https://github.com/gradido/gradido/pull/1835)
|
||||||
|
- feat: More User Resolver Tests [`#1827`](https://github.com/gradido/gradido/pull/1827)
|
||||||
|
- fix: Round Decay with Tranasction Links [`#1834`](https://github.com/gradido/gradido/pull/1834)
|
||||||
|
- Fix: config value for the redeem URL was missing [`#1828`](https://github.com/gradido/gradido/pull/1828)
|
||||||
|
- Refactor: Database admin pending creations use decimal [`#1748`](https://github.com/gradido/gradido/pull/1748)
|
||||||
|
- refactor: Drop Server User Table [`#1808`](https://github.com/gradido/gradido/pull/1808)
|
||||||
|
- 1816 expired link are not highlighted [`#1821`](https://github.com/gradido/gradido/pull/1821)
|
||||||
|
- 1812 put qr code into popup on generate [`#1820`](https://github.com/gradido/gradido/pull/1820)
|
||||||
|
- Docu: Federation image [`#1817`](https://github.com/gradido/gradido/pull/1817)
|
||||||
|
- 1813 qr code popup [`#1819`](https://github.com/gradido/gradido/pull/1819)
|
||||||
|
- Fix: cross-env for windows [`#1822`](https://github.com/gradido/gradido/pull/1822)
|
||||||
|
- fix: Double Load Transaction Links [`#1818`](https://github.com/gradido/gradido/pull/1818)
|
||||||
|
- Generated link in backend should also give back the base url [`#1745`](https://github.com/gradido/gradido/pull/1745)
|
||||||
|
- 1731 style startDecayStartblock, style Adapted across pages [`#1809`](https://github.com/gradido/gradido/pull/1809)
|
||||||
|
- Refactor: Frontend bake in community info [`#1750`](https://github.com/gradido/gradido/pull/1750)
|
||||||
|
- fix: Load Transaction Link Details on Click [`#1806`](https://github.com/gradido/gradido/pull/1806)
|
||||||
|
- devops: Deploy Seed in Backend [`#1790`](https://github.com/gradido/gradido/pull/1790)
|
||||||
|
- refactor: Balance Model and Decay Rounding [`#1780`](https://github.com/gradido/gradido/pull/1780)
|
||||||
|
- change config DECAY_START_TIME in UTC 0000 [`#1807`](https://github.com/gradido/gradido/pull/1807)
|
||||||
|
- 1751 make gdt visible only if explicitly clicked [`#1752`](https://github.com/gradido/gradido/pull/1752)
|
||||||
|
- add Tab system from bootstrap in SearchUserTable Userdata [`#1744`](https://github.com/gradido/gradido/pull/1744)
|
||||||
|
- Fix: Certbot renewal [`#1789`](https://github.com/gradido/gradido/pull/1789)
|
||||||
|
- 🍰 Add Wallet Link To Mails [`#1765`](https://github.com/gradido/gradido/pull/1765)
|
||||||
|
- 1633 display qr code on link in transaction list [`#1661`](https://github.com/gradido/gradido/pull/1661)
|
||||||
|
- 1755 insert additional text when redeeming [`#1756`](https://github.com/gradido/gradido/pull/1756)
|
||||||
|
- refactor: Define Context Interface [`#1762`](https://github.com/gradido/gradido/pull/1762)
|
||||||
|
- fix: Elopage Status [`#1742`](https://github.com/gradido/gradido/pull/1742)
|
||||||
|
- Refactor: Frontend decay start block as static config value [`#1749`](https://github.com/gradido/gradido/pull/1749)
|
||||||
|
- better date format for reddem valid date [`#1758`](https://github.com/gradido/gradido/pull/1758)
|
||||||
|
- add insert shadow in summary links transaction type [`#1754`](https://github.com/gradido/gradido/pull/1754)
|
||||||
|
- Feature: JWT duration is now 30min by default [`#1747`](https://github.com/gradido/gradido/pull/1747)
|
||||||
|
- Docu: Scope of Gradido [`#1746`](https://github.com/gradido/gradido/pull/1746)
|
||||||
|
- fix: Check That Recipient User Has Activated Account to Receive Coins [`#1743`](https://github.com/gradido/gradido/pull/1743)
|
||||||
|
- Fix: Fixed config dist version to properly reflect new password reset url [`#1737`](https://github.com/gradido/gradido/pull/1737)
|
||||||
|
- 503 transaction list pagination pages clickable [`#1677`](https://github.com/gradido/gradido/pull/1677)
|
||||||
|
- if no recipientEmail else form.email [`#1722`](https://github.com/gradido/gradido/pull/1722)
|
||||||
|
- 1727 change button text and observe spelling [`#1728`](https://github.com/gradido/gradido/pull/1728)
|
||||||
|
- 1729 load spinner if pending balance [`#1730`](https://github.com/gradido/gradido/pull/1730)
|
||||||
|
- transaction type remains when jumping from the verification back [`#1724`](https://github.com/gradido/gradido/pull/1724)
|
||||||
|
- text for toast expand link copied [`#1726`](https://github.com/gradido/gradido/pull/1726)
|
||||||
|
|
||||||
#### [1.7.1](https://github.com/gradido/gradido/compare/1.7.0...1.7.1)
|
#### [1.7.1](https://github.com/gradido/gradido/compare/1.7.0...1.7.1)
|
||||||
|
|
||||||
|
> 1 April 2022
|
||||||
|
|
||||||
|
- v1.7.1 [`#1721`](https://github.com/gradido/gradido/pull/1721)
|
||||||
- fix: Localize Dates on Redeem Transaction Link Page [`#1720`](https://github.com/gradido/gradido/pull/1720)
|
- 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)
|
- 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)
|
- larger icon and deacy information if center [`#1719`](https://github.com/gradido/gradido/pull/1719)
|
||||||
|
|||||||
@ -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.7.1",
|
"version": "1.8.0",
|
||||||
"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",
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import gql from 'graphql-tag'
|
|||||||
export const createPendingCreation = gql`
|
export const createPendingCreation = gql`
|
||||||
mutation (
|
mutation (
|
||||||
$email: String!
|
$email: String!
|
||||||
$amount: Float!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$creationDate: String!
|
$creationDate: String!
|
||||||
$moderator: Int!
|
$moderator: Int!
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const updatePendingCreation = gql`
|
|||||||
mutation (
|
mutation (
|
||||||
$id: Int!
|
$id: Int!
|
||||||
$email: String!
|
$email: String!
|
||||||
$amount: Float!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$creationDate: String!
|
$creationDate: String!
|
||||||
$moderator: Int!
|
$moderator: Int!
|
||||||
|
|||||||
@ -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==
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
CONFIG_VERSION=v5.2022-04-12
|
CONFIG_VERSION=v6.2022-04-21
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
PORT=4000
|
PORT=4000
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.7.1",
|
"version": "1.8.0",
|
||||||
"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 NODE_ENV=development 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",
|
||||||
|
|||||||
@ -10,11 +10,11 @@ Decimal.set({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0033-add_referrer_id',
|
DB_VERSION: '0035-admin_pending_creations_decimal',
|
||||||
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: 'v5.2022-04-12',
|
EXPECTED: 'v6.2022-04-21',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -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.',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ArgsType, Field, Float, InputType, Int } from 'type-graphql'
|
import { ArgsType, Field, InputType, Int } from 'type-graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
@ -6,8 +7,8 @@ export default class CreatePendingCreationArgs {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
@Field(() => Float)
|
@Field(() => Decimal)
|
||||||
amount: number
|
amount: Decimal
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
memo: string
|
memo: string
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ArgsType, Field, Float, Int } from 'type-graphql'
|
import { ArgsType, Field, Int } from 'type-graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export default class UpdatePendingCreationArgs {
|
export default class UpdatePendingCreationArgs {
|
||||||
@ -8,8 +9,8 @@ export default class UpdatePendingCreationArgs {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
@Field(() => Float)
|
@Field(() => Decimal)
|
||||||
amount: number
|
amount: Decimal
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
memo: string
|
memo: string
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { RIGHTS } from '@/auth/RIGHTS'
|
|||||||
import { getCustomRepository } from '@dbTools/typeorm'
|
import { getCustomRepository } from '@dbTools/typeorm'
|
||||||
import { UserRepository } from '@repository/User'
|
import { UserRepository } from '@repository/User'
|
||||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||||
import { ServerUser } from '@entity/ServerUser'
|
|
||||||
|
|
||||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
@ -36,8 +35,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
try {
|
try {
|
||||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||||
context.user = user
|
context.user = user
|
||||||
const countServerUsers = await ServerUser.count({ email: user.email })
|
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||||
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
|
|
||||||
} catch {
|
} catch {
|
||||||
// in case the database query fails (user deleted)
|
// in case the database query fails (user deleted)
|
||||||
throw new Error('401 Unauthorized')
|
throw new Error('401 Unauthorized')
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ObjectType, Field, Int } from 'type-graphql'
|
import { ObjectType, Field, Int } from 'type-graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class PendingCreation {
|
export class PendingCreation {
|
||||||
@ -23,12 +24,12 @@ export class PendingCreation {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
memo: string
|
memo: string
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Decimal)
|
||||||
amount: number
|
amount: Decimal
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Number)
|
||||||
moderator: number
|
moderator: number
|
||||||
|
|
||||||
@Field(() => [Number])
|
@Field(() => [Decimal])
|
||||||
creation: number[]
|
creation: Decimal[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ 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,6 +53,9 @@ export class TransactionLink {
|
|||||||
|
|
||||||
@Field(() => User, { nullable: true })
|
@Field(() => User, { nullable: true })
|
||||||
redeemedBy: User | null
|
redeemedBy: User | null
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
link: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ObjectType, Field } from 'type-graphql'
|
import { ObjectType, Field } from 'type-graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class UpdatePendingCreation {
|
export class UpdatePendingCreation {
|
||||||
@ -8,12 +9,12 @@ export class UpdatePendingCreation {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
memo: string
|
memo: string
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Decimal)
|
||||||
amount: number
|
amount: Decimal
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Number)
|
||||||
moderator: number
|
moderator: number
|
||||||
|
|
||||||
@Field(() => [Number])
|
@Field(() => [Decimal])
|
||||||
creation: number[]
|
creation: Decimal[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,8 @@ export class User {
|
|||||||
this.emailChecked = user.emailChecked
|
this.emailChecked = user.emailChecked
|
||||||
this.language = user.language
|
this.language = user.language
|
||||||
this.publisherId = user.publisherId
|
this.publisherId = user.publisherId
|
||||||
|
this.isAdmin = user.isAdmin
|
||||||
// TODO
|
// TODO
|
||||||
this.isAdmin = null
|
|
||||||
this.coinanimation = null
|
this.coinanimation = null
|
||||||
this.klickTipp = null
|
this.klickTipp = null
|
||||||
this.hasElopage = null
|
this.hasElopage = null
|
||||||
@ -58,11 +58,11 @@ export class User {
|
|||||||
|
|
||||||
// `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
// `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
|
||||||
|
@Field(() => Date, { nullable: true })
|
||||||
|
isAdmin: Date | null
|
||||||
|
|
||||||
// TODO this is a bit inconsistent with what we query from the database
|
// TODO this is a bit inconsistent with what we query from the database
|
||||||
// therefore all those fields are now nullable with default value null
|
// therefore all those fields are now nullable with default value null
|
||||||
@Field(() => Boolean, { nullable: true })
|
|
||||||
isAdmin: boolean | null
|
|
||||||
|
|
||||||
@Field(() => Boolean, { nullable: true })
|
@Field(() => Boolean, { nullable: true })
|
||||||
coinanimation: boolean | null
|
coinanimation: boolean | null
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { User } from '@entity/User'
|
|
||||||
import { ObjectType, Field, Int } from 'type-graphql'
|
import { ObjectType, Field, Int } from 'type-graphql'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { User } from '@entity/User'
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class UserAdmin {
|
export class UserAdmin {
|
||||||
constructor(user: User, creation: number[], hasElopage: boolean, emailConfirmationSend: string) {
|
constructor(user: User, creation: Decimal[], hasElopage: boolean, emailConfirmationSend: string) {
|
||||||
this.userId = user.id
|
this.userId = user.id
|
||||||
this.email = user.email
|
this.email = user.email
|
||||||
this.firstName = user.firstName
|
this.firstName = user.firstName
|
||||||
@ -27,8 +28,8 @@ export class UserAdmin {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
lastName: string
|
lastName: string
|
||||||
|
|
||||||
@Field(() => [Number])
|
@Field(() => [Decimal])
|
||||||
creation: number[]
|
creation: Decimal[]
|
||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
emailChecked: boolean
|
emailChecked: boolean
|
||||||
|
|||||||
@ -43,7 +43,7 @@ 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?
|
||||||
const MAX_CREATION_AMOUNT = 1000
|
const MAX_CREATION_AMOUNT = new Decimal(1000)
|
||||||
const FULL_CREATION_AVAILABLE = [MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT]
|
const FULL_CREATION_AVAILABLE = [MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT]
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@ -170,7 +170,7 @@ export class AdminResolver {
|
|||||||
@Mutation(() => [Number])
|
@Mutation(() => [Number])
|
||||||
async createPendingCreation(
|
async createPendingCreation(
|
||||||
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
||||||
): Promise<number[]> {
|
): Promise<Decimal[]> {
|
||||||
const user = await dbUser.findOne({ email }, { withDeleted: true })
|
const user = await dbUser.findOne({ email }, { withDeleted: true })
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error(`Could not find user with email: ${email}`)
|
throw new Error(`Could not find user with email: ${email}`)
|
||||||
@ -186,7 +186,7 @@ export class AdminResolver {
|
|||||||
if (isCreationValid(creations, amount, creationDateObj)) {
|
if (isCreationValid(creations, amount, creationDateObj)) {
|
||||||
const adminPendingCreation = AdminPendingCreation.create()
|
const adminPendingCreation = AdminPendingCreation.create()
|
||||||
adminPendingCreation.userId = user.id
|
adminPendingCreation.userId = user.id
|
||||||
adminPendingCreation.amount = BigInt(amount)
|
adminPendingCreation.amount = amount
|
||||||
adminPendingCreation.created = new Date()
|
adminPendingCreation.created = new Date()
|
||||||
adminPendingCreation.date = creationDateObj
|
adminPendingCreation.date = creationDateObj
|
||||||
adminPendingCreation.memo = memo
|
adminPendingCreation.memo = memo
|
||||||
@ -251,14 +251,14 @@ export class AdminResolver {
|
|||||||
if (!isCreationValid(creations, amount, creationDateObj)) {
|
if (!isCreationValid(creations, amount, creationDateObj)) {
|
||||||
throw new Error('Creation is not valid')
|
throw new Error('Creation is not valid')
|
||||||
}
|
}
|
||||||
pendingCreationToUpdate.amount = BigInt(amount)
|
pendingCreationToUpdate.amount = amount
|
||||||
pendingCreationToUpdate.memo = memo
|
pendingCreationToUpdate.memo = memo
|
||||||
pendingCreationToUpdate.date = new Date(creationDate)
|
pendingCreationToUpdate.date = new Date(creationDate)
|
||||||
pendingCreationToUpdate.moderator = moderator
|
pendingCreationToUpdate.moderator = moderator
|
||||||
|
|
||||||
await AdminPendingCreation.save(pendingCreationToUpdate)
|
await AdminPendingCreation.save(pendingCreationToUpdate)
|
||||||
const result = new UpdatePendingCreation()
|
const result = new UpdatePendingCreation()
|
||||||
result.amount = parseInt(amount.toString())
|
result.amount = amount
|
||||||
result.memo = pendingCreationToUpdate.memo
|
result.memo = pendingCreationToUpdate.memo
|
||||||
result.date = pendingCreationToUpdate.date
|
result.date = pendingCreationToUpdate.date
|
||||||
result.moderator = pendingCreationToUpdate.moderator
|
result.moderator = pendingCreationToUpdate.moderator
|
||||||
@ -286,7 +286,7 @@ export class AdminResolver {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...pendingCreation,
|
...pendingCreation,
|
||||||
amount: Number(pendingCreation.amount.toString()),
|
amount: pendingCreation.amount,
|
||||||
firstName: user ? user.firstName : '',
|
firstName: user ? user.firstName : '',
|
||||||
lastName: user ? user.lastName : '',
|
lastName: user ? user.lastName : '',
|
||||||
email: user ? user.email : '',
|
email: user ? user.email : '',
|
||||||
@ -318,7 +318,7 @@ export class AdminResolver {
|
|||||||
if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a creation.')
|
if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a creation.')
|
||||||
|
|
||||||
const creations = await getUserCreation(pendingCreation.userId, false)
|
const creations = await getUserCreation(pendingCreation.userId, false)
|
||||||
if (!isCreationValid(creations, Number(pendingCreation.amount), pendingCreation.date)) {
|
if (!isCreationValid(creations, pendingCreation.amount, pendingCreation.date)) {
|
||||||
throw new Error('Creation is not valid!!')
|
throw new Error('Creation is not valid!!')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,8 +333,7 @@ export class AdminResolver {
|
|||||||
decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, receivedCallDate)
|
decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, receivedCallDate)
|
||||||
newBalance = decay.balance
|
newBalance = decay.balance
|
||||||
}
|
}
|
||||||
// TODO pending creations decimal
|
newBalance = newBalance.add(pendingCreation.amount.toString())
|
||||||
newBalance = newBalance.add(new Decimal(Number(pendingCreation.amount)).toString())
|
|
||||||
|
|
||||||
const transaction = new DbTransaction()
|
const transaction = new DbTransaction()
|
||||||
transaction.typeId = TransactionTypeId.CREATION
|
transaction.typeId = TransactionTypeId.CREATION
|
||||||
@ -448,10 +447,10 @@ export class AdminResolver {
|
|||||||
|
|
||||||
interface CreationMap {
|
interface CreationMap {
|
||||||
id: number
|
id: number
|
||||||
creations: number[]
|
creations: Decimal[]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUserCreation(id: number, includePending = true): Promise<number[]> {
|
async function getUserCreation(id: number, includePending = true): Promise<Decimal[]> {
|
||||||
const creations = await getUserCreations([id], includePending)
|
const creations = await getUserCreations([id], includePending)
|
||||||
return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE
|
return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE
|
||||||
}
|
}
|
||||||
@ -493,30 +492,30 @@ async function getUserCreations(ids: number[], includePending = true): Promise<C
|
|||||||
(raw: { month: string; id: string; creation: number[] }) =>
|
(raw: { month: string; id: string; creation: number[] }) =>
|
||||||
parseInt(raw.month) === month && parseInt(raw.id) === id,
|
parseInt(raw.month) === month && parseInt(raw.id) === id,
|
||||||
)
|
)
|
||||||
return MAX_CREATION_AMOUNT - (creation ? Number(creation.sum) : 0)
|
return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCreations(creations: number[], pendingCreation: AdminPendingCreation): number[] {
|
function updateCreations(creations: Decimal[], pendingCreation: AdminPendingCreation): Decimal[] {
|
||||||
const index = getCreationIndex(pendingCreation.date.getMonth())
|
const index = getCreationIndex(pendingCreation.date.getMonth())
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
throw new Error('You cannot create GDD for a month older than the last three months.')
|
||||||
}
|
}
|
||||||
creations[index] += parseInt(pendingCreation.amount.toString())
|
creations[index] = creations[index].plus(pendingCreation.amount)
|
||||||
return creations
|
return creations
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCreationValid(creations: number[], amount: number, creationDate: Date) {
|
function isCreationValid(creations: Decimal[], amount: Decimal, creationDate: Date) {
|
||||||
const index = getCreationIndex(creationDate.getMonth())
|
const index = getCreationIndex(creationDate.getMonth())
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
throw new Error(`No Creation found!`)
|
throw new Error(`No Creation found!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount > creations[index]) {
|
if (amount.greaterThan(creations[index].toString())) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`,
|
`The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
|
import { testEnvironment, headerPushMock, resetToken, cleanDB, resetEntity } from '@test/helpers'
|
||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
import { createUser, setPassword, forgotPassword, updateUserInfos } from '@/seeds/graphql/mutations'
|
||||||
import { login, logout } from '@/seeds/graphql/queries'
|
import { login, logout, verifyLogin, queryOptIn } from '@/seeds/graphql/queries'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
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 { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail'
|
||||||
|
import { printTimeDuration, activationLink } from './UserResolver'
|
||||||
|
|
||||||
// import { klicktippSignIn } from '@/apis/KlicktippController'
|
// import { klicktippSignIn } from '@/apis/KlicktippController'
|
||||||
|
|
||||||
@ -22,6 +23,13 @@ jest.mock('@/mailer/sendAccountActivationEmail', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
jest.mock('@/mailer/sendResetPasswordEmail', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
sendResetPasswordEmail: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
jest.mock('@/apis/KlicktippController', () => {
|
jest.mock('@/apis/KlicktippController', () => {
|
||||||
return {
|
return {
|
||||||
@ -85,7 +93,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('filling all tables', () => {
|
describe('filling all tables', () => {
|
||||||
it('saves the user in login_user table', () => {
|
it('saves the user in users table', () => {
|
||||||
expect(user).toEqual([
|
expect(user).toEqual([
|
||||||
{
|
{
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
@ -100,6 +108,7 @@ describe('UserResolver', () => {
|
|||||||
emailChecked: false,
|
emailChecked: false,
|
||||||
passphrase: expect.any(String),
|
passphrase: expect.any(String),
|
||||||
language: 'de',
|
language: 'de',
|
||||||
|
isAdmin: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
publisherId: 1234,
|
publisherId: 1234,
|
||||||
referrerId: null,
|
referrerId: null,
|
||||||
@ -336,7 +345,7 @@ describe('UserResolver', () => {
|
|||||||
firstName: 'Bibi',
|
firstName: 'Bibi',
|
||||||
hasElopage: false,
|
hasElopage: false,
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
isAdmin: false,
|
isAdmin: null,
|
||||||
klickTipp: {
|
klickTipp: {
|
||||||
newsletterState: false,
|
newsletterState: false,
|
||||||
},
|
},
|
||||||
@ -412,6 +421,356 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('verifyLogin', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
resetToken()
|
||||||
|
await expect(query({ query: verifyLogin })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user exists but is not logged in', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
resetToken()
|
||||||
|
await expect(query({ query: verifyLogin })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
const variables = {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
password: 'Aa12345_',
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await query({ query: login, variables })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns user object', async () => {
|
||||||
|
await expect(query({ query: verifyLogin })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
verifyLogin: {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
language: 'de',
|
||||||
|
coinanimation: true,
|
||||||
|
klickTipp: {
|
||||||
|
newsletterState: false,
|
||||||
|
},
|
||||||
|
hasElopage: false,
|
||||||
|
publisherId: 1234,
|
||||||
|
isAdmin: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('forgotPassword', () => {
|
||||||
|
const variables = { email: 'bibi@bloxberg.de' }
|
||||||
|
describe('user is not in DB', () => {
|
||||||
|
it('returns true', async () => {
|
||||||
|
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
forgotPassword: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user exists in DB', () => {
|
||||||
|
let result: any
|
||||||
|
let loginEmailOptIn: LoginEmailOptIn[]
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await resetEntity(LoginEmailOptIn)
|
||||||
|
result = await mutate({ mutation: forgotPassword, variables })
|
||||||
|
loginEmailOptIn = await LoginEmailOptIn.find()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true', async () => {
|
||||||
|
await expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
forgotPassword: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends reset password email', () => {
|
||||||
|
expect(sendResetPasswordEmail).toBeCalledWith({
|
||||||
|
link: activationLink(loginEmailOptIn[0]),
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
duration: expect.any(String),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('request reset password again', () => {
|
||||||
|
it('thows an error', async () => {
|
||||||
|
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('email already sent less than 10 minutes minutes ago')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('queryOptIn', () => {
|
||||||
|
let loginEmailOptIn: LoginEmailOptIn[]
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
loginEmailOptIn = await LoginEmailOptIn.find()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('wrong optin code', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(
|
||||||
|
query({ query: queryOptIn, variables: { optIn: 'not-valid' } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
// keep Whitspace in error message!
|
||||||
|
new GraphQLError(`Could not find any entity of type "LoginEmailOptIn" matching: {
|
||||||
|
"verificationCode": "not-valid"
|
||||||
|
}`),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('correct optin code', () => {
|
||||||
|
it('returns true', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: queryOptIn,
|
||||||
|
variables: { optIn: loginEmailOptIn[0].verificationCode.toString() },
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
queryOptIn: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateUserInfos', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
resetToken()
|
||||||
|
await expect(mutate({ mutation: updateUserInfos })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await query({
|
||||||
|
query: login,
|
||||||
|
variables: {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
password: 'Aa12345_',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true', async () => {
|
||||||
|
await expect(mutate({ mutation: updateUserInfos })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
updateUserInfos: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('first-name, last-name and language', () => {
|
||||||
|
it('updates the fields in DB', async () => {
|
||||||
|
await mutate({
|
||||||
|
mutation: updateUserInfos,
|
||||||
|
variables: {
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await expect(User.findOne()).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
firstName: 'Benjamin',
|
||||||
|
lastName: 'Blümchen',
|
||||||
|
language: 'en',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('language is not valid', () => {
|
||||||
|
it('thows an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateUserInfos,
|
||||||
|
variables: {
|
||||||
|
locale: 'not-valid',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError(`"not-valid" isn't a valid language`)],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('password', () => {
|
||||||
|
describe('wrong old password', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateUserInfos,
|
||||||
|
variables: {
|
||||||
|
password: 'wrong password',
|
||||||
|
passwordNew: 'Aa12345_',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Old password is invalid')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('invalid new password', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateUserInfos,
|
||||||
|
variables: {
|
||||||
|
password: 'Aa12345_',
|
||||||
|
passwordNew: 'Aa12345',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('correct old and new password', () => {
|
||||||
|
it('returns true', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateUserInfos,
|
||||||
|
variables: {
|
||||||
|
password: 'Aa12345_',
|
||||||
|
passwordNew: 'Bb12345_',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: { updateUserInfos: true },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can login wtih new password', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: login,
|
||||||
|
variables: {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
password: 'Bb12345_',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
login: expect.objectContaining({
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot login wtih old password', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: login,
|
||||||
|
variables: {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
password: 'Aa12345_',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('No user with this credentials')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('printTimeDuration', () => {
|
describe('printTimeDuration', () => {
|
||||||
|
|||||||
@ -19,9 +19,7 @@ import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer
|
|||||||
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'
|
||||||
import { ROLE_ADMIN } from '@/auth/ROLES'
|
|
||||||
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
||||||
import { ServerUser } from '@entity/ServerUser'
|
|
||||||
|
|
||||||
// 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')
|
||||||
@ -207,7 +205,6 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
|
|
||||||
user.isAdmin = context.role === ROLE_ADMIN
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,16 +240,11 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = new User(dbUser)
|
const user = new User(dbUser)
|
||||||
// user.email = email
|
|
||||||
// user.pubkey = dbUser.pubKey.toString('hex')
|
|
||||||
user.language = dbUser.language
|
|
||||||
|
|
||||||
// Elopage Status & Stored PublisherId
|
// Elopage Status & Stored PublisherId
|
||||||
user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
|
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
|
|
||||||
// await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
|
|
||||||
dbUser.publisherId = publisherId
|
dbUser.publisherId = publisherId
|
||||||
DbUser.save(dbUser)
|
DbUser.save(dbUser)
|
||||||
}
|
}
|
||||||
@ -266,10 +258,6 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
|
|
||||||
// context.role is not set to the actual role yet on login
|
|
||||||
const countServerUsers = await ServerUser.count({ email: user.email })
|
|
||||||
user.isAdmin = countServerUsers > 0
|
|
||||||
|
|
||||||
context.setHeaders.push({
|
context.setHeaders.push({
|
||||||
key: 'token',
|
key: 'token',
|
||||||
value: encode(dbUser.pubKey),
|
value: encode(dbUser.pubKey),
|
||||||
@ -529,15 +517,7 @@ export class UserResolver {
|
|||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async updateUserInfos(
|
async updateUserInfos(
|
||||||
@Args()
|
@Args()
|
||||||
{
|
{ firstName, lastName, language, password, passwordNew, coinanimation }: UpdateUserInfosArgs,
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
language,
|
|
||||||
publisherId,
|
|
||||||
password,
|
|
||||||
passwordNew,
|
|
||||||
coinanimation,
|
|
||||||
}: UpdateUserInfosArgs,
|
|
||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const userEntity = getUser(context)
|
const userEntity = getUser(context)
|
||||||
@ -581,11 +561,6 @@ export class UserResolver {
|
|||||||
userEntity.privKey = encryptedPrivkey
|
userEntity.privKey = encryptedPrivkey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save publisherId only if Elopage is not yet registered
|
|
||||||
if (publisherId && !(await this.hasElopage(context))) {
|
|
||||||
userEntity.publisherId = publisherId
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||||
import { ServerUser } from '@entity/ServerUser'
|
|
||||||
import { UserInterface } from '@/seeds/users/UserInterface'
|
import { UserInterface } from '@/seeds/users/UserInterface'
|
||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
@ -29,23 +28,9 @@ export const userFactory = async (
|
|||||||
// get user from database
|
// get user from database
|
||||||
const dbUser = await User.findOneOrFail({ id })
|
const dbUser = await User.findOneOrFail({ id })
|
||||||
|
|
||||||
if (user.createdAt || user.deletedAt) {
|
|
||||||
if (user.createdAt) dbUser.createdAt = user.createdAt
|
if (user.createdAt) dbUser.createdAt = user.createdAt
|
||||||
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
|
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
|
||||||
|
if (user.isAdmin) dbUser.isAdmin = new Date()
|
||||||
await dbUser.save()
|
await dbUser.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isAdmin) {
|
|
||||||
const admin = new ServerUser()
|
|
||||||
admin.username = dbUser.firstName
|
|
||||||
admin.password = 'please_refactor'
|
|
||||||
admin.email = dbUser.email
|
|
||||||
admin.role = 'admin'
|
|
||||||
admin.activated = 1
|
|
||||||
admin.lastLogin = new Date()
|
|
||||||
admin.created = dbUser.createdAt
|
|
||||||
admin.modified = dbUser.createdAt
|
|
||||||
await admin.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,12 @@ export const setPassword = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const forgotPassword = gql`
|
||||||
|
mutation ($email: String!) {
|
||||||
|
forgotPassword(email: $email)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const updateUserInfos = gql`
|
export const updateUserInfos = gql`
|
||||||
mutation (
|
mutation (
|
||||||
$firstName: String
|
$firstName: String
|
||||||
@ -80,7 +86,7 @@ export const createTransactionLink = gql`
|
|||||||
export const createPendingCreation = gql`
|
export const createPendingCreation = gql`
|
||||||
mutation (
|
mutation (
|
||||||
$email: String!
|
$email: String!
|
||||||
$amount: Float!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$creationDate: String!
|
$creationDate: String!
|
||||||
$moderator: Int!
|
$moderator: Int!
|
||||||
|
|||||||
@ -43,6 +43,12 @@ export const logout = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const queryOptIn = gql`
|
||||||
|
query ($optIn: String!) {
|
||||||
|
queryOptIn(optIn: $optIn)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query (
|
query (
|
||||||
$currentPage: Int = 1
|
$currentPage: Int = 1
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const communityDbUser: dbUser = {
|
|||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
emailChecked: false,
|
emailChecked: false,
|
||||||
language: '',
|
language: '',
|
||||||
|
isAdmin: null,
|
||||||
publisherId: 0,
|
publisherId: 0,
|
||||||
passphrase: '',
|
passphrase: '',
|
||||||
settings: [],
|
settings: [],
|
||||||
|
|||||||
@ -70,6 +70,7 @@ const virtualDecayTransaction = (
|
|||||||
typeId: TransactionTypeId.DECAY,
|
typeId: TransactionTypeId.DECAY,
|
||||||
amount: decay.decay ? decay.roundedDecay : new Decimal(0),
|
amount: decay.decay ? decay.roundedDecay : new Decimal(0),
|
||||||
balance: decay.balance
|
balance: decay.balance
|
||||||
|
.toDecimalPlaces(2, Decimal.ROUND_DOWN)
|
||||||
.minus(holdAvailabeAmount.toString())
|
.minus(holdAvailabeAmount.toString())
|
||||||
.toDecimalPlaces(2, Decimal.ROUND_DOWN),
|
.toDecimalPlaces(2, Decimal.ROUND_DOWN),
|
||||||
balanceDate: time,
|
balanceDate: time,
|
||||||
|
|||||||
@ -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==
|
||||||
|
|||||||
@ -30,4 +30,3 @@ yarn dev_down
|
|||||||
yarn dev_reset
|
yarn dev_reset
|
||||||
```
|
```
|
||||||
Runs all down migrations and after this all up migrations.
|
Runs all down migrations and after this all up migrations.
|
||||||
|
|
||||||
|
|||||||
81
database/entity/0034-drop_server_user_table/User.ts
Normal file
81
database/entity/0034-drop_server_user_table/User.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
OneToMany,
|
||||||
|
DeleteDateColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { UserSetting } from '../UserSetting'
|
||||||
|
|
||||||
|
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class User extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
|
||||||
|
pubKey: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
|
||||||
|
privKey: Buffer
|
||||||
|
|
||||||
|
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'first_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
firstName: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'last_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
lastName: string
|
||||||
|
|
||||||
|
@DeleteDateColumn()
|
||||||
|
deletedAt: Date | null
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||||
|
password: BigInt
|
||||||
|
|
||||||
|
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
|
||||||
|
emailHash: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
|
||||||
|
emailChecked: boolean
|
||||||
|
|
||||||
|
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||||
|
language: string
|
||||||
|
|
||||||
|
@Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null })
|
||||||
|
isAdmin: Date | null
|
||||||
|
|
||||||
|
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||||
|
referrerId?: number | null
|
||||||
|
|
||||||
|
@Column({ name: 'publisher_id', default: 0 })
|
||||||
|
publisherId: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'text',
|
||||||
|
name: 'passphrase',
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
passphrase: string
|
||||||
|
|
||||||
|
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
|
||||||
|
settings: UserSetting[]
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||||
|
|
||||||
|
@Entity('admin_pending_creations')
|
||||||
|
export class AdminPendingCreation extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
created: Date
|
||||||
|
|
||||||
|
@Column({ type: 'datetime', nullable: false })
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 40,
|
||||||
|
scale: 20,
|
||||||
|
nullable: false,
|
||||||
|
transformer: DecimalTransformer,
|
||||||
|
})
|
||||||
|
amount: Decimal
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
moderator: number
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { AdminPendingCreation } from './0015-admin_pending_creations/AdminPendingCreation'
|
export { AdminPendingCreation } from './0035-admin_pending_creations_decimal/AdminPendingCreation'
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export { ServerUser } from './0001-init_db/ServerUser'
|
|
||||||
@ -1 +1 @@
|
|||||||
export { User } from './0033-add_referrer_id/User'
|
export { User } from './0034-drop_server_user_table/User'
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { LoginElopageBuys } from './LoginElopageBuys'
|
import { LoginElopageBuys } from './LoginElopageBuys'
|
||||||
import { LoginEmailOptIn } from './LoginEmailOptIn'
|
import { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||||
import { Migration } from './Migration'
|
import { Migration } from './Migration'
|
||||||
import { ServerUser } from './ServerUser'
|
|
||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
import { TransactionLink } from './TransactionLink'
|
import { TransactionLink } from './TransactionLink'
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
@ -13,7 +12,6 @@ export const entities = [
|
|||||||
LoginElopageBuys,
|
LoginElopageBuys,
|
||||||
LoginEmailOptIn,
|
LoginEmailOptIn,
|
||||||
Migration,
|
Migration,
|
||||||
ServerUser,
|
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionLink,
|
TransactionLink,
|
||||||
User,
|
User,
|
||||||
|
|||||||
37
database/migrations/0034-drop_server_user_table.ts
Normal file
37
database/migrations/0034-drop_server_user_table.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* MIGRATION DROP server_users TABLE
|
||||||
|
add isAdmin COLUMN to users TABLE */
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` datetime DEFAULT NULL AFTER `language`;')
|
||||||
|
|
||||||
|
await queryFn(
|
||||||
|
'UPDATE users AS users INNER JOIN server_users AS server_users ON users.email = server_users.email SET users.is_admin = server_users.modified WHERE users.email IN (SELECT email from server_users);',
|
||||||
|
)
|
||||||
|
|
||||||
|
await queryFn('DROP TABLE `server_users`;')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`server_users\` (
|
||||||
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`username\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
\`password\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
\`email\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
\`role\` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||||
|
\`activated\` tinyint(4) NOT NULL DEFAULT '0',
|
||||||
|
\`last_login\` datetime DEFAULT NULL,
|
||||||
|
\`created\` datetime NOT NULL,
|
||||||
|
\`modified\` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await queryFn(
|
||||||
|
'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `is_admin`, `is_admin` FROM `users` WHERE `is_admin` IS NOT NULL;',
|
||||||
|
)
|
||||||
|
|
||||||
|
await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;')
|
||||||
|
}
|
||||||
42
database/migrations/0035-admin_pending_creations_decimal.ts
Normal file
42
database/migrations/0035-admin_pending_creations_decimal.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* MIGRATION TO CHANGE SEVERAL FIELDS ON `admin_pending_creations`
|
||||||
|
* - `amount` FIELD TYPE TO `Decimal`
|
||||||
|
* - `memo` FIELD TYPE TO `varchar(255)`, collate `utf8mb4_unicode_ci`
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// rename `amount` to `amount_bigint`
|
||||||
|
await queryFn('ALTER TABLE `admin_pending_creations` RENAME COLUMN `amount` TO `amount_bigint`;')
|
||||||
|
// add `amount` (decimal)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `admin_pending_creations` ADD COLUMN `amount` DECIMAL(40,20) DEFAULT NULL AFTER `amount_bigint`;',
|
||||||
|
)
|
||||||
|
// fill new `amount` column
|
||||||
|
await queryFn('UPDATE `admin_pending_creations` SET `amount` = `amount_bigint` DIV 10000;')
|
||||||
|
// make `amount` not nullable
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `amount` DECIMAL(40,20) NOT NULL;',
|
||||||
|
)
|
||||||
|
// drop `amount_bitint` column
|
||||||
|
await queryFn('ALTER TABLE `admin_pending_creations` DROP COLUMN `amount_bigint`;')
|
||||||
|
|
||||||
|
// change `memo` to varchar(255), collate utf8mb4_unicode_ci
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `memo` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL;',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE `admin_pending_creations` MODIFY COLUMN `memo` text DEFAULT NULL;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `admin_pending_creations` ADD COLUMN `amount_bigint` bigint(20) DEFAULT NULL AFTER `amount`;',
|
||||||
|
)
|
||||||
|
await queryFn('UPDATE `admin_pending_creations` SET `amount_bigint` = `amount` * 10000;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `amount_bigint` bigint(20) NOT NULL;',
|
||||||
|
)
|
||||||
|
await queryFn('ALTER TABLE `admin_pending_creations` DROP COLUMN `amount`;')
|
||||||
|
await queryFn('ALTER TABLE `admin_pending_creations` RENAME COLUMN `amount_bigint` TO `amount`;')
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.7.1",
|
"version": "1.8.0",
|
||||||
"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,6 +37,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
@ -481,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==
|
||||||
|
|||||||
@ -21,10 +21,11 @@ WEBHOOK_GITHUB_BRANCH=master
|
|||||||
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_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
|
||||||
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
BACKEND_CONFIG_VERSION=v5.2022-04-12
|
BACKEND_CONFIG_VERSION=v6.2022-04-21
|
||||||
|
|
||||||
JWT_EXPIRES_IN=30m
|
JWT_EXPIRES_IN=30m
|
||||||
GDT_API_URL=https://gdt.gradido.net
|
GDT_API_URL=https://gdt.gradido.net
|
||||||
@ -37,7 +38,7 @@ KLICKTIPP_PASSWORD=
|
|||||||
KLICKTIPP_APIKEY_DE=
|
KLICKTIPP_APIKEY_DE=
|
||||||
KLICKTIPP_APIKEY_EN=
|
KLICKTIPP_APIKEY_EN=
|
||||||
|
|
||||||
EMAIL=true
|
EMAIL=false
|
||||||
EMAIL_USERNAME=peter@lustig.de
|
EMAIL_USERNAME=peter@lustig.de
|
||||||
EMAIL_SENDER=peter@lustig.de
|
EMAIL_SENDER=peter@lustig.de
|
||||||
EMAIL_PASSWORD=1234
|
EMAIL_PASSWORD=1234
|
||||||
|
|||||||
135
docu/graphics/federation.drawio
Normal file
135
docu/graphics/federation.drawio
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="IM2_X0c97E_FZMH_xTRz" 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="28" value="Other Community" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="360" y="410" width="120" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="Appolo Server" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="80" width="280" height="300" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" style="edgeStyle=none;html=1;" parent="1" target="5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="500" y="325" as="sourcePoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="25" style="edgeStyle=none;html=1;startArrow=none;startFill=0;" edge="1" parent="1" source="2" target="16">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="27" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;startArrow=none;startFill=0;" edge="1" parent="1" source="2" target="26">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="2" value="Node" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;fontColor=#ffffff;strokeColor=#2D7600;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="370" y="300" width="100" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="600" y="285" width="60" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="Node" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;fontColor=#ffffff;strokeColor=#2D7600;labelPosition=center;verticalLabelPosition=middle;align=center;verticalAlign=middle;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="260" y="575" width="240" height="230" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;startArrow=none;startFill=0;" parent="1" source="11" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="DHT (lookup/annoucne)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="280" y="600" width="200" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=none;startFill=0;" edge="1" parent="1" source="12" target="13">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="Direct Connection<br>(exchange endpoints)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="280" y="730" width="200" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="600" y="720" width="60" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="24" value="" style="edgeStyle=none;html=1;startArrow=none;startFill=0;" edge="1" parent="1" source="16" target="23">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="Verify via GraphQL" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;fontColor=#ffffff;strokeColor=#2D7600;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="240" y="300" width="100" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="Other Community" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="71" width="120" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="22" value="Other Community" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="176" width="120" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="Other Community" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="281" width="120" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="26" value="Node" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;fontColor=#ffffff;strokeColor=#2D7600;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="370" y="430" width="100" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="29" value="Appolo Server" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;verticalAlign=top;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="110" y="880" width="540" height="500" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="35" value="" style="edgeStyle=none;html=1;startArrow=classic;startFill=1;" edge="1" parent="1" source="30" target="34">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="690" y="940"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="30" value="Frontend GraphQL Endpoint (unversionated)" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="150" y="910" width="450" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" value="Versionated Endpoints" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="150" y="1010" width="450" height="310" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="34" value="Frontend" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="690" y="910" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="36" value="Other Community" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;verticalAlign=top;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="700" y="1012" width="140" height="250" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="38" value="<span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22Frontend%20GraphQL%20Endpoint%20(unversionated)%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22150%22%20y%3D%221010%22%20width%3D%22450%22%20height%3D%22310%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span><span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22Frontend%20GraphQL%20Endpoint%20(unversionated)%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22150%22%20y%3D%221010%22%20width%3D%22450%22%20height%3D%22310%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="710" y="1042" width="120" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="39" value="<span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22Frontend%20GraphQL%20Endpoint%20(unversionated)%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22150%22%20y%3D%221010%22%20width%3D%22450%22%20height%3D%22310%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span><span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22Frontend%20GraphQL%20Endpoint%20(unversionated)%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22150%22%20y%3D%221010%22%20width%3D%22450%22%20height%3D%22310%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="710" y="1082" width="120" height="170" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" style="edgeStyle=none;html=1;startArrow=classic;startFill=1;" edge="1" parent="1" source="31" target="39">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="1.0" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e51400;fontColor=#ffffff;strokeColor=#B20000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="481" y="1050" width="40" height="40" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="1.1" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e51400;fontColor=#ffffff;strokeColor=#B20000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="481" y="1100" width="40" height="40" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="2.1" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="480" y="1200" width="40" height="40" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="53" value="2.0" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fa6800;fontColor=#000000;strokeColor=#C73500;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="481" y="1150" width="40" height="40" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" value="3.0" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="481" y="1252" width="40" height="40" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" value="<span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%222.1%22%20style%3D%22whiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22480%22%20y%3D%221200%22%20width%3D%2240%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span><span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%222.1%22%20style%3D%22whiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22480%22%20y%3D%221200%22%20width%3D%2240%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="170" y="1150" width="300" height="142" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="<font color="rgba(0, 0, 0, 0)" face="monospace"><span style="font-size: 0px">Imp</span></font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="220" y="1100" width="250" height="140" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" value="<span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%222.1%22%20style%3D%22whiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22480%22%20y%3D%221200%22%20width%3D%2240%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span><span style="color: rgba(0 , 0 , 0 , 0) ; font-family: monospace ; font-size: 0px">%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%222.1%22%20style%3D%22whiteSpace%3Dwrap%3Bhtml%3D1%3Baspect%3Dfixed%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22480%22%20y%3D%221200%22%20width%3D%2240%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E</span>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e51400;fontColor=#ffffff;strokeColor=#B20000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="1050" width="190" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="Implementation 1.x" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#FFFFFF;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="317.5" y="1080" width="115" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="Implementation&nbsp;2.x<br>(uses parts of the 1.x implementation)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#000000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="287.5" y="1170" width="115" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" value="Implementation 3.x" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#000000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="1252" width="115" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
BIN
docu/graphics/federation.png
Normal file
BIN
docu/graphics/federation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.7.1",
|
"version": "1.8.0",
|
||||||
"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": {
|
||||||
@ -72,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",
|
||||||
|
|||||||
@ -1,32 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="clipboard-copy">
|
<div class="clipboard-copy">
|
||||||
<b-input-group size="lg" class="mb-3" prepend="Link">
|
<b-input-group v-if="canCopyLink" size="lg" class="mb-3" prepend="Link">
|
||||||
<b-form-input :value="text" 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="primary" @click="CopyLink">
|
||||||
{{ $t('gdd_per_link.copy') }}
|
{{ $t('gdd_per_link.copy') }}
|
||||||
</b-button>
|
</b-button>
|
||||||
|
<b-button variant="primary" class="text-light" @click="$emit('show-qr-code-button')">
|
||||||
|
<b-img src="img/svg/qr-code.svg" width="19" class="svg"></b-img>
|
||||||
|
</b-button>
|
||||||
</b-input-group-append>
|
</b-input-group-append>
|
||||||
</b-input-group>
|
</b-input-group>
|
||||||
|
<div v-else>
|
||||||
|
<div class="alert-danger p-3">{{ $t('gdd_per_link.not-copied') }}</div>
|
||||||
|
<div class="alert-muted h3 p-3">{{ link }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'ClipboardCopy',
|
name: 'ClipboardCopy',
|
||||||
props: {
|
props: {
|
||||||
text: { type: String, required: true },
|
link: { type: String, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
canCopyLink: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
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'))
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
this.canCopyLink = false
|
||||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.svg {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -17,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,
|
||||||
@ -27,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,
|
||||||
|
|||||||
@ -3,13 +3,12 @@
|
|||||||
<b-col>
|
<b-col>
|
||||||
<b-card class="p-0 gradido-custom-background">
|
<b-card class="p-0 gradido-custom-background">
|
||||||
<div class="h3 mb-4">{{ $t('gdd_per_link.created') }}</div>
|
<div class="h3 mb-4">{{ $t('gdd_per_link.created') }}</div>
|
||||||
|
<clipboard-copy :link="link" @show-qr-code-button="showQrCodeButton" />
|
||||||
<clipboard-copy :text="link" />
|
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<figure-qr-code :text="link" />
|
<figure-qr-code v-if="showQrcode" :link="link" />
|
||||||
|
|
||||||
<b-button variant="success" @click="$emit('on-reset')" class="mt-4">
|
<b-button variant="secondary" @click="$emit('on-reset')" class="mt-4">
|
||||||
{{ $t('form.close') }}
|
{{ $t('form.close') }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
@ -28,14 +27,19 @@ export default {
|
|||||||
FigureQrCode,
|
FigureQrCode,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
code: {
|
link: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
data() {
|
||||||
link() {
|
return {
|
||||||
return `${window.location.origin}/redeem/${this.code}`
|
showQrcode: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showQrCodeButton() {
|
||||||
|
this.showQrcode = !this.showQrcode
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,9 @@
|
|||||||
<div v-else>{{ errorResult }}</div>
|
<div v-else>{{ errorResult }}</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center mt-3">
|
<p class="text-center mt-3">
|
||||||
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
|
<b-button variant="secondary" @click="$emit('on-reset')">
|
||||||
|
{{ $t('form.close') }}
|
||||||
|
</b-button>
|
||||||
</p>
|
</p>
|
||||||
</b-card>
|
</b-card>
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
{{ $t('form.send_transaction_success') }}
|
{{ $t('form.send_transaction_success') }}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center mt-3">
|
<p class="text-center mt-3">
|
||||||
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
|
<b-button variant="primary" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
|
||||||
</p>
|
</p>
|
||||||
</b-card>
|
</b-card>
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import FigureQrCode from './FigureQrCode'
|
|||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
text: '',
|
link: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('FigureQrCode', () => {
|
describe('FigureQrCode', () => {
|
||||||
|
|||||||
@ -14,14 +14,14 @@ export default {
|
|||||||
QRCanvas,
|
QRCanvas,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
text: { type: String, required: true },
|
link: { type: String, required: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
options: {
|
options: {
|
||||||
cellSize: 8,
|
cellSize: 8,
|
||||||
correctLevel: 'H',
|
correctLevel: 'H',
|
||||||
data: this.text,
|
data: this.link,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const mocks = {
|
|||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
amount: '75',
|
amount: '75',
|
||||||
code: 'c00000000c000000c0000',
|
link: 'http://localhost/redeem/c00000000c000000c0000',
|
||||||
holdAvailableAmount: '5.13109484759482747111',
|
holdAvailableAmount: '5.13109484759482747111',
|
||||||
id: 12,
|
id: 12,
|
||||||
memo: 'Katzenauge, Eulenschrei, was verschwunden komm herbei!',
|
memo: 'Katzenauge, Eulenschrei, was verschwunden komm herbei!',
|
||||||
@ -44,6 +44,29 @@ describe('TransactionLink', () => {
|
|||||||
expect(wrapper.find('div.transaction-link').exists()).toBeTruthy()
|
expect(wrapper.find('div.transaction-link').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Link validUntil Date is not valid', () => {
|
||||||
|
it('has no copy link button', () => {
|
||||||
|
expect(wrapper.find('.test-copy-link').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no Qr-Code Button ', () => {
|
||||||
|
expect(wrapper.find('.test-qr-code').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has delete link button ', () => {
|
||||||
|
expect(wrapper.find('.test-delete-link').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Link validUntil Date is valid ', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const now = new Date()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({
|
||||||
|
validUntil: `${new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2)}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Copy link to Clipboard', () => {
|
describe('Copy link to Clipboard', () => {
|
||||||
const navigatorClipboard = navigator.clipboard
|
const navigatorClipboard = navigator.clipboard
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -57,8 +80,9 @@ describe('TransactionLink', () => {
|
|||||||
describe('copy with success', () => {
|
describe('copy with success', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
navigatorClipboardMock.mockResolvedValue()
|
navigatorClipboardMock.mockResolvedValue()
|
||||||
await wrapper.find('.test-copy-link').trigger('click')
|
await wrapper.find('.test-copy-link .dropdown-item').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call clipboard.writeText', () => {
|
it('should call clipboard.writeText', () => {
|
||||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||||
'http://localhost/redeem/c00000000c000000c0000',
|
'http://localhost/redeem/c00000000c000000c0000',
|
||||||
@ -73,7 +97,7 @@ describe('TransactionLink', () => {
|
|||||||
describe('qr code modal', () => {
|
describe('qr code modal', () => {
|
||||||
let spy
|
let spy
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -82,10 +106,10 @@ describe('TransactionLink', () => {
|
|||||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'show')
|
spy = jest.spyOn(wrapper.vm.$bvModal, 'show')
|
||||||
// spy.mockImplementation(() => Promise.resolve('some value'))
|
// spy.mockImplementation(() => Promise.resolve('some value'))
|
||||||
// mockAPIcall.mockResolvedValue()
|
// mockAPIcall.mockResolvedValue()
|
||||||
await wrapper.find('.test-qr-code').trigger('click')
|
await wrapper.find('.test-qr-code .dropdown-item').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('qr-code Modal if show', () => {
|
it('opens the qr-code Modal', () => {
|
||||||
expect(spy).toBeCalled()
|
expect(spy).toBeCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -94,7 +118,7 @@ describe('TransactionLink', () => {
|
|||||||
describe('delete link', () => {
|
describe('delete link', () => {
|
||||||
let spy
|
let spy
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -103,10 +127,10 @@ describe('TransactionLink', () => {
|
|||||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||||
mockAPIcall.mockResolvedValue()
|
mockAPIcall.mockResolvedValue()
|
||||||
await wrapper.find('.test-delete-link').trigger('click')
|
await wrapper.find('.test-delete-link .dropdown-item').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('test Modal if confirm true', () => {
|
it('opens the modal ', () => {
|
||||||
expect(spy).toBeCalled()
|
expect(spy).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -135,7 +159,7 @@ describe('TransactionLink', () => {
|
|||||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||||
mockAPIcall.mockRejectedValue({ message: 'Something went wrong :(' })
|
mockAPIcall.mockRejectedValue({ message: 'Something went wrong :(' })
|
||||||
await wrapper.find('.test-delete-link').trigger('click')
|
await wrapper.find('.test-delete-link .dropdown-item').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
it('toasts an error message', () => {
|
||||||
@ -148,7 +172,7 @@ describe('TransactionLink', () => {
|
|||||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||||
spy.mockImplementation(() => Promise.resolve(false))
|
spy.mockImplementation(() => Promise.resolve(false))
|
||||||
mockAPIcall.mockResolvedValue()
|
mockAPIcall.mockResolvedValue()
|
||||||
await wrapper.find('.test-delete-link').trigger('click')
|
await wrapper.find('.test-delete-link .dropdown-item').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not call the API', () => {
|
it('does not call the API', () => {
|
||||||
@ -158,3 +182,4 @@ describe('TransactionLink', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -1,55 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="transaction-link gradido-custom-background">
|
<div class="transaction-link gradido-custom-background">
|
||||||
<b-row class="mb-2 pt-2 pb-2">
|
<b-row :class="validLink ? '' : 'bg-muted text-light'" class="mb-2 pt-2 pb-2">
|
||||||
<b-col lg="2">
|
<b-col cols="1">
|
||||||
<type-icon color="text-danger" icon="link45deg" class="pt-4 pl-2" />
|
<type-icon color="text-danger" icon="link45deg" class="pt-4 pl-2" />
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col lg="9" md="9">
|
<b-col cols="11">
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col lg="11" md="10">
|
<b-col>
|
||||||
<amount-and-name-row :amount="amount" :text="$t('form.amount')" />
|
<amount-and-name-row :amount="amount" :text="$t('form.amount')" />
|
||||||
<memo-row :memo="memo" />
|
<memo-row :memo="memo" />
|
||||||
<date-row :date="validUntil" :diffNow="true" />
|
<date-row :date="validUntil" :diffNow="true" :validLink="validLink" />
|
||||||
<decay-row :decay="decay" />
|
<decay-row :decay="decay" />
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col lg="1" md="2" class="text-center text-lg-left qr-button">
|
<b-col cols="12" lg="1" md="1" class="text-center text-md-right pr-5 pr-lg-4">
|
||||||
<b-button
|
<b-dropdown no-caret right aria-expanded="false" size="sm">
|
||||||
@click="$bvModal.show('modalPopover-' + id)"
|
<template #button-content>
|
||||||
class="p-2 test-qr-code"
|
<b-icon icon="three-dots-vertical"></b-icon>
|
||||||
size="sm"
|
</template>
|
||||||
>
|
|
||||||
<b-img src="img/svg/qr-code.svg" width="60" class="filter"></b-img>
|
|
||||||
</b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
|
|
||||||
<b-col lg="1" md="1" class="text-center text-lg-right">
|
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copy">
|
||||||
<b-button
|
|
||||||
class="p-2 test-copy-link"
|
|
||||||
size="lg"
|
|
||||||
variant="outline-primary"
|
|
||||||
@click="copy"
|
|
||||||
:title="$t('gdd_per_link.copy')"
|
|
||||||
>
|
|
||||||
<b-icon icon="clipboard"></b-icon>
|
<b-icon icon="clipboard"></b-icon>
|
||||||
</b-button>
|
{{ $t('gdd_per_link.copy') }}
|
||||||
<br />
|
</b-dropdown-item>
|
||||||
<b-button
|
<b-dropdown-item
|
||||||
class="p-2 mt-3 test-delete-link"
|
v-if="validLink"
|
||||||
size="sm"
|
@click="$bvModal.show('modalPopover-' + id)"
|
||||||
@click="deleteLink()"
|
class="pt-3 pb-3 test-qr-code"
|
||||||
:title="$t('delete')"
|
|
||||||
>
|
>
|
||||||
|
<b-img src="img/svg/qr-code.svg" width="18" class="filter"></b-img>
|
||||||
|
{{ $t('qrCode') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item class="test-delete-link" @click="deleteLink()">
|
||||||
<b-icon icon="trash"></b-icon>
|
<b-icon icon="trash"></b-icon>
|
||||||
</b-button>
|
{{ $t('delete') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
</b-dropdown>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-modal :id="'modalPopover-' + id" title="QR-Code" ok-only hide-header-close>
|
</b-col>
|
||||||
<div class="text-center">
|
</b-row>
|
||||||
<figure-qr-code :text="link" />
|
<b-modal :id="'modalPopover-' + id" ok-only hide-header-close>
|
||||||
<p>{{ link }}</p>
|
<b-card header-tag="header" footer-tag="footer">
|
||||||
</div>
|
<template #header>
|
||||||
|
<h6 class="mb-0">{{ $t('qrCode') }}</h6>
|
||||||
|
</template>
|
||||||
|
<b-card-text><figure-qr-code class="text-center" :link="link" /></b-card-text>
|
||||||
|
<template #footer>
|
||||||
|
<em>{{ link }}</em>
|
||||||
|
</template>
|
||||||
|
</b-card>
|
||||||
|
</b-modal>
|
||||||
|
<b-modal :id="'modalPopoverCopyError' + id" ok-only hide-header-close>
|
||||||
|
<b-card header-tag="header" footer-tag="footer">
|
||||||
|
<b-card-text>
|
||||||
|
<div class="alert-danger p-3">{{ $t('gdd_per_link.not-copied') }}</div>
|
||||||
|
<div class="alert-muted h3 p-3">{{ link }}</div>
|
||||||
|
</b-card-text>
|
||||||
|
</b-card>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -74,7 +81,7 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
amount: { type: String, required: true },
|
amount: { type: String, required: true },
|
||||||
code: { type: String, required: true },
|
link: { type: String, required: true },
|
||||||
holdAvailableAmount: { type: String, required: true },
|
holdAvailableAmount: { type: String, required: true },
|
||||||
id: { type: Number, required: true },
|
id: { type: Number, required: true },
|
||||||
memo: { type: String, required: true },
|
memo: { type: String, required: true },
|
||||||
@ -88,6 +95,7 @@ export default {
|
|||||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
this.$bvModal.show('modalPopoverCopyError' + this.id)
|
||||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -115,8 +123,8 @@ export default {
|
|||||||
decay() {
|
decay() {
|
||||||
return `${this.amount - this.holdAvailableAmount}`
|
return `${this.amount - this.holdAvailableAmount}`
|
||||||
},
|
},
|
||||||
link() {
|
validLink() {
|
||||||
return `${window.location.origin}/redeem/${this.code}`
|
return new Date(this.validUntil) > new Date()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<b-row>
|
<b-row>
|
||||||
<b-col cols="5">
|
<b-col cols="5">
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
{{ diffNow ? $t('gdd_per_link.valid_until') : $t('form.date') }}
|
{{ text }}
|
||||||
</div>
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="7">
|
<b-col cols="7">
|
||||||
@ -27,6 +27,20 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
validLink: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
text() {
|
||||||
|
if (this.diffNow)
|
||||||
|
return this.validLink
|
||||||
|
? this.$t('gdd_per_link.validUntil')
|
||||||
|
: this.$t('gdd_per_link.expiredOn')
|
||||||
|
return this.$t('form.date')
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
listTransactionLinks: [
|
listTransactionLinks: [
|
||||||
{
|
{
|
||||||
amount: '75',
|
amount: '75',
|
||||||
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,
|
||||||
@ -55,7 +55,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '85',
|
amount: '85',
|
||||||
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: 107,
|
id: 107,
|
||||||
@ -65,7 +65,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '95',
|
amount: '95',
|
||||||
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: 92,
|
id: 92,
|
||||||
@ -76,7 +76,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '150',
|
amount: '150',
|
||||||
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: 16,
|
id: 16,
|
||||||
@ -132,6 +132,27 @@ describe('TransactionLinkSummary', () => {
|
|||||||
it('has no component CollapseLinksList', () => {
|
it('has no component CollapseLinksList', () => {
|
||||||
expect(wrapper.findComponent({ name: 'CollapseLinksList' }).isVisible()).toBe(false)
|
expect(wrapper.findComponent({ name: 'CollapseLinksList' }).isVisible()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('reopen transaction link details', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
wrapper.find('div.transaction-link-details').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API to get the list transaction links', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalledWith({
|
||||||
|
query: listTransactionLinks,
|
||||||
|
variables: {
|
||||||
|
currentPage: 1,
|
||||||
|
},
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has four transactionLinks', () => {
|
||||||
|
expect(wrapper.vm.transactionLinks).toHaveLength(4)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('load more transaction links', () => {
|
describe('load more transaction links', () => {
|
||||||
@ -142,7 +163,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
listTransactionLinks: [
|
listTransactionLinks: [
|
||||||
{
|
{
|
||||||
amount: '76',
|
amount: '76',
|
||||||
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,
|
||||||
@ -153,7 +174,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '86',
|
amount: '86',
|
||||||
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: 108,
|
id: 108,
|
||||||
@ -164,7 +185,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '96',
|
amount: '96',
|
||||||
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: 93,
|
id: 93,
|
||||||
@ -175,7 +196,7 @@ describe('TransactionLinkSummary', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: '150',
|
amount: '150',
|
||||||
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: 17,
|
id: 17,
|
||||||
|
|||||||
@ -83,6 +83,7 @@ export default {
|
|||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
} else {
|
} else {
|
||||||
|
this.transactionLinks = []
|
||||||
this.updateListTransactionLinks()
|
this.updateListTransactionLinks()
|
||||||
this.visible = true
|
this.visible = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export const sendCoins = gql`
|
|||||||
export const createTransactionLink = gql`
|
export const createTransactionLink = gql`
|
||||||
mutation($amount: Decimal!, $memo: String!) {
|
mutation($amount: Decimal!, $memo: String!) {
|
||||||
createTransactionLink(amount: $amount, memo: $memo) {
|
createTransactionLink(amount: $amount, memo: $memo) {
|
||||||
code
|
link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -139,7 +139,7 @@ export const listTransactionLinks = gql`
|
|||||||
amount
|
amount
|
||||||
holdAvailableAmount
|
holdAvailableAmount
|
||||||
memo
|
memo
|
||||||
code
|
link
|
||||||
createdAt
|
createdAt
|
||||||
validUntil
|
validUntil
|
||||||
redeemedAt
|
redeemedAt
|
||||||
|
|||||||
@ -102,6 +102,7 @@
|
|||||||
"decay-14-day": "Vergänglichkeit für 14 Tage",
|
"decay-14-day": "Vergänglichkeit für 14 Tage",
|
||||||
"delete-the-link": "Den Link löschen?",
|
"delete-the-link": "Den Link löschen?",
|
||||||
"deleted": "Der Link wurde gelöscht!",
|
"deleted": "Der Link wurde gelöscht!",
|
||||||
|
"expiredOn": "Abgelaufen am",
|
||||||
"has-account": "Du besitzt bereits ein Gradido Konto?",
|
"has-account": "Du besitzt bereits ein Gradido Konto?",
|
||||||
"header": "Gradidos versenden per Link",
|
"header": "Gradidos versenden per Link",
|
||||||
"isFree": "Gradido ist weltweit kostenfrei.",
|
"isFree": "Gradido ist weltweit kostenfrei.",
|
||||||
@ -113,7 +114,7 @@
|
|||||||
"links_sum": "Offene Links und QR-Codes",
|
"links_sum": "Offene Links und QR-Codes",
|
||||||
"no-account": "Du besitzt noch kein Gradido Konto?",
|
"no-account": "Du besitzt noch kein Gradido Konto?",
|
||||||
"no-redeem": "Du darfst deinen eigenen Link nicht einlösen!",
|
"no-redeem": "Du darfst deinen eigenen Link nicht einlösen!",
|
||||||
"not-copied": "Konnte den Link nicht kopieren: {err}",
|
"not-copied": "Dein Gerät lässt das Kopieren leider nicht zu! Bitte kopiere den Link von Hand!",
|
||||||
"redeem": "Einlösen",
|
"redeem": "Einlösen",
|
||||||
"redeem-text": "Willst du den Betrag jetzt einlösen?",
|
"redeem-text": "Willst du den Betrag jetzt einlösen?",
|
||||||
"redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.",
|
"redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.",
|
||||||
@ -121,7 +122,7 @@
|
|||||||
"redeemed-title": "eingelöst",
|
"redeemed-title": "eingelöst",
|
||||||
"to-login": "Log dich ein",
|
"to-login": "Log dich ein",
|
||||||
"to-register": "Registriere ein neues Konto.",
|
"to-register": "Registriere ein neues Konto.",
|
||||||
"valid_until": "Gültig bis"
|
"validUntil": "Gültig bis"
|
||||||
},
|
},
|
||||||
"gdt": {
|
"gdt": {
|
||||||
"calculation": "Berechnung der Gradido Transform",
|
"calculation": "Berechnung der Gradido Transform",
|
||||||
@ -164,6 +165,7 @@
|
|||||||
"infoText": "Wenn dir dein Empfehlungsgeber seine Publisher-Id gegeben hat, trage sie hier ein, sonst lass das Feld bitte unverändert!",
|
"infoText": "Wenn dir dein Empfehlungsgeber seine Publisher-Id gegeben hat, trage sie hier ein, sonst lass das Feld bitte unverändert!",
|
||||||
"publisherId": "Publisher-Id:"
|
"publisherId": "Publisher-Id:"
|
||||||
},
|
},
|
||||||
|
"qrCode": "QR Code",
|
||||||
"send_gdd": "GDD versenden",
|
"send_gdd": "GDD versenden",
|
||||||
"send_per_link": "GDD versenden per Link",
|
"send_per_link": "GDD versenden per Link",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
@ -102,6 +102,7 @@
|
|||||||
"decay-14-day": "Decay for 14 days",
|
"decay-14-day": "Decay for 14 days",
|
||||||
"delete-the-link": "Delete the link?",
|
"delete-the-link": "Delete the link?",
|
||||||
"deleted": "The link was deleted!",
|
"deleted": "The link was deleted!",
|
||||||
|
"expiredOn": "Expired on",
|
||||||
"has-account": "You already have a Gradido account?",
|
"has-account": "You already have a Gradido account?",
|
||||||
"header": "Send Gradidos via link",
|
"header": "Send Gradidos via link",
|
||||||
"isFree": "Gradido is free of charge worldwide.",
|
"isFree": "Gradido is free of charge worldwide.",
|
||||||
@ -113,7 +114,7 @@
|
|||||||
"links_sum": "Open links and QR codes",
|
"links_sum": "Open links and QR codes",
|
||||||
"no-account": "You don't have a Gradido account yet?",
|
"no-account": "You don't have a Gradido account yet?",
|
||||||
"no-redeem": "You not allowed to redeem your own link!",
|
"no-redeem": "You not allowed to redeem your own link!",
|
||||||
"not-copied": "Could not copy link: {err}",
|
"not-copied": "Unfortunately, your device does not allow copying! Please copy the link by hand!",
|
||||||
"redeem": "Redeem",
|
"redeem": "Redeem",
|
||||||
"redeem-text": "Do you want to redeem the amount now?",
|
"redeem-text": "Do you want to redeem the amount now?",
|
||||||
"redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.",
|
"redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.",
|
||||||
@ -121,7 +122,7 @@
|
|||||||
"redeemed-title": "redeemed",
|
"redeemed-title": "redeemed",
|
||||||
"to-login": "Log in",
|
"to-login": "Log in",
|
||||||
"to-register": "Register a new account.",
|
"to-register": "Register a new account.",
|
||||||
"valid_until": "Valid until"
|
"validUntil": "Valid until"
|
||||||
},
|
},
|
||||||
"gdt": {
|
"gdt": {
|
||||||
"calculation": "Calculation of Gradido Transform",
|
"calculation": "Calculation of Gradido Transform",
|
||||||
@ -164,6 +165,7 @@
|
|||||||
"infoText": "If your referrer has given you his publisher id, enter it here, otherwise leave the field unchanged!",
|
"infoText": "If your referrer has given you his publisher id, enter it here, otherwise leave the field unchanged!",
|
||||||
"publisherId": "PublisherID:"
|
"publisherId": "PublisherID:"
|
||||||
},
|
},
|
||||||
|
"qrCode": "QR Code",
|
||||||
"send_gdd": "GDD send",
|
"send_gdd": "GDD send",
|
||||||
"send_per_link": "GDD send via link",
|
"send_per_link": "GDD send via link",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
@ -162,7 +162,11 @@ describe('Send', () => {
|
|||||||
describe('transaction form link', () => {
|
describe('transaction form link', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
apolloMutationMock.mockResolvedValue({
|
apolloMutationMock.mockResolvedValue({
|
||||||
data: { createTransactionLink: { code: '0123456789' } },
|
data: {
|
||||||
|
createTransactionLink: {
|
||||||
|
link: 'http://localhost/redeem/0123456789',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||||
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
|
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
|
||||||
@ -249,7 +253,7 @@ describe('Send', () => {
|
|||||||
|
|
||||||
describe('close button click', () => {
|
describe('close button click', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('button').at(1).trigger('click')
|
await wrapper.findAll('button').at(2).trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Shows the TransactionForm', () => {
|
it('Shows the TransactionForm', () => {
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
></transaction-result-send-error>
|
></transaction-result-send-error>
|
||||||
</template>
|
</template>
|
||||||
<template #transactionResultLink>
|
<template #transactionResultLink>
|
||||||
<transaction-result-link :code="code" @on-reset="onReset"></transaction-result-link>
|
<transaction-result-link :link="link" @on-reset="onReset"></transaction-result-link>
|
||||||
</template>
|
</template>
|
||||||
</gdd-send>
|
</gdd-send>
|
||||||
<hr />
|
<hr />
|
||||||
@ -87,7 +87,7 @@ export default {
|
|||||||
errorResult: '',
|
errorResult: '',
|
||||||
currentTransactionStep: TRANSACTION_STEPS.transactionForm,
|
currentTransactionStep: TRANSACTION_STEPS.transactionForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
code: null,
|
link: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -144,7 +144,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.$emit('set-tunneled-email', null)
|
this.$emit('set-tunneled-email', null)
|
||||||
this.code = result.data.createTransactionLink.code
|
this.link = result.data.createTransactionLink.link
|
||||||
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
||||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionResultLink
|
this.currentTransactionStep = TRANSACTION_STEPS.transactionResultLink
|
||||||
this.updateTransactions({})
|
this.updateTransactions({})
|
||||||
|
|||||||
@ -5276,6 +5276,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.1.0:
|
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
||||||
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"
|
||||||
@ -5296,7 +5303,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==
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.7.1",
|
"version": "1.8.0",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user