mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 1106-first-transaction-cannot-be-expanded
This commit is contained in:
commit
b5a08d8c70
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@ -305,7 +305,7 @@ jobs:
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: generate changelog
|
||||
run: yarn auto-changelog --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
run: yarn auto-changelog --commit-limit 0 --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
- name: package-version-to-git-release
|
||||
continue-on-error: true # Will fail if tag exists
|
||||
id: create_release
|
||||
|
||||
13
.github/workflows/test.yml
vendored
13
.github/workflows/test.yml
vendored
@ -431,7 +431,7 @@ jobs:
|
||||
unit_test_backend:
|
||||
name: Unit tests - Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_backend,build_test_mariadb]
|
||||
needs: [build_test_mariadb]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -448,13 +448,6 @@ jobs:
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/mariadb.tar
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
##########################################################################
|
||||
# UNIT TESTS BACKEND #####################################################
|
||||
##########################################################################
|
||||
@ -469,7 +462,7 @@ jobs:
|
||||
run: sleep 30s
|
||||
shell: bash
|
||||
- name: backend Unit tests | test
|
||||
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn CI_workflow_test
|
||||
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
||||
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test
|
||||
##########################################################################
|
||||
# COVERAGE CHECK BACKEND #################################################
|
||||
@ -480,7 +473,7 @@ jobs:
|
||||
report_name: Coverage Backend
|
||||
type: lcov
|
||||
result_path: ./backend/coverage/lcov.info
|
||||
min_coverage: 38
|
||||
min_coverage: 48
|
||||
token: ${{ github.token }}
|
||||
|
||||
##########################################################################
|
||||
|
||||
69
CHANGELOG.md
69
CHANGELOG.md
@ -4,8 +4,18 @@ 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.6.5](https://github.com/gradido/gradido/compare/1.6.4...1.6.5)
|
||||
|
||||
> 15 February 2022
|
||||
|
||||
- v1.6.5 [`#1497`](https://github.com/gradido/gradido/pull/1497)
|
||||
- Fix: Elopage Hook Crash 2 [`#1481`](https://github.com/gradido/gradido/pull/1481)
|
||||
|
||||
#### [1.6.4](https://github.com/gradido/gradido/compare/1.6.3...1.6.4)
|
||||
|
||||
> 14 February 2022
|
||||
|
||||
- v1.6.4 [`#1478`](https://github.com/gradido/gradido/pull/1478)
|
||||
- fix: Admin Email Confirmation Date and Time [`#1448`](https://github.com/gradido/gradido/pull/1448)
|
||||
- Fix: Do not log password or token to the console [`#1477`](https://github.com/gradido/gradido/pull/1477)
|
||||
- Fix: Elopage Hook Crash [`#1474`](https://github.com/gradido/gradido/pull/1474)
|
||||
@ -14,9 +24,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- fix: Wrong Email Spelling in German [`#1446`](https://github.com/gradido/gradido/pull/1446)
|
||||
- fix: Redirect to Login after Register [`#1445`](https://github.com/gradido/gradido/pull/1445)
|
||||
- refactor: Split User Table Component in Admin Interface [`#1443`](https://github.com/gradido/gradido/pull/1443)
|
||||
- remove user table [`c6a469e`](https://github.com/gradido/gradido/commit/c6a469e08f16101c8fb78958eda69b163b815ed3)
|
||||
- open creations table [`9ef575d`](https://github.com/gradido/gradido/commit/9ef575d6b140a4c768e4330a18eaa3b04346b483)
|
||||
- User Search Table [`ae2d535`](https://github.com/gradido/gradido/commit/ae2d5355d62f525187dd1cdb1448aec63fb05d3f)
|
||||
|
||||
#### [1.6.3](https://github.com/gradido/gradido/compare/1.6.2...1.6.3)
|
||||
|
||||
@ -25,8 +32,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- v1.6.3 [`#1447`](https://github.com/gradido/gradido/pull/1447)
|
||||
- add .btn-outline-secondary in scss [`#1442`](https://github.com/gradido/gradido/pull/1442)
|
||||
- Profil settings and footer refactor [`#1440`](https://github.com/gradido/gradido/pull/1440)
|
||||
- profil settings header info refactor style [`cbaa016`](https://github.com/gradido/gradido/commit/cbaa0162b9366e5de722235aeb633908c59bb3e1)
|
||||
- margin footer refactor, more margin to top [`81b3dc0`](https://github.com/gradido/gradido/commit/81b3dc08ae6f60049feba690132dba37c84ad5c5)
|
||||
|
||||
#### [1.6.2](https://github.com/gradido/gradido/compare/1.6.1...1.6.2)
|
||||
|
||||
@ -64,9 +69,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- upgrade vue version from ^2.6.11 to 2.6.12 [`#1382`](https://github.com/gradido/gradido/pull/1382)
|
||||
- remove vue-qrcode from dashboard-plugin [`#1364`](https://github.com/gradido/gradido/pull/1364)
|
||||
- remove unused package from frontend [`#1360`](https://github.com/gradido/gradido/pull/1360)
|
||||
- Removed community_server folder, removed reference to community_server. [`e5c3c3c`](https://github.com/gradido/gradido/commit/e5c3c3c57a2343e1c4d7b8fbc658edcd78f1a292)
|
||||
- migration 20 [`c0156d4`](https://github.com/gradido/gradido/commit/c0156d4a298ac11f0d1ccabb859fdcbd56d391ee)
|
||||
- fix frontend tests [`3f0aa00`](https://github.com/gradido/gradido/commit/3f0aa00dc36499b13878eda469312e00100074f4)
|
||||
|
||||
#### [1.6.1](https://github.com/gradido/gradido/compare/1.6.0...1.6.1)
|
||||
|
||||
@ -76,9 +78,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- change standard text für creation [`#1343`](https://github.com/gradido/gradido/pull/1343)
|
||||
- Check if user email is activated to make a creation. [`#1356`](https://github.com/gradido/gradido/pull/1356)
|
||||
- fix: Creation Confirmation User Ids [`#1345`](https://github.com/gradido/gradido/pull/1345)
|
||||
- fix and improve test [`1c833d3`](https://github.com/gradido/gradido/commit/1c833d394f502a7aed2b5a648c0171a2fe4ee1e6)
|
||||
- rewrote elopage hook to handle actual elopage hook [`65bc347`](https://github.com/gradido/gradido/commit/65bc3479fa169920eff57b5a2fa662a4090d7364)
|
||||
- simple test for mass creation, improved test for single creation [`ffc4727`](https://github.com/gradido/gradido/commit/ffc4727e7a7105ac5dc97515b901be8dbe415627)
|
||||
|
||||
#### [1.6.0](https://github.com/gradido/gradido/compare/1.5.1...1.6.0)
|
||||
|
||||
@ -237,16 +236,12 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- analyse_bundle [`#1019`](https://github.com/gradido/gradido/pull/1019)
|
||||
- release_issue_template [`#1013`](https://github.com/gradido/gradido/pull/1013)
|
||||
- fix_changelog [`#1014`](https://github.com/gradido/gradido/pull/1014)
|
||||
- removed incorrect mnemonic lists [`08200f4`](https://github.com/gradido/gradido/commit/08200f49f2ceb5ac121534a19ad2a8347c900145)
|
||||
- update jest, install transform-require-context [`165ed18`](https://github.com/gradido/gradido/commit/165ed1801ba1aba862d0b0006d8c17e322c4b7ff)
|
||||
- rework roadmap [`b337bcd`](https://github.com/gradido/gradido/commit/b337bcd850423e67b2119c562575b0ec692dddf2)
|
||||
|
||||
#### [1.5.1](https://github.com/gradido/gradido/compare/1.5.0...1.5.1)
|
||||
|
||||
> 15 October 2021
|
||||
|
||||
- fix isExitInDb [`#994`](https://github.com/gradido/gradido/pull/994)
|
||||
- fix [`80228ef`](https://github.com/gradido/gradido/commit/80228ef842d4087ea4b80934b15b8112611e3e33)
|
||||
|
||||
#### [1.5.0](https://github.com/gradido/gradido/compare/1.4.0...1.5.0)
|
||||
|
||||
@ -323,9 +318,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- update docker files [`#830`](https://github.com/gradido/gradido/pull/830)
|
||||
- added publisher_id field to user [`#245`](https://github.com/gradido/gradido/pull/245)
|
||||
- webpack update [`#811`](https://github.com/gradido/gradido/pull/811)
|
||||
- resolvers [`562ad9a`](https://github.com/gradido/gradido/commit/562ad9ae31d97f90a371452bed1ffe10ebf2d3a5)
|
||||
- deleted inputs (now args) [`8ab542a`](https://github.com/gradido/gradido/commit/8ab542a28acf6b78d7a9e7fe7757363d225f7b4f)
|
||||
- fix UserCard_CoinAnimation to properly use the store, have 100% coverage and other minor fixes & simplifications [`ce826de`](https://github.com/gradido/gradido/commit/ce826deb1d6d92caba514713539dca2da3f74de7)
|
||||
|
||||
#### [1.4.0](https://github.com/gradido/gradido/compare/1.3.1...1.4.0)
|
||||
|
||||
@ -367,9 +359,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- feat: Vue Apollo Client [`#701`](https://github.com/gradido/gradido/pull/701)
|
||||
- change text from Geld to Gradidos [`#711`](https://github.com/gradido/gradido/pull/711)
|
||||
- fix fix [`#728`](https://github.com/gradido/gradido/pull/728)
|
||||
- sort locales [`ec12a28`](https://github.com/gradido/gradido/commit/ec12a28f81577d530f58b42b7f8c2c7d20dffd64)
|
||||
- feat: Unify and Sort Locales [`aba4f4d`](https://github.com/gradido/gradido/commit/aba4f4d20e0a13016e3528a1c5c30c111eb3a9f1)
|
||||
- feat: Increase Coverage [`3c061bc`](https://github.com/gradido/gradido/commit/3c061bcb8d1a3a47442ed6a351e1428e15b314aa)
|
||||
|
||||
#### [1.3.1](https://github.com/gradido/gradido/compare/1.3.0...1.3.1)
|
||||
|
||||
@ -378,9 +367,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- fix: Translations and Formula Display [`#727`](https://github.com/gradido/gradido/pull/727)
|
||||
- 612 docu structure [`#688`](https://github.com/gradido/gradido/pull/688)
|
||||
- Community update for gdt list GDT transaction format [`#726`](https://github.com/gradido/gradido/pull/726)
|
||||
- [#612] new directory structure in /docu/Concepts [`10bf3b0`](https://github.com/gradido/gradido/commit/10bf3b0cdfa6c44f879be0155e93f636601a051b)
|
||||
- #612 additional documents [`ac0ed4f`](https://github.com/gradido/gradido/commit/ac0ed4fee81caff26d09b5de47dd130f12abdb45)
|
||||
- #612 docu restructuring [`e67e1c4`](https://github.com/gradido/gradido/commit/e67e1c41e78264698e6fae4cf1d29751de7e7b29)
|
||||
|
||||
#### [1.3.0](https://github.com/gradido/gradido/compare/1.2.1...1.3.0)
|
||||
|
||||
@ -402,9 +388,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- Backend Setup [`#584`](https://github.com/gradido/gradido/pull/584)
|
||||
- text-size in textarea and font-variante if focus [`#677`](https://github.com/gradido/gradido/pull/677)
|
||||
- 680 app large maximum width [`#681`](https://github.com/gradido/gradido/pull/681)
|
||||
- linting, server is working [`34b30b2`](https://github.com/gradido/gradido/commit/34b30b216b6fafcb5b686d4b023b05f2e9766bdf)
|
||||
- server stack seems to work. Graphql does not load properly yet [`43f7cf8`](https://github.com/gradido/gradido/commit/43f7cf87679713d436a64d569d6af1594a12ee33)
|
||||
- initial commit, base packages [`fdf0979`](https://github.com/gradido/gradido/commit/fdf0979830fece04208a6b3bb06bb5323a3c149b)
|
||||
|
||||
#### [1.2.1](https://github.com/gradido/gradido/compare/1.2.0...1.2.1)
|
||||
|
||||
@ -415,9 +398,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- fix unneccessary migration run on fresh (docker) setup [`#654`](https://github.com/gradido/gradido/pull/654)
|
||||
- move back decay as standalone transaction in old frontend [`#656`](https://github.com/gradido/gradido/pull/656)
|
||||
- fix display error with creation [`#652`](https://github.com/gradido/gradido/pull/652)
|
||||
- release [`a0b8056`](https://github.com/gradido/gradido/commit/a0b8056c17b22570a1b1dbb6fa6ce71e561b04af)
|
||||
- update content for frontend [`d37ce09`](https://github.com/gradido/gradido/commit/d37ce0949ef97d2a6c6ffaf0be31db9f6d92e743)
|
||||
- exchange positions [`bc000ef`](https://github.com/gradido/gradido/commit/bc000efd87c9701480c4aeaa7b819ab49bfe8f01)
|
||||
|
||||
#### [1.2.0](https://github.com/gradido/gradido/compare/1.1.1...1.2.0)
|
||||
|
||||
@ -456,9 +436,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- Update Password Reset E-Mail Subject Encoding [`#579`](https://github.com/gradido/gradido/pull/579)
|
||||
- move decay between transactions into the transactions [`#483`](https://github.com/gradido/gradido/pull/483)
|
||||
- fix #591 [`#591`](https://github.com/gradido/gradido/issues/591)
|
||||
- fix style decay startblick [`cc7778b`](https://github.com/gradido/gradido/commit/cc7778b55d1baaa7be2d9440480e0fb27bb9a930)
|
||||
- Remove dynamic cast because it lead to errors again and agin (Poco::AutoPtr don't work correct with that) [`0db5912`](https://github.com/gradido/gradido/commit/0db5912a67158be8f313c01f06350f8339cb0e28)
|
||||
- Remove dynamic cast because it lead to errors again and agin (Poco::AutoPtr don't work correct with that) [`cee7d7a`](https://github.com/gradido/gradido/commit/cee7d7ac3c4c8c1f481cc3a87fb15422c858413b)
|
||||
|
||||
#### [1.1.1](https://github.com/gradido/gradido/compare/1.1.0...1.1.1)
|
||||
|
||||
@ -469,9 +446,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- update transfer email text [`#574`](https://github.com/gradido/gradido/pull/574)
|
||||
- update mysql because tuple has changed [`#576`](https://github.com/gradido/gradido/pull/576)
|
||||
- Login fix pending transactions [`#578`](https://github.com/gradido/gradido/pull/578)
|
||||
- add test to prevent bug in future [`630d667`](https://github.com/gradido/gradido/commit/630d667e996870a1bf9aa9586b0467d58419e525)
|
||||
- use standard path. add nginx example [`ac249b4`](https://github.com/gradido/gradido/commit/ac249b46830a8039aec52d30b48084b50a264b6f)
|
||||
- add autodeploy bash scripts [`f49cf4d`](https://github.com/gradido/gradido/commit/f49cf4d7f8054d87efa1e12055a7ef0c6d3b9872)
|
||||
|
||||
#### [1.1.0](https://github.com/gradido/gradido/compare/1.0.2...1.1.0)
|
||||
|
||||
@ -513,9 +487,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- login without hedera [`#478`](https://github.com/gradido/gradido/pull/478)
|
||||
- fix: Show Correct Version Number in Footer [`#475`](https://github.com/gradido/gradido/pull/475)
|
||||
- refactor: Remove Element-UI [`#476`](https://github.com/gradido/gradido/pull/476)
|
||||
- remove components Charts, Notification, SearchUser, ButtonCheckbox, Button RadioGroup, Breadcrumb [`159bff7`](https://github.com/gradido/gradido/commit/159bff71df20a5c48f93389b2f990f7fe54e53b9)
|
||||
- fix bug, update dockerfiles to use dependencies without grpc [`dedcebd`](https://github.com/gradido/gradido/commit/dedcebdb95ee0f3dfd2ad62074d4181af38476a2)
|
||||
- add warning to able to forward warnings from community server to client [`2fc3fe9`](https://github.com/gradido/gradido/commit/2fc3fe94a09bae199bf2f34f9df90e8fc3879c2b)
|
||||
|
||||
#### [1.0.2](https://github.com/gradido/gradido/compare/1.0.1...1.0.2)
|
||||
|
||||
@ -542,17 +513,12 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- add dynamic error email if transaction failed [`#452`](https://github.com/gradido/gradido/pull/452)
|
||||
- ceil the last decay [`#449`](https://github.com/gradido/gradido/pull/449)
|
||||
- feat: Raise Coverage of Frontend Unit Tets to 18% [`#447`](https://github.com/gradido/gradido/pull/447)
|
||||
- parse cpsp files automatic in build [`a4a12bb`](https://github.com/gradido/gradido/commit/a4a12bb62b4000e035ff15e17c5a5f5861653ff6)
|
||||
- translate german html encoded error messages to english and use gettext for automatic translation [`d339627`](https://github.com/gradido/gradido/commit/d33962736d94c1cb7a12ff775bc2c8d7505d646e)
|
||||
- 100% coverage of GddTransactionList [`96fb245`](https://github.com/gradido/gradido/commit/96fb245821c69f4d321204a663247d5eee60d92f)
|
||||
|
||||
#### [1.0.1](https://github.com/gradido/gradido/compare/1.0.0...1.0.1)
|
||||
|
||||
> 14 May 2021
|
||||
|
||||
- Login crash fix [`#444`](https://github.com/gradido/gradido/pull/444)
|
||||
- add try catch blocks to prevent login-server from crashing [`22ff220`](https://github.com/gradido/gradido/commit/22ff22072956f8b843037c75c5b16b7ff5d6a2a3)
|
||||
- fix [`14a4243`](https://github.com/gradido/gradido/commit/14a424347817b1fe6912a113bffd70e55d688112)
|
||||
|
||||
### [1.0.0](https://github.com/gradido/gradido/compare/0.9.4...1.0.0)
|
||||
|
||||
@ -672,9 +638,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- Background color change [`#117`](https://github.com/gradido/gradido/pull/117)
|
||||
- Delete unused files [`#116`](https://github.com/gradido/gradido/pull/116)
|
||||
- store aufräumen teil 1 [`#115`](https://github.com/gradido/gradido/pull/115)
|
||||
- add migrations table for automatic table data migration [`40a9a8c`](https://github.com/gradido/gradido/commit/40a9a8c2b587f5bef0fcc54136ed7bd13dd91b2b)
|
||||
- update yarn.lock after running yarn install [`7f38c80`](https://github.com/gradido/gradido/commit/7f38c801213ad886e9d34a8d43b00ae423f5f2a0)
|
||||
- use new function for balance overview in old frontend, update balance in session on every php-request [`97c570c`](https://github.com/gradido/gradido/commit/97c570c08cc51ed17a69eb8be8d987f95f3c2ce0)
|
||||
|
||||
#### [0.9.4](https://github.com/gradido/gradido/compare/0.9.3...0.9.4)
|
||||
|
||||
@ -682,9 +645,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
- Vue with nginx [`#84`](https://github.com/gradido/gradido/pull/84)
|
||||
- Build on run [`#103`](https://github.com/gradido/gradido/pull/103)
|
||||
- update debug docker to use dependencies container pushed to docker hub [`1f002f4`](https://github.com/gradido/gradido/commit/1f002f4ed0b12d4b2bf63efceabe546d0c5b58ea)
|
||||
- removed email tasks complete [`8a143be`](https://github.com/gradido/gradido/commit/8a143be8423d7bd894d4f512848895df8b9694b0)
|
||||
- build login-server on docker-compose up in a docker volume so it rebuild only neccessary parts if some c++ files have changed [`0da5279`](https://github.com/gradido/gradido/commit/0da527917523530186e6effe63dc001fc99bd3e3)
|
||||
|
||||
#### [0.9.3](https://github.com/gradido/gradido/compare/0.9.2...0.9.3)
|
||||
|
||||
@ -713,9 +673,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- Add Feature in user search old frontend because Support has requested the feature long ago [`#56`](https://github.com/gradido/gradido/pull/56)
|
||||
- sprache angepasst, for login, pwd, sigin [`#54`](https://github.com/gradido/gradido/pull/54)
|
||||
- Improve workflows [`#53`](https://github.com/gradido/gradido/pull/53)
|
||||
- setup eslint with tougher rules [`1f13507`](https://github.com/gradido/gradido/commit/1f13507eacfd93c2248fb841de5f481c9eb1e6bd)
|
||||
- semicolon rule implemented [`6762a02`](https://github.com/gradido/gradido/commit/6762a028f2a3e4f2713b26bed81029defe686ad7)
|
||||
- dev meeting, bernd [`a99de7f`](https://github.com/gradido/gradido/commit/a99de7f5d1f7557c0877eae565aa4263d65aaaf3)
|
||||
|
||||
#### [0.9.2](https://github.com/gradido/gradido/compare/0.9.1...0.9.2)
|
||||
|
||||
@ -725,9 +682,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- Reload after login fixed [`#50`](https://github.com/gradido/gradido/pull/50)
|
||||
- Monorepo login server [`#48`](https://github.com/gradido/gradido/pull/48)
|
||||
- Stage0 [`#3`](https://github.com/gradido/gradido/pull/3)
|
||||
- Add auto-sign Transaction functionality [`5592275`](https://github.com/gradido/gradido/commit/55922753a7ffd9552be132501d744da491c409b5)
|
||||
- read in login the real client ip X-Real-IP from nginx forwarded not from community server [`512d307`](https://github.com/gradido/gradido/commit/512d307a19b955bb6e26ae8b274def354829b50f)
|
||||
- move check if all passwords allow direct into pwdValidation so it will work with every code which ask for password [`e2c38c1`](https://github.com/gradido/gradido/commit/e2c38c1a0fc25a4a2bc922c4bbc44d86b6d00d8b)
|
||||
|
||||
#### 0.9.1
|
||||
|
||||
@ -739,6 +693,3 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- [WIP] 2 create a dockerfile for the frontend application [`#6`](https://github.com/gradido/gradido/pull/6)
|
||||
- Master - first step [`#1`](https://github.com/gradido/gradido/pull/1)
|
||||
- Add docker compose [`#7`](https://github.com/gradido/gradido/pull/7)
|
||||
- style 404 side :) [`c7bdf89`](https://github.com/gradido/gradido/commit/c7bdf8978594b932615e48f9bb1c19d3c3bf3fcf)
|
||||
- publish workflow test [`df6f66f`](https://github.com/gradido/gradido/commit/df6f66ffe70baa9ed3f70b460a6c0c14011bb944)
|
||||
- many translations. translation structure [`bf68547`](https://github.com/gradido/gradido/commit/bf685479767d19c246c4d6abe3577dc3cb666346)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.5",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -3,7 +3,11 @@
|
||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||
<div v-if="checked">{{ $t('unregister_mail.text_true') }}</div>
|
||||
<div v-else>
|
||||
{{ $t('unregister_mail.text_false', { date: dateLastSend, mail: email }) }}
|
||||
{{
|
||||
dateLastSend === ''
|
||||
? $t('unregister_mail.never_sent', { email })
|
||||
: $t('unregister_mail.text_false', { date: dateLastSend, email })
|
||||
}}
|
||||
|
||||
<!-- Using components -->
|
||||
<b-input-group :prepend="$t('unregister_mail.info')" class="mt-3">
|
||||
|
||||
@ -1,15 +1,45 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div>
|
||||
<hr />
|
||||
<br />
|
||||
<div class="text-center">
|
||||
{{ $t('gradido_admin_footer') }}
|
||||
<div><small>Version: 1.0.0</small></div>
|
||||
</div>
|
||||
<b-row align-v="center" class="mt-4 justify-content-lg-between">
|
||||
<b-col>
|
||||
<div class="copyright text-center text-lg-center text-muted">
|
||||
© {{ year }}
|
||||
<a
|
||||
:href="`https://gradido.net/${$i18n.locale}`"
|
||||
class="font-weight-bold ml-1"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('gradido_admin_footer') }}
|
||||
</a>
|
||||
|
|
||||
<a href="https://github.com/gradido/gradido/releases/latest" target="_blank">
|
||||
App version {{ version }}
|
||||
</a>
|
||||
<a
|
||||
v-if="hash"
|
||||
:href="'https://github.com/gradido/gradido/commit/' + hash"
|
||||
target="_blank"
|
||||
>
|
||||
({{ shortHash }})
|
||||
</a>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CONFIG from '../config'
|
||||
|
||||
export default {
|
||||
name: 'ContentFooter',
|
||||
data() {
|
||||
return {
|
||||
year: new Date().getFullYear(),
|
||||
version: CONFIG.APP_VERSION,
|
||||
hash: CONFIG.BUILD_COMMIT,
|
||||
shortHash: CONFIG.BUILD_COMMIT_SHORT,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -3,11 +3,15 @@ import NotFoundPage from './NotFoundPage'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
describe('NotFoundPage', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(NotFoundPage, { localVue })
|
||||
return mount(NotFoundPage, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
@ -18,5 +22,9 @@ describe('NotFoundPage', () => {
|
||||
it('has a svg', () => {
|
||||
expect(wrapper.find('svg').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a back button', () => {
|
||||
expect(wrapper.find('.test-back').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="header py-1 py-lg-1 pt-lg-3">
|
||||
<b-container>
|
||||
<div class="header-body text-center mb-3">
|
||||
<a href="login" to="login">
|
||||
<a href="#!" @click="goback">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 mt-5 mb-5">
|
||||
@ -1185,6 +1185,11 @@
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<b-button class="test-back" variant="light" @click="goback">
|
||||
{{ $t('back') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1213,6 +1218,11 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goback() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
31
admin/src/components/Overlay.spec.js
Normal file
31
admin/src/components/Overlay.spec.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Overlay from './Overlay.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
item: {},
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => String(d)),
|
||||
}
|
||||
|
||||
describe('Overlay', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Overlay, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a DIV element with the class.component-overlay', () => {
|
||||
expect(wrapper.find('.component-overlay').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
67
admin/src/components/Overlay.vue
Normal file
67
admin/src/components/Overlay.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="component-overlay">
|
||||
<b-jumbotron class="bg-light p-4">
|
||||
<template #header>{{ $t('overlay.confirm.title') }}</template>
|
||||
|
||||
<template #lead>
|
||||
<b-row class="mt-4">
|
||||
<b-col class="col-3">{{ $t('transactionlist.amount') }}</b-col>
|
||||
<b-col class="h3">
|
||||
<b>{{ item.amount }} GDD</b>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="col-3">{{ $t('creation_for_month') }}</b-col>
|
||||
<b-col class="h3">
|
||||
{{ $d(new Date(item.date), 'month') }} {{ $d(new Date(item.date), 'year') }}
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="col-3">{{ $t('transactionlist.memo') }}</b-col>
|
||||
<b-col>{{ item.memo }}</b-col>
|
||||
</b-row>
|
||||
<b-row class="mt-3">
|
||||
<b-col class="col-3">{{ $t('name') }}</b-col>
|
||||
<b-col>{{ item.firstName }} {{ item.lastName }}</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="col-3">{{ $t('e_mail') }}</b-col>
|
||||
<b-col>{{ item.email }}</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<hr class="my-4" />
|
||||
<p>{{ $t('overlay.confirm.text') }}</p>
|
||||
<p>
|
||||
{{ $t('overlay.confirm.question') }}
|
||||
</p>
|
||||
<b-container>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-button size="md" variant="danger" class="m-3" @click="$emit('overlay-cancel')">
|
||||
{{ $t('overlay.confirm.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button
|
||||
size="md"
|
||||
variant="success"
|
||||
class="m-3 text-right"
|
||||
@click="$emit('confirm-creation', item)"
|
||||
>
|
||||
{{ $t('overlay.confirm.yes') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</b-jumbotron>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'overlay',
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"all_emails": "Alle Nutzer",
|
||||
"back": "zurück",
|
||||
"bookmark": "bookmark",
|
||||
"confirmed": "bestätigt",
|
||||
"creation": "Schöpfung",
|
||||
@ -19,6 +20,7 @@
|
||||
"toasted_update": "`Offene Schöpfung {value} GDD) für {email} wurde geändert und liegt zur Bestätigung bereit",
|
||||
"update_creation": "Schöpfung aktualisieren"
|
||||
},
|
||||
"creation_for_month": "Schöpfung für Monat",
|
||||
"date": "Datum",
|
||||
"delete": "Löschen",
|
||||
"details": "Details",
|
||||
@ -30,6 +32,7 @@
|
||||
"lastname": "Nachname",
|
||||
"moderator": "Moderator",
|
||||
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"logout": "Abmelden",
|
||||
"multi_creation": "Mehrfachschöpfung",
|
||||
@ -43,7 +46,7 @@
|
||||
"open_creations": "Offene Schöpfungen",
|
||||
"overlay": {
|
||||
"confirm": {
|
||||
"no": "Nein, nicht speichern.",
|
||||
"cancel": "Abbrechen",
|
||||
"question": "Willst du diese vorgespeicherte Schöpfung wirklich vollziehen und endgültig speichern?",
|
||||
"text": "Nach dem Speichern ist der Datensatz nicht mehr änderbar und kann auch nicht mehr gelöscht werden. Bitte überprüfe genau, dass alles stimmt.",
|
||||
"title": "Schöpfung bestätigen!",
|
||||
@ -75,8 +78,9 @@
|
||||
"button": "Registrierungs-Email bestätigen, jetzt senden",
|
||||
"error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}",
|
||||
"info": "Email bestätigen, wiederholt senden an:",
|
||||
"never_sent": "Es scheint so, als ob wir nie eine E-Mail an {email} geschickt haben",
|
||||
"success": "Erfolgreiches Senden des Bestätigungs-Links an die E-Mail des Nutzers! ({email})",
|
||||
"text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({mail}) gesendet.",
|
||||
"text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({email}) gesendet.",
|
||||
"text_true": " Die Email wurde bestätigt."
|
||||
},
|
||||
"user_search": "Nutzer-Suche"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"all_emails": "All users",
|
||||
"back": "back",
|
||||
"bookmark": "Remember",
|
||||
"confirmed": "confirmed",
|
||||
"creation": "Creation",
|
||||
@ -19,6 +20,7 @@
|
||||
"toasted_update": "Open creation {value} GDD) for {email} has been changed and is ready for confirmation.",
|
||||
"update_creation": "Creation update"
|
||||
},
|
||||
"creation_for_month": "Creation for month",
|
||||
"date": "Date",
|
||||
"delete": "Delete",
|
||||
"details": "Details",
|
||||
@ -30,6 +32,7 @@
|
||||
"lastname": "Lastname",
|
||||
"moderator": "Moderator",
|
||||
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"logout": "Logout",
|
||||
"multi_creation": "Multiple creation",
|
||||
@ -43,7 +46,7 @@
|
||||
"open_creations": "Open creations",
|
||||
"overlay": {
|
||||
"confirm": {
|
||||
"no": "No, do not save.",
|
||||
"cancel": "Cancel",
|
||||
"question": "Do you really want to carry out and finally save this pre-stored creation?",
|
||||
"text": "After saving, the record can no longer be changed or deleted. Please check carefully that everything is correct.",
|
||||
"title": "Confirm creation!",
|
||||
@ -75,8 +78,9 @@
|
||||
"button": "Confirm registration email, send now",
|
||||
"error": "Error sending the confirmation link to the user: {message}",
|
||||
"info": "Confirm email, send repeatedly to:",
|
||||
"never_sent": "It seems we did never send an email to the member {email}",
|
||||
"success": "Successfully send the confirmation link to the user's email! ({email})",
|
||||
"text_false": "The last email was sent to the member ({mail}) on {date}.",
|
||||
"text_false": "The last email was sent to the member ({email}) on {date}.",
|
||||
"text_true": "The email was confirmed."
|
||||
},
|
||||
"user_search": "User search"
|
||||
|
||||
@ -132,8 +132,8 @@ describe('CreationConfirm', () => {
|
||||
await wrapper.find('#overlay').findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('closes the overlay', () => {
|
||||
expect(wrapper.find('#overlay').isVisible()).toBeFalsy()
|
||||
it('closes the overlay', async () => {
|
||||
expect(wrapper.find('#overlay').exists()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('still has 2 items in the table', () => {
|
||||
|
||||
@ -1,18 +1,7 @@
|
||||
<template>
|
||||
<div class="creation-confirm">
|
||||
<div v-show="overlay" id="overlay" class="">
|
||||
<b-jumbotron class="bg-light p-4">
|
||||
<template #header>{{ $t('overlay.confirm.title') }}</template>
|
||||
<template #lead>{{ $t('overlay.confirm.text') }}</template>
|
||||
<hr class="my-4" />
|
||||
<p>{{ $t('overlay.confirm.question') }}</p>
|
||||
<b-button size="md" variant="danger" class="m-3" @click="overlay = false">
|
||||
{{ $t('overlay.confirm.no') }}
|
||||
</b-button>
|
||||
<b-button size="md" variant="success" class="m-3 text-right" @click="confirmCreation">
|
||||
{{ $t('overlay.confirm.yes') }}
|
||||
</b-button>
|
||||
</b-jumbotron>
|
||||
<div v-if="overlay" id="overlay" @dblclick="overlay = false">
|
||||
<overlay :item="item" @overlay-cancel="overlay = false" @confirm-creation="confirmCreation" />
|
||||
</div>
|
||||
<open-creations-table
|
||||
class="mt-4"
|
||||
@ -24,6 +13,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Overlay from '../components/Overlay.vue'
|
||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
|
||||
import { getPendingCreations } from '../graphql/getPendingCreations'
|
||||
import { deletePendingCreation } from '../graphql/deletePendingCreation'
|
||||
@ -33,12 +23,13 @@ export default {
|
||||
name: 'CreationConfirm',
|
||||
components: {
|
||||
OpenCreationsTable,
|
||||
Overlay,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pendingCreations: [],
|
||||
overlay: false,
|
||||
item: [],
|
||||
item: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC'
|
||||
return {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
|
||||
moduleNameMapper: {
|
||||
'@entity/(.*)': '<rootDir>/../database/build/entity/$1',
|
||||
// This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state
|
||||
'@dbTools/(.*)': '<rootDir>/../database/src/$1',
|
||||
/*
|
||||
'@dbTools/(.*)':
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '<rootDir>/../database/src/$1'
|
||||
: '<rootDir>/../database/build/src/$1',
|
||||
*/
|
||||
},
|
||||
}
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
moduleNameMapper: {
|
||||
'@entity/(.*)':
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '<rootDir>/../database/entity/$1'
|
||||
: '<rootDir>/../database/build/entity/$1',
|
||||
'@dbTools/(.*)':
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '<rootDir>/../database/src/$1'
|
||||
: '<rootDir>/../database/build/src/$1',
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.5",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -10,11 +10,10 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"clean": "tsc --build --clean",
|
||||
"start": "node build/index.js",
|
||||
"start": "node build/src/index.js",
|
||||
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
|
||||
"lint": "eslint . --ext .js,.ts",
|
||||
"CI_workflow_test": "jest --runInBand --coverage ",
|
||||
"test": "NODE_ENV=development jest --runInBand --coverage "
|
||||
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
@ -32,7 +31,6 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"moment": "^2.29.1",
|
||||
"mysql2": "^2.3.0",
|
||||
"nodemailer": "^6.6.5",
|
||||
"random-bigint": "^0.0.1",
|
||||
|
||||
9
backend/src/config/index.test.ts
Normal file
9
backend/src/config/index.test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import CONFIG from './index'
|
||||
|
||||
describe('config/index', () => {
|
||||
describe('decay start block', () => {
|
||||
it('has the correct date set', () => {
|
||||
expect(CONFIG.DECAY_START_TIME).toEqual(new Date('2021-05-13 17:46:31'))
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -4,7 +4,8 @@ import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0021-elopagebuys_fields_nullable',
|
||||
DB_VERSION: '0024-combine_transaction_tables',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
|
||||
}
|
||||
|
||||
const server = {
|
||||
|
||||
@ -4,7 +4,7 @@ import { ObjectType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class Decay {
|
||||
constructor(json: any) {
|
||||
constructor(json?: any) {
|
||||
if (json) {
|
||||
this.balance = Number(json.balance)
|
||||
this.decayStart = json.decay_start
|
||||
|
||||
@ -13,8 +13,8 @@ export class TransactionList {
|
||||
this.decayDate = ''
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
gdtSum: number
|
||||
@Field(() => Number, { nullable: true })
|
||||
gdtSum: number | null
|
||||
|
||||
@Field(() => Number)
|
||||
count: number
|
||||
|
||||
@ -2,27 +2,26 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql'
|
||||
import { getCustomRepository, Raw } from '@dbTools/typeorm'
|
||||
import { getCustomRepository, ObjectLiteral, getConnection, In } from '@dbTools/typeorm'
|
||||
import { UserAdmin, SearchUsersResult } from '../model/UserAdmin'
|
||||
import { PendingCreation } from '../model/PendingCreation'
|
||||
import { CreatePendingCreations } from '../model/CreatePendingCreations'
|
||||
import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
||||
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
||||
import SearchUsersArgs from '../arg/SearchUsersArgs'
|
||||
import moment from 'moment'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { TransactionCreation } from '@entity/TransactionCreation'
|
||||
import { UserTransaction } from '@entity/UserTransaction'
|
||||
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
import { AdminPendingCreation } from '@entity/AdminPendingCreation'
|
||||
import { hasElopageBuys } from '../../util/hasElopageBuys'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { User } from '@entity/User'
|
||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { Balance } from '@entity/Balance'
|
||||
|
||||
// const EMAIL_OPT_IN_REGISTER = 1
|
||||
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
|
||||
@ -35,15 +34,34 @@ export class AdminResolver {
|
||||
@Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs,
|
||||
): Promise<SearchUsersResult> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const users = await userRepository.findBySearchCriteria(searchText)
|
||||
let adminUsers = await Promise.all(
|
||||
|
||||
const filterCriteria: ObjectLiteral[] = []
|
||||
if (notActivated) {
|
||||
filterCriteria.push({ emailChecked: false })
|
||||
}
|
||||
|
||||
const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked']
|
||||
const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered(
|
||||
userFields.map((fieldName) => {
|
||||
return 'user.' + fieldName
|
||||
}),
|
||||
searchText,
|
||||
filterCriteria,
|
||||
currentPage,
|
||||
pageSize,
|
||||
)
|
||||
|
||||
const creations = await getUserCreations(users.map((u) => u.id))
|
||||
|
||||
const adminUsers = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const adminUser = new UserAdmin()
|
||||
adminUser.userId = user.id
|
||||
adminUser.firstName = user.firstName
|
||||
adminUser.lastName = user.lastName
|
||||
adminUser.email = user.email
|
||||
adminUser.creation = await getUserCreations(user.id)
|
||||
const userCreations = creations.find((c) => c.id === user.id)
|
||||
adminUser.creation = userCreations ? userCreations.creations : [1000, 1000, 1000]
|
||||
adminUser.emailChecked = user.emailChecked
|
||||
adminUser.hasElopage = await hasElopageBuys(user.email)
|
||||
if (!user.emailChecked) {
|
||||
@ -56,6 +74,7 @@ export class AdminResolver {
|
||||
updatedAt: 'DESC',
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
select: ['updatedAt', 'createdAt'],
|
||||
},
|
||||
)
|
||||
if (emailOptIn) {
|
||||
@ -69,11 +88,9 @@ export class AdminResolver {
|
||||
return adminUser
|
||||
}),
|
||||
)
|
||||
if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked)
|
||||
const first = (currentPage - 1) * pageSize
|
||||
return {
|
||||
userCount: adminUsers.length,
|
||||
userList: adminUsers.slice(first, first + pageSize),
|
||||
userCount: count,
|
||||
userList: adminUsers,
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,12 +99,17 @@ export class AdminResolver {
|
||||
async createPendingCreation(
|
||||
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
||||
): Promise<number[]> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findByEmail(email)
|
||||
const user = await User.findOne({ email }, { withDeleted: true })
|
||||
if (!user) {
|
||||
throw new Error(`Could not find user with email: ${email}`)
|
||||
}
|
||||
if (user.deletedAt) {
|
||||
throw new Error('This user was deleted. Cannot make a creation.')
|
||||
}
|
||||
if (!user.emailChecked) {
|
||||
throw new Error('Creation could not be saved, Email is not activated')
|
||||
}
|
||||
const creations = await getUserCreations(user.id)
|
||||
const creations = await getUserCreation(user.id)
|
||||
const creationDateObj = new Date(creationDate)
|
||||
if (isCreationValid(creations, amount, creationDateObj)) {
|
||||
const adminPendingCreation = AdminPendingCreation.create()
|
||||
@ -100,7 +122,7 @@ export class AdminResolver {
|
||||
|
||||
await AdminPendingCreation.save(adminPendingCreation)
|
||||
}
|
||||
return getUserCreations(user.id)
|
||||
return getUserCreation(user.id)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.CREATE_PENDING_CREATION])
|
||||
@ -134,8 +156,13 @@ export class AdminResolver {
|
||||
async updatePendingCreation(
|
||||
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
|
||||
): Promise<UpdatePendingCreation> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findByEmail(email)
|
||||
const user = await User.findOne({ email }, { withDeleted: true })
|
||||
if (!user) {
|
||||
throw new Error(`Could not find user with email: ${email}`)
|
||||
}
|
||||
if (user.deletedAt) {
|
||||
throw new Error(`User was deleted (${email})`)
|
||||
}
|
||||
|
||||
const pendingCreationToUpdate = await AdminPendingCreation.findOneOrFail({ id })
|
||||
|
||||
@ -144,7 +171,7 @@ export class AdminResolver {
|
||||
}
|
||||
|
||||
const creationDateObj = new Date(creationDate)
|
||||
let creations = await getUserCreations(user.id)
|
||||
let creations = await getUserCreation(user.id)
|
||||
if (pendingCreationToUpdate.date.getMonth() === creationDateObj.getMonth()) {
|
||||
creations = updateCreations(creations, pendingCreationToUpdate)
|
||||
}
|
||||
@ -163,7 +190,8 @@ export class AdminResolver {
|
||||
result.memo = pendingCreationToUpdate.memo
|
||||
result.date = pendingCreationToUpdate.date
|
||||
result.moderator = pendingCreationToUpdate.moderator
|
||||
result.creation = await getUserCreations(user.id)
|
||||
|
||||
result.creation = await getUserCreation(user.id)
|
||||
|
||||
return result
|
||||
}
|
||||
@ -172,27 +200,27 @@ export class AdminResolver {
|
||||
@Query(() => [PendingCreation])
|
||||
async getPendingCreations(): Promise<PendingCreation[]> {
|
||||
const pendingCreations = await AdminPendingCreation.find()
|
||||
if (pendingCreations.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const pendingCreationsPromise = await Promise.all(
|
||||
pendingCreations.map(async (pendingCreation) => {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findOneOrFail({ id: pendingCreation.userId })
|
||||
const userIds = pendingCreations.map((p) => p.userId)
|
||||
const userCreations = await getUserCreations(userIds)
|
||||
const users = await User.find({ id: In(userIds) })
|
||||
|
||||
const parsedAmount = Number(parseInt(pendingCreation.amount.toString()) / 10000)
|
||||
// pendingCreation.amount = parsedAmount
|
||||
const newPendingCreation = {
|
||||
...pendingCreation,
|
||||
amount: parsedAmount,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
creation: await getUserCreations(user.id),
|
||||
}
|
||||
return pendingCreations.map((pendingCreation) => {
|
||||
const user = users.find((u) => u.id === pendingCreation.userId)
|
||||
const creation = userCreations.find((c) => c.id === pendingCreation.userId)
|
||||
|
||||
return newPendingCreation
|
||||
}),
|
||||
)
|
||||
return pendingCreationsPromise.reverse()
|
||||
return {
|
||||
...pendingCreation,
|
||||
amount: Number(parseInt(pendingCreation.amount.toString()) / 10000),
|
||||
firstName: user ? user.firstName : '',
|
||||
lastName: user ? user.lastName : '',
|
||||
email: user ? user.email : '',
|
||||
creation: creation ? creation.creations : [1000, 1000, 1000],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.DELETE_PENDING_CREATION])
|
||||
@ -212,23 +240,22 @@ export class AdminResolver {
|
||||
if (moderatorUser.id === pendingCreation.userId)
|
||||
throw new Error('Moderator can not confirm own pending creation')
|
||||
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const creations = await getUserCreation(pendingCreation.userId, false)
|
||||
if (!isCreationValid(creations, Number(pendingCreation.amount) / 10000, pendingCreation.date)) {
|
||||
throw new Error('Creation is not valid!!')
|
||||
}
|
||||
|
||||
const receivedCallDate = new Date()
|
||||
let transaction = new Transaction()
|
||||
transaction.transactionTypeId = 1
|
||||
transaction.transactionTypeId = TransactionTypeId.CREATION
|
||||
transaction.memo = pendingCreation.memo
|
||||
transaction.received = receivedCallDate
|
||||
transaction = await transactionRepository.save(transaction)
|
||||
transaction.userId = pendingCreation.userId
|
||||
transaction.amount = BigInt(parseInt(pendingCreation.amount.toString()))
|
||||
transaction.creationDate = pendingCreation.date
|
||||
transaction = await transaction.save()
|
||||
if (!transaction) throw new Error('Could not create transaction')
|
||||
|
||||
let transactionCreation = new TransactionCreation()
|
||||
transactionCreation.transactionId = transaction.id
|
||||
transactionCreation.userId = pendingCreation.userId
|
||||
transactionCreation.amount = parseInt(pendingCreation.amount.toString())
|
||||
transactionCreation.targetDate = pendingCreation.date
|
||||
transactionCreation = await TransactionCreation.save(transactionCreation)
|
||||
if (!transactionCreation) throw new Error('Could not create transactionCreation')
|
||||
|
||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
||||
const lastUserTransaction = await userTransactionRepository.findLastForUser(
|
||||
pendingCreation.userId,
|
||||
@ -237,11 +264,11 @@ export class AdminResolver {
|
||||
if (!lastUserTransaction) {
|
||||
newBalance = 0
|
||||
} else {
|
||||
newBalance = await calculateDecay(
|
||||
newBalance = calculateDecay(
|
||||
lastUserTransaction.balance,
|
||||
lastUserTransaction.balanceDate,
|
||||
receivedCallDate,
|
||||
)
|
||||
).balance
|
||||
}
|
||||
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||
|
||||
@ -256,143 +283,109 @@ export class AdminResolver {
|
||||
throw new Error('Error saving user transaction: ' + error)
|
||||
})
|
||||
|
||||
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||
let userBalance = await balanceRepository.findByUser(pendingCreation.userId)
|
||||
|
||||
if (!userBalance) userBalance = balanceRepository.create()
|
||||
userBalance.userId = pendingCreation.userId
|
||||
let userBalance = await Balance.findOne({ userId: pendingCreation.userId })
|
||||
if (!userBalance) {
|
||||
userBalance = new Balance()
|
||||
userBalance.userId = pendingCreation.userId
|
||||
}
|
||||
userBalance.amount = Number(newBalance)
|
||||
userBalance.modified = receivedCallDate
|
||||
userBalance.recordDate = receivedCallDate
|
||||
await balanceRepository.save(userBalance)
|
||||
await userBalance.save()
|
||||
await AdminPendingCreation.delete(pendingCreation)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserCreations(id: number): Promise<number[]> {
|
||||
const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01'
|
||||
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01'
|
||||
const beforeLastMonthNumber = moment().subtract(2, 'month').format('M')
|
||||
const lastMonthNumber = moment().subtract(1, 'month').format('M')
|
||||
const currentMonthNumber = moment().format('M')
|
||||
interface CreationMap {
|
||||
id: number
|
||||
creations: number[]
|
||||
}
|
||||
|
||||
const createdAmountsQuery = await TransactionCreation.createQueryBuilder('transaction_creations')
|
||||
.select('MONTH(transaction_creations.target_date)', 'target_month')
|
||||
.addSelect('SUM(transaction_creations.amount)', 'sum')
|
||||
.where('transaction_creations.state_user_id = :id', { id })
|
||||
.andWhere({
|
||||
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, {
|
||||
date: dateBeforeLastMonth,
|
||||
endDate: dateNextMonth,
|
||||
async function getUserCreation(id: number, includePending = true): Promise<number[]> {
|
||||
const creations = await getUserCreations([id], includePending)
|
||||
return creations[0] ? creations[0].creations : [1000, 1000, 1000]
|
||||
}
|
||||
|
||||
async function getUserCreations(ids: number[], includePending = true): Promise<CreationMap[]> {
|
||||
const months = getCreationMonths()
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
|
||||
const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day'
|
||||
|
||||
const unionString = includePending
|
||||
? `
|
||||
UNION
|
||||
SELECT date AS date, amount AS amount, userId AS userId FROM admin_pending_creations
|
||||
WHERE userId IN (${ids.toString()})
|
||||
AND date >= ${dateFilter}`
|
||||
: ''
|
||||
|
||||
const unionQuery = await queryRunner.manager.query(`
|
||||
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
|
||||
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
|
||||
WHERE user_id IN (${ids.toString()})
|
||||
AND transaction_type_id = ${TransactionTypeId.CREATION}
|
||||
AND creation_date >= ${dateFilter}
|
||||
${unionString}) AS result
|
||||
GROUP BY month, userId
|
||||
ORDER BY date DESC
|
||||
`)
|
||||
|
||||
await queryRunner.release()
|
||||
|
||||
return ids.map((id) => {
|
||||
return {
|
||||
id,
|
||||
creations: months.map((month) => {
|
||||
const creation = unionQuery.find(
|
||||
(raw: { month: string; id: string; creation: number[] }) =>
|
||||
parseInt(raw.month) === month && parseInt(raw.id) === id,
|
||||
)
|
||||
return 1000 - (creation ? Number(creation.sum) / 10000 : 0)
|
||||
}),
|
||||
})
|
||||
.groupBy('target_month')
|
||||
.orderBy('target_month', 'ASC')
|
||||
.getRawMany()
|
||||
|
||||
const pendingAmountsQuery = await AdminPendingCreation.createQueryBuilder(
|
||||
'admin_pending_creations',
|
||||
)
|
||||
.select('MONTH(admin_pending_creations.date)', 'target_month')
|
||||
.addSelect('SUM(admin_pending_creations.amount)', 'sum')
|
||||
.where('admin_pending_creations.userId = :id', { id })
|
||||
.andWhere({
|
||||
date: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, {
|
||||
date: dateBeforeLastMonth,
|
||||
endDate: dateNextMonth,
|
||||
}),
|
||||
})
|
||||
.groupBy('target_month')
|
||||
.orderBy('target_month', 'ASC')
|
||||
.getRawMany()
|
||||
|
||||
const map = new Map()
|
||||
if (Array.isArray(createdAmountsQuery) && createdAmountsQuery.length > 0) {
|
||||
createdAmountsQuery.forEach((createdAmount) => {
|
||||
if (!map.has(createdAmount.target_month)) {
|
||||
map.set(createdAmount.target_month, createdAmount.sum)
|
||||
} else {
|
||||
const store = map.get(createdAmount.target_month)
|
||||
map.set(createdAmount.target_month, Number(store) + Number(createdAmount.sum))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (Array.isArray(pendingAmountsQuery) && pendingAmountsQuery.length > 0) {
|
||||
pendingAmountsQuery.forEach((pendingAmount) => {
|
||||
if (!map.has(pendingAmount.target_month)) {
|
||||
map.set(pendingAmount.target_month, pendingAmount.sum)
|
||||
} else {
|
||||
const store = map.get(pendingAmount.target_month)
|
||||
map.set(pendingAmount.target_month, Number(store) + Number(pendingAmount.sum))
|
||||
}
|
||||
})
|
||||
}
|
||||
const usedCreationBeforeLastMonth = map.get(Number(beforeLastMonthNumber))
|
||||
? Number(map.get(Number(beforeLastMonthNumber))) / 10000
|
||||
: 0
|
||||
const usedCreationLastMonth = map.get(Number(lastMonthNumber))
|
||||
? Number(map.get(Number(lastMonthNumber))) / 10000
|
||||
: 0
|
||||
|
||||
const usedCreationCurrentMonth = map.get(Number(currentMonthNumber))
|
||||
? Number(map.get(Number(currentMonthNumber))) / 10000
|
||||
: 0
|
||||
|
||||
return [
|
||||
1000 - usedCreationBeforeLastMonth,
|
||||
1000 - usedCreationLastMonth,
|
||||
1000 - usedCreationCurrentMonth,
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function updateCreations(creations: number[], pendingCreation: AdminPendingCreation): number[] {
|
||||
const dateMonth = moment().format('YYYY-MM')
|
||||
const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM')
|
||||
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM')
|
||||
const creationDateMonth = moment(pendingCreation.date).format('YYYY-MM')
|
||||
const index = getCreationIndex(pendingCreation.date.getMonth())
|
||||
|
||||
switch (creationDateMonth) {
|
||||
case dateMonth:
|
||||
creations[2] += parseInt(pendingCreation.amount.toString())
|
||||
break
|
||||
case dateLastMonth:
|
||||
creations[1] += parseInt(pendingCreation.amount.toString())
|
||||
break
|
||||
case dateBeforeLastMonth:
|
||||
creations[0] += parseInt(pendingCreation.amount.toString())
|
||||
break
|
||||
default:
|
||||
throw new Error('UpdatedCreationDate is not in the last three months')
|
||||
if (index < 0) {
|
||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
||||
}
|
||||
creations[index] += parseInt(pendingCreation.amount.toString())
|
||||
return creations
|
||||
}
|
||||
|
||||
function isCreationValid(creations: number[], amount: number, creationDate: Date) {
|
||||
const dateMonth = moment().format('YYYY-MM')
|
||||
const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM')
|
||||
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM')
|
||||
const creationDateMonth = moment(creationDate).format('YYYY-MM')
|
||||
const index = getCreationIndex(creationDate.getMonth())
|
||||
|
||||
let openCreation
|
||||
switch (creationDateMonth) {
|
||||
case dateMonth:
|
||||
openCreation = creations[2]
|
||||
break
|
||||
case dateLastMonth:
|
||||
openCreation = creations[1]
|
||||
break
|
||||
case dateBeforeLastMonth:
|
||||
openCreation = creations[0]
|
||||
break
|
||||
default:
|
||||
throw new Error('CreationDate is not in last three months')
|
||||
if (index < 0) {
|
||||
throw new Error(`No Creation found!`)
|
||||
}
|
||||
|
||||
if (openCreation < amount) {
|
||||
throw new Error(`Open creation (${openCreation}) is less than amount (${amount})`)
|
||||
if (amount > creations[index]) {
|
||||
throw new Error(
|
||||
`The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`,
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const getCreationMonths = (): number[] => {
|
||||
const now = new Date(Date.now())
|
||||
return [
|
||||
now.getMonth() + 1,
|
||||
new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1,
|
||||
new Date(now.getFullYear(), now.getMonth() - 2, 1).getMonth() + 1,
|
||||
].reverse()
|
||||
}
|
||||
|
||||
const getCreationIndex = (month: number): number => {
|
||||
return getCreationMonths().findIndex((el) => el === month + 1)
|
||||
}
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { Balance } from '../model/Balance'
|
||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
import { roundFloorFrom4 } from '../../util/round'
|
||||
import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { Balance as dbBalance } from '@entity/Balance'
|
||||
|
||||
@Resolver()
|
||||
export class BalanceResolver {
|
||||
@ -16,11 +16,10 @@ export class BalanceResolver {
|
||||
@Query(() => Balance)
|
||||
async balance(@Ctx() context: any): Promise<Balance> {
|
||||
// load user and balance
|
||||
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
const balanceEntity = await balanceRepository.findByUser(userEntity.id)
|
||||
const balanceEntity = await dbBalance.findOne({ userId: userEntity.id })
|
||||
const now = new Date()
|
||||
|
||||
// No balance found
|
||||
@ -35,7 +34,7 @@ export class BalanceResolver {
|
||||
return new Balance({
|
||||
balance: roundFloorFrom4(balanceEntity.amount),
|
||||
decay: roundFloorFrom4(
|
||||
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
|
||||
calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now).balance,
|
||||
),
|
||||
decay_date: now.toString(),
|
||||
})
|
||||
|
||||
@ -25,13 +25,17 @@ export class GdtResolver {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
|
||||
const resultGDT = await apiGet(
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
throw new Error(resultGDT.data)
|
||||
try {
|
||||
const resultGDT = await apiGet(
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
throw new Error(resultGDT.data)
|
||||
}
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
} catch (err: any) {
|
||||
throw new Error('GDT Server is not reachable.')
|
||||
}
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.EXIST_PID])
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
||||
import { getCustomRepository, getConnection, QueryRunner } from '@dbTools/typeorm'
|
||||
import { getCustomRepository, getConnection, QueryRunner, In } from '@dbTools/typeorm'
|
||||
|
||||
import CONFIG from '../../config'
|
||||
import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
|
||||
@ -16,20 +16,17 @@ import Paginated from '../arg/Paginated'
|
||||
|
||||
import { Order } from '../enum/Order'
|
||||
|
||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
||||
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
||||
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionSendCoin as dbTransactionSendCoin } from '@entity/TransactionSendCoin'
|
||||
import { Balance as dbBalance } from '@entity/Balance'
|
||||
|
||||
import { apiPost } from '../../apis/HttpRequest'
|
||||
import { roundFloorFrom4, roundCeilFrom4 } from '../../util/round'
|
||||
import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { TransactionType } from '../enum/TransactionType'
|
||||
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||
@ -50,15 +47,13 @@ async function calculateAndAddDecayTransactions(
|
||||
transactionIds.push(userTransaction.transactionId)
|
||||
})
|
||||
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const transactions = await transactionRepository.joinFullTransactionsByIds(transactionIds)
|
||||
|
||||
const transactions = await dbTransaction.find({ where: { id: In(transactionIds) } })
|
||||
const transactionIndiced: dbTransaction[] = []
|
||||
transactions.forEach((transaction: dbTransaction) => {
|
||||
transactionIndiced[transaction.id] = transaction
|
||||
involvedUserIds.push(transaction.userId)
|
||||
if (transaction.transactionTypeId === TransactionTypeId.SEND) {
|
||||
involvedUserIds.push(transaction.transactionSendCoin.userId)
|
||||
involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId)
|
||||
involvedUserIds.push(transaction.sendReceiverUserId!) // TODO ensure not null properly
|
||||
}
|
||||
})
|
||||
// remove duplicates
|
||||
@ -67,8 +62,6 @@ async function calculateAndAddDecayTransactions(
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
|
||||
|
||||
const decayStartTransaction = await transactionRepository.findDecayStartBlock()
|
||||
|
||||
for (let i = 0; i < userTransactions.length; i++) {
|
||||
const userTransaction = userTransactions[i]
|
||||
const transaction = transactionIndiced[userTransaction.transactionId]
|
||||
@ -81,26 +74,22 @@ async function calculateAndAddDecayTransactions(
|
||||
|
||||
if (previousTransaction) {
|
||||
const currentTransaction = userTransaction
|
||||
const decay = await calculateDecayWithInterval(
|
||||
const decay = calculateDecay(
|
||||
previousTransaction.balance,
|
||||
previousTransaction.balanceDate,
|
||||
currentTransaction.balanceDate,
|
||||
)
|
||||
const balance = previousTransaction.balance - decay.balance
|
||||
|
||||
if (
|
||||
decayStartTransaction &&
|
||||
decayStartTransaction.received < currentTransaction.balanceDate
|
||||
) {
|
||||
if (CONFIG.DECAY_START_TIME < currentTransaction.balanceDate) {
|
||||
finalTransaction.decay = decay
|
||||
finalTransaction.decay.balance = roundFloorFrom4(balance)
|
||||
if (
|
||||
decayStartTransaction &&
|
||||
previousTransaction.transactionId < decayStartTransaction.id &&
|
||||
currentTransaction.transactionId > decayStartTransaction.id
|
||||
previousTransaction.balanceDate < CONFIG.DECAY_START_TIME &&
|
||||
currentTransaction.balanceDate > CONFIG.DECAY_START_TIME
|
||||
) {
|
||||
finalTransaction.decay.decayStartBlock = (
|
||||
decayStartTransaction.received.getTime() / 1000
|
||||
CONFIG.DECAY_START_TIME.getTime() / 1000
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
@ -116,24 +105,21 @@ async function calculateAndAddDecayTransactions(
|
||||
// balance
|
||||
if (userTransaction.transactionTypeId === TransactionTypeId.CREATION) {
|
||||
// creation
|
||||
const creation = transaction.transactionCreation
|
||||
|
||||
finalTransaction.name = 'Gradido Akademie'
|
||||
finalTransaction.type = TransactionType.CREATION
|
||||
// finalTransaction.targetDate = creation.targetDate
|
||||
finalTransaction.balance = roundFloorFrom4(creation.amount)
|
||||
finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion
|
||||
} else if (userTransaction.transactionTypeId === TransactionTypeId.SEND) {
|
||||
// send coin
|
||||
const sendCoin = transaction.transactionSendCoin
|
||||
let otherUser: dbUser | undefined
|
||||
finalTransaction.balance = roundFloorFrom4(sendCoin.amount)
|
||||
if (sendCoin.userId === user.id) {
|
||||
finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion
|
||||
if (transaction.userId === user.id) {
|
||||
finalTransaction.type = TransactionType.SEND
|
||||
otherUser = userIndiced[sendCoin.recipiantUserId]
|
||||
otherUser = userIndiced.find((u) => u.id === transaction.sendReceiverUserId)
|
||||
// finalTransaction.pubkey = sendCoin.recipiantPublic
|
||||
} else if (sendCoin.recipiantUserId === user.id) {
|
||||
} else if (transaction.sendReceiverUserId === user.id) {
|
||||
finalTransaction.type = TransactionType.RECIEVE
|
||||
otherUser = userIndiced[sendCoin.userId]
|
||||
otherUser = userIndiced.find((u) => u.id === transaction.userId)
|
||||
// finalTransaction.pubkey = sendCoin.senderPublic
|
||||
} else {
|
||||
throw new Error('invalid transaction')
|
||||
@ -149,11 +135,7 @@ async function calculateAndAddDecayTransactions(
|
||||
|
||||
if (i === userTransactions.length - 1 && decay) {
|
||||
const now = new Date()
|
||||
const decay = await calculateDecayWithInterval(
|
||||
userTransaction.balance,
|
||||
userTransaction.balanceDate,
|
||||
now.getTime(),
|
||||
)
|
||||
const decay = calculateDecay(userTransaction.balance, userTransaction.balanceDate, now)
|
||||
const balance = userTransaction.balance - decay.balance
|
||||
|
||||
const decayTransaction = new Transaction()
|
||||
@ -165,61 +147,9 @@ async function calculateAndAddDecayTransactions(
|
||||
finalTransactions.push(decayTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
return finalTransactions
|
||||
}
|
||||
|
||||
// Helper function
|
||||
async function listTransactions(
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
order: Order,
|
||||
user: dbUser,
|
||||
onlyCreations: boolean,
|
||||
): Promise<TransactionList> {
|
||||
let limit = pageSize
|
||||
let offset = 0
|
||||
let skipFirstTransaction = false
|
||||
if (currentPage > 1) {
|
||||
offset = (currentPage - 1) * pageSize - 1
|
||||
limit++
|
||||
}
|
||||
|
||||
if (offset && order === Order.ASC) {
|
||||
offset--
|
||||
}
|
||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
||||
let [userTransactions, userTransactionsCount] = await userTransactionRepository.findByUserPaged(
|
||||
user.id,
|
||||
limit,
|
||||
offset,
|
||||
order,
|
||||
onlyCreations,
|
||||
)
|
||||
skipFirstTransaction = userTransactionsCount > offset + limit
|
||||
const decay = !(currentPage > 1)
|
||||
let transactions: Transaction[] = []
|
||||
if (userTransactions.length) {
|
||||
if (order === Order.DESC) {
|
||||
userTransactions = userTransactions.reverse()
|
||||
}
|
||||
transactions = await calculateAndAddDecayTransactions(
|
||||
userTransactions,
|
||||
user,
|
||||
decay,
|
||||
skipFirstTransaction,
|
||||
)
|
||||
if (order === Order.DESC) {
|
||||
transactions = transactions.reverse()
|
||||
}
|
||||
}
|
||||
|
||||
const transactionList = new TransactionList()
|
||||
transactionList.count = userTransactionsCount
|
||||
transactionList.transactions = transactions
|
||||
return transactionList
|
||||
}
|
||||
|
||||
// helper helper function
|
||||
async function updateStateBalance(
|
||||
user: dbUser,
|
||||
@ -227,20 +157,15 @@ async function updateStateBalance(
|
||||
received: Date,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<dbBalance> {
|
||||
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||
let balance = await balanceRepository.findByUser(user.id)
|
||||
let balance = await dbBalance.findOne({ userId: user.id })
|
||||
if (!balance) {
|
||||
balance = new dbBalance()
|
||||
balance.userId = user.id
|
||||
balance.amount = centAmount
|
||||
balance.modified = received
|
||||
} else {
|
||||
const decaiedBalance = await calculateDecay(balance.amount, balance.recordDate, received).catch(
|
||||
() => {
|
||||
throw new Error('error by calculating decay')
|
||||
},
|
||||
)
|
||||
balance.amount = Number(decaiedBalance) + centAmount
|
||||
const decayedBalance = calculateDecay(balance.amount, balance.recordDate, received).balance
|
||||
balance.amount = Number(decayedBalance) + centAmount
|
||||
balance.modified = new Date()
|
||||
}
|
||||
if (balance.amount <= 0) {
|
||||
@ -264,13 +189,11 @@ async function addUserTransaction(
|
||||
const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id)
|
||||
if (lastUserTransaction) {
|
||||
newBalance += Number(
|
||||
await calculateDecay(
|
||||
calculateDecay(
|
||||
Number(lastUserTransaction.balance),
|
||||
lastUserTransaction.balanceDate,
|
||||
transaction.received,
|
||||
).catch(() => {
|
||||
throw new Error('error by calculating decay')
|
||||
}),
|
||||
).balance,
|
||||
)
|
||||
}
|
||||
|
||||
@ -290,16 +213,6 @@ async function addUserTransaction(
|
||||
})
|
||||
}
|
||||
|
||||
async function getPublicKey(email: string): Promise<string | null> {
|
||||
const user = await dbUser.findOne({ email: email })
|
||||
// User not found
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return user.pubKey.toString('hex')
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
export class TransactionResolver {
|
||||
@Authorized([RIGHTS.TRANSACTION_LIST])
|
||||
@ -317,41 +230,66 @@ export class TransactionResolver {
|
||||
): Promise<TransactionList> {
|
||||
// load user
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
let userEntity: dbUser | undefined
|
||||
if (userId) {
|
||||
userEntity = await userRepository.findOneOrFail({ id: userId })
|
||||
} else {
|
||||
userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
const user = userId
|
||||
? await userRepository.findOneOrFail({ id: userId }, { withDeleted: true })
|
||||
: await userRepository.findByPubkeyHex(context.pubKey)
|
||||
let limit = pageSize
|
||||
let offset = 0
|
||||
let skipFirstTransaction = false
|
||||
if (currentPage > 1) {
|
||||
offset = (currentPage - 1) * pageSize - 1
|
||||
limit++
|
||||
}
|
||||
if (offset && order === Order.ASC) {
|
||||
offset--
|
||||
}
|
||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
||||
const [userTransactions, userTransactionsCount] =
|
||||
await userTransactionRepository.findByUserPaged(user.id, limit, offset, order, onlyCreations)
|
||||
skipFirstTransaction = userTransactionsCount > offset + limit
|
||||
const decay = !(currentPage > 1)
|
||||
let transactions: Transaction[] = []
|
||||
if (userTransactions.length) {
|
||||
if (order === Order.DESC) {
|
||||
userTransactions.reverse()
|
||||
}
|
||||
transactions = await calculateAndAddDecayTransactions(
|
||||
userTransactions,
|
||||
user,
|
||||
decay,
|
||||
skipFirstTransaction,
|
||||
)
|
||||
if (order === Order.DESC) {
|
||||
transactions.reverse()
|
||||
}
|
||||
}
|
||||
|
||||
const transactions = await listTransactions(
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
userEntity,
|
||||
onlyCreations,
|
||||
)
|
||||
const transactionList = new TransactionList()
|
||||
transactionList.count = userTransactionsCount
|
||||
transactionList.transactions = transactions
|
||||
|
||||
// get gdt sum
|
||||
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
|
||||
email: userEntity.email,
|
||||
})
|
||||
if (!resultGDTSum.success) throw new Error(resultGDTSum.data)
|
||||
transactions.gdtSum = Number(resultGDTSum.data.sum) || 0
|
||||
transactionList.gdtSum = null
|
||||
try {
|
||||
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
|
||||
email: user.email,
|
||||
})
|
||||
if (resultGDTSum.success) transactionList.gdtSum = Number(resultGDTSum.data.sum) || 0
|
||||
} catch (err: any) {}
|
||||
|
||||
// get balance
|
||||
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||
const balanceEntity = await balanceRepository.findByUser(userEntity.id)
|
||||
const balanceEntity = await dbBalance.findOne({ userId: user.id })
|
||||
if (balanceEntity) {
|
||||
const now = new Date()
|
||||
transactions.balance = roundFloorFrom4(balanceEntity.amount)
|
||||
transactions.decay = roundFloorFrom4(
|
||||
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
|
||||
transactionList.balance = roundFloorFrom4(balanceEntity.amount)
|
||||
// TODO: Add a decay object here instead of static data representing the decay.
|
||||
transactionList.decay = roundFloorFrom4(
|
||||
calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now).balance,
|
||||
)
|
||||
transactions.decayDate = now.toString()
|
||||
transactionList.decayDate = now.toString()
|
||||
}
|
||||
|
||||
return transactions
|
||||
return transactionList
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEND_COINS])
|
||||
@ -359,37 +297,28 @@ export class TransactionResolver {
|
||||
async sendCoins(
|
||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||
@Ctx() context: any,
|
||||
): Promise<string> {
|
||||
): Promise<boolean> {
|
||||
// TODO this is subject to replay attacks
|
||||
// validate sender user (logged in)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const senderUser = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
if (senderUser.pubKey.length !== 32) {
|
||||
throw new Error('invalid sender public key')
|
||||
}
|
||||
// validate amount
|
||||
if (!hasUserAmount(senderUser, amount)) {
|
||||
throw new Error("user hasn't enough GDD")
|
||||
throw new Error("user hasn't enough GDD or amount is < 0")
|
||||
}
|
||||
|
||||
// validate recipient user
|
||||
// TODO: the detour over the public key is unnecessary
|
||||
const recipiantPublicKey = await getPublicKey(email)
|
||||
if (!recipiantPublicKey) {
|
||||
const recipientUser = await dbUser.findOne({ email: email }, { withDeleted: true })
|
||||
if (!recipientUser) {
|
||||
throw new Error('recipient not known')
|
||||
}
|
||||
if (!isHexPublicKey(recipiantPublicKey)) {
|
||||
throw new Error('invalid recipiant public key')
|
||||
if (recipientUser.deletedAt) {
|
||||
throw new Error('The recipient account was deleted')
|
||||
}
|
||||
const recipiantUser = await userRepository.findByPubkeyHex(recipiantPublicKey)
|
||||
if (!recipiantUser) {
|
||||
throw new Error('Cannot find recipiant user by local send coins transaction')
|
||||
} else if (recipiantUser.disabled) {
|
||||
throw new Error('recipiant user account is disabled')
|
||||
}
|
||||
|
||||
// validate amount
|
||||
if (amount <= 0) {
|
||||
throw new Error('invalid amount')
|
||||
if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) {
|
||||
throw new Error('invalid recipient public key')
|
||||
}
|
||||
|
||||
const centAmount = Math.trunc(amount * 10000)
|
||||
@ -399,17 +328,16 @@ export class TransactionResolver {
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
try {
|
||||
// transaction
|
||||
let transaction = new dbTransaction()
|
||||
const transaction = new dbTransaction()
|
||||
transaction.transactionTypeId = TransactionTypeId.SEND
|
||||
transaction.memo = memo
|
||||
transaction.userId = senderUser.id
|
||||
transaction.pubkey = senderUser.pubKey
|
||||
transaction.sendReceiverUserId = recipientUser.id
|
||||
transaction.sendReceiverPublicKey = recipientUser.pubKey
|
||||
transaction.amount = BigInt(centAmount)
|
||||
|
||||
// TODO: NO! this is problematic in its construction
|
||||
const insertResult = await queryRunner.manager.insert(dbTransaction, transaction)
|
||||
transaction = await queryRunner.manager
|
||||
.findOneOrFail(dbTransaction, insertResult.generatedMaps[0].id)
|
||||
.catch((error) => {
|
||||
throw new Error('error loading saved transaction: ' + error)
|
||||
})
|
||||
await queryRunner.manager.insert(dbTransaction, transaction)
|
||||
|
||||
// Insert Transaction: sender - amount
|
||||
const senderUserTransactionBalance = await addUserTransaction(
|
||||
@ -421,7 +349,7 @@ export class TransactionResolver {
|
||||
|
||||
// Insert Transaction: recipient + amount
|
||||
const recipiantUserTransactionBalance = await addUserTransaction(
|
||||
recipiantUser,
|
||||
recipientUser,
|
||||
transaction,
|
||||
centAmount,
|
||||
queryRunner,
|
||||
@ -437,7 +365,7 @@ export class TransactionResolver {
|
||||
|
||||
// Update Balance: recipiant + amount
|
||||
const recipiantStateBalance = await updateStateBalance(
|
||||
recipiantUser,
|
||||
recipientUser,
|
||||
centAmount,
|
||||
transaction.received,
|
||||
queryRunner,
|
||||
@ -450,18 +378,10 @@ export class TransactionResolver {
|
||||
throw new Error('db data corrupted, recipiant')
|
||||
}
|
||||
|
||||
// transactionSendCoin
|
||||
const transactionSendCoin = new dbTransactionSendCoin()
|
||||
transactionSendCoin.transactionId = transaction.id
|
||||
transactionSendCoin.userId = senderUser.id
|
||||
transactionSendCoin.senderPublic = senderUser.pubKey
|
||||
transactionSendCoin.recipiantUserId = recipiantUser.id
|
||||
transactionSendCoin.recipiantPublic = Buffer.from(recipiantPublicKey, 'hex')
|
||||
transactionSendCoin.amount = centAmount
|
||||
transactionSendCoin.senderFinalBalance = senderStateBalance.amount
|
||||
await queryRunner.manager.save(transactionSendCoin).catch((error) => {
|
||||
throw new Error('error saving transaction send coin: ' + error)
|
||||
})
|
||||
// TODO: WTF?
|
||||
// I just assume that due to implicit type conversion the decimal places were cut.
|
||||
// Using `Math.trunc` to simulate this behaviour
|
||||
transaction.sendSenderFinalBalance = BigInt(Math.trunc(senderStateBalance.amount))
|
||||
|
||||
await queryRunner.manager.save(transaction).catch((error) => {
|
||||
throw new Error('error saving transaction with tx hash: ' + error)
|
||||
@ -490,13 +410,13 @@ export class TransactionResolver {
|
||||
await sendTransactionReceivedEmail({
|
||||
senderFirstName: senderUser.firstName,
|
||||
senderLastName: senderUser.lastName,
|
||||
recipientFirstName: recipiantUser.firstName,
|
||||
recipientLastName: recipiantUser.lastName,
|
||||
email: recipiantUser.email,
|
||||
recipientFirstName: recipientUser.firstName,
|
||||
recipientLastName: recipientUser.lastName,
|
||||
email: recipientUser.email,
|
||||
amount,
|
||||
memo,
|
||||
})
|
||||
|
||||
return 'success'
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,14 +6,13 @@ import gql from 'graphql-tag'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import createServer from '../../server/createServer'
|
||||
import { resetDB, initialize } from '@dbTools/helpers'
|
||||
import { getRepository } from 'typeorm'
|
||||
import { LoginUser } from '@entity/LoginUser'
|
||||
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
import { User } from '@entity/User'
|
||||
import CONFIG from '../../config'
|
||||
import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
|
||||
import { klicktippSignIn } from '../../apis/KlicktippController'
|
||||
// import { klicktippSignIn } from '../../apis/KlicktippController'
|
||||
|
||||
jest.setTimeout(10000)
|
||||
|
||||
jest.mock('../../mailer/sendAccountActivationEmail', () => {
|
||||
return {
|
||||
@ -22,12 +21,14 @@ jest.mock('../../mailer/sendAccountActivationEmail', () => {
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
jest.mock('../../apis/KlicktippController', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
klicktippSignIn: jest.fn(),
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
let mutate: any
|
||||
let con: any
|
||||
@ -40,6 +41,11 @@ beforeAll(async () => {
|
||||
await resetDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await resetDB(true)
|
||||
await con.close()
|
||||
})
|
||||
|
||||
describe('UserResolver', () => {
|
||||
describe('createUser', () => {
|
||||
const variables = {
|
||||
@ -84,70 +90,32 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('valid input data', () => {
|
||||
let loginUser: LoginUser[]
|
||||
let user: User[]
|
||||
let loginUserBackup: LoginUserBackup[]
|
||||
let loginEmailOptIn: LoginEmailOptIn[]
|
||||
beforeAll(async () => {
|
||||
loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
|
||||
user = await getRepository(User).createQueryBuilder('state_user').getMany()
|
||||
loginUserBackup = await getRepository(LoginUserBackup)
|
||||
.createQueryBuilder('login_user_backup')
|
||||
.getMany()
|
||||
loginEmailOptIn = await getRepository(LoginEmailOptIn)
|
||||
.createQueryBuilder('login_email_optin')
|
||||
.getMany()
|
||||
user = await User.find()
|
||||
loginEmailOptIn = await LoginEmailOptIn.find()
|
||||
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
|
||||
})
|
||||
|
||||
describe('filling all tables', () => {
|
||||
it('saves the user in login_user table', () => {
|
||||
expect(loginUser).toEqual([
|
||||
expect(user).toEqual([
|
||||
{
|
||||
id: expect.any(Number),
|
||||
email: 'peter@lustig.de',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
username: '',
|
||||
description: '',
|
||||
password: '0',
|
||||
pubKey: null,
|
||||
privKey: null,
|
||||
emailHash: expect.any(Buffer),
|
||||
createdAt: expect.any(Date),
|
||||
emailChecked: false,
|
||||
passphraseShown: false,
|
||||
language: 'de',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
publisherId: 1234,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('saves the user in state_user table', () => {
|
||||
expect(user).toEqual([
|
||||
{
|
||||
id: expect.any(Number),
|
||||
indexId: 0,
|
||||
groupId: 0,
|
||||
pubkey: expect.any(Buffer),
|
||||
email: 'peter@lustig.de',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
username: '',
|
||||
disabled: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('saves the user in login_user_backup table', () => {
|
||||
expect(loginUserBackup).toEqual([
|
||||
{
|
||||
id: expect.any(Number),
|
||||
passphrase: expect.any(String),
|
||||
userId: loginUser[0].id,
|
||||
mnemonicType: 2,
|
||||
language: 'de',
|
||||
deletedAt: null,
|
||||
publisherId: 1234,
|
||||
},
|
||||
])
|
||||
})
|
||||
@ -156,7 +124,7 @@ describe('UserResolver', () => {
|
||||
expect(loginEmailOptIn).toEqual([
|
||||
{
|
||||
id: expect.any(Number),
|
||||
userId: loginUser[0].id,
|
||||
userId: user[0].id,
|
||||
verificationCode: expect.any(String),
|
||||
emailOptInTypeId: 1,
|
||||
createdAt: expect.any(Date),
|
||||
@ -196,9 +164,7 @@ describe('UserResolver', () => {
|
||||
mutation,
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' },
|
||||
})
|
||||
await expect(
|
||||
getRepository(LoginUser).createQueryBuilder('login_user').getMany(),
|
||||
).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
email: 'bibi@bloxberg.de',
|
||||
@ -215,9 +181,7 @@ describe('UserResolver', () => {
|
||||
mutation,
|
||||
variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined },
|
||||
})
|
||||
await expect(
|
||||
getRepository(LoginUser).createQueryBuilder('login_user').getMany(),
|
||||
).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
@ -265,23 +229,17 @@ describe('UserResolver', () => {
|
||||
let emailOptIn: string
|
||||
|
||||
describe('valid optin code and valid password', () => {
|
||||
let loginUser: any
|
||||
let newLoginUser: any
|
||||
let newUser: any
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUserMutation, variables: createUserVariables })
|
||||
const loginEmailOptIn = await getRepository(LoginEmailOptIn)
|
||||
.createQueryBuilder('login_email_optin')
|
||||
.getMany()
|
||||
loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
|
||||
const loginEmailOptIn = await LoginEmailOptIn.find()
|
||||
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
|
||||
result = await mutate({
|
||||
mutation: setPasswordMutation,
|
||||
variables: { code: emailOptIn, password: 'Aa12345_' },
|
||||
})
|
||||
newLoginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
|
||||
newUser = await getRepository(User).createQueryBuilder('state_user').getMany()
|
||||
newUser = await User.find()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -289,38 +247,27 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('sets email checked to true', () => {
|
||||
expect(newLoginUser[0].emailChecked).toBeTruthy()
|
||||
expect(newUser[0].emailChecked).toBeTruthy()
|
||||
})
|
||||
|
||||
it('updates the password', () => {
|
||||
expect(newLoginUser[0].password).toEqual('3917921995996627700')
|
||||
})
|
||||
|
||||
it('updates the public Key on both user tables', () => {
|
||||
expect(newLoginUser[0].pubKey).toEqual(expect.any(Buffer))
|
||||
expect(newLoginUser[0].pubKey).not.toEqual(loginUser[0].pubKey)
|
||||
expect(newLoginUser[0].pubKey).toEqual(newUser[0].pubkey)
|
||||
})
|
||||
|
||||
it('updates the private Key', () => {
|
||||
expect(newLoginUser[0].privKey).toEqual(expect.any(Buffer))
|
||||
expect(newLoginUser[0].privKey).not.toEqual(loginUser[0].privKey)
|
||||
expect(newUser[0].password).toEqual('3917921995996627700')
|
||||
})
|
||||
|
||||
it('removes the optin', async () => {
|
||||
await expect(
|
||||
getRepository(LoginEmailOptIn).createQueryBuilder('login_email_optin').getMany(),
|
||||
).resolves.toHaveLength(0)
|
||||
await expect(LoginEmailOptIn.find()).resolves.toHaveLength(0)
|
||||
})
|
||||
|
||||
/*
|
||||
it('calls the klicktipp API', () => {
|
||||
expect(klicktippSignIn).toBeCalledWith(
|
||||
loginUser[0].email,
|
||||
loginUser[0].language,
|
||||
loginUser[0].firstName,
|
||||
loginUser[0].lastName,
|
||||
user[0].email,
|
||||
user[0].language,
|
||||
user[0].firstName,
|
||||
user[0].lastName,
|
||||
)
|
||||
})
|
||||
*/
|
||||
|
||||
it('returns true', () => {
|
||||
expect(result).toBeTruthy()
|
||||
@ -330,9 +277,7 @@ describe('UserResolver', () => {
|
||||
describe('no valid password', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUserMutation, variables: createUserVariables })
|
||||
const loginEmailOptIn = await getRepository(LoginEmailOptIn)
|
||||
.createQueryBuilder('login_email_optin')
|
||||
.getMany()
|
||||
const loginEmailOptIn = await LoginEmailOptIn.find()
|
||||
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
|
||||
result = await mutate({
|
||||
mutation: setPasswordMutation,
|
||||
@ -380,8 +325,3 @@ describe('UserResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await resetDB(true)
|
||||
await con.close()
|
||||
})
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
import fs from 'fs'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||
import { getConnection, getCustomRepository, getRepository, QueryRunner } from '@dbTools/typeorm'
|
||||
import { getConnection, getCustomRepository, QueryRunner } from '@dbTools/typeorm'
|
||||
import CONFIG from '../../config'
|
||||
import { User } from '../model/User'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
@ -152,8 +152,7 @@ const createEmailOptIn = async (
|
||||
loginUserId: number,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<LoginEmailOptIn> => {
|
||||
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||
let emailOptIn = await loginEmailOptInRepository.findOne({
|
||||
let emailOptIn = await LoginEmailOptIn.findOne({
|
||||
userId: loginUserId,
|
||||
emailOptInTypeId: EMAIL_OPT_IN_REGISTER,
|
||||
})
|
||||
@ -182,8 +181,7 @@ const createEmailOptIn = async (
|
||||
}
|
||||
|
||||
const getOptInCode = async (loginUserId: number): Promise<LoginEmailOptIn> => {
|
||||
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||
let optInCode = await loginEmailOptInRepository.findOne({
|
||||
let optInCode = await LoginEmailOptIn.findOne({
|
||||
userId: loginUserId,
|
||||
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
|
||||
})
|
||||
@ -205,7 +203,7 @@ const getOptInCode = async (loginUserId: number): Promise<LoginEmailOptIn> => {
|
||||
optInCode.userId = loginUserId
|
||||
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
|
||||
}
|
||||
await loginEmailOptInRepository.save(optInCode)
|
||||
await LoginEmailOptIn.save(optInCode)
|
||||
return optInCode
|
||||
}
|
||||
|
||||
@ -250,9 +248,12 @@ export class UserResolver {
|
||||
@Ctx() context: any,
|
||||
): Promise<User> {
|
||||
email = email.trim().toLowerCase()
|
||||
const dbUser = await DbUser.findOneOrFail({ email }).catch(() => {
|
||||
const dbUser = await DbUser.findOneOrFail({ email }, { withDeleted: true }).catch(() => {
|
||||
throw new Error('No user with this credentials')
|
||||
})
|
||||
if (dbUser.deletedAt) {
|
||||
throw new Error('This user was permanently disabled. Contact support for questions.')
|
||||
}
|
||||
if (!dbUser.emailChecked) {
|
||||
throw new Error('User email not validated')
|
||||
}
|
||||
@ -335,9 +336,9 @@ export class UserResolver {
|
||||
|
||||
// Validate email unique
|
||||
// TODO: i can register an email in upper/lower case twice
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const usersFound = await userRepository.count({ email })
|
||||
if (usersFound !== 0) {
|
||||
// TODO we cannot use repository.count(), since it does not allow to specify if you want to include the soft deletes
|
||||
const userFound = await DbUser.findOne({ email }, { withDeleted: true })
|
||||
if (userFound) {
|
||||
// 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.
|
||||
throw new Error(`User already exists.`)
|
||||
}
|
||||
@ -487,12 +488,9 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
// Load code
|
||||
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||
const optInCode = await loginEmailOptInRepository
|
||||
.findOneOrFail({ verificationCode: code })
|
||||
.catch(() => {
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
})
|
||||
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: code }).catch(() => {
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
})
|
||||
|
||||
// Code is only valid for 10minutes
|
||||
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||
|
||||
@ -4,40 +4,42 @@
|
||||
import { ApolloLogPlugin, LogMutateData } from 'apollo-log'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
const plugins = [
|
||||
{
|
||||
requestDidStart() {
|
||||
return {
|
||||
willSendResponse(requestContext: any) {
|
||||
const { setHeaders = [] } = requestContext.context
|
||||
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
|
||||
if (requestContext.response.http.headers.get(key)) {
|
||||
requestContext.response.http.headers.set(key, value)
|
||||
} else {
|
||||
requestContext.response.http.headers.append(key, value)
|
||||
}
|
||||
})
|
||||
return requestContext
|
||||
},
|
||||
}
|
||||
},
|
||||
const setHeadersPlugin = {
|
||||
requestDidStart() {
|
||||
return {
|
||||
willSendResponse(requestContext: any) {
|
||||
const { setHeaders = [] } = requestContext.context
|
||||
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
|
||||
if (requestContext.response.http.headers.get(key)) {
|
||||
requestContext.response.http.headers.set(key, value)
|
||||
} else {
|
||||
requestContext.response.http.headers.append(key, value)
|
||||
}
|
||||
})
|
||||
return requestContext
|
||||
},
|
||||
}
|
||||
},
|
||||
ApolloLogPlugin({
|
||||
mutate: (data: LogMutateData) => {
|
||||
// We need to deep clone the object in order to not modify the actual request
|
||||
const dataCopy = cloneDeep(data)
|
||||
}
|
||||
|
||||
// mask password if part of the query
|
||||
if (dataCopy.context.request.variables && dataCopy.context.request.variables.password) {
|
||||
dataCopy.context.request.variables.password = '***'
|
||||
}
|
||||
const apolloLogPlugin = ApolloLogPlugin({
|
||||
mutate: (data: LogMutateData) => {
|
||||
// We need to deep clone the object in order to not modify the actual request
|
||||
const dataCopy = cloneDeep(data)
|
||||
|
||||
// mask token at all times
|
||||
dataCopy.context.context.token = '***'
|
||||
// mask password if part of the query
|
||||
if (dataCopy.context.request.variables && dataCopy.context.request.variables.password) {
|
||||
dataCopy.context.request.variables.password = '***'
|
||||
}
|
||||
|
||||
return dataCopy
|
||||
},
|
||||
}),
|
||||
]
|
||||
// mask token at all times
|
||||
dataCopy.context.context.token = '***'
|
||||
|
||||
return dataCopy
|
||||
},
|
||||
})
|
||||
|
||||
const plugins =
|
||||
process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin]
|
||||
|
||||
export default plugins
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { EntityRepository, Repository } from '@dbTools/typeorm'
|
||||
import { Balance } from '@entity/Balance'
|
||||
|
||||
@EntityRepository(Balance)
|
||||
export class BalanceRepository extends Repository<Balance> {
|
||||
findByUser(userId: number): Promise<Balance | undefined> {
|
||||
return this.createQueryBuilder('balance').where('balance.userId = :userId', { userId }).getOne()
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { EntityRepository, Repository } from '@dbTools/typeorm'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
@EntityRepository(Transaction)
|
||||
export class TransactionRepository extends Repository<Transaction> {
|
||||
async findDecayStartBlock(): Promise<Transaction | undefined> {
|
||||
return this.createQueryBuilder('transaction')
|
||||
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: 9 })
|
||||
.orderBy('received', 'ASC')
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async joinFullTransactionsByIds(transactionIds: number[]): Promise<Transaction[]> {
|
||||
return this.createQueryBuilder('transaction')
|
||||
.where('transaction.id IN (:...transactions)', { transactions: transactionIds })
|
||||
.leftJoinAndSelect(
|
||||
'transaction.transactionSendCoin',
|
||||
'transactionSendCoin',
|
||||
// 'transactionSendCoin.transaction_id = transaction.id',
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'transaction.transactionCreation',
|
||||
'transactionCreation',
|
||||
// 'transactionSendCoin.transaction_id = transaction.id',
|
||||
)
|
||||
.getMany()
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { EntityRepository, Repository } from '@dbTools/typeorm'
|
||||
import { Brackets, EntityRepository, ObjectLiteral, Repository } from '@dbTools/typeorm'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
@EntityRepository(User)
|
||||
@ -9,38 +9,39 @@ export class UserRepository extends Repository<User> {
|
||||
.getOneOrFail()
|
||||
}
|
||||
|
||||
async findByPubkeyHexBuffer(pubkeyHexBuffer: Buffer): Promise<User> {
|
||||
const pubKeyString = pubkeyHexBuffer.toString('hex')
|
||||
return await this.findByPubkeyHex(pubKeyString)
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User> {
|
||||
return this.createQueryBuilder('user').where('user.email = :email', { email }).getOneOrFail()
|
||||
}
|
||||
|
||||
async getUsersIndiced(userIds: number[]): Promise<User[]> {
|
||||
if (!userIds.length) return []
|
||||
const users = await this.createQueryBuilder('user')
|
||||
return this.createQueryBuilder('user')
|
||||
.withDeleted() // We need to show the name for deleted users for old transactions
|
||||
.select(['user.id', 'user.firstName', 'user.lastName', 'user.email'])
|
||||
.where('user.id IN (:...users)', { users: userIds })
|
||||
.where('user.id IN (:...userIds)', { userIds })
|
||||
.getMany()
|
||||
const usersIndiced: User[] = []
|
||||
users.forEach((value) => {
|
||||
usersIndiced[value.id] = value
|
||||
})
|
||||
return usersIndiced
|
||||
}
|
||||
|
||||
async findBySearchCriteria(searchCriteria: string): Promise<User[]> {
|
||||
async findBySearchCriteriaPagedFiltered(
|
||||
select: string[],
|
||||
searchCriteria: string,
|
||||
filterCriteria: ObjectLiteral[],
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
): Promise<[User[], number]> {
|
||||
return await this.createQueryBuilder('user')
|
||||
.select(select)
|
||||
.withDeleted()
|
||||
.where(
|
||||
'user.firstName like :name or user.lastName like :lastName or user.email like :email',
|
||||
{
|
||||
name: `%${searchCriteria}%`,
|
||||
lastName: `%${searchCriteria}%`,
|
||||
email: `%${searchCriteria}%`,
|
||||
},
|
||||
new Brackets((qb) => {
|
||||
qb.where(
|
||||
'user.firstName like :name or user.lastName like :lastName or user.email like :email',
|
||||
{
|
||||
name: `%${searchCriteria}%`,
|
||||
lastName: `%${searchCriteria}%`,
|
||||
email: `%${searchCriteria}%`,
|
||||
},
|
||||
)
|
||||
}),
|
||||
)
|
||||
.getMany()
|
||||
.andWhere(filterCriteria)
|
||||
.take(pageSize)
|
||||
.skip((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'reflect-metadata' // This might be wise to load in a test setup file
|
||||
import { decayFormula, calculateDecay } from './decay'
|
||||
|
||||
describe('utils/decay', () => {
|
||||
@ -35,6 +36,6 @@ describe('utils/decay', () => {
|
||||
|
||||
it('returns input amount when from and to is the same', async () => {
|
||||
const now = new Date()
|
||||
expect(await calculateDecay(100.0, now, now)).toBe(100.0)
|
||||
expect((await calculateDecay(100.0, now, now)).balance).toBe(100.0)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,70 +1,46 @@
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import CONFIG from '../config'
|
||||
import { Decay } from '../graphql/model/Decay'
|
||||
import { TransactionRepository } from '../typeorm/repository/Transaction'
|
||||
|
||||
function decayFormula(amount: number, seconds: number): number {
|
||||
return amount * Math.pow(0.99999997802044727, seconds) // This number represents 50% decay a year
|
||||
}
|
||||
|
||||
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
|
||||
if (amount === undefined || !from || !to) {
|
||||
throw new Error('at least one parameter is undefined')
|
||||
function calculateDecay(amount: number, from: Date, to: Date): Decay {
|
||||
const fromMs = from.getTime()
|
||||
const toMs = to.getTime()
|
||||
const decayStartBlockMs = CONFIG.DECAY_START_TIME.getTime()
|
||||
|
||||
if (toMs < fromMs) {
|
||||
throw new Error('to < from, reverse decay calculation is invalid')
|
||||
}
|
||||
if (from === to) {
|
||||
return amount
|
||||
|
||||
// Initialize with no decay
|
||||
const decay = new Decay({
|
||||
balance: amount,
|
||||
decayStart: null,
|
||||
decayEnd: null,
|
||||
decayDuration: 0,
|
||||
decayStartBlock: (decayStartBlockMs / 1000).toString(),
|
||||
})
|
||||
|
||||
// decay started after end date; no decay
|
||||
if (decayStartBlockMs > toMs) {
|
||||
return decay
|
||||
}
|
||||
if (to < from) {
|
||||
throw new Error('to < from, so the target date is in the past?')
|
||||
// decay started before start date; decay for full duration
|
||||
else if (decayStartBlockMs < fromMs) {
|
||||
decay.decayStart = (fromMs / 1000).toString()
|
||||
decay.decayDuration = (toMs - fromMs) / 1000
|
||||
}
|
||||
// load decay start block
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
||||
|
||||
// if decay hasn't started yet we return input amount
|
||||
if (!decayStartBlock) return amount
|
||||
|
||||
// what happens when from > to
|
||||
// Do we want to have negative decay?
|
||||
const decayDuration = (to.getTime() - from.getTime()) / 1000
|
||||
return decayFormula(amount, decayDuration)
|
||||
}
|
||||
|
||||
async function calculateDecayWithInterval(
|
||||
amount: number,
|
||||
from: number | Date,
|
||||
to: number | Date,
|
||||
): Promise<Decay> {
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
||||
|
||||
const result = new Decay(undefined)
|
||||
result.balance = amount
|
||||
const fromMillis = typeof from === 'number' ? from : from.getTime()
|
||||
const toMillis = typeof to === 'number' ? to : to.getTime()
|
||||
result.decayStart = (fromMillis / 1000).toString()
|
||||
result.decayEnd = (toMillis / 1000).toString()
|
||||
|
||||
// (amount, from.getTime(), to.getTime())
|
||||
|
||||
// if no decay start block exist or decay startet after end date
|
||||
if (!decayStartBlock || decayStartBlock.received.getTime() > toMillis) {
|
||||
return result
|
||||
}
|
||||
const decayStartBlockMillis = decayStartBlock.received.getTime()
|
||||
|
||||
// if decay start date is before start date we calculate decay for full duration
|
||||
if (decayStartBlockMillis < fromMillis) {
|
||||
result.decayDuration = toMillis - fromMillis
|
||||
}
|
||||
// if decay start in between start date and end date we caculcate decay from decay start time to end date
|
||||
// decay started between start and end date; decay from decay start till end date
|
||||
else {
|
||||
result.decayDuration = toMillis - decayStartBlockMillis
|
||||
result.decayStart = (decayStartBlockMillis / 1000).toString()
|
||||
decay.decayStart = (decayStartBlockMs / 1000).toString()
|
||||
decay.decayDuration = (toMs - decayStartBlockMs) / 1000
|
||||
}
|
||||
// js use timestamp in milliseconds but we calculate with seconds
|
||||
result.decayDuration /= 1000
|
||||
result.balance = decayFormula(amount, result.decayDuration)
|
||||
return result
|
||||
|
||||
decay.decayEnd = (toMs / 1000).toString()
|
||||
decay.balance = decayFormula(amount, decay.decayDuration)
|
||||
return decay
|
||||
}
|
||||
|
||||
export { decayFormula, calculateDecay, calculateDecayWithInterval }
|
||||
export { decayFormula, calculateDecay }
|
||||
|
||||
@ -21,7 +21,7 @@ async function hasUserAmount(user: dbUser, amount: number): Promise<boolean> {
|
||||
const balance = await balanceRepository.findOne({ userId: user.id })
|
||||
if (!balance) return false
|
||||
|
||||
const decay = await calculateDecay(balance.amount, balance.recordDate, new Date())
|
||||
const decay = calculateDecay(balance.amount, balance.recordDate, new Date()).balance
|
||||
return decay > amount
|
||||
}
|
||||
|
||||
|
||||
@ -53,6 +53,19 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
membership,
|
||||
} = req.body
|
||||
|
||||
// Do not process certain events
|
||||
if (['lesson.viewed', 'lesson.completed', 'lesson.commented'].includes(event)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('User viewed, completed or commented - not saving hook')
|
||||
return
|
||||
}
|
||||
|
||||
if (!product || !publisher || !membership || !payer) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Elopage Hook: Not an event we can process')
|
||||
return
|
||||
}
|
||||
|
||||
loginElopageBuy.affiliateProgramId = parseInt(product.affiliate_program_id) || null
|
||||
loginElopageBuy.publisherId = parseInt(publisher.id) || null
|
||||
loginElopageBuy.orderId = parseInt(order_id) || null
|
||||
@ -72,13 +85,6 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
const firstName = payer.first_name
|
||||
const lastName = payer.last_name
|
||||
|
||||
// Do not process certain events
|
||||
if (['lesson.viewed', 'lesson.completed', 'lesson.commented'].includes(loginElopageBuy.event)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('User viewed, completed or commented - not saving hook')
|
||||
return
|
||||
}
|
||||
|
||||
// Save the hook data
|
||||
try {
|
||||
await LoginElopageBuys.save(loginElopageBuy)
|
||||
|
||||
6
backend/test/testSetup.ts
Normal file
6
backend/test/testSetup.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// disable console.info for apollo log
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
console.info = () => {}
|
||||
@ -4092,11 +4092,6 @@ module-alias@^2.2.2:
|
||||
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
|
||||
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
|
||||
import { TransactionCreation } from '../TransactionCreation'
|
||||
import { TransactionSendCoin } from '../TransactionSendCoin'
|
||||
import { TransactionCreation } from './TransactionCreation'
|
||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
|
||||
import { TransactionCreation } from '../TransactionCreation'
|
||||
import { TransactionSendCoin } from '../TransactionSendCoin'
|
||||
import { TransactionCreation } from '../0001-init_db/TransactionCreation'
|
||||
import { TransactionSendCoin } from '../0001-init_db/TransactionSendCoin'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
|
||||
75
database/entity/0023-users_disabled_soft_delete/User.ts
Normal file
75
database/entity/0023-users_disabled_soft_delete/User.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
OneToMany,
|
||||
DeleteDateColumn,
|
||||
} from 'typeorm'
|
||||
import { UserSetting } from '../UserSetting'
|
||||
|
||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
|
||||
pubKey: Buffer
|
||||
|
||||
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
|
||||
privKey: Buffer
|
||||
|
||||
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
email: string
|
||||
|
||||
@Column({
|
||||
name: 'first_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
firstName: string
|
||||
|
||||
@Column({
|
||||
name: 'last_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
lastName: string
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt: Date | null
|
||||
|
||||
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||
password: BigInt
|
||||
|
||||
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
|
||||
emailHash: Buffer
|
||||
|
||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
|
||||
emailChecked: boolean
|
||||
|
||||
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||
language: string
|
||||
|
||||
@Column({ name: 'publisher_id', default: 0 })
|
||||
publisherId: number
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
name: 'passphrase',
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
passphrase: string
|
||||
|
||||
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
|
||||
settings: UserSetting[]
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
// TODO the id is defined as bigint(20) - there might be problems with that: https://github.com/typeorm/typeorm/issues/2400
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'transaction_type_id', unsigned: true, nullable: false })
|
||||
transactionTypeId: number
|
||||
|
||||
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||
userId: number
|
||||
|
||||
@Column({ type: 'bigint', nullable: false })
|
||||
amount: BigInt
|
||||
|
||||
@Column({ name: 'tx_hash', type: 'binary', length: 48, default: null, nullable: true })
|
||||
txHash: Buffer
|
||||
|
||||
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
memo: string
|
||||
|
||||
@Column({ type: 'timestamp', nullable: false, default: () => 'CURRENT_TIMESTAMP' })
|
||||
received: Date
|
||||
|
||||
@Column({ type: 'binary', length: 64, nullable: true, default: null })
|
||||
signature: Buffer
|
||||
|
||||
@Column({ type: 'binary', length: 32, nullable: true, default: null })
|
||||
pubkey: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'creation_ident_hash',
|
||||
type: 'binary',
|
||||
length: 32,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
creationIdentHash: Buffer
|
||||
|
||||
@Column({ name: 'creation_date', type: 'timestamp', nullable: true, default: null })
|
||||
creationDate: Date
|
||||
|
||||
@Column({
|
||||
name: 'send_receiver_public_key',
|
||||
type: 'binary',
|
||||
length: 32,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
sendReceiverPublicKey: Buffer | null
|
||||
|
||||
@Column({
|
||||
name: 'send_receiver_user_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
sendReceiverUserId?: number | null
|
||||
|
||||
@Column({
|
||||
name: 'send_sender_final_balance',
|
||||
type: 'bigint',
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
sendSenderFinalBalance: BigInt | null
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { Transaction } from './0016-transaction_signatures/Transaction'
|
||||
export { Transaction } from './0024-combine_transaction_tables/Transaction'
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { TransactionCreation } from './0001-init_db/TransactionCreation'
|
||||
@ -1 +0,0 @@
|
||||
export { TransactionSendCoin } from './0001-init_db/TransactionSendCoin'
|
||||
@ -1 +1 @@
|
||||
export { User } from './0020-rename_and_clean_state_users/User'
|
||||
export { User } from './0023-users_disabled_soft_delete/User'
|
||||
|
||||
@ -4,8 +4,6 @@ import { LoginEmailOptIn } from './LoginEmailOptIn'
|
||||
import { Migration } from './Migration'
|
||||
import { ServerUser } from './ServerUser'
|
||||
import { Transaction } from './Transaction'
|
||||
import { TransactionCreation } from './TransactionCreation'
|
||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||
import { User } from './User'
|
||||
import { UserSetting } from './UserSetting'
|
||||
import { UserTransaction } from './UserTransaction'
|
||||
@ -19,8 +17,6 @@ export const entities = [
|
||||
Migration,
|
||||
ServerUser,
|
||||
Transaction,
|
||||
TransactionCreation,
|
||||
TransactionSendCoin,
|
||||
User,
|
||||
UserSetting,
|
||||
UserTransaction,
|
||||
|
||||
@ -5,19 +5,20 @@
|
||||
* This also removes the trailing space
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const TARGET_MNEMONIC_TYPE = 2
|
||||
const PHRASE_WORD_COUNT = 24
|
||||
const WORDS_MNEMONIC_0 = fs
|
||||
.readFileSync('src/config/mnemonic.uncompressed_buffer18112.txt')
|
||||
.readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18112.txt'))
|
||||
.toString()
|
||||
.split(',')
|
||||
const WORDS_MNEMONIC_1 = fs
|
||||
.readFileSync('src/config/mnemonic.uncompressed_buffer18113.txt')
|
||||
.readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18113.txt'))
|
||||
.toString()
|
||||
.split(',')
|
||||
const WORDS_MNEMONIC_2 = fs
|
||||
.readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt')
|
||||
.readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer13116.txt'))
|
||||
.toString()
|
||||
.split(',')
|
||||
const WORDS_MNEMONIC = [WORDS_MNEMONIC_0, WORDS_MNEMONIC_1, WORDS_MNEMONIC_2]
|
||||
|
||||
18
database/migrations/0022-delete_decay_start_block.ts
Normal file
18
database/migrations/0022-delete_decay_start_block.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/* MIGRATION TO DELETE DECAY START BLOCK
|
||||
*
|
||||
* the decay start block is now specified as static value
|
||||
* we can delete it from the database
|
||||
*/
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// Remove transactions with type 9 (start decay block). This should affect exactly 1 row
|
||||
await queryFn(`DELETE FROM transactions WHERE transaction_type_id = 9;`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// When rolling back an empty database this entry is newly created. This should not hurt in any way tho.
|
||||
await queryFn(`
|
||||
INSERT INTO transactions
|
||||
VALUES(1682,9,0xC27BE999D7B4704E5F294099E780C5D6A275B165AFABFD8BECCEA39059CBB7B600000000000000000000000000000000,'','2021-05-13 15:46:31',NULL,NULL)
|
||||
`)
|
||||
}
|
||||
26
database/migrations/0023-users_disabled_soft_delete.ts
Normal file
26
database/migrations/0023-users_disabled_soft_delete.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* MIGRATION TO IMPLEMENT SOFT DELETE ON THE USERS TABLE
|
||||
*
|
||||
* Replace the `disabled` column with `deletedAt` containing
|
||||
* a date as it is standard for soft delete fields
|
||||
*/
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// Create new `deletedAt` column
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN `deletedAt` datetime DEFAULT NULL AFTER `disabled`;',
|
||||
)
|
||||
|
||||
// Insert a 1.1.2022 as date for those users with `disabled=1`
|
||||
await queryFn('UPDATE `users` SET `deletedAt` = "2022-01-01 00:00:00" WHERE `disabled` = 1;')
|
||||
|
||||
// Delete `disabled` column
|
||||
await queryFn('ALTER TABLE `users` DROP COLUMN `disabled`;')
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN `disabled` tinyint(4) NOT NULL DEFAULT 0 AFTER `deletedAt`;',
|
||||
)
|
||||
await queryFn('UPDATE `users` SET `disabled` = 1 WHERE `deletedAt` IS NOT NULL;')
|
||||
await queryFn('ALTER TABLE `users` DROP COLUMN `deletedAt`;')
|
||||
}
|
||||
125
database/migrations/0024-combine_transaction_tables.ts
Normal file
125
database/migrations/0024-combine_transaction_tables.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/* MIGRATION TO COMBINE ALL TRANSACTION TABLES
|
||||
*
|
||||
* Combine all transaction tables into one table with all data
|
||||
*/
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// Create new `user_id` column (former `state_user_id`), with a temporary default of null
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `user_id` int(10) unsigned DEFAULT NULL AFTER `transaction_type_id`;',
|
||||
)
|
||||
// Create new `amount` column, with a temporary default of null
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `amount` bigint(20) DEFAULT NULL AFTER `user_id`;',
|
||||
)
|
||||
// Create new `creation_ident_hash` column (former `ident_hash`)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `creation_ident_hash` binary(32) DEFAULT NULL AFTER `pubkey`;',
|
||||
)
|
||||
// Create new `creation_date` column (former `target_date`)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `creation_date` timestamp NULL DEFAULT NULL AFTER `creation_ident_hash`;',
|
||||
)
|
||||
// Create new `send_receiver_public_key` column (former `receiver_public_key`)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `send_receiver_public_key` binary(32) DEFAULT NULL AFTER `creation_date`;',
|
||||
)
|
||||
// Create new `send_receiver_user_id` column (former `receiver_user_id`)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `send_receiver_user_id` int(10) unsigned DEFAULT NULL AFTER `send_receiver_public_key`;',
|
||||
)
|
||||
// Create new `send_sender_final_balance` column (former `sender_final_balance`)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `send_sender_final_balance` bigint(20) DEFAULT NULL AFTER `send_receiver_user_id`;',
|
||||
)
|
||||
|
||||
// Insert Data from `transaction_creations`
|
||||
await queryFn(`
|
||||
UPDATE transactions
|
||||
INNER JOIN transaction_creations ON transaction_creations.transaction_id = transactions.id
|
||||
SET transactions.user_id = transaction_creations.state_user_id,
|
||||
transactions.amount = transaction_creations.amount,
|
||||
transactions.creation_ident_hash = transaction_creations.ident_hash,
|
||||
transactions.creation_date = transaction_creations.target_date;
|
||||
`)
|
||||
|
||||
// Insert Data from `transaction_send_coins`
|
||||
// Note: we drop `sender_public_key` in favor of `pubkey` from the original `transactions` table
|
||||
// the data from `transaction_send_coins` seems incomplete for half the dataset (zeroed pubkey)
|
||||
// with one key being different.
|
||||
await queryFn(`
|
||||
UPDATE transactions
|
||||
INNER JOIN transaction_send_coins ON transaction_send_coins.transaction_id = transactions.id
|
||||
SET transactions.user_id = transaction_send_coins.state_user_id,
|
||||
transactions.amount = transaction_send_coins.amount,
|
||||
transactions.send_receiver_public_key = transaction_send_coins.receiver_public_key,
|
||||
transactions.send_receiver_user_id = transaction_send_coins.receiver_user_id,
|
||||
transactions.send_sender_final_balance = transaction_send_coins.sender_final_balance;
|
||||
`)
|
||||
|
||||
// Modify defaults after our inserts
|
||||
await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `user_id` int(10) unsigned NOT NULL;')
|
||||
await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `amount` bigint(20) NOT NULL;')
|
||||
|
||||
// Drop table `transaction_creations`
|
||||
await queryFn('DROP TABLE `transaction_creations`;')
|
||||
// Drop table `transaction_send_coins`
|
||||
await queryFn('DROP TABLE `transaction_send_coins`;')
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE \`transaction_send_coins\` (
|
||||
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`transaction_id\` int(10) unsigned NOT NULL,
|
||||
\`sender_public_key\` binary(32) NOT NULL,
|
||||
\`state_user_id\` int(10) unsigned DEFAULT 0,
|
||||
\`receiver_public_key\` binary(32) NOT NULL,
|
||||
\`receiver_user_id\` int(10) unsigned DEFAULT 0,
|
||||
\`amount\` bigint(20) NOT NULL,
|
||||
\`sender_final_balance\` bigint(20) NOT NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=659 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`)
|
||||
await queryFn(`
|
||||
CREATE TABLE \`transaction_creations\` (
|
||||
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`transaction_id\` int(10) unsigned NOT NULL,
|
||||
\`state_user_id\` int(10) unsigned NOT NULL,
|
||||
\`amount\` bigint(20) NOT NULL,
|
||||
\`ident_hash\` binary(32) DEFAULT NULL,
|
||||
\`target_date\` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2769 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`)
|
||||
|
||||
await queryFn(`
|
||||
INSERT INTO transaction_send_coins
|
||||
( transaction_id, sender_public_key, state_user_id,
|
||||
receiver_public_key, receiver_user_id,
|
||||
amount, sender_final_balance )
|
||||
( SELECT id AS transaction_id, IF(pubkey, pubkey, 0x00000000000000000000000000000000) AS sender_public_key, user_id AS state_user_id,
|
||||
send_receiver_public_key AS receiver_public_key, send_receiver_user_id AS receiver_user_id,
|
||||
amount, send_sender_final_balance AS sender_final_balance
|
||||
FROM transactions
|
||||
WHERE transaction_type_id = 2 );
|
||||
`)
|
||||
|
||||
await queryFn(`
|
||||
INSERT INTO transaction_creations
|
||||
( transaction_id, state_user_id,
|
||||
amount, ident_hash, target_date )
|
||||
( SELECT id AS transaction_id, user_id AS state_user_id,
|
||||
amount, creation_ident_hash AS ident_hash, creation_date AS target_date
|
||||
FROM transactions
|
||||
WHERE transaction_type_id = 1 );
|
||||
`)
|
||||
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_sender_final_balance`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_receiver_user_id`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_receiver_public_key`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `creation_date`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `creation_ident_hash`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `amount`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_id`;')
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.5",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
@ -8,7 +8,7 @@
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"build": "mkdir -p build/src/config/ && cp src/config/*.txt build/src/config/ && tsc --build",
|
||||
"clean": "tsc --build --clean",
|
||||
"up": "node build/src/index.js up",
|
||||
"down": "node build/src/index.js down",
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import Faker from 'faker'
|
||||
import { define } from 'typeorm-seeding'
|
||||
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||
import { TransactionCreationContext } from '../interface/TransactionContext'
|
||||
|
||||
define(TransactionCreation, (faker: typeof Faker, context?: TransactionCreationContext) => {
|
||||
if (!context || !context.userId || !context.transaction) {
|
||||
throw new Error('TransactionCreation: No userId and/or transaction present!')
|
||||
}
|
||||
|
||||
const transactionCreation = new TransactionCreation()
|
||||
transactionCreation.userId = context.userId
|
||||
transactionCreation.amount = context.amount ? context.amount : 100000
|
||||
transactionCreation.targetDate = context.targetDate ? context.targetDate : new Date()
|
||||
transactionCreation.transaction = context.transaction
|
||||
|
||||
return transactionCreation
|
||||
})
|
||||
@ -5,17 +5,24 @@ import { TransactionContext } from '../interface/TransactionContext'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
||||
if (!context) context = {}
|
||||
if (!context) {
|
||||
throw new Error('TransactionContext not well defined.')
|
||||
}
|
||||
|
||||
const transaction = new Transaction()
|
||||
transaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 2
|
||||
transaction.txHash = context.txHash ? context.txHash : randomBytes(48)
|
||||
transaction.memo = context.memo || context.memo === '' ? context.memo : faker.lorem.sentence()
|
||||
transaction.received = context.received ? context.received : new Date()
|
||||
transaction.signature = context.signature ? context.signature : randomBytes(64)
|
||||
transaction.pubkey = context.signaturePubkey ? context.signaturePubkey : randomBytes(32)
|
||||
if (context.transactionSendCoin) transaction.transactionSendCoin = context.transactionSendCoin
|
||||
if (context.transactionCreation) transaction.transactionCreation = context.transactionCreation
|
||||
transaction.transactionTypeId = context.transactionTypeId // || 2
|
||||
transaction.userId = context.userId
|
||||
transaction.amount = context.amount
|
||||
transaction.txHash = context.txHash || randomBytes(48)
|
||||
transaction.memo = context.memo
|
||||
transaction.received = context.received || new Date()
|
||||
transaction.signature = context.signature || randomBytes(64)
|
||||
transaction.pubkey = context.pubkey || randomBytes(32)
|
||||
transaction.creationIdentHash = context.creationIdentHash || randomBytes(32)
|
||||
transaction.creationDate = context.creationDate || new Date()
|
||||
transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null
|
||||
transaction.sendReceiverUserId = context.sendReceiverUserId || null
|
||||
transaction.sendSenderFinalBalance = context.sendSenderFinalBalance || null
|
||||
|
||||
return transaction
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Faker from 'faker'
|
||||
import { define } from 'typeorm-seeding'
|
||||
import { User } from '../../entity/User'
|
||||
import { randomBytes, randomInt } from 'crypto'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { UserContext } from '../interface/UserContext'
|
||||
|
||||
define(User, (faker: typeof Faker, context?: UserContext) => {
|
||||
@ -12,7 +12,7 @@ define(User, (faker: typeof Faker, context?: UserContext) => {
|
||||
user.email = context.email ? context.email : faker.internet.email()
|
||||
user.firstName = context.firstName ? context.firstName : faker.name.firstName()
|
||||
user.lastName = context.lastName ? context.lastName : faker.name.lastName()
|
||||
user.disabled = context.disabled ? context.disabled : false
|
||||
user.deletedAt = context.deletedAt ? context.deletedAt : null
|
||||
// TODO Create real password and keys/hash
|
||||
user.password = context.password ? context.password : BigInt(0)
|
||||
user.privKey = context.privKey ? context.privKey : randomBytes(80)
|
||||
|
||||
@ -8,7 +8,6 @@ import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.
|
||||
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
||||
import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed'
|
||||
import { CreateUserSeed } from './seeds/create-user.seed'
|
||||
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
|
||||
import { resetDB, pool, migration } from './helpers'
|
||||
|
||||
const run = async (command: string) => {
|
||||
@ -41,7 +40,6 @@ const run = async (command: string) => {
|
||||
root: process.cwd(),
|
||||
configName: 'ormconfig.js',
|
||||
})
|
||||
await runSeeder(DecayStartBlockSeed)
|
||||
await runSeeder(CreatePeterLustigSeed)
|
||||
await runSeeder(CreateBibiBloxbergSeed)
|
||||
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import { Transaction } from '../../entity/Transaction'
|
||||
import { TransactionSendCoin } from '../../entity/TransactionSendCoin'
|
||||
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||
import { User } from '../../entity/User'
|
||||
|
||||
export interface TransactionContext {
|
||||
transactionTypeId?: number
|
||||
transactionTypeId: number
|
||||
userId: number
|
||||
amount: BigInt
|
||||
txHash?: Buffer
|
||||
memo?: string
|
||||
memo: string
|
||||
received?: Date
|
||||
blockchainTypeId?: number
|
||||
signature?: Buffer
|
||||
signaturePubkey?: Buffer
|
||||
transactionSendCoin?: TransactionSendCoin
|
||||
transactionCreation?: TransactionCreation
|
||||
pubkey?: Buffer
|
||||
creationIdentHash?: Buffer
|
||||
creationDate?: Date
|
||||
sendReceiverPublicKey?: Buffer
|
||||
sendReceiverUserId?: number
|
||||
sendSenderFinalBalance?: BigInt
|
||||
}
|
||||
|
||||
export interface BalanceContext {
|
||||
@ -32,13 +34,6 @@ export interface TransactionSendCoinContext {
|
||||
transaction?: Transaction
|
||||
}
|
||||
|
||||
export interface TransactionCreationContext {
|
||||
userId?: number
|
||||
amount?: number
|
||||
targetDate?: Date
|
||||
transaction?: Transaction
|
||||
}
|
||||
|
||||
export interface UserTransactionContext {
|
||||
userId?: number
|
||||
transactionId?: number
|
||||
|
||||
@ -3,7 +3,7 @@ export interface UserContext {
|
||||
email?: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
disabled?: boolean
|
||||
deletedAt?: Date
|
||||
password?: BigInt
|
||||
privKey?: Buffer
|
||||
emailHash?: Buffer
|
||||
|
||||
@ -10,8 +10,7 @@ export interface UserInterface {
|
||||
createdAt?: Date
|
||||
emailChecked?: boolean
|
||||
language?: string
|
||||
disabled?: boolean
|
||||
groupId?: number
|
||||
deletedAt?: Date
|
||||
publisherId?: number
|
||||
passphrase?: string
|
||||
// from server user
|
||||
@ -27,9 +26,8 @@ export interface UserInterface {
|
||||
// balance
|
||||
balanceModified?: Date
|
||||
recordDate?: Date
|
||||
targetDate?: Date
|
||||
creationDate?: Date
|
||||
amount?: number
|
||||
creationTxHash?: Buffer
|
||||
signature?: Buffer
|
||||
signaturePubkey?: Buffer
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { Factory, Seeder } from 'typeorm-seeding'
|
||||
import { Transaction } from '../../entity/Transaction'
|
||||
|
||||
export class DecayStartBlockSeed implements Seeder {
|
||||
public async run(factory: Factory): Promise<void> {
|
||||
await factory(Transaction)({
|
||||
transactionTypeId: 9,
|
||||
txHash: Buffer.from(
|
||||
'9c9c4152b8a4cfbac287eee18d2d262e9de756fae726fc0ca36b788564973fff00000000000000000000000000000000',
|
||||
'hex',
|
||||
),
|
||||
memo: '',
|
||||
received: new Date('2021-11-30T09:13:26'),
|
||||
blockchainTypeId: 1,
|
||||
}).create()
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ import { UserContext, ServerUserContext } from '../../interface/UserContext'
|
||||
import {
|
||||
BalanceContext,
|
||||
TransactionContext,
|
||||
TransactionCreationContext,
|
||||
UserTransactionContext,
|
||||
} from '../../interface/TransactionContext'
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
@ -11,7 +10,6 @@ import { ServerUser } from '../../../entity/ServerUser'
|
||||
import { Balance } from '../../../entity/Balance'
|
||||
import { Transaction } from '../../../entity/Transaction'
|
||||
import { UserTransaction } from '../../../entity/UserTransaction'
|
||||
import { TransactionCreation } from '../../../entity/TransactionCreation'
|
||||
import { Factory } from 'typeorm-seeding'
|
||||
|
||||
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
||||
@ -25,10 +23,7 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
|
||||
// create some GDD for the user
|
||||
await factory(Balance)(createBalanceContext(userData, user)).create()
|
||||
const transaction = await factory(Transaction)(
|
||||
createTransactionContext(userData, 1, 'Herzlich Willkommen bei Gradido!'),
|
||||
).create()
|
||||
await factory(TransactionCreation)(
|
||||
createTransactionCreationContext(userData, user, transaction),
|
||||
createTransactionContext(userData, user, 1, 'Herzlich Willkommen bei Gradido!'),
|
||||
).create()
|
||||
await factory(UserTransaction)(
|
||||
createUserTransactionContext(userData, user, transaction),
|
||||
@ -42,7 +37,7 @@ const createUserContext = (context: UserInterface): UserContext => {
|
||||
email: context.email,
|
||||
firstName: context.firstName,
|
||||
lastName: context.lastName,
|
||||
disabled: context.disabled,
|
||||
deletedAt: context.deletedAt,
|
||||
password: context.password,
|
||||
privKey: context.privKey,
|
||||
emailHash: context.emailHash,
|
||||
@ -76,27 +71,18 @@ const createBalanceContext = (context: UserInterface, user: User): BalanceContex
|
||||
|
||||
const createTransactionContext = (
|
||||
context: UserInterface,
|
||||
user: User,
|
||||
type: number,
|
||||
memo: string,
|
||||
): TransactionContext => {
|
||||
return {
|
||||
transactionTypeId: type,
|
||||
userId: user.id,
|
||||
amount: BigInt(context.amount || 100000),
|
||||
txHash: context.creationTxHash,
|
||||
memo,
|
||||
received: context.recordDate,
|
||||
}
|
||||
}
|
||||
|
||||
const createTransactionCreationContext = (
|
||||
context: UserInterface,
|
||||
user: User,
|
||||
transaction: Transaction,
|
||||
): TransactionCreationContext => {
|
||||
return {
|
||||
userId: user.id,
|
||||
amount: context.amount,
|
||||
targetDate: context.targetDate,
|
||||
transaction,
|
||||
creationDate: context.creationDate,
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,6 +98,6 @@ const createUserTransactionContext = (
|
||||
balance: context.amount,
|
||||
balanceDate: context.recordDate,
|
||||
signature: context.signature,
|
||||
pubkey: context.signaturePubkey,
|
||||
pubkey: context.pubKey,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export const bibiBloxberg = {
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
|
||||
export const bibiBloxberg: UserInterface = {
|
||||
email: 'bibi@bloxberg.de',
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
username: 'bibi',
|
||||
// description: 'Hex Hex',
|
||||
password: BigInt('12825419584724616625'),
|
||||
pubKey: Buffer.from('42de7e4754625b730018c3b4ea745a4d043d9d867af352d0f08871793dfa6743', 'hex'),
|
||||
@ -14,16 +15,13 @@ export const bibiBloxberg = {
|
||||
createdAt: new Date('2021-11-26T11:32:16'),
|
||||
emailChecked: true,
|
||||
language: 'de',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
passphrase:
|
||||
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
|
||||
mnemonicType: 2,
|
||||
isAdmin: false,
|
||||
addBalance: true,
|
||||
balanceModified: new Date('2021-11-30T10:37:11'),
|
||||
recordDate: new Date('2021-11-30T10:37:11'),
|
||||
targetDate: new Date('2021-08-01 00:00:00'),
|
||||
creationDate: new Date('2021-08-01 00:00:00'),
|
||||
amount: 10000000,
|
||||
creationTxHash: Buffer.from(
|
||||
'51103dc0fc2ca5d5d75a9557a1e899304e5406cfdb1328d8df6414d527b0118100000000000000000000000000000000',
|
||||
@ -33,8 +31,4 @@ export const bibiBloxberg = {
|
||||
'2a2c71f3e41adc060bbc3086577e2d57d24eeeb0a7727339c3f85aad813808f601d7e1df56a26e0929d2e67fc054fca429ccfa283ed2782185c7f009fe008f0c',
|
||||
'hex',
|
||||
),
|
||||
signaturePubkey: Buffer.from(
|
||||
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||
'hex',
|
||||
),
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export const bobBaumeister = {
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
|
||||
export const bobBaumeister: UserInterface = {
|
||||
email: 'bob@baumeister.de',
|
||||
firstName: 'Bob',
|
||||
lastName: 'der Baumeister',
|
||||
username: 'bob',
|
||||
// description: 'Können wir das schaffen? Ja, wir schaffen das!',
|
||||
password: BigInt('3296644341468822636'),
|
||||
pubKey: Buffer.from('a509d9a146374fc975e3677db801ae8a4a83bff9dea96da64053ff6de6b2dd7e', 'hex'),
|
||||
@ -14,16 +15,13 @@ export const bobBaumeister = {
|
||||
createdAt: new Date('2021-11-26T11:36:31'),
|
||||
emailChecked: true,
|
||||
language: 'de',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
passphrase:
|
||||
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
|
||||
mnemonicType: 2,
|
||||
isAdmin: false,
|
||||
addBalance: true,
|
||||
balanceModified: new Date('2021-11-30T10:37:14'),
|
||||
recordDate: new Date('2021-11-30T10:37:14'),
|
||||
targetDate: new Date('2021-08-01 00:00:00'),
|
||||
creationDate: new Date('2021-08-01 00:00:00'),
|
||||
amount: 10000000,
|
||||
creationTxHash: Buffer.from(
|
||||
'be095dc87acb94987e71168fee8ecbf50ecb43a180b1006e75d573b35725c69c00000000000000000000000000000000',
|
||||
@ -33,8 +31,4 @@ export const bobBaumeister = {
|
||||
'1fbd6b9a3d359923b2501557f3bc79fa7e428127c8090fb16bc490b4d87870ab142b3817ddd902d22f0b26472a483233784a0e460c0622661752a13978903905',
|
||||
'hex',
|
||||
),
|
||||
signaturePubkey: Buffer.from(
|
||||
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||
'hex',
|
||||
),
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export const garrickOllivander = {
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
|
||||
export const garrickOllivander: UserInterface = {
|
||||
email: 'garrick@ollivander.com',
|
||||
firstName: 'Garrick',
|
||||
lastName: 'Ollivander',
|
||||
username: 'garrick',
|
||||
// description: `Curious ... curious ...
|
||||
// Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`,
|
||||
password: BigInt('0'),
|
||||
@ -10,11 +11,8 @@ export const garrickOllivander = {
|
||||
createdAt: new Date('2022-01-10T10:23:17'),
|
||||
emailChecked: false,
|
||||
language: 'en',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
passphrase:
|
||||
'human glide theory clump wish history other duty door fringe neck industry ostrich equal plate diesel tornado neck people antenna door category moon hen ',
|
||||
mnemonicType: 2,
|
||||
isAdmin: false,
|
||||
addBalance: false,
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export const peterLustig = {
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
|
||||
export const peterLustig: UserInterface = {
|
||||
email: 'peter@lustig.de',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
username: 'peter',
|
||||
// description: 'Latzhose und Nickelbrille',
|
||||
password: BigInt('3917921995996627700'),
|
||||
pubKey: Buffer.from('7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', 'hex'),
|
||||
@ -14,11 +15,8 @@ export const peterLustig = {
|
||||
createdAt: new Date('2020-11-25T10:48:43'),
|
||||
emailChecked: true,
|
||||
language: 'de',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
passphrase:
|
||||
'okay property choice naive calm present weird increase stuff royal vibrant frame attend wood one else tribe pull hedgehog woman kitchen hawk snack smart ',
|
||||
mnemonicType: 2,
|
||||
role: 'admin',
|
||||
serverUserPassword: '$2y$10$TzIWLeZoKs251gwrhSQmHeKhKI/EQ4EV5ClfAT8Ufnb4lcUXPa5X.',
|
||||
activated: 1,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export const raeuberHotzenplotz = {
|
||||
import { UserInterface } from '../../interface/UserInterface'
|
||||
|
||||
export const raeuberHotzenplotz: UserInterface = {
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
firstName: 'Räuber',
|
||||
lastName: 'Hotzenplotz',
|
||||
username: 'räuber',
|
||||
// description: 'Pfefferpistole',
|
||||
password: BigInt('12123692783243004812'),
|
||||
pubKey: Buffer.from('d7c70f94234dff071d982aa8f41583876c356599773b5911b39080da2b8c2d2b', 'hex'),
|
||||
@ -14,16 +15,13 @@ export const raeuberHotzenplotz = {
|
||||
createdAt: new Date('2021-11-26T11:32:16'),
|
||||
emailChecked: true,
|
||||
language: 'de',
|
||||
disabled: false,
|
||||
groupId: 1,
|
||||
passphrase:
|
||||
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
|
||||
mnemonicType: 2,
|
||||
isAdmin: false,
|
||||
addBalance: true,
|
||||
balanceModified: new Date('2021-11-30T10:37:13'),
|
||||
recordDate: new Date('2021-11-30T10:37:13'),
|
||||
targetDate: new Date('2021-08-01 00:00:00'),
|
||||
creationDate: new Date('2021-08-01 00:00:00'),
|
||||
amount: 10000000,
|
||||
creationTxHash: Buffer.from(
|
||||
'23ba44fd84deb59b9f32969ad0cb18bfa4588be1bdb99c396888506474c16c1900000000000000000000000000000000',
|
||||
@ -33,8 +31,4 @@ export const raeuberHotzenplotz = {
|
||||
'756d3da061687c575d1dbc5073908f646aa5f498b0927b217c83b48af471450e571dfe8421fb8e1f1ebd1104526b7e7c6fa78684e2da59c8f7f5a8dc3d9e5b0b',
|
||||
'hex',
|
||||
),
|
||||
signaturePubkey: Buffer.from(
|
||||
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||
'hex',
|
||||
),
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
@ -62,7 +62,6 @@
|
||||
"vue-qrcode": "^0.3.5",
|
||||
"vue-qrcode-reader": "^2.3.16",
|
||||
"vue-router": "^3.0.6",
|
||||
"vue-toasted": "^1.1.28",
|
||||
"vue2-transitions": "^0.2.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persistedstate": "^4.0.0-beta.3"
|
||||
|
||||
@ -41,6 +41,15 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.gdd-toaster-title {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.gdd-toaster-body {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.gdd-toaster {
|
||||
color: #ffffff;
|
||||
}
|
||||
.btn-primary pim {
|
||||
background-color: #5a7b02;
|
||||
border-color: #5e72e4;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="decayinformation">
|
||||
<span v-if="decaytyp === 'short'">
|
||||
{{ decay ? ' - ' + $n(decay.balance, 'decimal') + ' ' + decayStartBlockTextShort : '' }}
|
||||
{{ decay ? ' − ' + $n(decay.balance, 'decimal') : '' }}
|
||||
</span>
|
||||
|
||||
<div v-if="decaytyp === 'new' || decaytyp === 'decayLastTransaction'">
|
||||
@ -19,14 +19,11 @@
|
||||
<b-col cols="6">
|
||||
<div v-if="decay.decayStartBlock > 0">
|
||||
<div class="display-4">{{ $t('decay.Starting_block_decay') }}</div>
|
||||
<div>
|
||||
{{ $t('decay.decay_introduced') }} :
|
||||
{{ $d($moment.unix(decay.decayStart), 'long') }}
|
||||
</div>
|
||||
<div>{{ $t('decay.decay_introduced') }} :</div>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="decay.decayStart">
|
||||
{{ $d($moment.unix(decay.decayStart), 'long') }}
|
||||
{{ $d(new Date(decay.decayStart * 1000), 'long') }}
|
||||
{{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
@ -80,7 +77,7 @@
|
||||
<div v-if="type === 'receive'">{{ $t('decay.received') }}</div>
|
||||
</b-col>
|
||||
<b-col cols="6">
|
||||
<div v-if="type === 'send'">- {{ $n(balance, 'decimal') }}</div>
|
||||
<div v-if="type === 'send'">− {{ $n(balance, 'decimal') }}</div>
|
||||
<div v-if="type === 'receive'">+ {{ $n(balance, 'decimal') }}</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -90,7 +87,7 @@
|
||||
<div>{{ $t('decay.decay') }}</div>
|
||||
</b-col>
|
||||
<b-col cols="6">
|
||||
<div>- {{ $n(decay.balance, 'decimal') }}</div>
|
||||
<div>− {{ $n(decay.balance, 'decimal') }}</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<!-- Total-->
|
||||
@ -100,13 +97,13 @@
|
||||
</b-col>
|
||||
<b-col cols="6">
|
||||
<div v-if="type === 'send'">
|
||||
<b>- {{ $n(balance + decay.balance, 'decimal') }}</b>
|
||||
<b>− {{ $n(balance + decay.balance, 'decimal') }}</b>
|
||||
</div>
|
||||
<div v-if="type === 'receive'">
|
||||
<b>{{ $n(balance - decay.balance, 'decimal') }}</b>
|
||||
</div>
|
||||
<div v-if="type === 'creation'">
|
||||
<b>- {{ $n(balance - decay.balance, 'decimal') }}</b>
|
||||
<b>− {{ $n(balance - decay.balance, 'decimal') }}</b>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -130,17 +127,8 @@ export default {
|
||||
decaytyp: { type: String, default: '' },
|
||||
},
|
||||
computed: {
|
||||
decayStartBlockTextShort() {
|
||||
return this.decay.decayStartBlock
|
||||
? this.$t('decay.decayStart') + this.$d(this.$moment.unix(this.decay.decayStartBlock))
|
||||
: ''
|
||||
},
|
||||
duration() {
|
||||
return this.$moment.duration(
|
||||
this.$moment
|
||||
.unix(new Date(this.decay.decayEnd))
|
||||
.diff(this.$moment.unix(new Date(this.decay.decayStart))),
|
||||
)._data
|
||||
return this.$moment.duration((this.decay.decayEnd - this.decay.decayStart) * 1000)._data
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="gdd-status">
|
||||
<div class="p-0 gdd-status-div">
|
||||
{{ pending ? '—' : $n(balance, 'decimal') }} {{ statusText }}
|
||||
{{ pending || balance === null ? '—' : $n(balance, 'decimal') }} {{ statusText }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
{{ $t('form.date') }}
|
||||
</b-col>
|
||||
<b-col cols="6">
|
||||
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
{{ $d(new Date(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
|
||||
@ -69,9 +69,9 @@ const dateTimeFormats = {
|
||||
},
|
||||
long: {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'short',
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
@ -84,9 +84,9 @@ const dateTimeFormats = {
|
||||
},
|
||||
long: {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
weekday: 'short',
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
},
|
||||
|
||||
@ -111,6 +111,7 @@
|
||||
"funding": "Zu den Förderbeiträgen",
|
||||
"gdt-received": "GradidoTransform (GDT) erhalten",
|
||||
"no-transactions": "Du hast noch keine GradidoTransform (GDT).",
|
||||
"not-reachable": "Der GDT Server ist nicht erreichbar.",
|
||||
"publisher": "Dein geworbenes Mitglied hat einen Beitrag bezahlt",
|
||||
"raise": "Erhöhung",
|
||||
"recruited-member": "Eingeladenes Mitglied"
|
||||
@ -210,6 +211,7 @@
|
||||
"title": "Danke!"
|
||||
}
|
||||
},
|
||||
"success": "Erfolg",
|
||||
"transaction": {
|
||||
"gdd-text": "Gradido Transaktionen",
|
||||
"gdt-text": "GradidoTransform Transaktionen",
|
||||
|
||||
@ -111,6 +111,7 @@
|
||||
"funding": "To the funding contributions",
|
||||
"gdt-received": "GradidoTransform (GDT) received",
|
||||
"no-transactions": "You do not have GradidoTransform (GDT) yet.",
|
||||
"not-reachable": "The GDT Server is not reachable.",
|
||||
"publisher": "A member you referred has paid a contribution",
|
||||
"raise": "Increase",
|
||||
"recruited-member": "Invited member"
|
||||
@ -210,6 +211,7 @@
|
||||
"title": "Thank you!"
|
||||
}
|
||||
},
|
||||
"success": "Success",
|
||||
"transaction": {
|
||||
"gdd-text": "Gradido Transactions",
|
||||
"gdt-text": "GradidoTransform Transactions",
|
||||
|
||||
@ -3,6 +3,7 @@ import DashboardPlugin from './plugins/dashboard-plugin'
|
||||
import App from './App.vue'
|
||||
import i18n from './i18n.js'
|
||||
import { loadAllRules } from './validation-rules'
|
||||
import { toasters } from './mixins/toaster'
|
||||
|
||||
import 'regenerator-runtime'
|
||||
|
||||
@ -18,15 +19,7 @@ import { apolloProvider } from './plugins/apolloProvider'
|
||||
Vue.use(DashboardPlugin)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.toasted.register(
|
||||
'error',
|
||||
(payload) => {
|
||||
return payload.replace(/^GraphQL error: /, '')
|
||||
},
|
||||
{
|
||||
type: 'error',
|
||||
},
|
||||
)
|
||||
Vue.mixin(toasters)
|
||||
|
||||
loadAllRules(i18n)
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ export const getCommunityInfoMixin = {
|
||||
return result.data.getCommunityInfo
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
29
frontend/src/mixins/toaster.js
Normal file
29
frontend/src/mixins/toaster.js
Normal file
@ -0,0 +1,29 @@
|
||||
export const toasters = {
|
||||
methods: {
|
||||
toastSuccess(message) {
|
||||
this.toast(message, {
|
||||
title: this.$t('success'),
|
||||
variant: 'success',
|
||||
})
|
||||
},
|
||||
toastError(message) {
|
||||
this.toast(message, {
|
||||
title: this.$t('error.error'),
|
||||
variant: 'danger',
|
||||
})
|
||||
},
|
||||
toast(message, options) {
|
||||
message = message.replace(/^GraphQL error: /, '')
|
||||
this.$bvToast.toast(message, {
|
||||
autoHideDelay: 5000,
|
||||
appendToast: false,
|
||||
solid: true,
|
||||
toaster: 'b-toaster-top-right',
|
||||
headerClass: 'gdd-toaster-title',
|
||||
bodyClass: 'gdd-toaster-body',
|
||||
toastClass: 'gdd-toaster',
|
||||
...options,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import GlobalComponents from './globalComponents'
|
||||
import GlobalDirectives from './globalDirectives'
|
||||
|
||||
import Toasted from 'vue-toasted'
|
||||
import PortalVue from 'portal-vue'
|
||||
|
||||
// vue-bootstrap
|
||||
@ -30,16 +29,5 @@ export default {
|
||||
Vue.use(FlatPickr)
|
||||
Vue.use(Loading)
|
||||
Vue.use(VueApollo)
|
||||
Vue.use(Toasted, {
|
||||
position: 'top-center',
|
||||
duration: 5000,
|
||||
fullWidth: true,
|
||||
action: {
|
||||
text: 'x',
|
||||
onClick: (e, toastObject) => {
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,13 +4,10 @@ import Vue from 'vue'
|
||||
import GlobalComponents from './globalComponents'
|
||||
import GlobalDirectives from './globalDirectives'
|
||||
|
||||
import Toasted from 'vue-toasted'
|
||||
|
||||
import './assets/scss/app.scss'
|
||||
|
||||
jest.mock('./globalComponents')
|
||||
jest.mock('./globalDirectives')
|
||||
jest.mock('vue-toasted')
|
||||
|
||||
jest.mock('vue')
|
||||
|
||||
@ -27,21 +24,4 @@ describe('dashboard plugin', () => {
|
||||
it('installs the global directives', () => {
|
||||
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
||||
})
|
||||
|
||||
describe('vue toasted', () => {
|
||||
const toastedAction = vueUseMock.mock.calls[9][1].action.onClick
|
||||
const goAwayMock = jest.fn()
|
||||
const toastObject = {
|
||||
goAway: goAwayMock,
|
||||
}
|
||||
|
||||
it('installs vue toasted', () => {
|
||||
expect(vueUseMock).toBeCalledWith(Toasted, expect.anything())
|
||||
})
|
||||
|
||||
it('onClick calls goAway(0)', () => {
|
||||
toastedAction({}, toastObject)
|
||||
expect(goAwayMock).toBeCalledWith(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,6 +2,8 @@ import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
import DashboardLayoutGdd from './DashboardLayout_gdd'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
jest.setTimeout(30000)
|
||||
@ -16,7 +18,6 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
logout: 'success',
|
||||
},
|
||||
})
|
||||
const toasterMock = jest.fn()
|
||||
|
||||
describe('DashboardLayoutGdd', () => {
|
||||
let wrapper
|
||||
@ -38,11 +39,6 @@ describe('DashboardLayoutGdd', () => {
|
||||
path: '/overview',
|
||||
},
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toasterMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloMock,
|
||||
},
|
||||
@ -219,8 +215,8 @@ describe('DashboardLayoutGdd', () => {
|
||||
expect(wrapper.vm.pending).toBeTruthy()
|
||||
})
|
||||
|
||||
it('calls $toasted.global.error method', () => {
|
||||
expect(toasterMock).toBeCalledWith('Ouch!')
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ export default {
|
||||
const {
|
||||
data: { transactionList },
|
||||
} = result
|
||||
this.GdtBalance = Number(transactionList.gdtSum)
|
||||
this.GdtBalance = transactionList.gdtSum === null ? null : Number(transactionList.gdtSum)
|
||||
this.transactions = transactionList.transactions
|
||||
this.balance = Number(transactionList.decay)
|
||||
this.bookedBalance = Number(transactionList.balance)
|
||||
@ -102,7 +102,7 @@ export default {
|
||||
.catch((error) => {
|
||||
this.pending = true
|
||||
this.transactionCount = -1
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
// what to do when loading balance fails?
|
||||
})
|
||||
},
|
||||
|
||||
@ -3,11 +3,15 @@ import NotFoundPage from './NotFoundPage'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
describe('NotFoundPage', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(NotFoundPage, { localVue })
|
||||
return mount(NotFoundPage, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
@ -18,5 +22,9 @@ describe('NotFoundPage', () => {
|
||||
it('has a svg', () => {
|
||||
expect(wrapper.find('svg').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a back button', () => {
|
||||
expect(wrapper.find('.test-back').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="header py-1 py-lg-1 pt-lg-3">
|
||||
<b-container>
|
||||
<div class="header-body text-center mb-3">
|
||||
<a href="login" to="login">
|
||||
<a href="#!" @click="goback">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 mt-5 mb-5">
|
||||
@ -1185,6 +1185,11 @@
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<b-button class="test-back" variant="light" @click="goback">
|
||||
{{ $t('back') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1213,6 +1218,11 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goback() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@ -164,7 +164,7 @@ describe('GddTransactionList', () => {
|
||||
|
||||
it('has a minus operator', () => {
|
||||
expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
|
||||
'-',
|
||||
'−',
|
||||
)
|
||||
})
|
||||
|
||||
@ -194,7 +194,7 @@ describe('GddTransactionList', () => {
|
||||
|
||||
it('shows the decay calculation', () => {
|
||||
expect(transaction.findAll('div.gdd-transaction-list-item-decay').at(0).text()).toContain(
|
||||
'- 0.5',
|
||||
'− 0.5',
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -326,7 +326,7 @@ describe('GddTransactionList', () => {
|
||||
|
||||
it('shows the decay calculation', () => {
|
||||
expect(transaction.findAll('.gdd-transaction-list-item-decay').at(0).text()).toContain(
|
||||
'- 1.5',
|
||||
'− 1.5',
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -366,7 +366,7 @@ describe('GddTransactionList', () => {
|
||||
|
||||
it('has a minus operator', () => {
|
||||
expect(transaction.findAll('.gdd-transaction-list-item-operator').at(0).text()).toContain(
|
||||
'-',
|
||||
'−',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -86,7 +86,7 @@
|
||||
</b-col>
|
||||
<b-col cols="7">
|
||||
<div class="gdd-transaction-list-item-date">
|
||||
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
{{ $d(new Date(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -183,10 +183,10 @@ import PaginationButtons from '../../../components/PaginationButtons'
|
||||
import DecayInformation from '../../../components/DecayInformation'
|
||||
|
||||
const iconsByType = {
|
||||
send: { icon: 'arrow-left-circle', classes: 'text-danger', operator: '-' },
|
||||
send: { icon: 'arrow-left-circle', classes: 'text-danger', operator: '−' },
|
||||
receive: { icon: 'arrow-right-circle', classes: 'gradido-global-color-accent', operator: '+' },
|
||||
creation: { icon: 'gift', classes: 'gradido-global-color-accent', operator: '+' },
|
||||
decay: { icon: 'droplet-half', classes: 'gradido-global-color-gray', operator: '-' },
|
||||
decay: { icon: 'droplet-half', classes: 'gradido-global-color-gray', operator: '−' },
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@ -2,6 +2,8 @@ import { mount } from '@vue/test-utils'
|
||||
import { GdtEntryType } from '../../../graphql/enums'
|
||||
import GdtTransactionList from './GdtTransactionList'
|
||||
|
||||
import { toastErrorSpy } from '../../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloMock = jest.fn().mockResolvedValue({
|
||||
@ -13,7 +15,6 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const windowScrollToMock = jest.fn()
|
||||
|
||||
window.scrollTo = windowScrollToMock
|
||||
@ -36,11 +37,6 @@ describe('GdtTransactionList ', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
$d: jest.fn((d) => d),
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloMock,
|
||||
},
|
||||
@ -152,7 +148,7 @@ describe('GdtTransactionList ', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Ouch!')
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
{{ $t('gdt.funding') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div v-else-if="typeof transactionGdtCount === 'object'" class="text-center">
|
||||
{{ $t('gdt.not-reachable') }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-for="{ id, amount, date, comment, gdtEntryType, factor, gdt } in transactionsGdt"
|
||||
@ -71,7 +74,7 @@ export default {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@ import { RouterLinkStub, mount } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
import Login from './Login'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
@ -15,7 +17,6 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const mockStoreDispach = jest.fn()
|
||||
const mockStoreCommit = jest.fn()
|
||||
const mockRouterPush = jest.fn()
|
||||
@ -51,11 +52,6 @@ describe('Login', () => {
|
||||
$router: {
|
||||
push: mockRouterPush,
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
@ -96,7 +92,7 @@ describe('Login', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Failed to get communities')
|
||||
expect(toastErrorSpy).toBeCalledWith('Failed to get communities')
|
||||
})
|
||||
})
|
||||
|
||||
@ -249,7 +245,7 @@ describe('Login', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('error.no-account')
|
||||
expect(toastErrorSpy).toBeCalledWith('error.no-account')
|
||||
})
|
||||
|
||||
describe('login fails with "User email not validated"', () => {
|
||||
|
||||
@ -105,7 +105,7 @@ export default {
|
||||
loader.hide()
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(this.$t('error.no-account'))
|
||||
this.toastError(this.$t('error.no-account'))
|
||||
if (error.message.includes('User email not validated')) {
|
||||
this.$router.push('/thx/login')
|
||||
} else if (error.message.includes('User has no password set yet')) {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import Register from './Register'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
@ -16,7 +17,6 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const mockStoreCommit = jest.fn()
|
||||
const registerUserMutationMock = jest.fn()
|
||||
const routerPushMock = jest.fn()
|
||||
@ -48,11 +48,6 @@ describe('Register', () => {
|
||||
publisherId: 12345,
|
||||
},
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -96,7 +91,7 @@ describe('Register', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Failed to get communities')
|
||||
expect(toastErrorSpy).toBeCalledWith('Failed to get communities')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import RegisterCommunity from './RegisterCommunity'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
@ -13,7 +15,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
},
|
||||
},
|
||||
})
|
||||
const toastErrorMock = jest.fn()
|
||||
|
||||
const mockStoreCommit = jest.fn()
|
||||
|
||||
describe('RegisterCommunity', () => {
|
||||
@ -36,11 +38,6 @@ describe('RegisterCommunity', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -78,7 +75,7 @@ describe('RegisterCommunity', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Failed to get communities')
|
||||
expect(toastErrorSpy).toBeCalledWith('Failed to get communities')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import { communities, communityInfo } from '../../graphql/queries'
|
||||
import RegisterSelectCommunity from './RegisterSelectCommunity'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const spinnerHideMock = jest.fn()
|
||||
@ -52,7 +54,6 @@ const apolloQueryMock = jest
|
||||
},
|
||||
})
|
||||
|
||||
const toasterMock = jest.fn()
|
||||
const mockStoreCommit = jest.fn()
|
||||
|
||||
describe('RegisterSelectCommunity', () => {
|
||||
@ -78,11 +79,6 @@ describe('RegisterSelectCommunity', () => {
|
||||
$loading: {
|
||||
show: spinnerMock,
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toasterMock,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -129,7 +125,7 @@ describe('RegisterSelectCommunity', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toasterMock).toBeCalledWith('Failed to get communities')
|
||||
expect(toastErrorSpy).toBeCalledWith('Failed to get communities')
|
||||
})
|
||||
})
|
||||
|
||||
@ -208,7 +204,7 @@ describe('RegisterSelectCommunity', () => {
|
||||
})
|
||||
|
||||
it('toast an error', () => {
|
||||
expect(toasterMock).toBeCalledWith('Wrong thing')
|
||||
expect(toastErrorSpy).toBeCalledWith('Wrong thing')
|
||||
})
|
||||
|
||||
it('hides the spinner', () => {
|
||||
|
||||
@ -76,7 +76,7 @@ export default {
|
||||
)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
loader.hide()
|
||||
this.pending = false
|
||||
|
||||
@ -2,13 +2,14 @@ import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import ResetPassword from './ResetPassword'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
// validation is tested in src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloMutationMock = jest.fn()
|
||||
|
||||
const toasterMock = jest.fn()
|
||||
const routerPushMock = jest.fn()
|
||||
|
||||
const stubs = {
|
||||
@ -29,11 +30,6 @@ const mocks = {
|
||||
includes: jest.fn((t) => t === mocks.$route.path.mock),
|
||||
},
|
||||
},
|
||||
$toasted: {
|
||||
global: {
|
||||
error: toasterMock,
|
||||
},
|
||||
},
|
||||
$router: {
|
||||
push: routerPushMock,
|
||||
},
|
||||
@ -65,7 +61,7 @@ describe('ResetPassword', () => {
|
||||
})
|
||||
|
||||
it.skip('toasts an error when no valid optin is given', () => {
|
||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith('error')
|
||||
})
|
||||
|
||||
it.skip('has a message suggesting to contact the support', () => {
|
||||
@ -157,7 +153,7 @@ describe('ResetPassword', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toasterMock).toHaveBeenCalledWith('...Code is older than 10 minutes')
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith('...Code is older than 10 minutes')
|
||||
})
|
||||
|
||||
it('router pushes to /password/reset', () => {
|
||||
@ -174,7 +170,7 @@ describe('ResetPassword', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toasterMock).toHaveBeenCalledWith('Error')
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith('Error')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ export default {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
if (error.message.includes('Code is older than 10 minutes'))
|
||||
this.$router.push('/password/reset')
|
||||
})
|
||||
|
||||
@ -2,12 +2,12 @@ import { mount } from '@vue/test-utils'
|
||||
import UserCardCoinAnimation from './UserCard_CoinAnimation'
|
||||
import { updateUserInfos } from '../../../graphql/mutations'
|
||||
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserCard_CoinAnimation', () => {
|
||||
@ -22,12 +22,6 @@ describe('UserCard_CoinAnimation', () => {
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
@ -78,7 +72,7 @@ describe('UserCard_CoinAnimation', () => {
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('settings.coinanimation.True')
|
||||
expect(toastSuccessSpy).toBeCalledWith('settings.coinanimation.True')
|
||||
})
|
||||
})
|
||||
|
||||
@ -109,7 +103,7 @@ describe('UserCard_CoinAnimation', () => {
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('settings.coinanimation.False')
|
||||
expect(toastSuccessSpy).toBeCalledWith('settings.coinanimation.False')
|
||||
})
|
||||
})
|
||||
|
||||
@ -126,7 +120,7 @@ describe('UserCard_CoinAnimation', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Ouch')
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouch')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -50,7 +50,7 @@ export default {
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.commit('coinanimation', this.CoinAnimationStatus)
|
||||
this.$toasted.success(
|
||||
this.toastSuccess(
|
||||
this.CoinAnimationStatus
|
||||
? this.$t('settings.coinanimation.True')
|
||||
: this.$t('settings.coinanimation.False'),
|
||||
@ -58,7 +58,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
this.CoinAnimationStatus = this.$store.state.coinanimation
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,12 +2,12 @@ import { mount } from '@vue/test-utils'
|
||||
import UserCardFormUserData from './UserCard_FormUserData'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserCard_FormUserData', () => {
|
||||
@ -22,12 +22,6 @@ describe('UserCard_FormUserData', () => {
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
@ -126,7 +120,7 @@ describe('UserCard_FormUserData', () => {
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('settings.name.change-success')
|
||||
expect(toastSuccessSpy).toBeCalledWith('settings.name.change-success')
|
||||
})
|
||||
|
||||
it('has an edit button again', () => {
|
||||
@ -159,7 +153,7 @@ describe('UserCard_FormUserData', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Error')
|
||||
expect(toastErrorSpy).toBeCalledWith('Error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -108,10 +108,10 @@ export default {
|
||||
this.$store.commit('firstName', this.form.firstName)
|
||||
this.$store.commit('lastName', this.form.lastName)
|
||||
this.showUserData = true
|
||||
this.$toasted.success(this.$t('settings.name.change-success'))
|
||||
this.toastSuccess(this.$t('settings.name.change-success'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,25 +2,18 @@ import { mount } from '@vue/test-utils'
|
||||
import UserCardFormPasswort from './UserCard_FormUserPasswort'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const changePasswordProfileMock = jest.fn()
|
||||
changePasswordProfileMock.mockReturnValue({ success: true })
|
||||
|
||||
const toastSuccessMock = jest.fn()
|
||||
const toastErrorMock = jest.fn()
|
||||
|
||||
describe('UserCard_FormUserPasswort', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
mutate: changePasswordProfileMock,
|
||||
},
|
||||
@ -196,7 +189,7 @@ describe('UserCard_FormUserPasswort', () => {
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('site.thx.reset')
|
||||
expect(toastSuccessSpy).toBeCalledWith('site.thx.reset')
|
||||
})
|
||||
|
||||
it('cancels the edit process', () => {
|
||||
@ -217,7 +210,7 @@ describe('UserCard_FormUserPasswort', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('error')
|
||||
expect(toastErrorSpy).toBeCalledWith('error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -89,11 +89,11 @@ export default {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$toasted.success(this.$t('site.thx.reset'))
|
||||
this.toastSuccess(this.$t('site.thx.reset'))
|
||||
this.cancelEdit()
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.global.error(error.message)
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@ -3,6 +3,8 @@ import UserCardFormUsername from './UserCard_FormUsername'
|
||||
import flushPromises from 'flush-promises'
|
||||
import { extend } from 'vee-validate'
|
||||
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../../test/testSetup'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
@ -14,8 +16,6 @@ extend('gddUsernameUnique', {
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserCard_FormUsername', () => {
|
||||
@ -29,12 +29,6 @@ describe('UserCard_FormUsername', () => {
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
global: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
},
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
@ -125,7 +119,7 @@ describe('UserCard_FormUsername', () => {
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('settings.name.change-success')
|
||||
expect(toastSuccessSpy).toBeCalledWith('settings.name.change-success')
|
||||
})
|
||||
|
||||
it('has no edit button anymore', () => {
|
||||
@ -155,7 +149,7 @@ describe('UserCard_FormUsername', () => {
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Ouch!')
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||
})
|
||||
|
||||
it('renders an empty username', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user