mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge remote-tracking branch 'origin/master' into
1798-feature-gradidoid-1-adapt-and-migrate-database-schema
This commit is contained in:
commit
e946d96efe
47
CHANGELOG.md
47
CHANGELOG.md
@ -4,8 +4,55 @@ 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.11.0](https://github.com/gradido/gradido/compare/1.10.1...1.11.0)
|
||||
|
||||
- Fix navbar community item [`#2102`](https://github.com/gradido/gradido/pull/2102)
|
||||
- Add validation date info to copied text after transaction link creation [`#2101`](https://github.com/gradido/gradido/pull/2101)
|
||||
- Remove member area from menu (desktop and mobile), when user has no elopage account [`#2099`](https://github.com/gradido/gradido/pull/2099)
|
||||
- fix: Use Inner Join for Contribution and User [`#2100`](https://github.com/gradido/gradido/pull/2100)
|
||||
- Enable copying the link, username, amount, and memo text after transaction link creation [`#2098`](https://github.com/gradido/gradido/pull/2098)
|
||||
- feat: Insert Missing Contributions Migration [`#2053`](https://github.com/gradido/gradido/pull/2053)
|
||||
- [Bug] Wallet improvments for Contributions [`#2090`](https://github.com/gradido/gradido/pull/2090)
|
||||
- [Fix] Add createdAt & contributionDate to ContributionListItems [`#2093`](https://github.com/gradido/gradido/pull/2093)
|
||||
- fix: 🍰 Reset Amount In Contribution Form And Write A Test [`#2086`](https://github.com/gradido/gradido/pull/2086)
|
||||
- [Feat] Replace logic to validation-provider. [`#2088`](https://github.com/gradido/gradido/pull/2088)
|
||||
- fix: Add Confirm Dialog on Delete Contribution [`#2087`](https://github.com/gradido/gradido/pull/2087)
|
||||
- fix: Admin Cannot Edit User Contribution [`#2085`](https://github.com/gradido/gradido/pull/2085)
|
||||
- fix: Update contribution_date when Moved by Seed [`#2083`](https://github.com/gradido/gradido/pull/2083)
|
||||
- chore: 🍰 Provide Volume For Backend Log-Files In Docker [`#2067`](https://github.com/gradido/gradido/pull/2067)
|
||||
- feat: 🍰 Community Contribution Site And Form [`#2042`](https://github.com/gradido/gradido/pull/2042)
|
||||
- [Refactor] Move MEMO_MIN_CHARS and MEMO_MAX_CHARS to const file [`#2082`](https://github.com/gradido/gradido/pull/2082)
|
||||
- Fix: Test memo length on createContribution & updateContribution [`#2080`](https://github.com/gradido/gradido/pull/2080)
|
||||
- chore: 🍰 Change `image` Entries In Docker Compose Files And Get Apple M1 Running [`#2050`](https://github.com/gradido/gradido/pull/2050)
|
||||
- fix: Windows 0D 0A Linebreaks to Unix 0A [`#2064`](https://github.com/gradido/gradido/pull/2064)
|
||||
- Add contributionDate to the Contribution object. [`#2066`](https://github.com/gradido/gradido/pull/2066)
|
||||
- fix: Add Contributions to User [`#2062`](https://github.com/gradido/gradido/pull/2062)
|
||||
- Feat: ContributionResolver - delete mutation [`#2035`](https://github.com/gradido/gradido/pull/2035)
|
||||
- Fix: Add count to list contributions [`#2061`](https://github.com/gradido/gradido/pull/2061)
|
||||
- docu: Explain how `.env` Files are Working [`#2022`](https://github.com/gradido/gradido/pull/2022)
|
||||
- [WIP] 1794 feature event protocol 1 implement the basics of the business event protocol [`#1997`](https://github.com/gradido/gradido/pull/1997)
|
||||
- docs: 🍰 Document The Setup Of The GraphQL Playground [`#2060`](https://github.com/gradido/gradido/pull/2060)
|
||||
- Feat: List all contribution [`#2057`](https://github.com/gradido/gradido/pull/2057)
|
||||
- feat: Do not log IntrospectionQuery from Query Browser [`#2059`](https://github.com/gradido/gradido/pull/2059)
|
||||
- Feat: Add confirmedBy and confirmedAt for the contribution query. [`#2052`](https://github.com/gradido/gradido/pull/2052)
|
||||
- Add open creations to webapp [`#2048`](https://github.com/gradido/gradido/pull/2048)
|
||||
- Prevent session expiration modal from displaying negative seconds, when session is expired for more than 0 seconds [`#2054`](https://github.com/gradido/gradido/pull/2054)
|
||||
- feat: mutation contribution update [`#2032`](https://github.com/gradido/gradido/pull/2032)
|
||||
- feat: Login Returns Open Creations for User [`#2046`](https://github.com/gradido/gradido/pull/2046)
|
||||
- Migrate transaction to valid dataset for gradido node [`#2029`](https://github.com/gradido/gradido/pull/2029)
|
||||
- feat: implement contribution list query [`#2031`](https://github.com/gradido/gradido/pull/2031)
|
||||
- add code for moving user creation date if transaction before exist [`#2034`](https://github.com/gradido/gradido/pull/2034)
|
||||
- change text from page [`#2037`](https://github.com/gradido/gradido/pull/2037)
|
||||
- Transaction link: copy link, text and more [`#2030`](https://github.com/gradido/gradido/pull/2030)
|
||||
- change welcome in community text [`#2025`](https://github.com/gradido/gradido/pull/2025)
|
||||
- changed link color in navbar and language switch [`#2024`](https://github.com/gradido/gradido/pull/2024)
|
||||
- feat: ContributionResolver - createContribution [`#2009`](https://github.com/gradido/gradido/pull/2009)
|
||||
|
||||
#### [1.10.1](https://github.com/gradido/gradido/compare/1.10.0...1.10.1)
|
||||
|
||||
> 30 June 2022
|
||||
|
||||
- release: 1.10.1 [`#2021`](https://github.com/gradido/gradido/pull/2021)
|
||||
- automatic session logout with info modal [`#2001`](https://github.com/gradido/gradido/pull/2001)
|
||||
- 1910 separate text for the slideshow images. [`#1998`](https://github.com/gradido/gradido/pull/1998)
|
||||
- Origin/1921 additional parameter checks for createContributionLinks [`#1996`](https://github.com/gradido/gradido/pull/1996)
|
||||
|
||||
@ -26,5 +26,5 @@ module.exports = {
|
||||
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
||||
// snapshotSerializers: ['jest-serializer-vue'],
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
||||
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||
// testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
@ -70,7 +70,6 @@
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-promise": "^5.1.1",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"jest-environment-jsdom-sixteen": "^2.0.0",
|
||||
"postcss": "^8.4.8",
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-scss": "^4.0.3",
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
:per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
15
admin/src/graphql/communityStatistics.js
Normal file
15
admin/src/graphql/communityStatistics.js
Normal file
@ -0,0 +1,15 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const communityStatistics = gql`
|
||||
query {
|
||||
communityStatistics {
|
||||
totalUsers
|
||||
activeUsers
|
||||
deletedUsers
|
||||
totalGradidoCreated
|
||||
totalGradidoDecayed
|
||||
totalGradidoAvailable
|
||||
totalGradidoUnbookedDecayed
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -29,6 +29,7 @@
|
||||
per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</b-col>
|
||||
<b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
@ -1282,17 +1282,6 @@
|
||||
jest-message-util "^24.9.0"
|
||||
jest-mock "^24.9.0"
|
||||
|
||||
"@jest/fake-timers@^25.1.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
|
||||
integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
jest-message-util "^25.5.0"
|
||||
jest-mock "^25.5.0"
|
||||
jest-util "^25.5.0"
|
||||
lolex "^5.0.0"
|
||||
|
||||
"@jest/fake-timers@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
|
||||
@ -1504,16 +1493,6 @@
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@jest/types@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
|
||||
@ -4130,14 +4109,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
@ -8012,16 +7983,6 @@ jest-environment-jsdom-fifteen@^1.0.2:
|
||||
jest-util "^24.0.0"
|
||||
jsdom "^15.2.1"
|
||||
|
||||
jest-environment-jsdom-sixteen@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1"
|
||||
integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ==
|
||||
dependencies:
|
||||
"@jest/fake-timers" "^25.1.0"
|
||||
jest-mock "^25.1.0"
|
||||
jest-util "^25.1.0"
|
||||
jsdom "^16.2.1"
|
||||
|
||||
jest-environment-jsdom@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
|
||||
@ -8236,20 +8197,6 @@ jest-message-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
|
||||
integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@jest/types" "^25.5.0"
|
||||
"@types/stack-utils" "^1.0.1"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
micromatch "^4.0.2"
|
||||
slash "^3.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
@ -8272,13 +8219,6 @@ jest-mock@^24.0.0, jest-mock@^24.9.0:
|
||||
dependencies:
|
||||
"@jest/types" "^24.9.0"
|
||||
|
||||
jest-mock@^25.1.0, jest-mock@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
|
||||
integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
|
||||
@ -8555,17 +8495,6 @@ jest-util@^24.0.0, jest-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
jest-util@^25.1.0, jest-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
|
||||
integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
is-ci "^2.0.0"
|
||||
make-dir "^3.0.0"
|
||||
|
||||
jest-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1"
|
||||
@ -8812,7 +8741,7 @@ jsdom@^15.2.1:
|
||||
ws "^7.0.0"
|
||||
xml-name-validator "^3.0.0"
|
||||
|
||||
jsdom@^16.2.1, jsdom@^16.4.0:
|
||||
jsdom@^16.4.0:
|
||||
version "16.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
||||
integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
|
||||
@ -9167,13 +9096,6 @@ loglevel@^1.6.8:
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||
|
||||
lolex@^5.0.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
|
||||
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
|
||||
@ -25,6 +25,14 @@
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_"
|
||||
},
|
||||
"klicktipp":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/backend/klicktipp.log",
|
||||
"pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m",
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_"
|
||||
},
|
||||
"errorFile":
|
||||
{
|
||||
"type": "dateFile",
|
||||
@ -90,6 +98,17 @@
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"klicktipp":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"klicktipp",
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"http":
|
||||
{
|
||||
"appenders":
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -40,7 +40,8 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sodium-native": "^3.3.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"type-graphql": "^1.1.1"
|
||||
"type-graphql": "^1.1.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.12",
|
||||
|
||||
@ -30,6 +30,9 @@ export enum RIGHTS {
|
||||
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
|
||||
LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS',
|
||||
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',
|
||||
LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS',
|
||||
COMMUNITY_STATISTICS = 'COMMUNITY_STATISTICS',
|
||||
SEARCH_ADMIN_USERS = 'SEARCH_ADMIN_USERS',
|
||||
// Admin
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
@ -45,7 +48,6 @@ export enum RIGHTS {
|
||||
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
|
||||
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
|
||||
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
||||
LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS',
|
||||
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||
}
|
||||
|
||||
@ -28,6 +28,9 @@ export const ROLE_USER = new Role('user', [
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0045-adapt_users_table_for_gradidoid',
|
||||
DB_VERSION: '0046-adapt_users_table_for_gradidoid',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
14
backend/src/graphql/enum/ContributionStatus.ts
Normal file
14
backend/src/graphql/enum/ContributionStatus.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum ContributionStatus {
|
||||
PENDING = 'PENDING',
|
||||
DELETED = 'DELETED',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
DENIED = 'DENIED',
|
||||
CONFIRMED = 'CONFIRMED',
|
||||
}
|
||||
|
||||
registerEnumType(ContributionStatus, {
|
||||
name: 'ContributionStatus',
|
||||
description: 'Name of the Type of the Contribution Status',
|
||||
})
|
||||
12
backend/src/graphql/enum/ContributionType.ts
Normal file
12
backend/src/graphql/enum/ContributionType.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum ContributionType {
|
||||
ADMIN = 'ADMIN',
|
||||
USER = 'USER',
|
||||
LINK = 'LINK',
|
||||
}
|
||||
|
||||
registerEnumType(ContributionType, {
|
||||
name: 'ContributionType',
|
||||
description: 'Name of the Type of the Contribution',
|
||||
})
|
||||
25
backend/src/graphql/model/AdminUser.ts
Normal file
25
backend/src/graphql/model/AdminUser.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { User } from '@entity/User'
|
||||
import { Field, Int, ObjectType } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class AdminUser {
|
||||
constructor(user: User) {
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
firstName: string
|
||||
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SearchAdminUsersResult {
|
||||
@Field(() => Int)
|
||||
userCount: number
|
||||
|
||||
@Field(() => [AdminUser])
|
||||
userList: AdminUser[]
|
||||
}
|
||||
26
backend/src/graphql/model/CommunityStatistics.ts
Normal file
26
backend/src/graphql/model/CommunityStatistics.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
export class CommunityStatistics {
|
||||
@Field(() => Number)
|
||||
totalUsers: number
|
||||
|
||||
@Field(() => Number)
|
||||
activeUsers: number
|
||||
|
||||
@Field(() => Number)
|
||||
deletedUsers: number
|
||||
|
||||
@Field(() => Decimal)
|
||||
totalGradidoCreated: Decimal
|
||||
|
||||
@Field(() => Decimal)
|
||||
totalGradidoDecayed: Decimal
|
||||
|
||||
@Field(() => Decimal)
|
||||
totalGradidoAvailable: Decimal
|
||||
|
||||
@Field(() => Decimal)
|
||||
totalGradidoUnbookedDecayed: Decimal
|
||||
}
|
||||
@ -8,6 +8,9 @@ import { FULL_CREATION_AVAILABLE } from '../resolver/const/const'
|
||||
export class User {
|
||||
constructor(user: dbUser, creation: Decimal[] = FULL_CREATION_AVAILABLE) {
|
||||
this.id = user.id
|
||||
this.gradidoID = user.gradidoID
|
||||
this.alias = user.alias
|
||||
// this.email = user.email
|
||||
this.email = user.emailContact.email
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
@ -28,6 +31,12 @@ export class User {
|
||||
// `public_key` binary(32) DEFAULT NULL,
|
||||
// `privkey` binary(80) DEFAULT NULL,
|
||||
|
||||
@Field(() => String)
|
||||
gradidoID: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
alias: string
|
||||
|
||||
// TODO privacy issue here
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
@ -1857,11 +1857,17 @@ describe('AdminResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Set this test in new location to have datas
|
||||
describe('listContributionLinks', () => {
|
||||
it('returns an error', async () => {
|
||||
it('returns an empty object', async () => {
|
||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
data: {
|
||||
listContributionLinks: {
|
||||
count: 0,
|
||||
links: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -36,6 +36,8 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { User } from '@model/User'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Decay } from '@model/Decay'
|
||||
import Paginated from '@arg/Paginated'
|
||||
@ -260,6 +262,8 @@ export class AdminResolver {
|
||||
contribution.contributionDate = creationDateObj
|
||||
contribution.memo = memo
|
||||
contribution.moderatorId = moderator.id
|
||||
contribution.contributionType = ContributionType.ADMIN
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
|
||||
logger.trace('contribution to save', contribution)
|
||||
await Contribution.save(contribution)
|
||||
@ -337,6 +341,7 @@ export class AdminResolver {
|
||||
contributionToUpdate.memo = memo
|
||||
contributionToUpdate.contributionDate = new Date(creationDate)
|
||||
contributionToUpdate.moderatorId = moderator.id
|
||||
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
||||
|
||||
await Contribution.save(contributionToUpdate)
|
||||
const result = new AdminUpdateContribution()
|
||||
@ -387,6 +392,8 @@ export class AdminResolver {
|
||||
if (!contribution) {
|
||||
throw new Error('Contribution not found for given id.')
|
||||
}
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
await contribution.save()
|
||||
const res = await contribution.softRemove()
|
||||
return !!res
|
||||
}
|
||||
@ -454,6 +461,7 @@ export class AdminResolver {
|
||||
contribution.confirmedAt = receivedCallDate
|
||||
contribution.confirmedBy = moderatorUser.id
|
||||
contribution.transactionId = transaction.id
|
||||
contribution.contributionStatus = ContributionStatus.CONFIRMED
|
||||
await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution)
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
@ -501,7 +509,7 @@ export class AdminResolver {
|
||||
order: { updatedAt: 'DESC' },
|
||||
})
|
||||
|
||||
optInCode = await checkOptInCode(optInCode, user.id)
|
||||
optInCode = await checkOptInCode(optInCode, user)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const emailSent = await sendAccountActivationEmail({
|
||||
|
||||
@ -3,10 +3,12 @@ import { Context, getUser } from '@/server/context'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Contribution as dbContribution } from '@entity/Contribution'
|
||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { FindOperator, IsNull } from '@dbTools/typeorm'
|
||||
import { FindOperator, IsNull, getConnection } from '@dbTools/typeorm'
|
||||
import ContributionArgs from '@arg/ContributionArgs'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { Contribution, ContributionListResult } from '@model/Contribution'
|
||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||
import { User } from '@model/User'
|
||||
@ -43,6 +45,8 @@ export class ContributionResolver {
|
||||
contribution.createdAt = new Date()
|
||||
contribution.contributionDate = creationDateObj
|
||||
contribution.memo = memo
|
||||
contribution.contributionType = ContributionType.USER
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
|
||||
logger.trace('contribution to save', contribution)
|
||||
await dbContribution.save(contribution)
|
||||
@ -66,6 +70,8 @@ export class ContributionResolver {
|
||||
if (contribution.confirmedAt) {
|
||||
throw new Error('A confirmed contribution can not be deleted')
|
||||
}
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
await contribution.save()
|
||||
const res = await contribution.softRemove()
|
||||
return !!res
|
||||
}
|
||||
@ -106,14 +112,15 @@ export class ContributionResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionListResult> {
|
||||
const [dbContributions, count] = await dbContribution.findAndCount({
|
||||
relations: ['user'],
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
const [dbContributions, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('c')
|
||||
.from(dbContribution, 'c')
|
||||
.innerJoinAndSelect('c.user', 'u')
|
||||
.orderBy('c.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
return new ContributionListResult(
|
||||
count,
|
||||
dbContributions.map(
|
||||
@ -163,6 +170,7 @@ export class ContributionResolver {
|
||||
contributionToUpdate.amount = amount
|
||||
contributionToUpdate.memo = memo
|
||||
contributionToUpdate.contributionDate = new Date(creationDate)
|
||||
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
||||
dbContribution.save(contributionToUpdate)
|
||||
|
||||
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
||||
|
||||
77
backend/src/graphql/resolver/StatisticsResolver.ts
Normal file
77
backend/src/graphql/resolver/StatisticsResolver.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { Resolver, Query, Authorized } from 'type-graphql'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { CommunityStatistics } from '@model/CommunityStatistics'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
|
||||
@Resolver()
|
||||
export class StatisticsResolver {
|
||||
@Authorized([RIGHTS.COMMUNITY_STATISTICS])
|
||||
@Query(() => CommunityStatistics)
|
||||
async communityStatistics(): Promise<CommunityStatistics> {
|
||||
const allUsers = await DbUser.find({ withDeleted: true })
|
||||
|
||||
let totalUsers = 0
|
||||
let activeUsers = 0
|
||||
let deletedUsers = 0
|
||||
|
||||
let totalGradidoAvailable: Decimal = new Decimal(0)
|
||||
let totalGradidoUnbookedDecayed: Decimal = new Decimal(0)
|
||||
|
||||
const receivedCallDate = new Date()
|
||||
|
||||
for (let i = 0; i < allUsers.length; i++) {
|
||||
if (allUsers[i].deletedAt) {
|
||||
deletedUsers++
|
||||
} else {
|
||||
totalUsers++
|
||||
const lastTransaction = await DbTransaction.findOne({
|
||||
where: { userId: allUsers[i].id },
|
||||
order: { balanceDate: 'DESC' },
|
||||
})
|
||||
if (lastTransaction) {
|
||||
activeUsers++
|
||||
const decay = calculateDecay(
|
||||
lastTransaction.balance,
|
||||
lastTransaction.balanceDate,
|
||||
receivedCallDate,
|
||||
)
|
||||
if (decay) {
|
||||
totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString())
|
||||
totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
|
||||
const { totalGradidoCreated } = await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('SUM(transaction.amount) AS totalGradidoCreated')
|
||||
.from(DbTransaction, 'transaction')
|
||||
.where('transaction.typeId = 1')
|
||||
.getRawOne()
|
||||
|
||||
const { totalGradidoDecayed } = await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('SUM(transaction.decay) AS totalGradidoDecayed')
|
||||
.from(DbTransaction, 'transaction')
|
||||
.where('transaction.decay IS NOT NULL')
|
||||
.getRawOne()
|
||||
|
||||
return {
|
||||
totalUsers,
|
||||
activeUsers,
|
||||
deletedUsers,
|
||||
totalGradidoCreated,
|
||||
totalGradidoDecayed,
|
||||
totalGradidoAvailable,
|
||||
totalGradidoUnbookedDecayed,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,8 @@ import { User } from '@model/User'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
import { executeTransaction } from './TransactionResolver'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { getUserCreation, validateContribution } from './util/creations'
|
||||
@ -231,6 +233,9 @@ export class TransactionLinkResolver {
|
||||
contribution.memo = contributionLink.memo
|
||||
contribution.amount = contributionLink.amount
|
||||
contribution.contributionLinkId = contributionLink.id
|
||||
contribution.contributionType = ContributionType.LINK
|
||||
contribution.contributionStatus = ContributionStatus.CONFIRMED
|
||||
|
||||
await queryRunner.manager.insert(DbContribution, contribution)
|
||||
|
||||
const lastTransaction = await queryRunner.manager
|
||||
|
||||
@ -5,7 +5,7 @@ import { testEnvironment, headerPushMock, resetToken, cleanDB, resetEntity } fro
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { createUser, setPassword, forgotPassword, updateUserInfos } from '@/seeds/graphql/mutations'
|
||||
import { login, logout, verifyLogin, queryOptIn } from '@/seeds/graphql/queries'
|
||||
import { login, logout, verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { User } from '@entity/User'
|
||||
@ -20,6 +20,8 @@ import { ContributionLink } from '@model/ContributionLink'
|
||||
// import { TransactionLink } from '@entity/TransactionLink'
|
||||
|
||||
import { logger } from '@test/testSetup'
|
||||
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
|
||||
// import { klicktippSignIn } from '@/apis/KlicktippController'
|
||||
|
||||
@ -132,6 +134,10 @@ describe('UserResolver', () => {
|
||||
contributionLinkId: null,
|
||||
},
|
||||
])
|
||||
const valUUID = validateUUID(user[0].gradidoID)
|
||||
const verUUID = versionUUID(user[0].gradidoID)
|
||||
expect(valUUID).toEqual(true)
|
||||
expect(verUUID).toEqual(4)
|
||||
})
|
||||
|
||||
it('creates an email optin', () => {
|
||||
@ -201,7 +207,7 @@ describe('UserResolver', () => {
|
||||
it('sets "de" as default language', async () => {
|
||||
await mutate({
|
||||
mutation: createUser,
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' },
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'fr' },
|
||||
})
|
||||
await expect(User.find()).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
@ -874,6 +880,51 @@ bei Gradidio sei dabei!`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('searchAdminUsers', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws an error', async () => {
|
||||
resetToken()
|
||||
await expect(mutate({ mutation: searchAdminUsers })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeAll(async () => {
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
await userFactory(testEnv, peterLustig)
|
||||
await query({
|
||||
query: login,
|
||||
variables: {
|
||||
email: 'bibi@bloxberg.de',
|
||||
password: 'Aa12345_',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('finds peter@lustig.de', async () => {
|
||||
await expect(mutate({ mutation: searchAdminUsers })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
searchAdminUsers: {
|
||||
userCount: 1,
|
||||
userList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('printTimeDuration', () => {
|
||||
|
||||
@ -2,7 +2,7 @@ import fs from 'fs'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeorm'
|
||||
import CONFIG from '@/config'
|
||||
import { User } from '@model/User'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
@ -33,6 +33,11 @@ import {
|
||||
} from '@/event/Event'
|
||||
import { getUserCreation } from './util/creations'
|
||||
import { UserContactType } from '../enum/UserContactType'
|
||||
import { UserRepository } from '@/typeorm/repository/User'
|
||||
import { SearchAdminUsersResult } from '@model/AdminUser'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sodium = require('sodium-native')
|
||||
@ -44,7 +49,7 @@ const isPassword = (password: string): boolean => {
|
||||
return !!password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$/)
|
||||
}
|
||||
|
||||
const LANGUAGES = ['de', 'en']
|
||||
const LANGUAGES = ['de', 'en', 'es']
|
||||
const DEFAULT_LANGUAGE = 'de'
|
||||
const isLanguage = (language: string): boolean => {
|
||||
return LANGUAGES.includes(language)
|
||||
@ -202,7 +207,7 @@ const newEmailOptIn = (userId: number): LoginEmailOptIn => {
|
||||
// if optIn does not exits, it is created
|
||||
export const checkOptInCode = async (
|
||||
optInCode: LoginEmailOptIn | undefined,
|
||||
userId: number,
|
||||
user: DbUser,
|
||||
optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER,
|
||||
): Promise<LoginEmailOptIn> => {
|
||||
logger.info(`checkOptInCode... ${optInCode}`)
|
||||
@ -222,26 +227,33 @@ export const checkOptInCode = async (
|
||||
optInCode.updatedAt = new Date()
|
||||
optInCode.resendCount++
|
||||
} else {
|
||||
logger.trace('create new OptIn for userId=' + userId)
|
||||
optInCode = newEmailOptIn(userId)
|
||||
logger.trace('create new OptIn for userId=' + user.id)
|
||||
optInCode = newEmailOptIn(user.id)
|
||||
}
|
||||
|
||||
if (user.emailChecked) {
|
||||
optInCode.emailOptInTypeId = optInType
|
||||
}
|
||||
optInCode.emailOptInTypeId = optInType
|
||||
await LoginEmailOptIn.save(optInCode).catch(() => {
|
||||
logger.error('Unable to save optin code= ' + optInCode)
|
||||
throw new Error('Unable to save optin code.')
|
||||
})
|
||||
logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${userId}`)
|
||||
logger.debug(`checkOptInCode...successful: ${optInCode} for userid=${user.id}`)
|
||||
return optInCode
|
||||
}
|
||||
*/
|
||||
export const checkEmailVerificationCode = async (
|
||||
emailContact: DbUserContact,
|
||||
optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER
|
||||
optInType: OptInType = OptInType.EMAIL_OPT_IN_REGISTER,
|
||||
): Promise<DbUserContact> => {
|
||||
logger.info(`checkEmailVerificationCode... ${emailContact}`)
|
||||
if (emailContact.updatedAt) {
|
||||
if (!canEmailResend(emailContact.updatedAt)) {
|
||||
logger.error(`email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} minutes ago`)
|
||||
logger.error(
|
||||
`email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
)} minutes ago`,
|
||||
)
|
||||
throw new Error(
|
||||
`email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
@ -264,9 +276,22 @@ export const checkEmailVerificationCode = async (
|
||||
return emailContact
|
||||
}
|
||||
|
||||
export const activationLink = (optInCode: LoginEmailOptIn): string => {
|
||||
logger.debug(`activationLink(${LoginEmailOptIn})...`)
|
||||
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, optInCode.verificationCode.toString())
|
||||
export const activationLink = (verificationCode: BigInt): string => {
|
||||
logger.debug(`activationLink(${verificationCode})...`)
|
||||
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, verificationCode.toString())
|
||||
}
|
||||
|
||||
const newGradidoID = async (): Promise<string> => {
|
||||
let gradidoId: string
|
||||
let countIds: number
|
||||
do {
|
||||
gradidoId = uuidv4()
|
||||
countIds = await DbUser.count({ where: { gradidoID: gradidoId } })
|
||||
if (countIds > 0) {
|
||||
logger.info('Gradido-ID creation conflict...')
|
||||
}
|
||||
} while (countIds > 0)
|
||||
return gradidoId
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
@ -407,11 +432,13 @@ export class UserResolver {
|
||||
logger.info(`DbUser.findOne(email=${email}) = ${foundUser}`)
|
||||
|
||||
if (foundUser) {
|
||||
logger.info('User already exists with this email=' + email)
|
||||
// ATTENTION: this logger-message will be exactly expected during tests
|
||||
logger.info(`User already exists with this email=${email}`)
|
||||
// TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent.
|
||||
|
||||
const user = new User(communityDbUser)
|
||||
user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in?
|
||||
user.gradidoID = uuidv4()
|
||||
user.email = email
|
||||
user.firstName = firstName
|
||||
user.lastName = lastName
|
||||
@ -441,6 +468,7 @@ export class UserResolver {
|
||||
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||
// const emailHash = getEmailHash(email)
|
||||
const gradidoID = await newGradidoID()
|
||||
|
||||
const eventRegister = new EventRegister()
|
||||
const eventRedeemRegister = new EventRedeemRegister()
|
||||
@ -450,6 +478,8 @@ export class UserResolver {
|
||||
|
||||
const dbUser = new DbUser()
|
||||
// dbUser.emailContact = dbEmailContact
|
||||
dbUser.gradidoID = gradidoID
|
||||
// dbUser.email = email
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
// dbUser.emailHash = emailHash
|
||||
@ -572,14 +602,17 @@ export class UserResolver {
|
||||
// let optInCode = await LoginEmailOptIn.findOne({
|
||||
// userId: user.id,
|
||||
// })
|
||||
let optInCode = user.emailContact.emailVerificationCode
|
||||
optInCode = await checkEmailVerificationCode(user.emailContact, OptInType.EMAIL_OPT_IN_RESET_PASSWORD)
|
||||
// let optInCode = user.emailContact.emailVerificationCode
|
||||
const dbUserContact = await checkEmailVerificationCode(
|
||||
user.emailContact,
|
||||
OptInType.EMAIL_OPT_IN_RESET_PASSWORD,
|
||||
)
|
||||
|
||||
optInCode = await checkOptInCode(optInCode, user.id, OptInType.EMAIL_OPT_IN_RESET_PASSWORD)
|
||||
logger.info(`optInCode for ${email}=${optInCode}`)
|
||||
// optInCode = await checkOptInCode(optInCode, user, OptInType.EMAIL_OPT_IN_RESET_PASSWORD)
|
||||
logger.info(`optInCode for ${email}=${dbUserContact}`)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const emailSent = await sendResetPasswordEmailMailer({
|
||||
link: activationLink(optInCode),
|
||||
link: activationLink(dbUserContact.emailVerificationCode),
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email,
|
||||
@ -589,7 +622,7 @@ export class UserResolver {
|
||||
/* uncomment this, when you need the activation link on the console */
|
||||
// In case EMails are disabled log the activation link for the user
|
||||
if (!emailSent) {
|
||||
logger.debug(`Reset password link: ${activationLink(optInCode)}`)
|
||||
logger.debug(`Reset password link: ${activationLink(dbUserContact.emailVerificationCode)}`)
|
||||
}
|
||||
logger.info(`forgotPassword(${email}) successful...`)
|
||||
|
||||
@ -611,13 +644,21 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
// Load code
|
||||
/*
|
||||
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: code }).catch(() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
})
|
||||
logger.debug('optInCode loaded...')
|
||||
*/
|
||||
const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: code }).catch(
|
||||
() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
},
|
||||
)
|
||||
logger.debug('userContact loaded...')
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isOptInValid(optInCode)) {
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
@ -628,7 +669,7 @@ export class UserResolver {
|
||||
logger.debug('optInCode is valid...')
|
||||
|
||||
// load user
|
||||
const user = await DbUser.findOneOrFail({ id: optInCode.userId }).catch(() => {
|
||||
const user = await DbUser.findOneOrFail({ id: userContact.userId }).catch(() => {
|
||||
logger.error('Could not find corresponding Login User')
|
||||
throw new Error('Could not find corresponding Login User')
|
||||
})
|
||||
@ -652,10 +693,10 @@ export class UserResolver {
|
||||
logger.debug('Passphrase is valid...')
|
||||
|
||||
// Activate EMail
|
||||
user.emailChecked = true
|
||||
userContact.emailChecked = true
|
||||
|
||||
// Update Password
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(user.email, password) // return short and long hash
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash
|
||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||
user.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||
@ -686,11 +727,11 @@ export class UserResolver {
|
||||
|
||||
// Sign into Klicktipp
|
||||
// TODO do we always signUp the user? How to handle things with old users?
|
||||
if (optInCode.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) {
|
||||
if (userContact.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) {
|
||||
try {
|
||||
await klicktippSignIn(user.email, user.language, user.firstName, user.lastName)
|
||||
await klicktippSignIn(userContact.email, user.language, user.firstName, user.lastName)
|
||||
logger.debug(
|
||||
`klicktippSignIn(${user.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
|
||||
`klicktippSignIn(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
|
||||
)
|
||||
} catch (e) {
|
||||
logger.error('Error subscribe to klicktipp:' + e)
|
||||
@ -709,10 +750,10 @@ export class UserResolver {
|
||||
@Query(() => Boolean)
|
||||
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
|
||||
logger.info(`queryOptIn(${optIn})...`)
|
||||
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: optIn })
|
||||
logger.debug(`found optInCode=${optInCode}`)
|
||||
const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: optIn })
|
||||
logger.debug(`found optInCode=${userContact}`)
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isOptInValid(optInCode)) {
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
@ -760,7 +801,10 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
// TODO: This had some error cases defined - like missing private key. This is no longer checked.
|
||||
const oldPasswordHash = SecretKeyCryptographyCreateKey(userEntity.email, password)
|
||||
const oldPasswordHash = SecretKeyCryptographyCreateKey(
|
||||
userEntity.emailContact.email,
|
||||
password,
|
||||
)
|
||||
if (BigInt(userEntity.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) {
|
||||
logger.error(`Old password is invalid`)
|
||||
throw new Error(`Old password is invalid`)
|
||||
@ -768,7 +812,10 @@ export class UserResolver {
|
||||
|
||||
const privKey = SecretKeyCryptographyDecrypt(userEntity.privKey, oldPasswordHash[1])
|
||||
logger.debug('oldPassword decrypted...')
|
||||
const newPasswordHash = SecretKeyCryptographyCreateKey(userEntity.email, passwordNew) // return short and long hash
|
||||
const newPasswordHash = SecretKeyCryptographyCreateKey(
|
||||
userEntity.emailContact.email,
|
||||
passwordNew,
|
||||
) // return short and long hash
|
||||
logger.debug('newPasswordHash created...')
|
||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
|
||||
logger.debug('PrivateKey encrypted...')
|
||||
@ -813,6 +860,36 @@ export class UserResolver {
|
||||
logger.debug(`has ElopageBuys = ${elopageBuys}`)
|
||||
return elopageBuys
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEARCH_ADMIN_USERS])
|
||||
@Query(() => SearchAdminUsersResult)
|
||||
async searchAdminUsers(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||
): Promise<SearchAdminUsersResult> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
const [users, count] = await userRepository.findAndCount({
|
||||
where: {
|
||||
isAdmin: Not(IsNull()),
|
||||
},
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
return {
|
||||
userCount: count,
|
||||
userList: users.map((user) => {
|
||||
return {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
@ -847,7 +924,10 @@ const isOptInValid = (optIn: LoginEmailOptIn): boolean => {
|
||||
return isTimeExpired(optIn, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
*/
|
||||
const isEmailVerificationCodeValid = (updatedAt: Date): boolean => {
|
||||
const isEmailVerificationCodeValid = (updatedAt: Date | null): boolean => {
|
||||
if (updatedAt == null) {
|
||||
return true
|
||||
}
|
||||
return isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
/*
|
||||
@ -875,4 +955,3 @@ export const printTimeDuration = (duration: number): string => {
|
||||
if (time.hours) return `${time.hours} hours` + (result !== '' ? ` and ${result}` : '')
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { MiddlewareFn } from 'type-graphql'
|
||||
import { /* klicktippSignIn, */ getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { KlickTipp } from '@model/KlickTipp'
|
||||
import CONFIG from '@/config'
|
||||
import { klickTippLogger as logger } from '@/server/logger'
|
||||
|
||||
// export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
||||
// // Only for demo
|
||||
@ -29,7 +30,9 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||
if (klickTippUser) {
|
||||
klickTipp = new KlickTipp(klickTippUser)
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) {
|
||||
logger.error(`There is no user for (email='${result.email}') ${err}`)
|
||||
}
|
||||
}
|
||||
result.klickTipp = klickTipp
|
||||
return result
|
||||
|
||||
@ -280,3 +280,15 @@ export const listContributionLinks = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const searchAdminUsers = gql`
|
||||
query {
|
||||
searchAdminUsers {
|
||||
userCount
|
||||
userList {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -12,7 +12,8 @@ log4js.configure(options)
|
||||
|
||||
const apolloLogger = log4js.getLogger('apollo')
|
||||
const backendLogger = log4js.getLogger('backend')
|
||||
const klickTippLogger = log4js.getLogger('klicktipp')
|
||||
|
||||
backendLogger.addContext('user', 'unknown')
|
||||
|
||||
export { apolloLogger, backendLogger }
|
||||
export { apolloLogger, backendLogger, klickTippLogger }
|
||||
|
||||
@ -1,47 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { SaveOptions, RemoveOptions } from '@dbTools/typeorm'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
// import { UserContact as EmailContact } from '@entity/UserContact'
|
||||
import { User } from '@model/User'
|
||||
|
||||
const communityDbUser: dbUser = {
|
||||
id: -1,
|
||||
gradidoID: '11111111-2222-3333-4444-55555555',
|
||||
alias: '',
|
||||
// email: 'support@gradido.net',
|
||||
firstName: 'Gradido',
|
||||
lastName: 'Akademie',
|
||||
pubKey: Buffer.from(''),
|
||||
privKey: Buffer.from(''),
|
||||
deletedAt: null,
|
||||
password: BigInt(0),
|
||||
// emailHash: Buffer.from(''),
|
||||
createdAt: new Date(),
|
||||
// emailChecked: false,
|
||||
language: '',
|
||||
isAdmin: null,
|
||||
publisherId: 0,
|
||||
passphrase: '',
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
save: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
remove: function (options?: RemoveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
softRemove: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
recover: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
reload: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
}
|
||||
const communityUser = new User(communityDbUser)
|
||||
|
||||
export { communityDbUser, communityUser }
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { SaveOptions, RemoveOptions } from '@dbTools/typeorm'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { UserContact } from '@entity/UserContact'
|
||||
// import { UserContact as EmailContact } from '@entity/UserContact'
|
||||
import { User } from '@model/User'
|
||||
|
||||
const communityDbUser: dbUser = {
|
||||
id: -1,
|
||||
gradidoID: '11111111-2222-4333-4444-55555555',
|
||||
alias: '',
|
||||
// email: 'support@gradido.net',
|
||||
emailContact: new UserContact(),
|
||||
firstName: 'Gradido',
|
||||
lastName: 'Akademie',
|
||||
pubKey: Buffer.from(''),
|
||||
privKey: Buffer.from(''),
|
||||
deletedAt: null,
|
||||
password: BigInt(0),
|
||||
// emailHash: Buffer.from(''),
|
||||
createdAt: new Date(),
|
||||
// emailChecked: false,
|
||||
language: '',
|
||||
isAdmin: null,
|
||||
publisherId: 0,
|
||||
passphrase: '',
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
save: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
remove: function (options?: RemoveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
softRemove: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
recover: function (options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
reload: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
}
|
||||
const communityUser = new User(communityDbUser)
|
||||
|
||||
export { communityDbUser, communityUser }
|
||||
|
||||
@ -5442,7 +5442,7 @@ uuid@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^8.0.0:
|
||||
uuid@^8.0.0, uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
DeleteDateColumn,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from 'typeorm'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { User } from '../User'
|
||||
|
||||
@Entity('contributions')
|
||||
export class Contribution extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ unsigned: true, nullable: false, name: 'user_id' })
|
||||
userId: number
|
||||
|
||||
@ManyToOne(() => User, (user) => user.contributions)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: User
|
||||
|
||||
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ type: 'datetime', nullable: false, name: 'contribution_date' })
|
||||
contributionDate: 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({ unsigned: true, nullable: true, name: 'moderator_id' })
|
||||
moderatorId: number
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'contribution_link_id' })
|
||||
contributionLinkId: number
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'confirmed_by' })
|
||||
confirmedBy: number
|
||||
|
||||
@Column({ nullable: true, name: 'confirmed_at' })
|
||||
confirmedAt: Date
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'denied_by' })
|
||||
deniedBy: number
|
||||
|
||||
@Column({ nullable: true, name: 'denied_at' })
|
||||
deniedAt: Date
|
||||
|
||||
@Column({
|
||||
name: 'contribution_type',
|
||||
length: 12,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
contributionType: string
|
||||
|
||||
@Column({
|
||||
name: 'contribution_status',
|
||||
length: 12,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
contributionStatus: string
|
||||
|
||||
@Column({ unsigned: true, nullable: true, name: 'transaction_id' })
|
||||
transactionId: number
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at' })
|
||||
deletedAt: Date | null
|
||||
}
|
||||
111
database/entity/0046-adapt_users_table_for_gradidoid/User.ts
Normal file
111
database/entity/0046-adapt_users_table_for_gradidoid/User.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from '../Contribution'
|
||||
|
||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({
|
||||
name: 'gradido_id',
|
||||
length: 36,
|
||||
nullable: false,
|
||||
unique: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
gradidoID: string
|
||||
|
||||
@Column({
|
||||
name: 'alias',
|
||||
length: 20,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
alias: string
|
||||
|
||||
@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: 'contribution_link_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
contributionLinkId?: 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(() => Contribution, (contribution) => contribution.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
contributions?: Contribution[]
|
||||
}
|
||||
@ -47,10 +47,8 @@ export class User extends BaseEntity {
|
||||
@JoinColumn({ name: 'email_id' })
|
||||
emailContact: UserContact
|
||||
|
||||
/*
|
||||
@Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
emailId?: number | null
|
||||
*/
|
||||
|
||||
@Column({
|
||||
name: 'first_name',
|
||||
@ -1 +1 @@
|
||||
export { Contribution } from './0039-contributions_table/Contribution'
|
||||
export { Contribution } from './0045-add_denied_type_and_status_to_contributions/Contribution'
|
||||
|
||||
@ -1 +1 @@
|
||||
export { User } from './0045-adapt_users_table_for_gradidoid/User'
|
||||
export { User } from './0047-add_user_contacts_table/User'
|
||||
|
||||
@ -1 +1 @@
|
||||
export { UserContact } from './0045-adapt_users_table_for_gradidoid/UserContact'
|
||||
export { UserContact } from './0047-add_user_contacts_table/UserContact'
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/* MIGRATION TO ADD denied_by, denied_at, contribution_type and contrinution_status
|
||||
FIELDS TO contributions */
|
||||
|
||||
/* 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 `contributions` ADD COLUMN `denied_at` datetime DEFAULT NULL AFTER `confirmed_at`;',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `contributions` ADD COLUMN `denied_by` int(10) unsigned DEFAULT NULL AFTER `denied_at`;',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `contributions` ADD COLUMN `contribution_type` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT "ADMIN" AFTER `denied_by`;',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `contributions` ADD COLUMN `contribution_status` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT "PENDING" AFTER `contribution_type`;',
|
||||
)
|
||||
await queryFn(
|
||||
'UPDATE `contributions` SET `contribution_type` = "LINK" WHERE `contribution_link_id` IS NOT NULL;',
|
||||
)
|
||||
await queryFn(
|
||||
'UPDATE `contributions` SET `contribution_type` = "USER" WHERE `contribution_link_id` IS NULL AND `moderator_id` IS NULL;',
|
||||
)
|
||||
await queryFn(
|
||||
'UPDATE `contributions` SET `contribution_status` = "CONFIRMED" WHERE `confirmed_at` IS NOT NULL;',
|
||||
)
|
||||
await queryFn(
|
||||
'UPDATE `contributions` SET `contribution_status` = "DELETED" WHERE `deleted_at` IS NOT NULL;',
|
||||
)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE `contributions` DROP COLUMN `contribution_status`;')
|
||||
await queryFn('ALTER TABLE `contributions` DROP COLUMN `contribution_type`;')
|
||||
await queryFn('ALTER TABLE `contributions` DROP COLUMN `denied_by`;')
|
||||
await queryFn('ALTER TABLE `contributions` DROP COLUMN `denied_at`;')
|
||||
}
|
||||
44
database/migrations/0046-adapt_users_table_for_gradidoid.ts
Normal file
44
database/migrations/0046-adapt_users_table_for_gradidoid.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/* MIGRATION TO ADD GRADIDO_ID
|
||||
*
|
||||
* This migration adds new columns to the table `users` and creates the
|
||||
* new table `user_contacts`
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// First add gradido_id as nullable column without Default
|
||||
await queryFn('ALTER TABLE `users` ADD COLUMN `gradido_id` CHAR(36) NULL AFTER `id`;')
|
||||
|
||||
// Second update gradido_id with ensured unique uuidv4
|
||||
const usersToUpdate = await queryFn('SELECT `id`, `gradido_id` FROM `users`') // WHERE 'u.gradido_id' is null`,)
|
||||
for (const id in usersToUpdate) {
|
||||
const user = usersToUpdate[id]
|
||||
let gradidoId = null
|
||||
let countIds = null
|
||||
do {
|
||||
gradidoId = uuidv4()
|
||||
countIds = await queryFn(
|
||||
`SELECT COUNT(*) FROM \`users\` WHERE \`gradido_id\` = "${gradidoId}"`,
|
||||
)
|
||||
} while (countIds[0] > 0)
|
||||
await queryFn(
|
||||
`UPDATE \`users\` SET \`gradido_id\` = "${gradidoId}" WHERE \`id\` = "${user.id}"`,
|
||||
)
|
||||
}
|
||||
|
||||
// third modify gradido_id to not nullable and unique
|
||||
await queryFn('ALTER TABLE `users` MODIFY COLUMN `gradido_id` CHAR(36) NOT NULL UNIQUE;')
|
||||
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN `alias` varchar(20) NULL UNIQUE AFTER `gradido_id`;',
|
||||
)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE users DROP COLUMN gradido_id;')
|
||||
await queryFn('ALTER TABLE users DROP COLUMN alias;')
|
||||
}
|
||||
@ -27,33 +27,6 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
|
||||
// First add gradido_id as nullable column without Default
|
||||
await queryFn('ALTER TABLE `users` ADD COLUMN `gradido_id` CHAR(36) NULL AFTER `id`;')
|
||||
|
||||
// Second update gradido_id with ensured unique uuidv4
|
||||
const usersToUpdate = await queryFn('SELECT `id`, `gradido_id` FROM `users`') // WHERE 'u.gradido_id' is null`,)
|
||||
for (const id in usersToUpdate) {
|
||||
const user = usersToUpdate[id]
|
||||
let gradidoId = null
|
||||
let countIds = null
|
||||
do {
|
||||
gradidoId = uuidv4()
|
||||
countIds = await queryFn(
|
||||
`SELECT COUNT(*) FROM \`users\` WHERE \`gradido_id\` = "${gradidoId}"`,
|
||||
)
|
||||
} while (countIds[0] > 0)
|
||||
await queryFn(
|
||||
`UPDATE \`users\` SET \`gradido_id\` = "${gradidoId}" WHERE \`id\` = "${user.id}"`,
|
||||
)
|
||||
}
|
||||
|
||||
// third modify gradido_id to not nullable and unique
|
||||
await queryFn('ALTER TABLE `users` MODIFY COLUMN `gradido_id` CHAR(36) NOT NULL UNIQUE;')
|
||||
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN `alias` varchar(20) NULL UNIQUE AFTER `gradido_id`;',
|
||||
)
|
||||
|
||||
await queryFn('ALTER TABLE `users` ADD COLUMN `email_id` int(10) NULL AFTER `email`;')
|
||||
|
||||
// merge values from login_email_opt_in table with users.email in new user_contacts table
|
||||
@ -104,7 +77,5 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom
|
||||
// write downgrade logic as parameter of queryFn
|
||||
await queryFn(`DROP TABLE IF EXISTS user_contacts;`)
|
||||
|
||||
await queryFn('ALTER TABLE users DROP COLUMN gradido_id;')
|
||||
await queryFn('ALTER TABLE users DROP COLUMN alias;')
|
||||
await queryFn('ALTER TABLE users DROP COLUMN email_id;')
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
|
||||
BIN
docu/Locales/GRADIDO_register_page_spanish.xlsx
Normal file
BIN
docu/Locales/GRADIDO_register_page_spanish.xlsx
Normal file
Binary file not shown.
@ -23,5 +23,5 @@ module.exports = {
|
||||
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
||||
// snapshotSerializers: ['jest-serializer-vue'],
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
||||
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||
// testEnvironment: 'jest-environment-jsdom-sixteen', // not needed anymore since jest@26, see: https://www.npmjs.com/package/jest-environment-jsdom-sixteen
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
@ -44,7 +44,6 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom-sixteen": "^2.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"portal-vue": "^2.1.7",
|
||||
"prettier": "^2.2.1",
|
||||
|
||||
@ -3,8 +3,11 @@
|
||||
<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="primary" @click="CopyLink">
|
||||
{{ $t('gdd_per_link.copy') }}
|
||||
<b-button size="sm" text="Button" variant="primary" @click="copyLinkWithText">
|
||||
{{ $t('gdd_per_link.copy-link-with-text') }}
|
||||
</b-button>
|
||||
<b-button size="sm" text="Button" variant="primary" @click="copyLink">
|
||||
{{ $t('gdd_per_link.copy-link') }}
|
||||
</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>
|
||||
@ -18,29 +21,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { copyLinks } from '../mixins/copyLinks'
|
||||
export default {
|
||||
name: 'ClipboardCopy',
|
||||
props: {
|
||||
link: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canCopyLink: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
CopyLink() {
|
||||
navigator.clipboard
|
||||
.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'))
|
||||
})
|
||||
},
|
||||
},
|
||||
mixins: [copyLinks],
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@ -97,24 +97,14 @@ describe('ContentFooter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).text()).toEqual('navigation.members_area')
|
||||
})
|
||||
|
||||
it('links to the elopage', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://elopage.com/s/gradido/sign_in?locale=en',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the whitepaper', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://docs.google.com/document/d/1kcX1guOi6tDgnFHD9tf7fB_MneKTx-0nHJxzdN8ygNs/edit?usp=sharing',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the support', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
'https://gradido.net/en/contact/',
|
||||
)
|
||||
})
|
||||
@ -142,20 +132,14 @@ describe('ContentFooter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German elopage when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://elopage.com/s/gradido/sign_in?locale=de',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German whitepaper when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(2).attributes('href')).toEqual(
|
||||
'https://docs.google.com/document/d/1jZp-DiiMPI9ZPNXmjsvOQ1BtnfDFfx8BX7CDmA8KKjY/edit?usp=sharing',
|
||||
)
|
||||
})
|
||||
|
||||
it('links to the German support-page when locale is de', () => {
|
||||
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
|
||||
expect(wrapper.findAll('a.nav-link').at(3).attributes('href')).toEqual(
|
||||
'https://gradido.net/de/contact/',
|
||||
)
|
||||
})
|
||||
|
||||
@ -34,12 +34,6 @@
|
||||
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/datenschutz/`" target="_blank">
|
||||
{{ $t('footer.privacy_policy') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('navigation.members_area') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item
|
||||
:href="
|
||||
$i18n.locale === 'de'
|
||||
|
||||
@ -46,46 +46,274 @@ describe('ContributionForm', () => {
|
||||
})
|
||||
|
||||
describe('empty form data', () => {
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
describe('button', () => {
|
||||
it('has submit disabled', () => {
|
||||
expect(wrapper.find('button[data-test="button-submit"]').attributes('disabled')).toBe(
|
||||
'disabled',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('dates', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
date: '',
|
||||
memo: '',
|
||||
amount: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('actual date', () => {
|
||||
describe('same month', () => {
|
||||
beforeEach(async () => {
|
||||
const now = new Date().toISOString()
|
||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has true', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has submit disabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
describe('month before', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'BFormDatepicker' })
|
||||
.vm.$emit('input', wrapper.vm.minimalDate)
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has false', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('date in middle of year', () => {
|
||||
describe('same month', () => {
|
||||
beforeEach(async () => {
|
||||
// jest.useFakeTimers('modern')
|
||||
// jest.setSystemTime(new Date('2020-07-06'))
|
||||
// await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
await wrapper.setData({
|
||||
maximalDate: new Date(2020, 6, 6),
|
||||
form: { date: new Date(2020, 6, 6) },
|
||||
})
|
||||
})
|
||||
|
||||
describe('minimalDate', () => {
|
||||
it('has "2020-06-01T00:00:00.000Z"', () => {
|
||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has true', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('month before', () => {
|
||||
beforeEach(async () => {
|
||||
// jest.useFakeTimers('modern')
|
||||
// jest.setSystemTime(new Date('2020-07-06'))
|
||||
// console.log('middle of year date – now:', wrapper.vm.minimalDate)
|
||||
// await wrapper
|
||||
// .findComponent({ name: 'BFormDatepicker' })
|
||||
// .vm.$emit('input', wrapper.vm.minimalDate)
|
||||
await wrapper.setData({
|
||||
maximalDate: new Date(2020, 6, 6),
|
||||
form: { date: new Date(2020, 5, 6) },
|
||||
})
|
||||
})
|
||||
|
||||
describe('minimalDate', () => {
|
||||
it('has "2020-06-01T00:00:00.000Z"', () => {
|
||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has false', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('date in january', () => {
|
||||
describe('same month', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
maximalDate: new Date(2020, 0, 6),
|
||||
form: { date: new Date(2020, 0, 6) },
|
||||
})
|
||||
})
|
||||
|
||||
describe('minimalDate', () => {
|
||||
it('has "2019-12-01T00:00:00.000Z"', () => {
|
||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has true', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('month before', () => {
|
||||
beforeEach(async () => {
|
||||
// jest.useFakeTimers('modern')
|
||||
// jest.setSystemTime(new Date('2020-07-06'))
|
||||
// console.log('middle of year date – now:', wrapper.vm.minimalDate)
|
||||
// await wrapper
|
||||
// .findComponent({ name: 'BFormDatepicker' })
|
||||
// .vm.$emit('input', wrapper.vm.minimalDate)
|
||||
await wrapper.setData({
|
||||
maximalDate: new Date(2020, 0, 6),
|
||||
form: { date: new Date(2019, 11, 6) },
|
||||
})
|
||||
})
|
||||
|
||||
describe('minimalDate', () => {
|
||||
it('has "2019-12-01T00:00:00.000Z"', () => {
|
||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isThisMonth', () => {
|
||||
it('has false', () => {
|
||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('set contrubtion', () => {
|
||||
describe('fill in form data', () => {
|
||||
describe('fill in form data with "id === null"', () => {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
date: '',
|
||||
memo: '',
|
||||
amount: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
describe('invalid form data', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
await wrapper.find('#contribution-amount').find('input').setValue('200')
|
||||
})
|
||||
|
||||
it('has submit enabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy()
|
||||
describe('memo lenght < 5, others are valid', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('#contribution-memo').find('textarea').setValue('1234')
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has disabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('memo lenght > 255, others are valid', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper
|
||||
.find('#contribution-memo')
|
||||
.find('textarea')
|
||||
.setValue(
|
||||
'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
|
||||
'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
|
||||
'01234567890123456789012345678901234567890123456789012345',
|
||||
)
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has disabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid form data', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
await wrapper
|
||||
.find('#contribution-memo')
|
||||
.find('textarea')
|
||||
.setValue('Mein Beitrag zur Gemeinschaft für diesen Monat ...')
|
||||
await wrapper.find('#contribution-amount').find('input').setValue('200')
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has enabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has label "contribution.submit"', () => {
|
||||
expect(wrapper.find('button[data-test="button-submit"]').text()).toContain(
|
||||
'contribution.submit',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on trigger submit', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emits "set-contribution"', () => {
|
||||
expect(wrapper.emitted('set-contribution')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
id: null,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update contrubtion', () => {
|
||||
describe('fill in form data and "id"', () => {
|
||||
describe('fill in form data with set "id"', () => {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -93,19 +321,104 @@ describe('ContributionForm', () => {
|
||||
form: {
|
||||
id: 2,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
memo: 'Mein kommerzieller Beitrag für diesen Monat ...',
|
||||
amount: '100',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('buttons', () => {
|
||||
it('has reset enabled', () => {
|
||||
expect(wrapper.find('button[type="reset"]').attributes('disabled')).toBeFalsy()
|
||||
describe('invalid form data', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
await wrapper.find('#contribution-amount').find('input').setValue('200')
|
||||
})
|
||||
|
||||
it('has submit enabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeFalsy()
|
||||
describe('memo lenght < 5, others are valid', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('#contribution-memo').find('textarea').setValue('1234')
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has disabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('memo lenght > 255, others are valid', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper
|
||||
.find('#contribution-memo')
|
||||
.find('textarea')
|
||||
.setValue(
|
||||
'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
|
||||
'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
|
||||
'01234567890123456789012345678901234567890123456789012345',
|
||||
)
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has disabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid form data', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
||||
await wrapper
|
||||
.find('#contribution-memo')
|
||||
.find('textarea')
|
||||
.setValue('Mein Beitrag zur Gemeinschaft für diesen Monat ...')
|
||||
await wrapper.find('#contribution-amount').find('input').setValue('200')
|
||||
})
|
||||
|
||||
describe('button', () => {
|
||||
describe('submit', () => {
|
||||
it('has enabled', () => {
|
||||
expect(
|
||||
wrapper.find('button[data-test="button-submit"]').attributes('disabled'),
|
||||
).toBeFalsy()
|
||||
})
|
||||
|
||||
it('has label "form.change"', () => {
|
||||
expect(wrapper.find('button[data-test="button-submit"]').text()).toContain(
|
||||
'form.change',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on trigger submit', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emits "update-contribution"', () => {
|
||||
expect(wrapper.emitted('update-contribution')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
id: 2,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
<h3>{{ $t('contribution.formText.yourContribution') }}</h3>
|
||||
{{ $t('contribution.formText.bringYourTalentsTo') }}
|
||||
<ul class="my-3">
|
||||
<li v-html="lastMonthObject"></li>
|
||||
<li v-html="thisMonthObject"></li>
|
||||
<li v-html="textForMonth(new Date(minimalDate), maxGddLastMonth)"></li>
|
||||
<li v-html="textForMonth(new Date(), maxGddThisMonth)"></li>
|
||||
</ul>
|
||||
|
||||
<div class="my-3">
|
||||
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<b-form ref="form" @submit.prevent="submit" class="border p-3">
|
||||
<label>{{ $t('contribution.selectDate') }}</label>
|
||||
<label>{{ $t('contribution.selectDate') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-form-datepicker
|
||||
id="contribution-date"
|
||||
v-model="form.date"
|
||||
@ -31,33 +31,32 @@
|
||||
</b-form-datepicker>
|
||||
<validation-provider
|
||||
:rules="{
|
||||
required: true,
|
||||
min: minlength,
|
||||
max: maxlength,
|
||||
}"
|
||||
:name="$t('form.message')"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<label class="mt-3">{{ $t('contribution.activity') }}</label>
|
||||
<label class="mt-3">{{ $t('contribution.activity') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-form-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
rows="3"
|
||||
max-rows="6"
|
||||
:placeholder="$t('contribution.yourActivity')"
|
||||
required
|
||||
></b-form-textarea>
|
||||
<b-col v-if="errors">
|
||||
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
|
||||
</b-col>
|
||||
</validation-provider>
|
||||
<label class="mt-3">{{ $t('form.amount') }}</label>
|
||||
<b-input-group size="lg" prepend="GDD" append=".00">
|
||||
<label class="mt-3">{{ $t('form.amount') }} {{ $t('math.asterisk') }}</label>
|
||||
<b-input-group size="lg" prepend="GDD">
|
||||
<b-form-input
|
||||
id="contribution-amount"
|
||||
v-model="form.amount"
|
||||
type="number"
|
||||
min="1"
|
||||
:max="isThisMonth ? maxGddThisMonth : maxGddLastMonth"
|
||||
type="text"
|
||||
:formatter="numberFormat"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
<div
|
||||
@ -74,17 +73,18 @@
|
||||
</div>
|
||||
<b-row class="mt-3">
|
||||
<b-col>
|
||||
<b-button class="test-cancel" type="reset" variant="secondary" @click="reset">
|
||||
<b-button type="reset" variant="secondary" @click="reset" data-test="button-cancel">
|
||||
{{ $t('form.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button class="test-submit" type="submit" variant="primary" :disabled="disabled">
|
||||
<b-button type="submit" variant="primary" :disabled="disabled" data-test="button-submit">
|
||||
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
<p class="p-2">{{ $t('math.asterisk') }} {{ $t('form.mandatoryField') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -103,12 +103,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberFormat(value) {
|
||||
return value.replace(/\D/g, '')
|
||||
},
|
||||
submit() {
|
||||
if (this.form.id) {
|
||||
this.$emit('update-contribution', this.form)
|
||||
} else {
|
||||
this.$emit('set-contribution', this.form)
|
||||
}
|
||||
this.form.amount = this.numberFormat(this.form.amount)
|
||||
// spreading is needed for testing
|
||||
this.$emit(this.form.id ? 'update-contribution' : 'set-contribution', { ...this.form })
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
@ -118,53 +119,47 @@ export default {
|
||||
this.form.memo = ''
|
||||
this.form.amount = ''
|
||||
},
|
||||
textForMonth(date, availableAmount) {
|
||||
const obj = {
|
||||
monthAndYear: this.$d(date, 'monthAndYear'),
|
||||
creation: availableAmount,
|
||||
}
|
||||
return this.$t('contribution.formText.openAmountForMonth', obj)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
/*
|
||||
* minimalDate() = Sets the date to the 1st of the previous month.
|
||||
*
|
||||
*/
|
||||
minimalDate() {
|
||||
return new Date(this.maximalDate.getFullYear(), this.maximalDate.getMonth() - 1, 1)
|
||||
// sets the date to the 1st of the previous month
|
||||
let date = new Date(this.maximalDate) // has to be a new object, because of 'setMonth' changes the objects date
|
||||
date = new Date(date.setMonth(date.getMonth() - 1))
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1)
|
||||
},
|
||||
disabled() {
|
||||
if (
|
||||
return (
|
||||
this.form.date === '' ||
|
||||
this.form.memo.length < this.minlength ||
|
||||
this.form.amount <= 0 ||
|
||||
this.form.amount > 1000 ||
|
||||
this.form.memo.length > this.maxlength ||
|
||||
parseInt(this.form.amount) <= 0 ||
|
||||
parseInt(this.form.amount) > 1000 ||
|
||||
(this.isThisMonth && parseInt(this.form.amount) > parseInt(this.maxGddThisMonth)) ||
|
||||
(!this.isThisMonth && parseInt(this.form.amount) > parseInt(this.maxGddLastMonth))
|
||||
)
|
||||
return true
|
||||
return false
|
||||
},
|
||||
lastMonthObject() {
|
||||
// new Date().getMonth === 1 If the current month is January, then one year must be gone back in the previous month
|
||||
const obj = {
|
||||
monthAndYear: this.$d(new Date(this.minimalDate), 'monthAndYear'),
|
||||
creation: this.maxGddLastMonth,
|
||||
}
|
||||
return this.$t('contribution.formText.openAmountForMonth', obj)
|
||||
},
|
||||
thisMonthObject() {
|
||||
const obj = {
|
||||
monthAndYear: this.$d(new Date(), 'monthAndYear'),
|
||||
creation: this.maxGddThisMonth,
|
||||
}
|
||||
return this.$t('contribution.formText.openAmountForMonth', obj)
|
||||
},
|
||||
isThisMonth() {
|
||||
return new Date(this.form.date).getMonth() === new Date().getMonth()
|
||||
const formDate = new Date(this.form.date)
|
||||
return (
|
||||
formDate.getFullYear() === this.maximalDate.getFullYear() &&
|
||||
formDate.getMonth() === this.maximalDate.getMonth()
|
||||
)
|
||||
},
|
||||
maxGddLastMonth() {
|
||||
// When edited, the amount is added back on top of the amount
|
||||
// when existing contribution is edited, the amount is added back on top of the amount
|
||||
return this.form.id && !this.isThisMonth
|
||||
? parseInt(this.$store.state.creation[1]) + parseInt(this.updateAmount)
|
||||
: this.$store.state.creation[1]
|
||||
},
|
||||
maxGddThisMonth() {
|
||||
// When edited, the amount is added back on top of the amount
|
||||
// when existing contribution is edited, the amount is added back on top of the amount
|
||||
return this.form.id && this.isThisMonth
|
||||
? parseInt(this.$store.state.creation[2]) + parseInt(this.updateAmount)
|
||||
: this.$store.state.creation[2]
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="contributionCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -46,5 +46,17 @@ describe('GddSend confirm', () => {
|
||||
expect(wrapper.findAll('div.confirm-box-link').at(0).exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('has total balance equal 0', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({
|
||||
balance: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('has send button disabled', () => {
|
||||
expect(wrapper.find('.send-button').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -42,7 +42,12 @@
|
||||
<b-button @click="$emit('on-reset')">{{ $t('back') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button variant="primary" :disabled="disabled" @click="$emit('send-transaction')">
|
||||
<b-button
|
||||
class="send-button"
|
||||
variant="primary"
|
||||
:disabled="disabled"
|
||||
@click="$emit('send-transaction')"
|
||||
>
|
||||
{{ $t('form.generate_now') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
|
||||
@ -3,7 +3,13 @@
|
||||
<b-col>
|
||||
<b-card class="p-0 gradido-custom-background">
|
||||
<div class="h3 mb-4">{{ $t('gdd_per_link.created') }}</div>
|
||||
<clipboard-copy :link="link" @show-qr-code-button="showQrCodeButton" />
|
||||
<clipboard-copy
|
||||
:link="link"
|
||||
:amount="amount"
|
||||
:memo="memo"
|
||||
:validUntil="validUntil"
|
||||
@show-qr-code-button="showQrCodeButton"
|
||||
></clipboard-copy>
|
||||
|
||||
<div class="text-center">
|
||||
<figure-qr-code v-if="showQrcode" :link="link" />
|
||||
@ -27,10 +33,10 @@ export default {
|
||||
FigureQrCode,
|
||||
},
|
||||
props: {
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
link: { type: String, required: true },
|
||||
amount: { type: String, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -69,6 +69,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="transactionCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
|
||||
<div v-if="transactionCount <= 0" class="mt-4 text-center">
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
:per-page="pageSize"
|
||||
:total-rows="transactionGdtCount"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
></b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -45,7 +45,7 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('with locales en and de', () => {
|
||||
describe('with locales en, de and es', () => {
|
||||
describe('empty store', () => {
|
||||
describe('navigator language is "en-US"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
@ -69,11 +69,22 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "es-ES" (not supported)', () => {
|
||||
describe('navigator language is "es-ES"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows Español as language ', async () => {
|
||||
languageGetter.mockReturnValue('es-ES')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Español - es')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigator language is "fr-FR" (not supported)', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue('es-ES')
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en')
|
||||
@ -101,9 +112,18 @@ describe('LanguageSwitch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('language "es" in store', () => {
|
||||
it('shows Español as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'es'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Español - es')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dropdown menu', () => {
|
||||
it('has English and German as languages to choose', () => {
|
||||
expect(wrapper.findAll('li')).toHaveLength(2)
|
||||
expect(wrapper.findAll('li')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('has English as first language to choose', () => {
|
||||
@ -113,6 +133,10 @@ describe('LanguageSwitch', () => {
|
||||
it('has German as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
|
||||
it('has Español as second language to choose', () => {
|
||||
expect(wrapper.findAll('li').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -66,10 +66,19 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
})
|
||||
describe('navigator language is "es-ES" (not supported)', () => {
|
||||
describe('navigator language is "es-ES"', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows Español as language ', async () => {
|
||||
languageGetter.mockReturnValue('es-ES')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
describe('navigator language is "fr-FR" (not supported)', () => {
|
||||
const languageGetter = jest.spyOn(navigator, 'language', 'get')
|
||||
it('shows English as language ', async () => {
|
||||
languageGetter.mockReturnValue('es-ES')
|
||||
languageGetter.mockReturnValue('fr-FR')
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
@ -93,9 +102,17 @@ describe('LanguageSwitch', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
})
|
||||
describe('language "es" in store', () => {
|
||||
it('shows Español as language', async () => {
|
||||
wrapper.vm.$store.state.language = 'es'
|
||||
wrapper.vm.setCurrentLanguage()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
describe('language menu', () => {
|
||||
it('has English and German as languages to choose', () => {
|
||||
expect(wrapper.findAll('span.locales')).toHaveLength(2)
|
||||
it('has English, German and Español as languages to choose', () => {
|
||||
expect(wrapper.findAll('span.locales')).toHaveLength(3)
|
||||
})
|
||||
it('has English as first language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(0).text()).toBe('English')
|
||||
@ -103,6 +120,9 @@ describe('LanguageSwitch', () => {
|
||||
it('has German as second language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(1).text()).toBe('Deutsch')
|
||||
})
|
||||
it('has Español as third language to choose', () => {
|
||||
expect(wrapper.findAll('span.locales').at(2).text()).toBe('Español')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ export default {
|
||||
options: [
|
||||
{ value: 'de', text: this.$t('settings.language.de') },
|
||||
{ value: 'en', text: this.$t('settings.language.en') },
|
||||
{ value: 'es', text: this.$t('settings.language.es') },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@ const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
hasElopage: false,
|
||||
hasElopage: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
@ -39,15 +39,17 @@ describe('Navbar', () => {
|
||||
expect(wrapper.find('div.component-navbar').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navigation Navbar', () => {
|
||||
describe('navigation Navbar (general elements)', () => {
|
||||
it('has .navbar-brand in the navbar', () => {
|
||||
expect(wrapper.find('.navbar-brand').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has b-navbar-toggle in the navbar', () => {
|
||||
expect(wrapper.find('.navbar-toggler').exists()).toBeTruthy()
|
||||
})
|
||||
it('has ten b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(10)
|
||||
|
||||
it('has twelve b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(12)
|
||||
})
|
||||
|
||||
it('has first nav-item "amount GDD" in navbar', () => {
|
||||
@ -57,31 +59,61 @@ describe('Navbar', () => {
|
||||
it('has first nav-item "navigation.overview" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('navigation.overview')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.send" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.send')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.transactions" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.transactions')
|
||||
})
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.profile')
|
||||
|
||||
it('has first nav-item "gdt.gdt" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('gdt.gdt')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.community" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.community')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.profile')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe(
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toContain('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(9).find('a').attributes('href')).toBe(
|
||||
'https://elopage.com',
|
||||
)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
|
||||
expect(wrapper.findAll('.nav-item').at(11).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has no elopage account)', () => {
|
||||
beforeAll(() => {
|
||||
mocks.$store.state.hasElopage = false
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(10).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('check watch visible true', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setProps({ visible: true })
|
||||
|
||||
@ -52,17 +52,22 @@
|
||||
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.transactions') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/gdt" class="mb-3">
|
||||
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
|
||||
{{ $t('gdt.gdt') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/community" class="mb-3">
|
||||
<b-icon icon="people" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.community') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/profile" class="mb-3">
|
||||
<b-icon icon="gear" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.profile') }}
|
||||
</b-nav-item>
|
||||
<br />
|
||||
<b-nav-item :href="elopageUri" class="mb-3" target="_blank">
|
||||
<b-nav-item v-if="$store.state.hasElopage" :href="elopageUri" class="mb-3" target="_blank">
|
||||
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.members_area') }}
|
||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
|
||||
{{ $t('math.exclaim') }}
|
||||
</b-badge>
|
||||
</b-nav-item>
|
||||
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
|
||||
|
||||
@ -27,15 +27,12 @@ describe('Sidebar', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#component-sidebar').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navigation Navbar', () => {
|
||||
it('has seven b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(8)
|
||||
})
|
||||
|
||||
describe('navigation Navbar (general elements)', () => {
|
||||
it('has first nav-item "navigation.overview" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
|
||||
})
|
||||
@ -48,17 +45,46 @@ describe('Sidebar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions')
|
||||
})
|
||||
|
||||
it('has first nav-item "gdt.gdt" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('gdt.gdt')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.community" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(3).text()).toContain('navigation.community')
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.community')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.profile" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.profile')
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.profile')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has an elopage account)', () => {
|
||||
it('has eight b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(9)
|
||||
})
|
||||
|
||||
it('has a link to the members area', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(5).find('a').attributes('href')).toBe('#')
|
||||
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.members_area')
|
||||
expect(wrapper.findAll('.nav-item').at(6).find('a').attributes('href')).toBe('#')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(7).text()).toEqual('navigation.admin_area')
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.logout" in navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.logout')
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigation Navbar (user has no elopage account)', () => {
|
||||
beforeAll(() => {
|
||||
mocks.$store.state.hasElopage = false
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has seven b-nav-item in the navbar', () => {
|
||||
expect(wrapper.findAll('.nav-item')).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('has first nav-item "navigation.admin_area" in navbar', () => {
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.transactions') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/gdt" class="mb-3">
|
||||
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
|
||||
{{ $t('gdt.gdt') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item to="/community" class="mb-3">
|
||||
<b-icon icon="people" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.community') }}
|
||||
@ -27,12 +31,14 @@
|
||||
</b-nav>
|
||||
<hr />
|
||||
<b-nav vertical class="w-100">
|
||||
<b-nav-item class="mb-3" :href="elopageUri" target="_blank">
|
||||
<b-nav-item
|
||||
v-if="$store.state.hasElopage"
|
||||
class="mb-3"
|
||||
:href="elopageUri"
|
||||
target="_blank"
|
||||
>
|
||||
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
|
||||
{{ $t('navigation.members_area') }}
|
||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
|
||||
{{ $t('math.exclaim') }}
|
||||
</b-badge>
|
||||
</b-nav-item>
|
||||
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
|
||||
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
|
||||
|
||||
@ -18,17 +18,17 @@
|
||||
<b-icon icon="three-dots-vertical"></b-icon>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copy">
|
||||
<b-dropdown-item v-if="validLink" class="test-copy-link" @click="copyLink">
|
||||
<b-icon icon="clipboard"></b-icon>
|
||||
{{ $t('gdd_per_link.copy') }}
|
||||
{{ $t('gdd_per_link.copy-link') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-if="validLink"
|
||||
class="test-copy-text pt-3"
|
||||
@click="copyLinkWithText()"
|
||||
@click="copyLinkWithText"
|
||||
>
|
||||
<b-icon icon="clipboard-plus"></b-icon>
|
||||
{{ $t('gdd_per_link.copy-with-text') }}
|
||||
{{ $t('gdd_per_link.copy-link-with-text') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
v-if="validLink"
|
||||
@ -76,9 +76,11 @@ import MemoRow from '../TransactionRows/MemoRow'
|
||||
import DateRow from '../TransactionRows/DateRow'
|
||||
import DecayRow from '../TransactionRows/DecayRow'
|
||||
import FigureQrCode from '@/components/QrCode/FigureQrCode.vue'
|
||||
import { copyLinks } from '../../mixins/copyLinks'
|
||||
|
||||
export default {
|
||||
name: 'TransactionLink',
|
||||
mixins: [copyLinks],
|
||||
components: {
|
||||
TypeIcon,
|
||||
AmountAndNameRow,
|
||||
@ -88,43 +90,10 @@ export default {
|
||||
FigureQrCode,
|
||||
},
|
||||
props: {
|
||||
amount: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
holdAvailableAmount: { type: String, required: true },
|
||||
id: { type: Number, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
methods: {
|
||||
copy() {
|
||||
navigator.clipboard
|
||||
.writeText(this.link)
|
||||
.then(() => {
|
||||
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'))
|
||||
})
|
||||
},
|
||||
copyLinkWithText() {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}`,
|
||||
)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$bvModal.show('modalPopoverCopyError' + this.id)
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
deleteLink() {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('gdd_per_link.delete-the-link')).then(async (value) => {
|
||||
if (value)
|
||||
|
||||
@ -74,6 +74,9 @@ export const createTransactionLink = gql`
|
||||
mutation($amount: Decimal!, $memo: String!) {
|
||||
createTransactionLink(amount: $amount, memo: $memo) {
|
||||
link
|
||||
amount
|
||||
memo
|
||||
validUntil
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -3,6 +3,7 @@ import VueI18n from 'vue-i18n'
|
||||
|
||||
import en from 'vee-validate/dist/locale/en'
|
||||
import de from 'vee-validate/dist/locale/de'
|
||||
import es from 'vee-validate/dist/locale/es'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
@ -26,6 +27,12 @@ function loadLocaleMessages() {
|
||||
...messages[locale],
|
||||
}
|
||||
}
|
||||
if (locale === 'es') {
|
||||
messages[locale] = {
|
||||
validations: es,
|
||||
...messages[locale],
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return messages
|
||||
@ -58,6 +65,19 @@ const numberFormats = {
|
||||
useGrouping: false,
|
||||
},
|
||||
},
|
||||
es: {
|
||||
decimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
},
|
||||
ungroupedDecimal: {
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
useGrouping: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const dateTimeFormats = {
|
||||
@ -117,6 +137,34 @@ const dateTimeFormats = {
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
es: {
|
||||
short: {
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
long: {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
monthShort: {
|
||||
month: 'short',
|
||||
},
|
||||
month: {
|
||||
month: 'long',
|
||||
},
|
||||
year: {
|
||||
year: 'numeric',
|
||||
},
|
||||
monthAndYear: {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default new VueI18n({
|
||||
|
||||
@ -55,7 +55,8 @@
|
||||
"selectDate": "Wann war dein Beitrag?",
|
||||
"submit": "Einreichen",
|
||||
"submitted": "Der Beitrag wurde eingereicht.",
|
||||
"updated": "Der Beitrag wurde geändert."
|
||||
"updated": "Der Beitrag wurde geändert.",
|
||||
"yourActivity": "Bitte trage eine Tätigkeit ein!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "dankt dir mit"
|
||||
@ -117,6 +118,7 @@
|
||||
"from": "Von",
|
||||
"generate_now": "Jetzt generieren",
|
||||
"lastname": "Nachname",
|
||||
"mandatoryField": "Pflichtfeld",
|
||||
"memo": "Nachricht",
|
||||
"message": "Nachricht",
|
||||
"new_balance": "Neuer Kontostand nach Bestätigung",
|
||||
@ -151,8 +153,8 @@
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest. Du kannst auch noch eine Nachricht eintragen. Beim Klick „Jetzt generieren“ wird ein Link erstellt, den du versenden kannst.",
|
||||
"copy": "kopieren",
|
||||
"copy-with-text": "Link und Text kopieren",
|
||||
"copy-link": "Link kopieren",
|
||||
"copy-link-with-text": "Link und Text kopieren",
|
||||
"created": "Der Link wurde erstellt!",
|
||||
"credit-your-gradido": "Damit die Gradido gutgeschrieben werden können, klicke auf den Link!",
|
||||
"decay-14-day": "Vergänglichkeit für 14 Tage",
|
||||
@ -204,8 +206,8 @@
|
||||
"login": "Anmeldung",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"exclaim": "!",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
@ -244,6 +246,7 @@
|
||||
"changeLanguage": "Sprache ändern",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"success": "Deine Sprache wurde erfolgreich geändert."
|
||||
},
|
||||
"name": {
|
||||
|
||||
@ -55,7 +55,8 @@
|
||||
"selectDate": "When was your contribution?",
|
||||
"submit": "Submit",
|
||||
"submitted": "The contribution was submitted.",
|
||||
"updated": "The contribution was changed."
|
||||
"updated": "The contribution was changed.",
|
||||
"yourActivity": "Please enter an activity!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "thanks you with"
|
||||
@ -117,6 +118,7 @@
|
||||
"from": "from",
|
||||
"generate_now": "Generate now",
|
||||
"lastname": "Lastname",
|
||||
"mandatoryField": "mandatory field",
|
||||
"memo": "Message",
|
||||
"message": "Message",
|
||||
"new_balance": "Account balance after confirmation",
|
||||
@ -151,8 +153,8 @@
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Select an amount that you would like to send via link. You can also enter a message. Click 'Generate now' to create a link that you can share.",
|
||||
"copy": "copy",
|
||||
"copy-with-text": "Copy link and text",
|
||||
"copy-link": "Copy link",
|
||||
"copy-link-with-text": "Copy link and text",
|
||||
"created": "Link was created!",
|
||||
"credit-your-gradido": "For the Gradido to be credited, click on the link!",
|
||||
"decay-14-day": "Decay for 14 days",
|
||||
@ -204,8 +206,8 @@
|
||||
"login": "Login",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"exclaim": "!",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
@ -244,6 +246,7 @@
|
||||
"changeLanguage": "Change language",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"success": "Your language has been successfully updated."
|
||||
},
|
||||
"name": {
|
||||
|
||||
324
frontend/src/locales/es.json
Normal file
324
frontend/src/locales/es.json
Normal file
@ -0,0 +1,324 @@
|
||||
{
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 Gracias, por estar con nosotros!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"advanced-calculation": "Proyección",
|
||||
"auth": {
|
||||
"left": {
|
||||
"dignity": "Dignidad",
|
||||
"donation": "Donación",
|
||||
"gratitude": "Gratitud",
|
||||
"hasAccount": "Ya estas registrado?",
|
||||
"hereLogin": "Regístrate aquí",
|
||||
"learnMore": "Infórmate aquí …",
|
||||
"oneDignity": "Damos los unos a los otros y agradecemos con Gradido.",
|
||||
"oneDonation": "Eres un regalo para la comunidad. 1000 gracias por estar con nosotros.",
|
||||
"oneGratitude": "Por los demás, por toda la humanidad, por la naturaleza."
|
||||
},
|
||||
"navbar": {
|
||||
"aboutGradido": "Sobre Gradido"
|
||||
}
|
||||
},
|
||||
"back": "Volver",
|
||||
"community": {
|
||||
"choose-another-community": "Escoger otra comunidad",
|
||||
"community": "Comunidad",
|
||||
"continue-to-registration": "Continuar con el registro",
|
||||
"current-community": "Comunidad actual",
|
||||
"myContributions": "Mis contribuciones al bien común",
|
||||
"other-communities": "Otras comunidades",
|
||||
"submitContribution": "Aportar una contribución",
|
||||
"switch-to-this-community": "cambiar a esta comunidad"
|
||||
},
|
||||
"contribution": {
|
||||
"activity": "Actividad",
|
||||
"alert": {
|
||||
"communityNoteList": "Aquí encontrarás todas las contribuciones enviadas y confirmadas de todos los miembros de esta comunidad.",
|
||||
"confirm": "confirmado",
|
||||
"myContributionNoteList": "Puedes editar o eliminar las contribuciones enviadas que aún no han sido confirmadas en cualquier momento.",
|
||||
"myContributionNoteSupport": "Pronto existirá la posibilidad de que puedas dialogar con los moderadores. Si tienes algún problema ahora, ponte en contacto con el equipo de asistencia.",
|
||||
"pending": "Enviado y a la espera de confirmación",
|
||||
"rejected": "rechazado"
|
||||
},
|
||||
"date": "Contribución para:",
|
||||
"delete": "Eliminar la contribución. ¿Estás seguro?",
|
||||
"deleted": "¡La contribución ha sido borrada! Pero seguirá siendo visible.",
|
||||
"formText": {
|
||||
"bringYourTalentsTo": "¡Contribuye a la comunidad con tus talentos! Premiamos tu compromiso voluntario con 20 GDD por hora hasta un máximo de 1.000 GDD al mes.",
|
||||
"describeYourCommunity": "¡Describe tu contribución al bien-común con detalles de las horas e introduce una cantidad de 20 GDD por hora! Tras la confirmación de un moderador, el importe se abonará en tu cuenta.",
|
||||
"maxGDDforMonth": "Sólo puede presentar un máximo de {amount} GDD para el mes seleccionado.",
|
||||
"openAmountForMonth": "Para <b>{monthAndYear}</b> aún puedes presentar <b>{creation}</b> GDD.",
|
||||
"yourContribution": "Tu contribución a la comunidad."
|
||||
},
|
||||
"noDateSelected": "Elige cualquier fecha del mes.",
|
||||
"selectDate": "¿Cuando fue tu contribución?",
|
||||
"submit": "Enviar",
|
||||
"submitted": "Tu contribución ha sido enviada.",
|
||||
"updated": "La contribución se modificó.",
|
||||
"yourActivity": "¡Por favor, introduce una actividad!"
|
||||
},
|
||||
"contribution-link": {
|
||||
"thanksYouWith": "agradecidos con"
|
||||
},
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Esta transacción no implica disminución en su valor.",
|
||||
"calculation_decay": "Cálculo de la disminución gradual del valor",
|
||||
"calculation_total": "Cálculo de la suma total",
|
||||
"decay": "Disminución gradual del valor",
|
||||
"decay_introduced": "La disminución gradual empezó el:",
|
||||
"decay_since_last_transaction": "Disminución gradual",
|
||||
"last_transaction": "Transacción anterior",
|
||||
"past_time": "Tiempo transcurrido",
|
||||
"Starting_block_decay": "Startblock disminución gradual",
|
||||
"total": "Total",
|
||||
"types": {
|
||||
"creation": "Creado",
|
||||
"noDecay": "sin disminución gradual",
|
||||
"receive": "Recibido",
|
||||
"send": "Enviado"
|
||||
}
|
||||
},
|
||||
"delete": "Eliminar",
|
||||
"em-dash": "—",
|
||||
"error": {
|
||||
"email-already-sent": "Ya te hemos enviado un correo electrónico hace menos de 10 minutos.",
|
||||
"empty-transactionlist": "Ha habido un error en la transmisión del número de sus transacciones.",
|
||||
"error": "Error!",
|
||||
"no-account": "Lamentablemente no hemos podido encontrar una cuenta (activada) con estos datos.",
|
||||
"no-transactionlist": "Lamentablemente, hubo un error. No se ha transmitido ninguna transacción desde el servidor.",
|
||||
"no-user": "No hay usuario con estas referencias.",
|
||||
"session-expired": "La sesión se cerró por razones de seguridad.",
|
||||
"unknown-error": "Error desconocido: "
|
||||
},
|
||||
"followUs": "sigue nos:",
|
||||
"footer": {
|
||||
"app_version": "App versión {version}",
|
||||
"copyright": {
|
||||
"link": "Gradido-Akademie",
|
||||
"year": "© {year}"
|
||||
},
|
||||
"imprint": "Aviso legal",
|
||||
"privacy_policy": "Protección de Datos",
|
||||
"short_hash": "({shortHash})",
|
||||
"whitepaper": "Whitepaper"
|
||||
},
|
||||
"form": {
|
||||
"amount": "Importe",
|
||||
"at": "am",
|
||||
"cancel": "Cancelar",
|
||||
"change": "Cambiar",
|
||||
"check_now": "Revisar",
|
||||
"close": "Cerrar",
|
||||
"current_balance": "Saldo de cuenta actual",
|
||||
"date": "Fecha",
|
||||
"description": "Descripción",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Nombre",
|
||||
"from": "De",
|
||||
"generate_now": "crear ahora",
|
||||
"lastname": "Apellido",
|
||||
"mandatoryField": "campo obligatorio",
|
||||
"memo": "Mensaje",
|
||||
"message": "Noticia",
|
||||
"new_balance": "Saldo de cuenta nuevo depués de confirmación",
|
||||
"no_gdd_available": "No dispones de GDD para enviar.",
|
||||
"password": "Contraseña",
|
||||
"passwordRepeat": "Repetir contraseña",
|
||||
"password_new": "contraseña nueva",
|
||||
"password_new_repeat": "Repetir contraseña nueva",
|
||||
"password_old": "contraseña antigua",
|
||||
"recipient": "Destinatario",
|
||||
"reset": "Restablecer",
|
||||
"save": "Guardar",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Escanea el código QR de tu pareja",
|
||||
"sender": "Remitente",
|
||||
"send_check": "Confirma tu transacción. Por favor revisa toda la información nuevamente!",
|
||||
"send_now": "Enviar ahora",
|
||||
"send_transaction_error": "Desafortunadamente, la transacción no se pudo ejecutar!",
|
||||
"send_transaction_success": "Su transacción fue ejecutada con éxito",
|
||||
"sorry": "Disculpa",
|
||||
"thx": "Gracias",
|
||||
"time": "Tiempo",
|
||||
"to": "hasta",
|
||||
"to1": "para",
|
||||
"validation": {
|
||||
"gddSendAmount": "El campo {_field_} debe ser un número entre {min} y {max} con un máximo de dos decimales",
|
||||
"is-not": "No es posible transferirte Gradidos a ti mismo",
|
||||
"usernmae-regex": "El nombre de usuario debe comenzar con una letra seguida de al menos dos caracteres alfanuméricos.",
|
||||
"usernmae-unique": "Este nombre de usuario ya está adjudicado."
|
||||
},
|
||||
"your_amount": "Tu importe"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Selecciona una cantidad que te gustaría enviar a través de un enlace. También puedes ingresar un mensaje. Cuando haces clic en 'Generar ahora', se crea un enlace que puedes enviar.",
|
||||
"copy-link": "Copiar enlace",
|
||||
"copy-link-with-text": "Copiar texto y enlace",
|
||||
"created": "El enlace ha sido creado",
|
||||
"credit-your-gradido": "Para que se te acrediten los Gradidos, haz clic en el enlace!",
|
||||
"decay-14-day": "Disminución gradual por 14 días",
|
||||
"delete-the-link": "Eliminar el enlace?",
|
||||
"deleted": "El enlace ha sido eliminado!",
|
||||
"expiredOn": "Vencido el:",
|
||||
"has-account": "Ya tienes una cuenta Gradido?",
|
||||
"header": "Transferir Gradidos por medio de un enlace",
|
||||
"isFree": "Gradido es gratis en todo el mundo.",
|
||||
"link-and-text-copied": "El enlace y su mensaje se han copiado en el portapapeles. Ahora puedes ponerlo en un correo electrónico o mensaje.",
|
||||
"link-copied": "El enlace se ha copiado en el portapapeles. Ahora puedes pegarlo en un correo electrónico o mensaje.",
|
||||
"link-deleted": "El enlace se eliminó el {date}.",
|
||||
"link-expired": "El enlace ya no es válido. La validez expiró el {date}.",
|
||||
"link-overview": "Resumen de enlaces",
|
||||
"links_count": "Enlaces activos",
|
||||
"links_sum": "Enlaces abiertos y códigos QR",
|
||||
"no-account": "Aún no tienes una cuenta de Gradido?",
|
||||
"no-redeem": "No puedes canjear tu propio enlace!",
|
||||
"not-copied": "¡Desafortunadamente, su dispositivo no permite copiar! Copie el enlace manualmente!",
|
||||
"redeem": "Canjear",
|
||||
"redeem-text": "¿Quieres canjear el importe ahora?",
|
||||
"redeemed": "¡Canjeado con éxito! Tu cuenta ha sido acreditada con {n} GDD.",
|
||||
"redeemed-at": "El enlace ya se canjeó el {date}.",
|
||||
"redeemed-title": "canjeado",
|
||||
"to-login": "iniciar sesión",
|
||||
"to-register": "Registre una nueva cuenta.",
|
||||
"validUntil": "Válido hasta",
|
||||
"validUntilDate": "El enlace es válido hasta el {date} ."
|
||||
},
|
||||
"gdt": {
|
||||
"calculation": "Cálculo del Gradido Transform",
|
||||
"contribution": "Importe",
|
||||
"conversion": "Conversión",
|
||||
"conversion-gdt-euro": "Conversión Euro / Gradido Transform (GDT)",
|
||||
"credit": "Abono",
|
||||
"factor": "Factor",
|
||||
"formula": "Formula de cálculo",
|
||||
"funding": "Las donaciones",
|
||||
"gdt": "Gradido Transform",
|
||||
"gdt-received": "Gradido Transform (GDT) recibido",
|
||||
"no-transactions": "Aún no tienes un Gradido Transform (GDT).",
|
||||
"not-reachable": "No es posible acceder al servidor GDT.",
|
||||
"publisher": "Tu nuevo miembro referido ha pagado la cuota",
|
||||
"raise": "Aumento",
|
||||
"recruited-member": "Miembro invitado"
|
||||
},
|
||||
"language": "Idioma",
|
||||
"link-load": "recargar el último enlace |recargar los últimos {n} enlaces | descargar más {n} enlaces",
|
||||
"login": "iniciar sesión",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
"message": {
|
||||
"activateEmail": "Tu cuenta aún no ha sido activada. Por favor revisa tu correo electrónico y haz clic en el enlace de activación o solicita uno nuevo enlace de activación a través de la página restablecer contraseña.",
|
||||
"checkEmail": "Tu correo electrónico ha sido verificado con éxito. Puedes registrarte ahora.",
|
||||
"email": "Te hemos enviado un correo electrónico.",
|
||||
"errorTitle": "Atención!",
|
||||
"register": "Ya estás registrado, por favor revisa tu correo electrónico y haz clic en el enlace de activación.",
|
||||
"reset": "Tu contraseña ha sido cambiada.",
|
||||
"title": "Gracias!",
|
||||
"unsetPassword": "Tu contraseña aún no ha sido configurada. Por favor reinícialo."
|
||||
},
|
||||
"navigation": {
|
||||
"admin_area": "Área de administración",
|
||||
"community": "Comunidad",
|
||||
"logout": "Salir",
|
||||
"members_area": "Área de afiliados",
|
||||
"overview": "Resumen",
|
||||
"profile": "Mi Perfil",
|
||||
"send": "Enviar",
|
||||
"support": "Soporte",
|
||||
"transactions": "Transacciones"
|
||||
},
|
||||
"qrCode": "Código QR",
|
||||
"send_gdd": "Enviar GDD",
|
||||
"send_per_link": "Enviar GDD mediante un enlace",
|
||||
"session": {
|
||||
"extend": "Permanecer en sesión iniciada",
|
||||
"lightText": "Si no has realizado ninguna acción durante más de 10 minutos, se cerrará tu sesión por razones de seguridad.",
|
||||
"logoutIn": "Cerrar sesión en ",
|
||||
"warningText": "Aún estas?"
|
||||
},
|
||||
"settings": {
|
||||
"language": {
|
||||
"changeLanguage": "Cambiar idioma",
|
||||
"de": "Deutsch",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"success": "Tu idioma ha sido cambiado con éxito."
|
||||
},
|
||||
"name": {
|
||||
"change-name": "Cambiar nombre",
|
||||
"change-success": "Tu nombre ha sido cambiado con éxito."
|
||||
},
|
||||
"newsletter": {
|
||||
"newsletter": "Informaciones por correo electrónico",
|
||||
"newsletterFalse": "No recibirás informaciones por correo electrónico.",
|
||||
"newsletterTrue": "Recibirás informaciones por correo electrónico."
|
||||
},
|
||||
"password": {
|
||||
"change-password": "Cambiar contraseña",
|
||||
"forgot_pwd": "Olvide la contraseña?",
|
||||
"resend_subtitle": "Su enlace de activación ha caducado. Puedes solicitar uno nuevo aquí.",
|
||||
"reset": "Restablecer contraseña",
|
||||
"reset-password": {
|
||||
"text": "Ahora introduce una nueva contraseña con la que quieras acceder a tu cuenta de Gradido en el futuro.."
|
||||
},
|
||||
"send_now": "Enviar",
|
||||
"set": "Establecer contraseña",
|
||||
"set-password": {
|
||||
"text": "Ahora guarda tu nueva contraseña, que podrás utilizar para acceder a tu cuenta de Gradido en el futuro."
|
||||
},
|
||||
"subtitle": "Si has olvidado tu contraseña, puedes restablecerla aquí."
|
||||
}
|
||||
},
|
||||
"signin": "Iniciar sesión",
|
||||
"signup": "Registrarse",
|
||||
"site": {
|
||||
"forgotPassword": {
|
||||
"heading": "Por favor, introduce la dirección de correo electrónico con la que estas registrado en Gradido."
|
||||
},
|
||||
"login": {
|
||||
"heading": "Inicia sesión con tus datos de acceso. Manténlos seguros en todo momento!"
|
||||
},
|
||||
"resetPassword": {
|
||||
"heading": "Por favor, introduce tu contraseña y repítela."
|
||||
},
|
||||
"signup": {
|
||||
"agree": "Acepto la <a href='https://gradido.net/de/datenschutz/' target='_blank' >Política de privacidad</a>.",
|
||||
"dont_match": "Las contraseñas no coinciden.",
|
||||
"heading": "Regístrate introduciendo todos los datos completos y en los campos correctos.",
|
||||
"lowercase": "Se requiere una letra minúscula.",
|
||||
"minimum": "Al menos 8 caracteres.",
|
||||
"no-whitespace": "Sin espacios ni tabulaciones.",
|
||||
"one_number": "Se requiere un número.",
|
||||
"special-char": "Caracteres especiales requeridos (por ejemplo, _ o &)",
|
||||
"uppercase": "Letra mayúscula requerida."
|
||||
}
|
||||
},
|
||||
"success": "Lo lograste",
|
||||
"time": {
|
||||
"days": "Días",
|
||||
"hours": "Horas",
|
||||
"minutes": "Minutos",
|
||||
"month": "Mes",
|
||||
"months": "Meses",
|
||||
"seconds": "Segundos",
|
||||
"years": "Año"
|
||||
},
|
||||
"transaction": {
|
||||
"gdd-text": "Transacciones Gradido",
|
||||
"gdt-text": "Transacciones GradidoTransform ",
|
||||
"nullTransactions": "Todavía no tienes ninguna transacción en tu cuenta.",
|
||||
"receiverDeleted": "La cuenta del destinatario ha sido eliminada.",
|
||||
"receiverNotFound": "Destinatario no encontrado",
|
||||
"show_all": "Ver todas las transacciones de <strong>{count}</strong>"
|
||||
},
|
||||
"transaction-link": {
|
||||
"send_you": "te envía"
|
||||
},
|
||||
"via_link": "atraves de un enlace",
|
||||
"welcome": "Bienvenido a la comunidad."
|
||||
}
|
||||
@ -11,6 +11,12 @@ const locales = [
|
||||
iso: 'de-DE',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
iso: 'es-ES',
|
||||
enabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
export default locales
|
||||
|
||||
44
frontend/src/mixins/copyLinks.js
Normal file
44
frontend/src/mixins/copyLinks.js
Normal file
@ -0,0 +1,44 @@
|
||||
export const copyLinks = {
|
||||
props: {
|
||||
link: { type: String, required: true },
|
||||
amount: { type: String, required: true },
|
||||
memo: { type: String, required: true },
|
||||
validUntil: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canCopyLink: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyLink() {
|
||||
navigator.clipboard
|
||||
.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'))
|
||||
})
|
||||
},
|
||||
copyLinkWithText() {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}`,
|
||||
)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.canCopyLink = false
|
||||
this.toastError(this.$t('gdd_per_link.not-copied'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -25,9 +25,11 @@ describe('Send', () => {
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => String(n)),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
email: 'sender@example.org',
|
||||
firstName: 'Testy',
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
@ -160,11 +162,15 @@ describe('Send', () => {
|
||||
})
|
||||
|
||||
describe('transaction form link', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: {
|
||||
createTransactionLink: {
|
||||
link: 'http://localhost/redeem/0123456789',
|
||||
amount: '56.78',
|
||||
memo: 'Make the best of the link!',
|
||||
validUntil: now,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -228,18 +234,64 @@ describe('Send', () => {
|
||||
navigator.clipboard = navigatorClipboard
|
||||
})
|
||||
|
||||
describe('copy with success', () => {
|
||||
describe('copy link with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
await wrapper.findAll('button').at(1).trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
'http://localhost/redeem/0123456789',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-copied')
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy with error', () => {
|
||||
describe('copy link with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.findAll('button').at(1).trigger('click')
|
||||
})
|
||||
|
||||
it('toasts error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('gdd_per_link.not-copied')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy link and text with success', () => {
|
||||
const navigatorClipboard = navigator.clipboard
|
||||
beforeAll(() => {
|
||||
delete navigator.clipboard
|
||||
navigator.clipboard = { writeText: navigatorClipboardMock }
|
||||
})
|
||||
afterAll(() => {
|
||||
navigator.clipboard = navigatorClipboard
|
||||
})
|
||||
|
||||
describe('copy link and text with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
'http://localhost/redeem/0123456789\n' +
|
||||
'Testy transaction-link.send_you 56.78 Gradido.\n' +
|
||||
'"Make the best of the link!"\n' +
|
||||
'gdd_per_link.credit-your-gradido gdd_per_link.validUntilDate',
|
||||
)
|
||||
})
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-and-text-copied')
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy link and text with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
@ -253,7 +305,7 @@ describe('Send', () => {
|
||||
|
||||
describe('close button click', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('button').at(2).trigger('click')
|
||||
await wrapper.findAll('button').at(3).trigger('click')
|
||||
})
|
||||
|
||||
it('Shows the TransactionForm', () => {
|
||||
|
||||
@ -41,7 +41,13 @@
|
||||
></transaction-result-send-error>
|
||||
</template>
|
||||
<template #transactionResultLink>
|
||||
<transaction-result-link :link="link" @on-reset="onReset"></transaction-result-link>
|
||||
<transaction-result-link
|
||||
:link="link"
|
||||
:amount="amount"
|
||||
:memo="memo"
|
||||
:validUntil="validUntil"
|
||||
@on-reset="onReset"
|
||||
></transaction-result-link>
|
||||
</template>
|
||||
</gdd-send>
|
||||
<hr />
|
||||
@ -144,7 +150,15 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('set-tunneled-email', null)
|
||||
this.link = result.data.createTransactionLink.link
|
||||
const {
|
||||
data: {
|
||||
createTransactionLink: { link, amount, memo, validUntil },
|
||||
},
|
||||
} = result
|
||||
this.link = link
|
||||
this.amount = amount
|
||||
this.memo = memo
|
||||
this.validUntil = validUntil
|
||||
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionResultLink
|
||||
this.updateTransactions({})
|
||||
|
||||
@ -6,6 +6,7 @@ import { toastErrorSpy } from '@test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockRouterReplace = jest.fn()
|
||||
const windowScrollToMock = jest.fn()
|
||||
window.scrollTo = windowScrollToMock
|
||||
|
||||
@ -39,6 +40,9 @@ describe('Transactions', () => {
|
||||
$apollo: {
|
||||
query: apolloMock,
|
||||
},
|
||||
$router: {
|
||||
replace: mockRouterReplace,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="pb-4">
|
||||
<b-tabs v-model="tabIndex" content-class="" justified>
|
||||
<b-tab :title="`Gradido (${$n(balance, 'decimal')} GDD)`" class="px-4">
|
||||
<b-tab
|
||||
:title="`Gradido (${$n(balance, 'decimal')} GDD)`"
|
||||
class="px-4"
|
||||
@click="$router.replace('/transactions')"
|
||||
>
|
||||
<p class="tab-tex">{{ $t('transaction.gdd-text') }}</p>
|
||||
|
||||
<gdd-transaction-list
|
||||
@ -42,6 +46,7 @@ export default {
|
||||
GdtTransactionList,
|
||||
},
|
||||
props: {
|
||||
gdt: { type: Boolean, default: false },
|
||||
balance: { type: Number, default: 0 },
|
||||
GdtBalance: { type: Number, default: 0 },
|
||||
transactions: {
|
||||
@ -77,6 +82,8 @@ export default {
|
||||
this.transactionsGdt = listGDTEntries.gdtEntries
|
||||
this.transactionGdtCount = listGDTEntries.count
|
||||
window.scrollTo(0, 0)
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.$route.path === '/transactions' ? this.$router.replace('/gdt') : ''
|
||||
})
|
||||
.catch((error) => {
|
||||
this.transactionGdtCount = -1
|
||||
@ -94,10 +101,26 @@ export default {
|
||||
return this.$t('gdt.gdt')
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.gdt) {
|
||||
this.tabIndex = 1
|
||||
this.updateGdt()
|
||||
} else {
|
||||
this.tabIndex = 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentPage() {
|
||||
this.updateGdt()
|
||||
},
|
||||
gdt() {
|
||||
if (this.gdt) {
|
||||
this.tabIndex = 1
|
||||
this.updateGdt()
|
||||
} else {
|
||||
this.tabIndex = 0
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -50,7 +50,7 @@ describe('router', () => {
|
||||
})
|
||||
|
||||
it('has sixteen routes defined', () => {
|
||||
expect(routes).toHaveLength(17)
|
||||
expect(routes).toHaveLength(18)
|
||||
})
|
||||
|
||||
describe('overview', () => {
|
||||
@ -108,6 +108,17 @@ describe('router', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('gdt', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/gdt').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "GDT" page', async () => {
|
||||
const component = await routes.find((r) => r.path === '/gdt').component()
|
||||
expect(component.default.name).toBe('Transactions')
|
||||
})
|
||||
})
|
||||
|
||||
describe('login', () => {
|
||||
it('loads the "Login" page', async () => {
|
||||
const component = await routes.find((r) => r.path === '/login/:code?').component()
|
||||
|
||||
@ -34,6 +34,15 @@ const routes = [
|
||||
{
|
||||
path: '/transactions',
|
||||
component: () => import('@/pages/Transactions.vue'),
|
||||
props: { gdt: false },
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/gdt',
|
||||
component: () => import('@/pages/Transactions.vue'),
|
||||
props: { gdt: true },
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
|
||||
@ -2156,17 +2156,6 @@
|
||||
jest-message-util "^24.9.0"
|
||||
jest-mock "^24.9.0"
|
||||
|
||||
"@jest/fake-timers@^25.1.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
|
||||
integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
jest-message-util "^25.5.0"
|
||||
jest-mock "^25.5.0"
|
||||
jest-util "^25.5.0"
|
||||
lolex "^5.0.0"
|
||||
|
||||
"@jest/fake-timers@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
|
||||
@ -2378,16 +2367,6 @@
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@jest/types@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
|
||||
@ -2482,11 +2461,6 @@
|
||||
string-width "^2.0.0"
|
||||
strip-ansi "^5"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
||||
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7":
|
||||
version "7.1.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64"
|
||||
@ -3293,11 +3267,6 @@ acorn@^8.0.5:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe"
|
||||
integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==
|
||||
|
||||
acorn@^8.2.4:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895"
|
||||
integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==
|
||||
|
||||
acorn@^8.5.0, acorn@^8.7.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
|
||||
@ -3308,13 +3277,6 @@ address@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
|
||||
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
ajv-errors@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
|
||||
@ -4657,14 +4619,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
@ -4995,7 +4949,7 @@ colors@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
@ -5578,13 +5532,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^3.1.0, debug@^3.1.1, debug@^3.2.6:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
@ -5599,6 +5546,13 @@ debug@^4.0.1, debug@^4.1.1:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
decamelize-keys@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
|
||||
@ -7162,15 +7116,6 @@ forever-agent@~0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
@ -7864,15 +7809,6 @@ http-parser-js@>=0.5.1:
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
|
||||
integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==
|
||||
|
||||
http-proxy-agent@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
|
||||
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
|
||||
dependencies:
|
||||
"@tootallnate/once" "1"
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
http-proxy-middleware@0.19.1:
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
|
||||
@ -7906,14 +7842,6 @@ https-browserify@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
human-signals@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
@ -8430,11 +8358,6 @@ is-potential-custom-element-name@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
|
||||
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
|
||||
|
||||
is-potential-custom-element-name@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
|
||||
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
|
||||
|
||||
is-regex@^1.0.4, is-regex@^1.1.1, is-regex@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
|
||||
@ -8849,16 +8772,6 @@ jest-environment-jsdom-fifteen@^1.0.2:
|
||||
jest-util "^24.0.0"
|
||||
jsdom "^15.2.1"
|
||||
|
||||
jest-environment-jsdom-sixteen@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-2.0.0.tgz#0f8c12663ccd9836d248574decffc575bfb091e1"
|
||||
integrity sha512-BF+8P67aEJcd78TQzwSb9P4a73cArOWb5KgqI8eU6cHRWDIJdDRE8XTeZAmOuDSDhKpuEXjKkXwWB3GOJvqHJQ==
|
||||
dependencies:
|
||||
"@jest/fake-timers" "^25.1.0"
|
||||
jest-mock "^25.1.0"
|
||||
jest-util "^25.1.0"
|
||||
jsdom "^16.2.1"
|
||||
|
||||
jest-environment-jsdom@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
|
||||
@ -9073,20 +8986,6 @@ jest-message-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
|
||||
integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@jest/types" "^25.5.0"
|
||||
"@types/stack-utils" "^1.0.1"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
micromatch "^4.0.2"
|
||||
slash "^3.0.0"
|
||||
stack-utils "^1.0.1"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
@ -9109,13 +9008,6 @@ jest-mock@^24.0.0, jest-mock@^24.9.0:
|
||||
dependencies:
|
||||
"@jest/types" "^24.9.0"
|
||||
|
||||
jest-mock@^25.1.0, jest-mock@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
|
||||
integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
|
||||
@ -9392,17 +9284,6 @@ jest-util@^24.0.0, jest-util@^24.9.0:
|
||||
slash "^2.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
jest-util@^25.1.0, jest-util@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
|
||||
integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
chalk "^3.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
is-ci "^2.0.0"
|
||||
make-dir "^3.0.0"
|
||||
|
||||
jest-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1"
|
||||
@ -9655,39 +9536,6 @@ jsdom@^15.2.1:
|
||||
ws "^7.0.0"
|
||||
xml-name-validator "^3.0.0"
|
||||
|
||||
jsdom@^16.2.1:
|
||||
version "16.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
||||
integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
|
||||
dependencies:
|
||||
abab "^2.0.5"
|
||||
acorn "^8.2.4"
|
||||
acorn-globals "^6.0.0"
|
||||
cssom "^0.4.4"
|
||||
cssstyle "^2.3.0"
|
||||
data-urls "^2.0.0"
|
||||
decimal.js "^10.2.1"
|
||||
domexception "^2.0.1"
|
||||
escodegen "^2.0.0"
|
||||
form-data "^3.0.0"
|
||||
html-encoding-sniffer "^2.0.1"
|
||||
http-proxy-agent "^4.0.1"
|
||||
https-proxy-agent "^5.0.0"
|
||||
is-potential-custom-element-name "^1.0.1"
|
||||
nwsapi "^2.2.0"
|
||||
parse5 "6.0.1"
|
||||
saxes "^5.0.1"
|
||||
symbol-tree "^3.2.4"
|
||||
tough-cookie "^4.0.0"
|
||||
w3c-hr-time "^1.0.2"
|
||||
w3c-xmlserializer "^2.0.0"
|
||||
webidl-conversions "^6.1.0"
|
||||
whatwg-encoding "^1.0.5"
|
||||
whatwg-mimetype "^2.3.0"
|
||||
whatwg-url "^8.5.0"
|
||||
ws "^7.4.6"
|
||||
xml-name-validator "^3.0.0"
|
||||
|
||||
jsdom@^16.4.0:
|
||||
version "16.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.5.1.tgz#4ced6bbd7b77d67fb980e64d9e3e6fb900f97dd6"
|
||||
@ -10050,7 +9898,7 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.7.0:
|
||||
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -10067,13 +9915,6 @@ loglevel@^1.6.8:
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||
|
||||
lolex@^5.0.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
|
||||
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
@ -13848,13 +13689,6 @@ tr46@^2.0.2:
|
||||
dependencies:
|
||||
punycode "^2.1.1"
|
||||
|
||||
tr46@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
|
||||
integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
|
||||
dependencies:
|
||||
punycode "^2.1.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
@ -14792,15 +14626,6 @@ whatwg-url@^8.0.0:
|
||||
tr46 "^2.0.2"
|
||||
webidl-conversions "^6.1.0"
|
||||
|
||||
whatwg-url@^8.5.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
|
||||
integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==
|
||||
dependencies:
|
||||
lodash "^4.7.0"
|
||||
tr46 "^2.1.0"
|
||||
webidl-conversions "^6.1.0"
|
||||
|
||||
which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
@ -14923,7 +14748,7 @@ ws@^6.0.0, ws@^6.2.1:
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.0.0, ws@^7.4.6:
|
||||
ws@^7.0.0:
|
||||
version "7.5.6"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
|
||||
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user