diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 999863dd9..b7000100e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -528,7 +528,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 65 + min_coverage: 68 token: ${{ github.token }} ########################################################################## diff --git a/.gitignore b/.gitignore index f771e49f4..1a111760f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +.dbeaver +.project *.log +*.bak /node_modules/* messages.pot nbproject @@ -11,4 +14,9 @@ package-lock.json /deployment/bare_metal/nginx/update-page/updating.html /deployment/bare_metal/log /deployment/bare_metal/backup -/.nvmrc + +# Node Version Manager configuration file +.nvmrc + +# Apple macOS folder attribute file +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8e748af..49fdfd07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,103 @@ 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.10.1](https://github.com/gradido/gradido/compare/1.10.0...1.10.1) + +- automatic session logout with info modal [`#2001`](https://github.com/gradido/gradido/pull/2001) +- 1910 separate text for the slideshow images. [`#1998`](https://github.com/gradido/gradido/pull/1998) +- Origin/1921 additional parameter checks for createContributionLinks [`#1996`](https://github.com/gradido/gradido/pull/1996) +- add missing locales [`#1999`](https://github.com/gradido/gradido/pull/1999) +- 1906 feature concept for gdd creation per linkqr code [`#1907`](https://github.com/gradido/gradido/pull/1907) +- refactor: 🍰 Not Throwing An Error When Register With Existing Email [`#1962`](https://github.com/gradido/gradido/pull/1962) +- feat: 🍰 Set Role In Admin Interface [`#1974`](https://github.com/gradido/gradido/pull/1974) +- refactor mobile style step 1 [`#1977`](https://github.com/gradido/gradido/pull/1977) +- changed mobil stage picture [`#1995`](https://github.com/gradido/gradido/pull/1995) + +#### [1.10.0](https://github.com/gradido/gradido/compare/1.9.0...1.10.0) + +> 17 June 2022 + +- release: v1.10.0 [`#1993`](https://github.com/gradido/gradido/pull/1993) +- frontend redeem contribution link [`#1988`](https://github.com/gradido/gradido/pull/1988) +- change new start picture [`#1990`](https://github.com/gradido/gradido/pull/1990) +- feat: Redeem Contribution Link [`#1987`](https://github.com/gradido/gradido/pull/1987) +- fix: Max Amount on Slider for Edit Contribution [`#1986`](https://github.com/gradido/gradido/pull/1986) +- CRUD contribution link admin interface [`#1981`](https://github.com/gradido/gradido/pull/1981) +- fix: `.env` log level for apollo and backend category [`#1967`](https://github.com/gradido/gradido/pull/1967) +- refactor: Admin Pending Creations Table to Contributions Table [`#1949`](https://github.com/gradido/gradido/pull/1949) +- devops: Update Browser List for Unit Tests as Recomended [`#1984`](https://github.com/gradido/gradido/pull/1984) +- feat: CRUD for Contribution Links in Admin Resolver [`#1979`](https://github.com/gradido/gradido/pull/1979) +- 1920 feature create contribution link table [`#1957`](https://github.com/gradido/gradido/pull/1957) +- refactor: 🍰 Delete `user_setting` Table From DB [`#1960`](https://github.com/gradido/gradido/pull/1960) +- locales link german, english navbar [`#1969`](https://github.com/gradido/gradido/pull/1969) + +#### [1.9.0](https://github.com/gradido/gradido/compare/1.8.3...1.9.0) + +> 2 June 2022 + +- devops: Release Version 1.9.0 [`#1968`](https://github.com/gradido/gradido/pull/1968) +- refactor: 🍰 Refactor To `filters` Object And Rename Filters Properties [`#1914`](https://github.com/gradido/gradido/pull/1914) +- refactor register button position [`#1964`](https://github.com/gradido/gradido/pull/1964) +- fixed redeem link is mobile start false [`#1958`](https://github.com/gradido/gradido/pull/1958) +- 1951 remove back link and remove gray box [`#1959`](https://github.com/gradido/gradido/pull/1959) +- 1952 change footer icons color an remove save login [`#1955`](https://github.com/gradido/gradido/pull/1955) +- fix: License should be a valid SPDX license expression [`#1954`](https://github.com/gradido/gradido/pull/1954) +- refactor: 🍰 Refactor THX Page – 2. Step [`#1858`](https://github.com/gradido/gradido/pull/1858) +- fix: Add Timezone to Decay Start Block [`#1931`](https://github.com/gradido/gradido/pull/1931) +- devops: Update License in all package.json [`#1925`](https://github.com/gradido/gradido/pull/1925) +- docu: Creation Flowchart [`#1918`](https://github.com/gradido/gradido/pull/1918) +- refactor: Use Logger Categories [`#1912`](https://github.com/gradido/gradido/pull/1912) +- 1883 remove the animated coins in the profile settings [`#1946`](https://github.com/gradido/gradido/pull/1946) +- 1942 replace pictures for carousel [`#1943`](https://github.com/gradido/gradido/pull/1943) +- 1933 auth footer is not on one level [`#1941`](https://github.com/gradido/gradido/pull/1941) +- 1929 styling new template for password component [`#1935`](https://github.com/gradido/gradido/pull/1935) +- 1926 button concept for gradido template [`#1927`](https://github.com/gradido/gradido/pull/1927) +- 1916 remove select language from register form [`#1930`](https://github.com/gradido/gradido/pull/1930) +- rename files from auth folder, rule vue name = name files [`#1937`](https://github.com/gradido/gradido/pull/1937) +- Add files Bild_1_2400.jpg [`#1945`](https://github.com/gradido/gradido/pull/1945) +- Bilder für Slider [`#1940`](https://github.com/gradido/gradido/pull/1940) +- contribution analysis of elopage and concept proposal [`#1917`](https://github.com/gradido/gradido/pull/1917) +- 1676 feature federation technical concept [`#1711`](https://github.com/gradido/gradido/pull/1711) +- more details about Windows installation [`#1842`](https://github.com/gradido/gradido/pull/1842) +- Concept to Introduce Gradido ID [`#1797`](https://github.com/gradido/gradido/pull/1797) +- first draft of concept event protocol [`#1796`](https://github.com/gradido/gradido/pull/1796) +- 1682 new design for the login and registration area [`#1693`](https://github.com/gradido/gradido/pull/1693) +- fix: Database Connection Charset to utf8mb4_unicode_ci [`#1915`](https://github.com/gradido/gradido/pull/1915) +- refactor: 🍰 Create Filter Object in GQL And Rename Args [`#1860`](https://github.com/gradido/gradido/pull/1860) +- feat: 🍰 Improve Apollo Logging [`#1859`](https://github.com/gradido/gradido/pull/1859) +- Add files via upload [`#1903`](https://github.com/gradido/gradido/pull/1903) +- 🍰 Hide Pagenation On Short Transactionlist [`#1875`](https://github.com/gradido/gradido/pull/1875) +- 🍰 Ignore macOS .DS_Store Files [`#1902`](https://github.com/gradido/gradido/pull/1902) +- pre I from #1682, add images, svg for new styling [`#1900`](https://github.com/gradido/gradido/pull/1900) +- add browserstack logo image [`#1888`](https://github.com/gradido/gradido/pull/1888) + +#### [1.8.3](https://github.com/gradido/gradido/compare/1.8.2...1.8.3) + +> 13 May 2022 + +- Release 1.8.3 [`#1899`](https://github.com/gradido/gradido/pull/1899) +- Checkbox [`#1894`](https://github.com/gradido/gradido/pull/1894) +- fix: Count Deprecated Links as Well [`#1892`](https://github.com/gradido/gradido/pull/1892) + +#### [1.8.2](https://github.com/gradido/gradido/compare/1.8.1...1.8.2) + +> 12 May 2022 + +- Release 1.8.2 [`#1890`](https://github.com/gradido/gradido/pull/1890) +- Update README.md [`#1878`](https://github.com/gradido/gradido/pull/1878) +- fix: Unique Previous Column in Transactions Table [`#1879`](https://github.com/gradido/gradido/pull/1879) +- fix: Up and Down Migrations for Older SQL Versions [`#1861`](https://github.com/gradido/gradido/pull/1861) +- 🍰 Refactor THX Page – 1. Step [`#1856`](https://github.com/gradido/gradido/pull/1856) +- Create LICENSE [`#1803`](https://github.com/gradido/gradido/pull/1803) +- docu: Update Deployment Documentation [`#1864`](https://github.com/gradido/gradido/pull/1864) +- fix: Loading Transaction Links after Reopening Link List [`#1863`](https://github.com/gradido/gradido/pull/1863) +- 🍰 Add NVM Config Files To '.gitignore' [`#1846`](https://github.com/gradido/gradido/pull/1846) + #### [1.8.1](https://github.com/gradido/gradido/compare/1.8.0...1.8.1) +> 28 April 2022 + +- v1.8.1 [`#1855`](https://github.com/gradido/gradido/pull/1855) - 1851 integrate and test the behaviour of clipboard polyfill [`#1853`](https://github.com/gradido/gradido/pull/1853) - fix: Deprecated Warning from Faker on Seeding [`#1854`](https://github.com/gradido/gradido/pull/1854) - feat: Test Admin Resolver [`#1848`](https://github.com/gradido/gradido/pull/1848) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e97055a78..3d086018e 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,26 @@ The Corona crisis has fundamentally changed our world within a very short time. The dominant financial system threatens to fail around the globe, followed by mass insolvencies, record unemployment and abject poverty. Only with a sustainable new monetary system can humanity master these challenges of the 21st century. The Gradido Academy for Bionic Economy has developed such a system. Find out more about the Project on its [Website](https://gradido.net/). It is offering vast resources about the idea. The remaining document will discuss the gradido software only. + ## Software requirements Currently we only support `docker` install instructions to run all services, since many different programming languages and frameworks are used. -- [docker](https://www.docker.com/) +- [docker](https://www.docker.com/) - [docker-compose] +- [yarn](https://phoenixnap.com/kb/yarn-windows) ### For Arch Linux + Install the required packages: + ```bash sudo pacman -S docker sudo pacman -S docker-compose ``` -Add group `docker` and then your user to it in order to allow you to run docker without sudo +Add group `docker` and then your user to it in order to allow you to run docker without sudo + ```bash sudo groupadd docker # may already exist `groupadd: group 'docker' already exists` sudo usermod -aG docker $USER @@ -31,26 +36,58 @@ groups # verify you have the group (requires relog) ``` Start the docker service: + ```bash sudo systemctrl start docker ``` +### For Windows + +#### docker + +The installation of dockers depends on your selected product package from the [dockers page](https://www.docker.com/). For windows the product *docker desktop* will be the choice. Please follow the installation instruction of your selected product. + +##### known problems + +* In case the docker desktop will not start correctly because of previous docker installations, then please clean the used directories of previous docker installation - `C:\Users` - before you retry starting docker desktop. For further problems executing docker desktop please take a look in this description "[logs and trouble shooting](https://docs.docker.com/desktop/windows/troubleshoot/)" +* In case your docker desktop installation causes high memory consumption per vmmem process, then please take a look at this description "[vmmen process consuming too much memory (Docker Desktop)](https://dev.to/tallesl/vmmen-process-consuming-too-much-memory-docker-desktop-273p)" + +#### yarn + +For the Gradido build process the yarn package manager will be used. Please download and install [yarn for windows](https://phoenixnap.com/kb/yarn-windows) by following the instructions there. + ## How to run? +As soon as the software requirements are fulfilled and a docker installation is up and running then open a powershell on Windows or an other commandline prompt on Linux. + +Create and navigate to the directory, where you want to create the Gradido runtime environment. + +``` +mkdir \Gradido +cd \Gradido +``` + ### 1. Clone Sources + Clone the repo and pull all submodules + ```bash git clone git@github.com:gradido/gradido.git git submodule update --recursive --init ``` ### 2. Run docker-compose -Run docker-compose to bring up the development environment + +Run docker-compose to bring up the development environment + ```bash docker-compose up ``` + ### Additional Build options + If you want to build for production you can do this aswell: + ```bash docker-compose -f docker-compose.yml up ``` @@ -73,6 +110,7 @@ A release is tagged on Github by its version number and published as github rele Each release is accompanied with release notes automatically generated from the git log which is available as [CHANGELOG.md](./CHANGELOG.md). To generate the Changelog and set a new Version you should use the following commands in the main folder + ```bash git fetch --all yarn release @@ -83,13 +121,38 @@ After generating a new version you should commit the changes. This will be the C Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command. +## How the different .env work on deploy + +Each component (frontend, admin, backend and database) has its own `.env` file. When running in development with docker and nginx you usually do not have to care about the `.env`. The defaults are set by the respective config file, found in the `src/config/` folder of each component. But if you have a local `.env`, the defaults set in the config are overwritten by the `.env`. If you do not use docker, you need the `.env` in the frontend and admin interface because nginx is not running in order to find the backend. + +Each component has a `.env.dist` file. This file contains all environment variables used by the component and can be used as pattern. If you want to use a local `.env`, copy the `.env.dist` and adjust the variables accordingly. + +Each component has a `.env.template` file. These files are very important on deploy. + +There is one `.env.dist` in the `deployment/bare_metal/` folder. This `.env.dist` contains all variables used by the components, e.g. unites all `.env.dist` from the components. On deploy, we copy this `.env.dist` to `.env` and set all variables in this new file. The deploy script loads this variables and provides them by the `.env.templates` of each component, creating an `.env` for each component (see in `deployment/bare_metal/start.sh` the `envsubst`). + +To avoid forgetting to update an existing `.env` in the `deployment/bare_metal/` folder when deploying, we have an environment version variable inside the codebase of each component. You should update this version, when environment variables must be changed or added on deploy. The code checks, that the environement version provided by the `.env` is the one expected by the codebase. + + ## Troubleshooting -| Problem | Issue | Solution | Description | -| ------- | ----- | -------- | ----------- | +| Problem | Issue | Solution | Description | +| ------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | docker-compose raises database connection errors | [#1062](https://github.com/gradido/gradido/issues/1062) | End `ctrl+c` and restart the `docker-compose up` after a successful build | Several Database connection related errors occur in the docker-compose log. | -| Wallet page is empty | [#1063](https://github.com/gradido/gradido/issues/1063) | Accept Cookies and Local Storage in your Browser | The page stays empty when navigating to [http://localhost/](http://localhost/) | +| Wallet page is empty | [#1063](https://github.com/gradido/gradido/issues/1063) | Accept Cookies and Local Storage in your Browser | The page stays empty when navigating to[http://localhost/](http://localhost/) | ## Useful Links - [Gradido.net](https://gradido.net/) + + +## Attributions + +![browserstack_logo-freelogovectors net_](https://user-images.githubusercontent.com/1324583/167782608-0e4db0d4-3d34-45fb-ab06-344aa5e5ef4b.png) + +Browser compatibility testing with [BrowserStack](https://www.browserstack.com/). + + +## License +See the [LICENSE](LICENSE.md) file for license rights and limitations (Apache-2.0 license). + diff --git a/admin/.gitignore b/admin/.gitignore index d78b066c1..a67d270bc 100644 --- a/admin/.gitignore +++ b/admin/.gitignore @@ -10,4 +10,3 @@ coverage/ # emacs *~ -/.nvmrc diff --git a/admin/.prettierrc.js b/admin/.prettierrc.js index e88113754..bc1d767d7 100644 --- a/admin/.prettierrc.js +++ b/admin/.prettierrc.js @@ -4,5 +4,6 @@ module.exports = { singleQuote: true, trailingComma: "all", tabWidth: 2, - bracketSpacing: true + bracketSpacing: true, + endOfLine: "auto", }; diff --git a/admin/jest.config.js b/admin/jest.config.js index b7226bd8f..9b9842bad 100644 --- a/admin/jest.config.js +++ b/admin/jest.config.js @@ -22,7 +22,7 @@ module.exports = { '^.+\\.(js|jsx)?$': 'babel-jest', '/node_modules/vee-validate/dist/rules': 'babel-jest', }, - setupFiles: ['/test/testSetup.js'], + setupFiles: ['/test/testSetup.js', 'jest-canvas-mock'], testMatch: ['**/?(*.)+(spec|test).js?(x)'], // snapshotSerializers: ['jest-serializer-vue'], transformIgnorePatterns: ['/node_modules/(?!vee-validate/dist/rules)'], diff --git a/admin/package.json b/admin/package.json index 1ff6de770..50145d44a 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,8 +3,8 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.8.1", - "license": "MIT", + "version": "1.10.1", + "license": "Apache-2.0", "private": false, "scripts": { "start": "node run/server.js", @@ -38,7 +38,9 @@ "graphql": "^15.6.1", "identity-obj-proxy": "^3.0.0", "jest": "26.6.3", + "jest-canvas-mock": "^2.3.1", "portal-vue": "^2.1.7", + "qrcanvas-vue": "2.1.1", "regenerator-runtime": "^0.13.9", "stats-webpack-plugin": "^0.7.0", "vue": "^2.6.11", diff --git a/admin/public/img/gdd-coin.png b/admin/public/img/gdd-coin.png new file mode 100644 index 000000000..32cb8b2b2 Binary files /dev/null and b/admin/public/img/gdd-coin.png differ diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js new file mode 100644 index 000000000..8bcd4d8e5 --- /dev/null +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -0,0 +1,254 @@ +import { mount } from '@vue/test-utils' +import ChangeUserRoleFormular from './ChangeUserRoleFormular.vue' +import { setUserRole } from '../graphql/setUserRole' +import { toastSuccessSpy, toastErrorSpy } from '../../test/testSetup' + +const localVue = global.localVue + +const apolloMutateMock = jest.fn().mockResolvedValue({ + data: { + setUserRole: null, + }, +}) + +const mocks = { + $t: jest.fn((t) => t), + $apollo: { + mutate: apolloMutateMock, + }, + $store: { + state: { + moderator: { + id: 0, + name: 'test moderator', + }, + }, + }, +} + +let propsData +let wrapper + +describe('ChangeUserRoleFormular', () => { + const Wrapper = () => { + return mount(ChangeUserRoleFormular, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('DOM has', () => { + beforeEach(() => { + propsData = { + item: { + userId: 1, + isAdmin: null, + }, + } + wrapper = Wrapper() + }) + + it('has a DIV element with the class.delete-user-formular', () => { + expect(wrapper.find('.change-user-role-formular').exists()).toBe(true) + }) + }) + + describe('change own role', () => { + beforeEach(() => { + propsData = { + item: { + userId: 0, + isAdmin: null, + }, + } + wrapper = Wrapper() + }) + + it('has the text that you cannot change own role', () => { + expect(wrapper.text()).toContain('userRole.notChangeYourSelf') + }) + + it('has role select disabled', () => { + expect(wrapper.find('select[disabled="disabled"]').exists()).toBe(true) + }) + }) + + describe('change others role', () => { + let rolesToSelect + + describe('general', () => { + beforeEach(() => { + propsData = { + item: { + userId: 1, + isAdmin: null, + }, + } + wrapper = Wrapper() + rolesToSelect = wrapper.find('select.role-select').findAll('option') + }) + + it('has no text that you cannot change own role', () => { + expect(wrapper.text()).not.toContain('userRole.notChangeYourSelf') + }) + + it('has the select label', () => { + expect(wrapper.text()).toContain('userRole.selectLabel') + }) + + it('has a select', () => { + expect(wrapper.find('select.role-select').exists()).toBe(true) + }) + + it('has role select enabled', () => { + expect(wrapper.find('select.role-select[disabled="disabled"]').exists()).toBe(false) + }) + + describe('on API error', () => { + beforeEach(() => { + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + rolesToSelect.at(1).setSelected() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + + describe('user is usual user', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setUserRole: new Date(), + }, + }) + propsData = { + item: { + userId: 1, + isAdmin: null, + }, + } + wrapper = Wrapper() + rolesToSelect = wrapper.find('select.role-select').findAll('option') + }) + + it('has selected option set to "usual user"', () => { + expect(wrapper.find('select.role-select').element.value).toBe('user') + }) + + describe('change select to', () => { + describe('same role', () => { + it('does not call the API', () => { + rolesToSelect.at(0).setSelected() + expect(apolloMutateMock).not.toHaveBeenCalled() + }) + }) + + describe('new role', () => { + beforeEach(() => { + rolesToSelect.at(1).setSelected() + }) + + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + isAdmin: true, + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + isAdmin: expect.any(Date), + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + }) + }) + + describe('user is admin', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setUserRole: null, + }, + }) + propsData = { + item: { + userId: 1, + isAdmin: new Date(), + }, + } + wrapper = Wrapper() + rolesToSelect = wrapper.find('select.role-select').findAll('option') + }) + + it('has selected option set to "admin"', () => { + expect(wrapper.find('select.role-select').element.value).toBe('admin') + }) + + describe('change select to', () => { + describe('same role', () => { + it('does not call the API', () => { + rolesToSelect.at(1).setSelected() + expect(apolloMutateMock).not.toHaveBeenCalled() + }) + }) + + describe('new role', () => { + beforeEach(() => { + rolesToSelect.at(0).setSelected() + }) + + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + isAdmin: false, + }, + }), + ) + }) + + it('emits "updateIsAdmin"', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + isAdmin: null, + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + }) + }) + }) + }) +}) diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue new file mode 100644 index 000000000..1217ce7f0 --- /dev/null +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/admin/src/components/ContributionLink.spec.js b/admin/src/components/ContributionLink.spec.js new file mode 100644 index 000000000..f1b9cfb97 --- /dev/null +++ b/admin/src/components/ContributionLink.spec.js @@ -0,0 +1,49 @@ +import { mount } from '@vue/test-utils' +import ContributionLink from './ContributionLink.vue' + +const localVue = global.localVue + +const mocks = { + $t: jest.fn((t) => t), +} + +const propsData = { + items: [ + { + id: 1, + name: 'Meditation', + memo: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l', + amount: '200', + validFrom: '2022-04-01', + validTo: '2022-08-01', + cycle: 'täglich', + maxPerCycle: '3', + maxAmountPerMonth: 0, + link: 'https://localhost/redeem/CL-1a2345678', + }, + ], + count: 1, +} + +describe('ContributionLink', () => { + let wrapper + + const Wrapper = () => { + return mount(ContributionLink, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the Div Element ".contribution-link"', () => { + expect(wrapper.find('div.contribution-link').exists()).toBe(true) + }) + + it('emits toggle::collapse new Contribution', async () => { + wrapper.vm.editContributionLinkData() + expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy() + }) + }) +}) diff --git a/admin/src/components/ContributionLink.vue b/admin/src/components/ContributionLink.vue new file mode 100644 index 000000000..893e202f4 --- /dev/null +++ b/admin/src/components/ContributionLink.vue @@ -0,0 +1,66 @@ + + diff --git a/admin/src/components/ContributionLinkForm.spec.js b/admin/src/components/ContributionLinkForm.spec.js new file mode 100644 index 000000000..9c7c33c52 --- /dev/null +++ b/admin/src/components/ContributionLinkForm.spec.js @@ -0,0 +1,102 @@ +import { mount } from '@vue/test-utils' +import ContributionLinkForm from './ContributionLinkForm.vue' + +const localVue = global.localVue + +global.alert = jest.fn() + +const propsData = { + contributionLinkData: {}, +} + +const mocks = { + $t: jest.fn((t) => t), +} + +// const mockAPIcall = jest.fn() + +describe('ContributionLinkForm', () => { + let wrapper + + const Wrapper = () => { + return mount(ContributionLinkForm, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the Div Element ".contribution-link-form"', () => { + expect(wrapper.find('div.contribution-link-form').exists()).toBe(true) + }) + + describe('call onReset', () => { + it('form has the set data', () => { + beforeEach(() => { + wrapper.setData({ + form: { + name: 'name', + memo: 'memo', + amount: 100, + validFrom: 'validFrom', + validTo: 'validTo', + cycle: 'ONCE', + maxPerCycle: 1, + maxAmountPerMonth: 100, + }, + }) + wrapper.vm.onReset() + }) + expect(wrapper.vm.form).toEqual({ + amount: null, + cycle: 'ONCE', + validTo: null, + maxAmountPerMonth: '0', + memo: null, + name: null, + maxPerCycle: 1, + validFrom: null, + }) + }) + }) + + describe('call onSubmit', () => { + it('response with the contribution link url', () => { + wrapper.vm.onSubmit() + }) + }) + + // describe('successfull submit', () => { + // beforeEach(async () => { + // mockAPIcall.mockResolvedValue({ + // data: { + // createContributionLink: { + // link: 'https://localhost/redeem/CL-1a2345678', + // }, + // }, + // }) + // await wrapper.find('input.test-validFrom').setValue('2022-6-18') + // await wrapper.find('input.test-validTo').setValue('2022-7-18') + // await wrapper.find('input.test-name').setValue('test name') + // await wrapper.find('input.test-memo').setValue('test memo') + // await wrapper.find('input.test-amount').setValue('100') + // await wrapper.find('form').trigger('submit') + // }) + + // it('calls the API', () => { + // expect(mockAPIcall).toHaveBeenCalledWith( + // expect.objectContaining({ + // variables: { + // link: 'https://localhost/redeem/CL-1a2345678', + // }, + // }), + // ) + // }) + + // it('displays the new username', () => { + // expect(wrapper.find('div.display-username').text()).toEqual('@username') + // }) + // }) + }) +}) diff --git a/admin/src/components/ContributionLinkForm.vue b/admin/src/components/ContributionLinkForm.vue new file mode 100644 index 000000000..a159d33d3 --- /dev/null +++ b/admin/src/components/ContributionLinkForm.vue @@ -0,0 +1,218 @@ + + diff --git a/admin/src/components/ContributionLinkList.spec.js b/admin/src/components/ContributionLinkList.spec.js new file mode 100644 index 000000000..0b9d131bd --- /dev/null +++ b/admin/src/components/ContributionLinkList.spec.js @@ -0,0 +1,147 @@ +import { mount } from '@vue/test-utils' +import ContributionLinkList from './ContributionLinkList.vue' +import { toastSuccessSpy, toastErrorSpy } from '../../test/testSetup' +// import { deleteContributionLink } from '../graphql/deleteContributionLink' + +const localVue = global.localVue + +const mockAPIcall = jest.fn() + +const mocks = { + $t: jest.fn((t) => t), + $apollo: { + mutate: mockAPIcall, + }, +} + +const propsData = { + items: [ + { + id: 1, + name: 'Meditation', + memo: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l', + amount: '200', + validFrom: '2022-04-01', + validTo: '2022-08-01', + cycle: 'täglich', + maxPerCycle: '3', + maxAmountPerMonth: 0, + link: 'https://localhost/redeem/CL-1a2345678', + }, + ], +} + +describe('ContributionLinkList', () => { + let wrapper + + const Wrapper = () => { + return mount(ContributionLinkList, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the Div Element ".contribution-link-list"', () => { + expect(wrapper.find('div.contribution-link-list').exists()).toBe(true) + }) + + it('renders table with contribution link', () => { + expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain( + 'Meditation', + ) + }) + + describe('edit contribution link', () => { + beforeEach(() => { + wrapper.vm.editContributionLink() + }) + + it('emits editContributionLinkData', async () => { + expect(wrapper.vm.$emit('editContributionLinkData')).toBeTruthy() + }) + }) + + describe('delete contribution link', () => { + let spy + + beforeEach(async () => { + jest.clearAllMocks() + wrapper.vm.deleteContributionLink() + }) + + describe('with success', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve('some value')) + mockAPIcall.mockResolvedValue() + await wrapper.find('.test-delete-link').trigger('click') + }) + + it('opens the modal ', () => { + expect(spy).toBeCalled() + }) + + it.skip('calls the API', () => { + // expect(mockAPIcall).toBeCalledWith( + // expect.objectContaining({ + // mutation: deleteContributionLink, + // variables: { + // id: 1, + // }, + // }), + // ) + }) + + it('toasts a success message', () => { + expect(toastSuccessSpy).toBeCalledWith('TODO: request message deleted ') + }) + }) + + describe('with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve('some value')) + mockAPIcall.mockRejectedValue({ message: 'Something went wrong :(' }) + await wrapper.find('.test-delete-link').trigger('click') + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Something went wrong :(') + }) + }) + + describe('cancel delete', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(false)) + mockAPIcall.mockResolvedValue() + await wrapper.find('.test-delete-link').trigger('click') + }) + + it('does not call the API', () => { + expect(mockAPIcall).not.toBeCalled() + }) + }) + }) + + describe('onClick showButton', () => { + it('modelData contains contribution link', () => { + wrapper.find('button.test-show').trigger('click') + expect(wrapper.vm.modalData).toEqual({ + amount: '200', + cycle: 'täglich', + id: 1, + link: 'https://localhost/redeem/CL-1a2345678', + maxAmountPerMonth: 0, + maxPerCycle: '3', + memo: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l', + name: 'Meditation', + validFrom: '2022-04-01', + validTo: '2022-08-01', + }) + }) + }) + }) +}) diff --git a/admin/src/components/ContributionLinkList.vue b/admin/src/components/ContributionLinkList.vue new file mode 100644 index 000000000..518d7d57e --- /dev/null +++ b/admin/src/components/ContributionLinkList.vue @@ -0,0 +1,106 @@ + + diff --git a/admin/src/components/CreationFormular.spec.js b/admin/src/components/CreationFormular.spec.js index 08ec71bdc..6e4c1dc6e 100644 --- a/admin/src/components/CreationFormular.spec.js +++ b/admin/src/components/CreationFormular.spec.js @@ -1,14 +1,14 @@ import { mount } from '@vue/test-utils' import CreationFormular from './CreationFormular.vue' -import { createPendingCreation } from '../graphql/createPendingCreation' -import { createPendingCreations } from '../graphql/createPendingCreations' +import { adminCreateContribution } from '../graphql/adminCreateContribution' +import { adminCreateContributions } from '../graphql/adminCreateContributions' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' const localVue = global.localVue const apolloMutateMock = jest.fn().mockResolvedValue({ data: { - createPendingCreation: [0, 0, 0], + adminCreateContribution: [0, 0, 0], }, }) const stateCommitMock = jest.fn() @@ -110,7 +110,7 @@ describe('CreationFormular', () => { it('sends ... to apollo', () => { expect(apolloMutateMock).toBeCalledWith( expect.objectContaining({ - mutation: createPendingCreation, + mutation: adminCreateContribution, variables: { email: 'benjamin@bluemchen.de', creationDate: getCreationDate(2), @@ -334,10 +334,10 @@ describe('CreationFormular', () => { jest.clearAllMocks() apolloMutateMock.mockResolvedValue({ data: { - createPendingCreations: { + adminCreateContributions: { success: true, - successfulCreation: ['bob@baumeister.de', 'bibi@bloxberg.de'], - failedCreation: [], + successfulContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'], + failedContribution: [], }, }, }) @@ -355,7 +355,7 @@ describe('CreationFormular', () => { it('calls the API', () => { expect(apolloMutateMock).toBeCalledWith( expect.objectContaining({ - mutation: createPendingCreations, + mutation: adminCreateContributions, variables: { pendingCreations: [ { @@ -390,10 +390,10 @@ describe('CreationFormular', () => { jest.clearAllMocks() apolloMutateMock.mockResolvedValue({ data: { - createPendingCreations: { + adminCreateContributions: { success: true, - successfulCreation: [], - failedCreation: ['bob@baumeister.de', 'bibi@bloxberg.de'], + successfulContribution: [], + failedContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'], }, }, }) diff --git a/admin/src/components/CreationFormular.vue b/admin/src/components/CreationFormular.vue index cdcd6ef1d..2201838de 100644 --- a/admin/src/components/CreationFormular.vue +++ b/admin/src/components/CreationFormular.vue @@ -85,8 +85,8 @@ + diff --git a/admin/src/components/Tables/OpenCreationsTable.spec.js b/admin/src/components/Tables/OpenCreationsTable.spec.js index 9ff348562..2b41a9b96 100644 --- a/admin/src/components/Tables/OpenCreationsTable.spec.js +++ b/admin/src/components/Tables/OpenCreationsTable.spec.js @@ -69,6 +69,7 @@ const propsData = { { key: 'edit_creation', label: 'edit' }, { key: 'confirm', label: 'save' }, ], + toggleDetails: false, } const mocks = { @@ -101,7 +102,7 @@ describe('OpenCreationsTable', () => { }) it('has a DIV element with the class .open-creations-table', () => { - expect(wrapper.find('div.open-creations-table').exists()).toBeTruthy() + expect(wrapper.find('div.open-creations-table').exists()).toBe(true) }) it('has a table with three rows', () => { @@ -109,7 +110,7 @@ describe('OpenCreationsTable', () => { }) it('find first button.bi-pencil-square for open EditCreationFormular ', () => { - expect(wrapper.findAll('tr').at(1).find('.bi-pencil-square').exists()).toBeTruthy() + expect(wrapper.findAll('tr').at(1).find('.bi-pencil-square').exists()).toBe(true) }) describe('show edit details', () => { @@ -122,7 +123,15 @@ describe('OpenCreationsTable', () => { }) it.skip('renders the component component-edit-creation-formular', () => { - expect(wrapper.find('div.component-edit-creation-formular').exists()).toBeTruthy() + expect(wrapper.find('div.component-edit-creation-formular').exists()).toBe(true) + }) + }) + + describe('call updateUserData', () => { + it('user creations has updated data', async () => { + wrapper.vm.updateUserData(propsData.items[0], [444, 555, 666]) + await wrapper.vm.$nextTick() + expect(wrapper.vm.items[0].creation).toEqual([444, 555, 666]) }) }) }) diff --git a/admin/src/components/Tables/OpenCreationsTable.vue b/admin/src/components/Tables/OpenCreationsTable.vue index d2e9669e6..1e61f00b0 100644 --- a/admin/src/components/Tables/OpenCreationsTable.vue +++ b/admin/src/components/Tables/OpenCreationsTable.vue @@ -70,12 +70,23 @@ export default { required: true, }, }, + data() { + return { + creationUserData: { + amount: null, + date: null, + memo: null, + moderator: null, + }, + } + }, methods: { updateCreationData(data) { - this.creationUserData.amount = data.amount - this.creationUserData.date = data.date - this.creationUserData.memo = data.memo - this.creationUserData.moderator = data.moderator + this.creationUserData = data + // this.creationUserData.amount = data.amount + // this.creationUserData.date = data.date + // this.creationUserData.memo = data.memo + // this.creationUserData.moderator = data.moderator data.row.toggleDetails() }, updateUserData(rowItem, newCreation) { diff --git a/admin/src/components/Tables/SearchUserTable.spec.js b/admin/src/components/Tables/SearchUserTable.spec.js index eb87357cc..e9072e54b 100644 --- a/admin/src/components/Tables/SearchUserTable.spec.js +++ b/admin/src/components/Tables/SearchUserTable.spec.js @@ -1,8 +1,6 @@ import { mount } from '@vue/test-utils' import SearchUserTable from './SearchUserTable.vue' -const date = new Date() - const localVue = global.localVue const apolloMutateMock = jest.fn().mockResolvedValue({}) @@ -96,16 +94,29 @@ describe('SearchUserTable', () => { await wrapper.findAll('tbody > tr').at(1).trigger('click') }) + describe('isAdmin', () => { + beforeEach(async () => { + await wrapper.find('div.change-user-role-formular').vm.$emit('updateIsAdmin', { + userId: 1, + isAdmin: new Date(), + }) + }) + + it('emits updateIsAdmin', () => { + expect(wrapper.emitted('updateIsAdmin')).toEqual([[1, expect.any(Date)]]) + }) + }) + describe('deleted at', () => { beforeEach(async () => { await wrapper.find('div.deleted-user-formular').vm.$emit('updateDeletedAt', { userId: 1, - deletedAt: date, + deletedAt: new Date(), }) }) it('emits updateDeletedAt', () => { - expect(wrapper.emitted('updateDeletedAt')).toEqual([[1, date]]) + expect(wrapper.emitted('updateDeletedAt')).toEqual([[1, expect.any(Date)]]) }) }) diff --git a/admin/src/components/Tables/SearchUserTable.vue b/admin/src/components/Tables/SearchUserTable.vue index 0be24a099..772160202 100644 --- a/admin/src/components/Tables/SearchUserTable.vue +++ b/admin/src/components/Tables/SearchUserTable.vue @@ -18,7 +18,7 @@