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
|
||||
type: lcov
|
||||
result_path: ./backend/coverage/lcov.info
|
||||
min_coverage: 55
|
||||
min_coverage: 58
|
||||
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).
|
||||
|
||||
#### [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 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: 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)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
@ -14,7 +14,7 @@
|
||||
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
|
||||
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
|
||||
"test": "TZ=UTC jest --coverage",
|
||||
"test": "cross-env TZ=UTC jest --coverage",
|
||||
"locales": "scripts/sort.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -57,6 +57,7 @@
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-transform-require-context": "^0.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "7.25.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
|
||||
@ -3,7 +3,7 @@ import gql from 'graphql-tag'
|
||||
export const createPendingCreation = gql`
|
||||
mutation (
|
||||
$email: String!
|
||||
$amount: Float!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$creationDate: String!
|
||||
$moderator: Int!
|
||||
|
||||
@ -4,7 +4,7 @@ export const updatePendingCreation = gql`
|
||||
mutation (
|
||||
$id: Int!
|
||||
$email: String!
|
||||
$amount: Float!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$creationDate: String!
|
||||
$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"
|
||||
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:
|
||||
version "5.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
CONFIG_VERSION=v5.2022-04-12
|
||||
CONFIG_VERSION=v6.2022-04-21
|
||||
|
||||
# Server
|
||||
PORT=4000
|
||||
@ -27,6 +27,7 @@ KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
|
||||
COMMUNITY_NAME=Gradido Entwicklung
|
||||
COMMUNITY_URL=http://localhost/
|
||||
COMMUNITY_REGISTER_URL=http://localhost/register
|
||||
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
|
||||
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
||||
|
||||
# Login Server
|
||||
|
||||
@ -26,6 +26,7 @@ KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
|
||||
COMMUNITY_NAME=$COMMUNITY_NAME
|
||||
COMMUNITY_URL=$COMMUNITY_URL
|
||||
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
|
||||
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
|
||||
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||
|
||||
# Login Server
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -10,11 +10,11 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"clean": "tsc --build --clean",
|
||||
"start": "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",
|
||||
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
|
||||
"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 .",
|
||||
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"seed": "TZ=UTC ts-node -r tsconfig-paths/register src/seeds/index.ts"
|
||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
@ -25,6 +25,7 @@
|
||||
"axios": "^0.21.1",
|
||||
"class-validator": "^0.13.1",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@ -10,11 +10,11 @@ Decimal.set({
|
||||
})
|
||||
|
||||
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
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v5.2022-04-12',
|
||||
EXPECTED: 'v6.2022-04-21',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
@ -50,6 +50,7 @@ const community = {
|
||||
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
||||
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
|
||||
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:
|
||||
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()
|
||||
@ArgsType()
|
||||
@ -6,8 +7,8 @@ export default class CreatePendingCreationArgs {
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
@Field(() => Float)
|
||||
amount: number
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => 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()
|
||||
export default class UpdatePendingCreationArgs {
|
||||
@ -8,8 +9,8 @@ export default class UpdatePendingCreationArgs {
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
@Field(() => Float)
|
||||
amount: number
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@ -8,7 +8,6 @@ import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { UserRepository } from '@repository/User'
|
||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||
import { ServerUser } from '@entity/ServerUser'
|
||||
|
||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||
@ -36,8 +35,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
try {
|
||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
context.user = user
|
||||
const countServerUsers = await ServerUser.count({ email: user.email })
|
||||
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
|
||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||
} catch {
|
||||
// in case the database query fails (user deleted)
|
||||
throw new Error('401 Unauthorized')
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
export class PendingCreation {
|
||||
@ -23,12 +24,12 @@ export class PendingCreation {
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => Number)
|
||||
amount: number
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => Number)
|
||||
moderator: number
|
||||
|
||||
@Field(() => [Number])
|
||||
creation: number[]
|
||||
@Field(() => [Decimal])
|
||||
creation: Decimal[]
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User } from './User'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
@ObjectType()
|
||||
export class TransactionLink {
|
||||
@ -17,6 +18,7 @@ export class TransactionLink {
|
||||
this.deletedAt = transactionLink.deletedAt
|
||||
this.redeemedAt = transactionLink.redeemedAt
|
||||
this.redeemedBy = redeemedBy
|
||||
this.link = CONFIG.COMMUNITY_REDEEM_URL.replace(/{code}/g, this.code)
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@ -51,6 +53,9 @@ export class TransactionLink {
|
||||
|
||||
@Field(() => User, { nullable: true })
|
||||
redeemedBy: User | null
|
||||
|
||||
@Field(() => String)
|
||||
link: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
export class UpdatePendingCreation {
|
||||
@ -8,12 +9,12 @@ export class UpdatePendingCreation {
|
||||
@Field(() => String)
|
||||
memo: string
|
||||
|
||||
@Field(() => Number)
|
||||
amount: number
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => Number)
|
||||
moderator: number
|
||||
|
||||
@Field(() => [Number])
|
||||
creation: number[]
|
||||
@Field(() => [Decimal])
|
||||
creation: Decimal[]
|
||||
}
|
||||
|
||||
@ -14,8 +14,8 @@ export class User {
|
||||
this.emailChecked = user.emailChecked
|
||||
this.language = user.language
|
||||
this.publisherId = user.publisherId
|
||||
this.isAdmin = user.isAdmin
|
||||
// TODO
|
||||
this.isAdmin = null
|
||||
this.coinanimation = null
|
||||
this.klickTipp = null
|
||||
this.hasElopage = null
|
||||
@ -58,11 +58,11 @@ export class User {
|
||||
|
||||
// `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
|
||||
// therefore all those fields are now nullable with default value null
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
isAdmin: boolean | null
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
coinanimation: boolean | null
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { User } from '@entity/User'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
@ObjectType()
|
||||
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.email = user.email
|
||||
this.firstName = user.firstName
|
||||
@ -27,8 +28,8 @@ export class UserAdmin {
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => [Number])
|
||||
creation: number[]
|
||||
@Field(() => [Decimal])
|
||||
creation: Decimal[]
|
||||
|
||||
@Field(() => Boolean)
|
||||
emailChecked: boolean
|
||||
|
||||
@ -43,7 +43,7 @@ import CONFIG from '@/config'
|
||||
|
||||
// const EMAIL_OPT_IN_REGISTER = 1
|
||||
// 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]
|
||||
|
||||
@Resolver()
|
||||
@ -170,7 +170,7 @@ export class AdminResolver {
|
||||
@Mutation(() => [Number])
|
||||
async createPendingCreation(
|
||||
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
||||
): Promise<number[]> {
|
||||
): Promise<Decimal[]> {
|
||||
const user = await dbUser.findOne({ email }, { withDeleted: true })
|
||||
if (!user) {
|
||||
throw new Error(`Could not find user with email: ${email}`)
|
||||
@ -186,7 +186,7 @@ export class AdminResolver {
|
||||
if (isCreationValid(creations, amount, creationDateObj)) {
|
||||
const adminPendingCreation = AdminPendingCreation.create()
|
||||
adminPendingCreation.userId = user.id
|
||||
adminPendingCreation.amount = BigInt(amount)
|
||||
adminPendingCreation.amount = amount
|
||||
adminPendingCreation.created = new Date()
|
||||
adminPendingCreation.date = creationDateObj
|
||||
adminPendingCreation.memo = memo
|
||||
@ -251,14 +251,14 @@ export class AdminResolver {
|
||||
if (!isCreationValid(creations, amount, creationDateObj)) {
|
||||
throw new Error('Creation is not valid')
|
||||
}
|
||||
pendingCreationToUpdate.amount = BigInt(amount)
|
||||
pendingCreationToUpdate.amount = amount
|
||||
pendingCreationToUpdate.memo = memo
|
||||
pendingCreationToUpdate.date = new Date(creationDate)
|
||||
pendingCreationToUpdate.moderator = moderator
|
||||
|
||||
await AdminPendingCreation.save(pendingCreationToUpdate)
|
||||
const result = new UpdatePendingCreation()
|
||||
result.amount = parseInt(amount.toString())
|
||||
result.amount = amount
|
||||
result.memo = pendingCreationToUpdate.memo
|
||||
result.date = pendingCreationToUpdate.date
|
||||
result.moderator = pendingCreationToUpdate.moderator
|
||||
@ -286,7 +286,7 @@ export class AdminResolver {
|
||||
|
||||
return {
|
||||
...pendingCreation,
|
||||
amount: Number(pendingCreation.amount.toString()),
|
||||
amount: pendingCreation.amount,
|
||||
firstName: user ? user.firstName : '',
|
||||
lastName: user ? user.lastName : '',
|
||||
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.')
|
||||
|
||||
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!!')
|
||||
}
|
||||
|
||||
@ -333,8 +333,7 @@ export class AdminResolver {
|
||||
decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, receivedCallDate)
|
||||
newBalance = decay.balance
|
||||
}
|
||||
// TODO pending creations decimal
|
||||
newBalance = newBalance.add(new Decimal(Number(pendingCreation.amount)).toString())
|
||||
newBalance = newBalance.add(pendingCreation.amount.toString())
|
||||
|
||||
const transaction = new DbTransaction()
|
||||
transaction.typeId = TransactionTypeId.CREATION
|
||||
@ -448,10 +447,10 @@ export class AdminResolver {
|
||||
|
||||
interface CreationMap {
|
||||
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)
|
||||
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[] }) =>
|
||||
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())
|
||||
|
||||
if (index < 0) {
|
||||
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
|
||||
}
|
||||
|
||||
function isCreationValid(creations: number[], amount: number, creationDate: Date) {
|
||||
function isCreationValid(creations: Decimal[], amount: Decimal, creationDate: Date) {
|
||||
const index = getCreationIndex(creationDate.getMonth())
|
||||
|
||||
if (index < 0) {
|
||||
throw new Error(`No Creation found!`)
|
||||
}
|
||||
|
||||
if (amount > creations[index]) {
|
||||
if (amount.greaterThan(creations[index].toString())) {
|
||||
throw new Error(
|
||||
`The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`,
|
||||
)
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* 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 { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
||||
import { login, logout } from '@/seeds/graphql/queries'
|
||||
import { createUser, setPassword, forgotPassword, updateUserInfos } from '@/seeds/graphql/mutations'
|
||||
import { login, logout, verifyLogin, queryOptIn } from '@/seeds/graphql/queries'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { User } from '@entity/User'
|
||||
import CONFIG from '@/config'
|
||||
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||
import { printTimeDuration } from './UserResolver'
|
||||
import { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail'
|
||||
import { printTimeDuration, activationLink } from './UserResolver'
|
||||
|
||||
// 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', () => {
|
||||
return {
|
||||
@ -85,7 +93,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('filling all tables', () => {
|
||||
it('saves the user in login_user table', () => {
|
||||
it('saves the user in users table', () => {
|
||||
expect(user).toEqual([
|
||||
{
|
||||
id: expect.any(Number),
|
||||
@ -100,6 +108,7 @@ describe('UserResolver', () => {
|
||||
emailChecked: false,
|
||||
passphrase: expect.any(String),
|
||||
language: 'de',
|
||||
isAdmin: null,
|
||||
deletedAt: null,
|
||||
publisherId: 1234,
|
||||
referrerId: null,
|
||||
@ -336,7 +345,7 @@ describe('UserResolver', () => {
|
||||
firstName: 'Bibi',
|
||||
hasElopage: false,
|
||||
id: expect.any(Number),
|
||||
isAdmin: false,
|
||||
isAdmin: null,
|
||||
klickTipp: {
|
||||
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', () => {
|
||||
|
||||
@ -19,9 +19,7 @@ import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer
|
||||
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||
import { klicktippSignIn } from '@/apis/KlicktippController'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { ROLE_ADMIN } from '@/auth/ROLES'
|
||||
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
||||
import { ServerUser } from '@entity/ServerUser'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sodium = require('sodium-native')
|
||||
@ -207,7 +205,6 @@ export class UserResolver {
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
|
||||
user.isAdmin = context.role === ROLE_ADMIN
|
||||
return user
|
||||
}
|
||||
|
||||
@ -243,16 +240,11 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
const user = new User(dbUser)
|
||||
// user.email = email
|
||||
// user.pubkey = dbUser.pubKey.toString('hex')
|
||||
user.language = dbUser.language
|
||||
|
||||
// Elopage Status & Stored PublisherId
|
||||
user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
|
||||
if (!user.hasElopage && publisherId) {
|
||||
user.publisherId = publisherId
|
||||
// TODO: Check if we can use updateUserInfos
|
||||
// await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
|
||||
dbUser.publisherId = publisherId
|
||||
DbUser.save(dbUser)
|
||||
}
|
||||
@ -266,10 +258,6 @@ export class UserResolver {
|
||||
})
|
||||
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({
|
||||
key: 'token',
|
||||
value: encode(dbUser.pubKey),
|
||||
@ -529,15 +517,7 @@ export class UserResolver {
|
||||
@Mutation(() => Boolean)
|
||||
async updateUserInfos(
|
||||
@Args()
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
language,
|
||||
publisherId,
|
||||
password,
|
||||
passwordNew,
|
||||
coinanimation,
|
||||
}: UpdateUserInfosArgs,
|
||||
{ firstName, lastName, language, password, passwordNew, coinanimation }: UpdateUserInfosArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const userEntity = getUser(context)
|
||||
@ -581,11 +561,6 @@ export class UserResolver {
|
||||
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()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
||||
import { User } from '@entity/User'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { ServerUser } from '@entity/ServerUser'
|
||||
import { UserInterface } from '@/seeds/users/UserInterface'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -29,23 +28,9 @@ export const userFactory = async (
|
||||
// get user from database
|
||||
const dbUser = await User.findOneOrFail({ id })
|
||||
|
||||
if (user.createdAt || user.deletedAt) {
|
||||
if (user.createdAt) dbUser.createdAt = user.createdAt
|
||||
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
|
||||
if (user.isAdmin) dbUser.isAdmin = new Date()
|
||||
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`
|
||||
mutation (
|
||||
$firstName: String
|
||||
@ -80,7 +86,7 @@ export const createTransactionLink = gql`
|
||||
export const createPendingCreation = gql`
|
||||
mutation (
|
||||
$email: String!
|
||||
$amount: Float!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$creationDate: String!
|
||||
$moderator: Int!
|
||||
|
||||
@ -43,6 +43,12 @@ export const logout = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const queryOptIn = gql`
|
||||
query ($optIn: String!) {
|
||||
queryOptIn(optIn: $optIn)
|
||||
}
|
||||
`
|
||||
|
||||
export const transactionsQuery = gql`
|
||||
query (
|
||||
$currentPage: Int = 1
|
||||
|
||||
@ -17,6 +17,7 @@ const communityDbUser: dbUser = {
|
||||
createdAt: new Date(),
|
||||
emailChecked: false,
|
||||
language: '',
|
||||
isAdmin: null,
|
||||
publisherId: 0,
|
||||
passphrase: '',
|
||||
settings: [],
|
||||
|
||||
@ -70,6 +70,7 @@ const virtualDecayTransaction = (
|
||||
typeId: TransactionTypeId.DECAY,
|
||||
amount: decay.decay ? decay.roundedDecay : new Decimal(0),
|
||||
balance: decay.balance
|
||||
.toDecimalPlaces(2, Decimal.ROUND_DOWN)
|
||||
.minus(holdAvailabeAmount.toString())
|
||||
.toDecimalPlaces(2, Decimal.ROUND_DOWN),
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
|
||||
@ -30,4 +30,3 @@ yarn dev_down
|
||||
yarn dev_reset
|
||||
```
|
||||
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 { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||
import { Migration } from './Migration'
|
||||
import { ServerUser } from './ServerUser'
|
||||
import { Transaction } from './Transaction'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
import { User } from './User'
|
||||
@ -13,7 +12,6 @@ export const entities = [
|
||||
LoginElopageBuys,
|
||||
LoginEmailOptIn,
|
||||
Migration,
|
||||
ServerUser,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
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",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
@ -10,15 +10,15 @@
|
||||
"scripts": {
|
||||
"build": "mkdir -p build/src/config/ && cp src/config/*.txt build/src/config/ && tsc --build",
|
||||
"clean": "tsc --build --clean",
|
||||
"up": "TZ=UTC node build/src/index.js up",
|
||||
"down": "TZ=UTC node build/src/index.js down",
|
||||
"reset": "TZ=UTC node build/src/index.js reset",
|
||||
"dev_up": "TZ=UTC ts-node src/index.ts up",
|
||||
"dev_down": "TZ=UTC ts-node src/index.ts down",
|
||||
"dev_reset": "TZ=UTC ts-node src/index.ts reset",
|
||||
"up": "cross-env TZ=UTC node build/src/index.js up",
|
||||
"down": "cross-env TZ=UTC node build/src/index.js down",
|
||||
"reset": "cross-env TZ=UTC node build/src/index.js reset",
|
||||
"dev_up": "cross-env TZ=UTC ts-node src/index.ts up",
|
||||
"dev_down": "cross-env TZ=UTC ts-node src/index.ts down",
|
||||
"dev_reset": "cross-env TZ=UTC ts-node src/index.ts reset",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
||||
"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": {
|
||||
"@types/faker": "^5.5.9",
|
||||
@ -37,6 +37,7 @@
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto": "^1.0.1",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
|
||||
@ -21,10 +21,11 @@ WEBHOOK_GITHUB_BRANCH=master
|
||||
COMMUNITY_NAME="Gradido Development Stage1"
|
||||
COMMUNITY_URL=https://stage1.gradido.net/
|
||||
COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
|
||||
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
|
||||
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
||||
|
||||
# backend
|
||||
BACKEND_CONFIG_VERSION=v5.2022-04-12
|
||||
BACKEND_CONFIG_VERSION=v6.2022-04-21
|
||||
|
||||
JWT_EXPIRES_IN=30m
|
||||
GDT_API_URL=https://gdt.gradido.net
|
||||
@ -37,7 +38,7 @@ KLICKTIPP_PASSWORD=
|
||||
KLICKTIPP_APIKEY_DE=
|
||||
KLICKTIPP_APIKEY_EN=
|
||||
|
||||
EMAIL=true
|
||||
EMAIL=false
|
||||
EMAIL_USERNAME=peter@lustig.de
|
||||
EMAIL_SENDER=peter@lustig.de
|
||||
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",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
@ -10,7 +10,7 @@
|
||||
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
|
||||
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
|
||||
"test": "TZ=UTC jest --coverage",
|
||||
"test": "cross-env TZ=UTC jest --coverage",
|
||||
"locales": "scripts/sort.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -72,6 +72,7 @@
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-component": "^1.1.0",
|
||||
"babel-plugin-transform-require-context": "^0.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"postcss": "^8.4.8",
|
||||
"postcss-html": "^1.3.0",
|
||||
|
||||
@ -1,32 +1,50 @@
|
||||
<template>
|
||||
<div class="clipboard-copy">
|
||||
<b-input-group size="lg" class="mb-3" prepend="Link">
|
||||
<b-form-input :value="text" type="text" readonly></b-form-input>
|
||||
<b-input-group v-if="canCopyLink" size="lg" class="mb-3" prepend="Link">
|
||||
<b-form-input :value="link" type="text" readonly></b-form-input>
|
||||
<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') }}
|
||||
</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>
|
||||
<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>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClipboardCopy',
|
||||
props: {
|
||||
text: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canCopyLink: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
CopyLink() {
|
||||
navigator.clipboard
|
||||
.writeText(this.url)
|
||||
.writeText(this.link)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.canCopyLink = false
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.svg {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -17,6 +17,7 @@ const propsData = {
|
||||
{
|
||||
amount: '5',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 87,
|
||||
@ -27,6 +28,7 @@ const propsData = {
|
||||
{
|
||||
amount: '6',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 86,
|
||||
|
||||
@ -3,13 +3,12 @@
|
||||
<b-col>
|
||||
<b-card class="p-0 gradido-custom-background">
|
||||
<div class="h3 mb-4">{{ $t('gdd_per_link.created') }}</div>
|
||||
|
||||
<clipboard-copy :text="link" />
|
||||
<clipboard-copy :link="link" @show-qr-code-button="showQrCodeButton" />
|
||||
|
||||
<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') }}
|
||||
</b-button>
|
||||
</div>
|
||||
@ -28,14 +27,19 @@ export default {
|
||||
FigureQrCode,
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
link() {
|
||||
return `${window.location.origin}/redeem/${this.code}`
|
||||
data() {
|
||||
return {
|
||||
showQrcode: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showQrCodeButton() {
|
||||
this.showQrcode = !this.showQrcode
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
<div v-else>{{ errorResult }}</div>
|
||||
</div>
|
||||
<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>
|
||||
</b-card>
|
||||
</b-col>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{{ $t('form.send_transaction_success') }}
|
||||
</div>
|
||||
<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>
|
||||
</b-card>
|
||||
</b-col>
|
||||
|
||||
@ -4,7 +4,7 @@ import FigureQrCode from './FigureQrCode'
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
text: '',
|
||||
link: '',
|
||||
}
|
||||
|
||||
describe('FigureQrCode', () => {
|
||||
|
||||
@ -14,14 +14,14 @@ export default {
|
||||
QRCanvas,
|
||||
},
|
||||
props: {
|
||||
text: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
cellSize: 8,
|
||||
correctLevel: 'H',
|
||||
data: this.text,
|
||||
data: this.link,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@ -22,7 +22,7 @@ const mocks = {
|
||||
|
||||
const propsData = {
|
||||
amount: '75',
|
||||
code: 'c00000000c000000c0000',
|
||||
link: 'http://localhost/redeem/c00000000c000000c0000',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 12,
|
||||
memo: 'Katzenauge, Eulenschrei, was verschwunden komm herbei!',
|
||||
@ -44,6 +44,29 @@ describe('TransactionLink', () => {
|
||||
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', () => {
|
||||
const navigatorClipboard = navigator.clipboard
|
||||
beforeAll(() => {
|
||||
@ -57,8 +80,9 @@ describe('TransactionLink', () => {
|
||||
describe('copy with success', () => {
|
||||
beforeEach(async () => {
|
||||
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', () => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
'http://localhost/redeem/c00000000c000000c0000',
|
||||
@ -73,7 +97,7 @@ describe('TransactionLink', () => {
|
||||
describe('qr code modal', () => {
|
||||
let spy
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -82,10 +106,10 @@ describe('TransactionLink', () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'show')
|
||||
// spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
@ -94,7 +118,7 @@ describe('TransactionLink', () => {
|
||||
describe('delete link', () => {
|
||||
let spy
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -103,10 +127,10 @@ describe('TransactionLink', () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
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()
|
||||
})
|
||||
|
||||
@ -135,7 +159,7 @@ describe('TransactionLink', () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
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', () => {
|
||||
@ -148,7 +172,7 @@ describe('TransactionLink', () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(false))
|
||||
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', () => {
|
||||
@ -157,4 +181,5 @@ describe('TransactionLink', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,55 +1,62 @@
|
||||
<template>
|
||||
<div class="transaction-link gradido-custom-background">
|
||||
<b-row class="mb-2 pt-2 pb-2">
|
||||
<b-col lg="2">
|
||||
<b-row :class="validLink ? '' : 'bg-muted text-light'" class="mb-2 pt-2 pb-2">
|
||||
<b-col cols="1">
|
||||
<type-icon color="text-danger" icon="link45deg" class="pt-4 pl-2" />
|
||||
</b-col>
|
||||
<b-col lg="9" md="9">
|
||||
<b-col cols="11">
|
||||
<b-row>
|
||||
<b-col lg="11" md="10">
|
||||
<b-col>
|
||||
<amount-and-name-row :amount="amount" :text="$t('form.amount')" />
|
||||
<memo-row :memo="memo" />
|
||||
<date-row :date="validUntil" :diffNow="true" />
|
||||
<date-row :date="validUntil" :diffNow="true" :validLink="validLink" />
|
||||
<decay-row :decay="decay" />
|
||||
</b-col>
|
||||
<b-col lg="1" md="2" class="text-center text-lg-left qr-button">
|
||||
<b-button
|
||||
@click="$bvModal.show('modalPopover-' + id)"
|
||||
class="p-2 test-qr-code"
|
||||
size="sm"
|
||||
>
|
||||
<b-img src="img/svg/qr-code.svg" width="60" class="filter"></b-img>
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" lg="1" md="1" class="text-center text-md-right pr-5 pr-lg-4">
|
||||
<b-dropdown no-caret right aria-expanded="false" size="sm">
|
||||
<template #button-content>
|
||||
<b-icon icon="three-dots-vertical"></b-icon>
|
||||
</template>
|
||||
|
||||
<b-col lg="1" md="1" class="text-center text-lg-right">
|
||||
<b-button
|
||||
class="p-2 test-copy-link"
|
||||
size="lg"
|
||||
variant="outline-primary"
|
||||
@click="copy"
|
||||
:title="$t('gdd_per_link.copy')"
|
||||
>
|
||||
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copy">
|
||||
<b-icon icon="clipboard"></b-icon>
|
||||
</b-button>
|
||||
<br />
|
||||
<b-button
|
||||
class="p-2 mt-3 test-delete-link"
|
||||
size="sm"
|
||||
@click="deleteLink()"
|
||||
:title="$t('delete')"
|
||||
{{ $t('gdd_per_link.copy') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-if="validLink"
|
||||
@click="$bvModal.show('modalPopover-' + id)"
|
||||
class="pt-3 pb-3 test-qr-code"
|
||||
>
|
||||
<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-button>
|
||||
{{ $t('delete') }}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-modal :id="'modalPopover-' + id" title="QR-Code" ok-only hide-header-close>
|
||||
<div class="text-center">
|
||||
<figure-qr-code :text="link" />
|
||||
<p>{{ link }}</p>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-modal :id="'modalPopover-' + id" ok-only hide-header-close>
|
||||
<b-card header-tag="header" footer-tag="footer">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
@ -74,7 +81,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
amount: { type: String, required: true },
|
||||
code: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
holdAvailableAmount: { type: String, required: true },
|
||||
id: { type: Number, required: true },
|
||||
memo: { type: String, required: true },
|
||||
@ -88,6 +95,7 @@ export default {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$bvModal.show('modalPopoverCopyError' + this.id)
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
@ -115,8 +123,8 @@ export default {
|
||||
decay() {
|
||||
return `${this.amount - this.holdAvailableAmount}`
|
||||
},
|
||||
link() {
|
||||
return `${window.location.origin}/redeem/${this.code}`
|
||||
validLink() {
|
||||
return new Date(this.validUntil) > new Date()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<b-row>
|
||||
<b-col cols="5">
|
||||
<div class="text-right">
|
||||
{{ diffNow ? $t('gdd_per_link.valid_until') : $t('form.date') }}
|
||||
{{ text }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col cols="7">
|
||||
@ -27,6 +27,20 @@ export default {
|
||||
required: 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>
|
||||
|
||||
@ -44,7 +44,7 @@ describe('TransactionLinkSummary', () => {
|
||||
listTransactionLinks: [
|
||||
{
|
||||
amount: '75',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 86,
|
||||
@ -55,7 +55,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '85',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 107,
|
||||
@ -65,7 +65,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '95',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 92,
|
||||
@ -76,7 +76,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '150',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 16,
|
||||
@ -132,6 +132,27 @@ describe('TransactionLinkSummary', () => {
|
||||
it('has no component CollapseLinksList', () => {
|
||||
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', () => {
|
||||
@ -142,7 +163,7 @@ describe('TransactionLinkSummary', () => {
|
||||
listTransactionLinks: [
|
||||
{
|
||||
amount: '76',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 87,
|
||||
@ -153,7 +174,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '86',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 108,
|
||||
@ -164,7 +185,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '96',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 93,
|
||||
@ -175,7 +196,7 @@ describe('TransactionLinkSummary', () => {
|
||||
},
|
||||
{
|
||||
amount: '150',
|
||||
code: 'ce28664b5308c17f931c0367',
|
||||
link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
|
||||
createdAt: '2022-03-16T14:22:40.000Z',
|
||||
holdAvailableAmount: '5.13109484759482747111',
|
||||
id: 17,
|
||||
|
||||
@ -83,6 +83,7 @@ export default {
|
||||
if (this.visible) {
|
||||
this.visible = false
|
||||
} else {
|
||||
this.transactionLinks = []
|
||||
this.updateListTransactionLinks()
|
||||
this.visible = true
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ export const sendCoins = gql`
|
||||
export const createTransactionLink = gql`
|
||||
mutation($amount: Decimal!, $memo: String!) {
|
||||
createTransactionLink(amount: $amount, memo: $memo) {
|
||||
code
|
||||
link
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -139,7 +139,7 @@ export const listTransactionLinks = gql`
|
||||
amount
|
||||
holdAvailableAmount
|
||||
memo
|
||||
code
|
||||
link
|
||||
createdAt
|
||||
validUntil
|
||||
redeemedAt
|
||||
|
||||
@ -102,6 +102,7 @@
|
||||
"decay-14-day": "Vergänglichkeit für 14 Tage",
|
||||
"delete-the-link": "Den Link löschen?",
|
||||
"deleted": "Der Link wurde gelöscht!",
|
||||
"expiredOn": "Abgelaufen am",
|
||||
"has-account": "Du besitzt bereits ein Gradido Konto?",
|
||||
"header": "Gradidos versenden per Link",
|
||||
"isFree": "Gradido ist weltweit kostenfrei.",
|
||||
@ -113,7 +114,7 @@
|
||||
"links_sum": "Offene Links und QR-Codes",
|
||||
"no-account": "Du besitzt noch kein Gradido Konto?",
|
||||
"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-text": "Willst du den Betrag jetzt einlösen?",
|
||||
"redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.",
|
||||
@ -121,7 +122,7 @@
|
||||
"redeemed-title": "eingelöst",
|
||||
"to-login": "Log dich ein",
|
||||
"to-register": "Registriere ein neues Konto.",
|
||||
"valid_until": "Gültig bis"
|
||||
"validUntil": "Gültig bis"
|
||||
},
|
||||
"gdt": {
|
||||
"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!",
|
||||
"publisherId": "Publisher-Id:"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
"send_gdd": "GDD versenden",
|
||||
"send_per_link": "GDD versenden per Link",
|
||||
"settings": {
|
||||
|
||||
@ -102,6 +102,7 @@
|
||||
"decay-14-day": "Decay for 14 days",
|
||||
"delete-the-link": "Delete the link?",
|
||||
"deleted": "The link was deleted!",
|
||||
"expiredOn": "Expired on",
|
||||
"has-account": "You already have a Gradido account?",
|
||||
"header": "Send Gradidos via link",
|
||||
"isFree": "Gradido is free of charge worldwide.",
|
||||
@ -113,7 +114,7 @@
|
||||
"links_sum": "Open links and QR codes",
|
||||
"no-account": "You don't have a Gradido account yet?",
|
||||
"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-text": "Do you want to redeem the amount now?",
|
||||
"redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.",
|
||||
@ -121,7 +122,7 @@
|
||||
"redeemed-title": "redeemed",
|
||||
"to-login": "Log in",
|
||||
"to-register": "Register a new account.",
|
||||
"valid_until": "Valid until"
|
||||
"validUntil": "Valid until"
|
||||
},
|
||||
"gdt": {
|
||||
"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!",
|
||||
"publisherId": "PublisherID:"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
"send_gdd": "GDD send",
|
||||
"send_per_link": "GDD send via link",
|
||||
"settings": {
|
||||
|
||||
@ -162,7 +162,11 @@ describe('Send', () => {
|
||||
describe('transaction form link', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: { createTransactionLink: { code: '0123456789' } },
|
||||
data: {
|
||||
createTransactionLink: {
|
||||
link: 'http://localhost/redeem/0123456789',
|
||||
},
|
||||
},
|
||||
})
|
||||
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||
await transactionForm.findAll('input[type="radio"]').at(1).setChecked()
|
||||
@ -249,7 +253,7 @@ describe('Send', () => {
|
||||
|
||||
describe('close button click', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('button').at(1).trigger('click')
|
||||
await wrapper.findAll('button').at(2).trigger('click')
|
||||
})
|
||||
|
||||
it('Shows the TransactionForm', () => {
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
></transaction-result-send-error>
|
||||
</template>
|
||||
<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>
|
||||
</gdd-send>
|
||||
<hr />
|
||||
@ -87,7 +87,7 @@ export default {
|
||||
errorResult: '',
|
||||
currentTransactionStep: TRANSACTION_STEPS.transactionForm,
|
||||
loading: false,
|
||||
code: null,
|
||||
link: null,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -144,7 +144,7 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('set-tunneled-email', null)
|
||||
this.code = result.data.createTransactionLink.code
|
||||
this.link = result.data.createTransactionLink.link
|
||||
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionResultLink
|
||||
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"
|
||||
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:
|
||||
version "5.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user