Merge branch 'master' into 1319-email-optin

This commit is contained in:
Ulf Gebhardt 2022-03-21 12:30:57 +01:00 committed by GitHub
commit c83959fc58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 2479 additions and 1180 deletions

View File

@ -1,3 +1,4 @@
node_modules
.git
.gitignore
.gitignore
!.eslintignore

View File

@ -1,4 +1,3 @@
node_modules
coverage
**/*.min.js
dist
node_modules/
dist/
coverage/

View File

@ -8,9 +8,20 @@ module.exports = {
parserOptions: {
parser: 'babel-eslint',
},
extends: ['standard', 'plugin:vue/essential', 'plugin:prettier/recommended'],
extends: [
'standard',
'plugin:vue/essential',
'plugin:prettier/recommended',
'plugin:@intlify/vue-i18n/recommended',
],
// required to lint *.vue files
plugins: ['vue', 'prettier', 'jest'],
overrides: [
{
files: ['*.json'],
extends: ['plugin:@intlify/vue-i18n/recommended'],
},
],
// add your custom rules here
rules: {
'no-console': ['error'],
@ -22,6 +33,17 @@ module.exports = {
allowBinding: false,
},
],
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/no-unused-keys': [
'error',
{
src: './src',
extensions: ['.js', '.vue'],
ignores: [],
enableFix: false,
},
],
'@intlify/vue-i18n/no-missing-keys-in-other-locales': 'error',
'prettier/prettier': [
'error',
{
@ -29,4 +51,12 @@ module.exports = {
},
],
},
settings: {
'vue-i18n': {
localeDir: './src/locales/*.json',
// Specify the version of `vue-i18n` you are using.
// If not specified, the message will be parsed twice.
messageSyntaxVersion: '^8.26.5',
},
},
}

View File

@ -12,11 +12,10 @@
"build": "vue-cli-service build",
"dev": "yarn run serve",
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue .",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
"test": "TZ=UTC jest --coverage",
"locales": "scripts/missing-keys.sh && scripts/sort.sh"
"locales": "scripts/sort.sh"
},
"dependencies": {
"@babel/core": "^7.15.8",
@ -52,6 +51,7 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.8",
"@intlify/eslint-plugin-vue-i18n": "^1.4.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",

View File

@ -4,24 +4,24 @@
<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 }}
{{ $t('footer.copyright.year', { year }) }}
<a
:href="`https://gradido.net/${$i18n.locale}`"
class="font-weight-bold ml-1"
target="_blank"
>
{{ $t('gradido_admin_footer') }}
{{ $t('footer.copyright.link') }}
</a>
|
{{ $t('math.pipe') }}
<a href="https://github.com/gradido/gradido/releases/latest" target="_blank">
App version {{ version }}
{{ $t('footer.app_version', { version }) }}
</a>
<a
v-if="hash"
:href="'https://github.com/gradido/gradido/commit/' + hash"
target="_blank"
>
({{ shortHash }})
{{ $t('footer.short_hash', { shortHash }) }}
</a>
</div>
</b-col>

View File

@ -6,32 +6,30 @@ const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
transactionList: {
transactions: [
{
id: 1,
amount: 100,
balanceDate: 0,
creationDate: new Date(),
memo: 'Testing',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
creationTransactionList: [
{
id: 1,
amount: 100,
balanceDate: 0,
creationDate: new Date(),
memo: 'Testing',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
{
id: 2,
amount: 200,
balanceDate: 0,
creationDate: new Date(),
memo: 'Testing 2',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
},
{
id: 2,
amount: 200,
balanceDate: 0,
creationDate: new Date(),
memo: 'Testing 2',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
],
},
},
],
},
})
@ -67,7 +65,6 @@ describe('CreationTransactionListFormular', () => {
currentPage: 1,
pageSize: 25,
order: 'DESC',
onlyCreations: true,
userId: 1,
},
}),

View File

@ -5,7 +5,7 @@
</div>
</template>
<script>
import { transactionList } from '../graphql/transactionList'
import { creationTransactionList } from '../graphql/creationTransactionList'
export default {
name: 'CreationTransactionList',
props: {
@ -51,17 +51,16 @@ export default {
getTransactions() {
this.$apollo
.query({
query: transactionList,
query: creationTransactionList,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
onlyCreations: true,
userId: parseInt(this.userId),
},
})
.then((result) => {
this.items = result.data.transactionList.transactions
this.items = result.data.creationTransactionList
})
.catch((error) => {
this.toastError(error.message)

View File

@ -7,7 +7,7 @@
<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>{{ item.amount }} {{ $t('GDD') }}</b>
</b-col>
</b-row>
<b-row>

View File

@ -12,7 +12,7 @@
>
<b-icon icon="plus" variant="success"></b-icon>
</b-button>
<div v-else>{{ $t('e_mail') }}!</div>
<div v-else>{{ $t('e_mail') }}{{ $t('math.exclaim') }}</div>
</div>
</template>
</b-table-lite>

View File

@ -0,0 +1,22 @@
import gql from 'graphql-tag'
export const creationTransactionList = gql`
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC, $userId: Int!) {
creationTransactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
userId: $userId
) {
id
amount
balanceDate
creationDate
memo
linkedUser {
firstName
lastName
}
}
}
`

View File

@ -1,31 +0,0 @@
import gql from 'graphql-tag'
export const transactionList = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 25
$order: Order = DESC
$onlyCreations: Boolean = false
$userId: Int = null
) {
transactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
onlyCreations: $onlyCreations
userId: $userId
) {
transactions {
id
amount
balanceDate
creationDate
memo
linkedUser {
firstName
lastName
}
}
}
}
`

View File

@ -1,8 +1,6 @@
{
"all_emails": "Alle Nutzer",
"back": "zurück",
"bookmark": "bookmark",
"confirmed": "bestätigt",
"creation": "Schöpfung",
"creation_form": {
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
@ -16,7 +14,6 @@
"submit_creation": "Schöpfung einreichen",
"toasted": "Offene Schöpfung ({value} GDD) für {email} wurde gespeichert und liegt zur Bestätigung bereit",
"toasted_created": "Schöpfung wurde erfolgreich gespeichert",
"toasted_default": "`Fall {event} wird nicht unterstützt`",
"toasted_delete": "Offene Schöpfung wurde gelöscht",
"toasted_update": "`Offene Schöpfung {value} GDD) für {email} wurde geändert und liegt zur Bestätigung bereit",
"update_creation": "Schöpfung aktualisieren"
@ -27,14 +24,26 @@
"deleted": "gelöscht",
"deleted_user": "Alle gelöschten Nutzer",
"delete_user": "Nutzer löschen",
"details": "Details",
"edit": "Bearbeiten",
"enabled": "aktiviert",
"error": "Fehler",
"e_mail": "E-Mail",
"firstname": "Vorname",
"gradido_admin_footer": "Gradido Akademie Adminkonsole",
"footer": {
"app_version": "App version {version}",
"copyright": {
"link": "Gradido Akademie Adminkonsole",
"year": "© {year}"
},
"short_hash": "({shortHash})"
},
"GDD": "GDD",
"hide_details": "Details verbergen",
"lastname": "Nachname",
"math": {
"exclaim": "!",
"pipe": "|"
},
"moderator": "Moderator",
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
"name": "Name",
@ -47,7 +56,6 @@
"user_search": "Nutzersuche"
},
"not_open_creations": "Keine offenen Schöpfungen",
"open_creation": "Offene Schöpfung",
"open_creations": "Offene Schöpfungen",
"overlay": {
"confirm": {
@ -56,13 +64,6 @@
"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!",
"yes": "Ja, Schöpfung bestätigen und speichern!"
},
"remove": {
"no": "Nein, nicht löschen.",
"question": "Willst du die vorgespeicherte Schöpfung wirklich löschen?",
"text": "Nach dem Löschen gibt es keine Möglichkeit mehr diesen Datensatz wiederherzustellen. Es wird aber der gesamte Vorgang in der Logdatei als Übersicht gespeichert.",
"title": "Achtung! Schöpfung löschen!",
"yes": "Ja, Schöpfung löschen!"
}
},
"remove": "Entfernen",
@ -72,13 +73,11 @@
"status": "Status",
"success": "Erfolg",
"text": "Text",
"transaction": "Transaktion",
"transactionlist": {
"amount": "Betrag",
"balanceDate": "Schöpfungsdatum",
"community": "Gemeinschaft",
"date": "Datum",
"decay": "Vergänglichkeit",
"memo": "Nachricht",
"title": "Alle geschöpften Transaktionen für den Nutzer"
},

View File

@ -1,8 +1,6 @@
{
"all_emails": "All users",
"back": "back",
"bookmark": "Remember",
"confirmed": "confirmed",
"creation": "Creation",
"creation_form": {
"creation_failed": "Could not create pending creation for {email}",
@ -16,7 +14,6 @@
"submit_creation": "Submit creation",
"toasted": "Open creation ({value} GDD) for {email} has been saved and is ready for confirmation.",
"toasted_created": "Creation has been successfully saved",
"toasted_default": "`Case {event} is not supported`",
"toasted_delete": "Open creation has been deleted",
"toasted_update": "Open creation {value} GDD) for {email} has been changed and is ready for confirmation.",
"update_creation": "Creation update"
@ -27,14 +24,26 @@
"deleted": "deleted",
"deleted_user": "All deleted user",
"delete_user": "Delete user",
"details": "Details",
"edit": "Edit",
"enabled": "enabled",
"error": "Error",
"e_mail": "E-mail",
"firstname": "Firstname",
"gradido_admin_footer": "Gradido Academy Admin Console",
"footer": {
"app_version": "App version {version}",
"copyright": {
"link": "Gradido Academy Admin Console",
"year": "© {year}"
},
"short_hash": "({shortHash})"
},
"GDD": "GDD",
"hide_details": "Hide details",
"lastname": "Lastname",
"math": {
"exclaim": "!",
"pipe": "|"
},
"moderator": "Moderator",
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
"name": "Name",
@ -47,7 +56,6 @@
"user_search": "User search"
},
"not_open_creations": "No open creations",
"open_creation": "Open creation",
"open_creations": "Open creations",
"overlay": {
"confirm": {
@ -56,13 +64,6 @@
"text": "After saving, the record can no longer be changed or deleted. Please check carefully that everything is correct.",
"title": "Confirm creation!",
"yes": "Yes, confirm and save creation!"
},
"remove": {
"no": "No, do not delete.",
"question": "Do you really want to delete the pre-stored creation?",
"text": "After deletion, there is no possibility to restore this data record. However, the entire process is saved in the log file as an overview.",
"title": "Attention! Delete creation!",
"yes": "Yes, delete creation!"
}
},
"remove": "Remove",
@ -72,13 +73,11 @@
"status": "Status",
"success": "Success",
"text": "Text",
"transaction": "Transaction",
"transactionlist": {
"amount": "Amount",
"balanceDate": "Creation date",
"community": "Community",
"date": "Date",
"decay": "Decay",
"memo": "Message",
"title": "All creation-transactions for the user"
},

View File

@ -15,7 +15,7 @@ export const toasters = {
toast(message, options) {
// for unit tests, check that replace is present
if (message.replace) message = message.replace(/^GraphQL error: /, '')
this.$bvToast.toast(message, {
this.$root.$bvToast.toast(message, {
autoHideDelay: 5000,
appendToast: true,
solid: true,

View File

@ -2,7 +2,7 @@
<div class="creation">
<b-row>
<b-col cols="12" lg="6">
<label>Usersuche</label>
<label>{{ $t('user_search') }}</label>
<b-input-group>
<b-form-input
type="text"

View File

@ -2,7 +2,6 @@ import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
import VueApollo from 'vue-apollo'
import CONFIG from '../config'
import store from '../store/store'
import i18n from '../i18n'
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
@ -15,7 +14,6 @@ const authLink = new ApolloLink((operation, forward) => {
})
return forward(operation).map((response) => {
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
response.errors[0].message = i18n.t('error.session-expired')
store.dispatch('logout', null)
window.location.assign(CONFIG.WALLET_URL)
return response

View File

@ -14,6 +14,7 @@ module.exports = {
fallbackLocale: 'de',
localeDir: 'locales',
enableInSFC: false,
enableLegacy: false,
},
},
lintOnSave: true,

View File

@ -932,6 +932,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.14.0":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@7", "@babel/template@^7.0.0", "@babel/template@^7.15.4", "@babel/template@^7.3.3", "@babel/template@^7.4.0":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194"
@ -1001,6 +1008,21 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^1.2.0":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.3.1"
globals "^13.9.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@ -1042,6 +1064,83 @@
cssnano-preset-default "^4.0.0"
postcss "^7.0.0"
"@intlify/core-base@^9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.1.9.tgz#e4e8c951010728e4af3a0d13d74cf3f9e7add7f6"
integrity sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==
dependencies:
"@intlify/devtools-if" "9.1.9"
"@intlify/message-compiler" "9.1.9"
"@intlify/message-resolver" "9.1.9"
"@intlify/runtime" "9.1.9"
"@intlify/shared" "9.1.9"
"@intlify/vue-devtools" "9.1.9"
"@intlify/devtools-if@9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.1.9.tgz#a30e1dd1256ff2c5c98d8d75d075384fba898e5d"
integrity sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==
dependencies:
"@intlify/shared" "9.1.9"
"@intlify/eslint-plugin-vue-i18n@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@intlify/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-1.4.0.tgz#f8fe791892c2dce7d189a364b6a908c87e1c3ac9"
integrity sha512-anB1eBf6rpxpWyW883gi6O1hozQy4Q02VyzyodOUnohOqT07GATVSxnr2J9/qQSV47xWukV+9LiRErJcU7d/uA==
dependencies:
"@eslint/eslintrc" "^1.2.0"
"@intlify/core-base" "^9.1.9"
"@intlify/message-compiler" "^9.1.9"
debug "^4.3.1"
glob "^7.1.3"
ignore "^5.0.5"
is-language-code "^3.1.0"
js-yaml "^4.0.0"
json5 "^2.1.3"
jsonc-eslint-parser "^2.0.0"
lodash "^4.17.11"
parse5 "^6.0.0"
semver "^7.3.4"
vue-eslint-parser "^8.0.0"
yaml-eslint-parser "^0.5.0"
"@intlify/message-compiler@9.1.9", "@intlify/message-compiler@^9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.1.9.tgz#1193cbd224a71c2fb981455b8534a3c766d2948d"
integrity sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==
dependencies:
"@intlify/message-resolver" "9.1.9"
"@intlify/shared" "9.1.9"
source-map "0.6.1"
"@intlify/message-resolver@9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/message-resolver/-/message-resolver-9.1.9.tgz#3155ccd2f5e6d0dc16cad8b7f1d8e97fcda05bfc"
integrity sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==
"@intlify/runtime@9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/runtime/-/runtime-9.1.9.tgz#2c12ce29518a075629efed0a8ed293ee740cb285"
integrity sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==
dependencies:
"@intlify/message-compiler" "9.1.9"
"@intlify/message-resolver" "9.1.9"
"@intlify/shared" "9.1.9"
"@intlify/shared@9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.1.9.tgz#0baaf96128b85560666bec784ffb01f6623cc17a"
integrity sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==
"@intlify/vue-devtools@9.1.9":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.1.9.tgz#2be8f4dbe7f7ed4115676eb32348141d411e426b"
integrity sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==
dependencies:
"@intlify/message-resolver" "9.1.9"
"@intlify/runtime" "9.1.9"
"@intlify/shared" "9.1.9"
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@ -2399,6 +2498,11 @@ acorn@^8.2.4:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
acorn@^8.5.0, acorn@^8.7.0:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
address@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
@ -2645,6 +2749,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@ -4892,7 +5001,7 @@ debug@^3.1.1, debug@^3.2.6, debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.3.0, debug@^4.3.3:
debug@^4.3.0, debug@^4.3.1, debug@^4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
@ -5615,6 +5724,14 @@ eslint-scope@^5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-scope@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-utils@^2.0.0, eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
@ -5644,6 +5761,11 @@ eslint-visitor-keys@^3.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186"
integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==
eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint@7.25.0:
version "7.25.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.25.0.tgz#1309e4404d94e676e3e831b3a3ad2b050031eb67"
@ -5710,6 +5832,15 @@ espree@^7.3.0, espree@^7.3.1:
acorn-jsx "^5.3.1"
eslint-visitor-keys "^1.3.0"
espree@^9.0.0, espree@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd"
integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==
dependencies:
acorn "^8.7.0"
acorn-jsx "^5.3.1"
eslint-visitor-keys "^3.3.0"
esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -7003,16 +7134,16 @@ ignore@^4.0.3, ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
ignore@^5.0.5, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
ignore@^5.1.1, ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
import-cwd@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@ -7386,6 +7517,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-language-code@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-language-code/-/is-language-code-3.1.0.tgz#b2386b49227e7010636f16d0c2c681ca40136ab5"
integrity sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==
dependencies:
"@babel/runtime" "^7.14.0"
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
@ -8571,6 +8709,13 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.0.0, js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -8723,7 +8868,7 @@ json3@^3.3.3:
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
json5@2.x, json5@^2.1.2:
json5@2.x, json5@^2.1.2, json5@^2.1.3:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
@ -8742,6 +8887,16 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
jsonc-eslint-parser@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/jsonc-eslint-parser/-/jsonc-eslint-parser-2.1.0.tgz#4c126b530aa583d85308d0b3041ff81ce402bbb2"
integrity sha512-qCRJWlbP2v6HbmKW7R3lFbeiVWHo+oMJ0j+MizwvauqnCV/EvtAeEeuCgoc/ErtsuoKgYB8U4Ih8AxJbXoE6/g==
dependencies:
acorn "^8.5.0"
eslint-visitor-keys "^3.0.0"
espree "^9.0.0"
semver "^7.3.5"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@ -10014,7 +10169,7 @@ parse5@5.1.0:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
parse5@6.0.1, parse5@^6.0.1:
parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
@ -11700,16 +11855,16 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
@ -12970,6 +13125,19 @@ vue-eslint-parser@^7.10.0:
lodash "^4.17.21"
semver "^6.3.0"
vue-eslint-parser@^8.0.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d"
integrity sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==
dependencies:
debug "^4.3.2"
eslint-scope "^7.0.0"
eslint-visitor-keys "^3.1.0"
espree "^9.0.0"
esquery "^1.4.0"
lodash "^4.17.21"
semver "^7.3.5"
vue-functional-data-merge@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657"
@ -13515,7 +13683,16 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0:
yaml-eslint-parser@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-0.5.0.tgz#01d4e4d992a820769ea85ef5fd526dfc20ebc6f5"
integrity sha512-nJeyLA3YHAzhBTZbRAbu3W6xrSCucyxExmA+ZDtEdUFpGllxAZpto2Zxo2IG0r0eiuEiBM4e+wiAdxTziTq94g==
dependencies:
eslint-visitor-keys "^3.0.0"
lodash "^4.17.21"
yaml "^1.10.2"
yaml@^1.10.0, yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==

View File

@ -3,8 +3,9 @@ module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
setupFiles: ['<rootDir>/test/testSetup.ts'],
modulePathIgnorePatterns: ['<rootDir>/build/'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@model/(.*)': '<rootDir>/src/graphql/model/$1',

View File

@ -10,10 +10,11 @@
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"start": "node build/src/index.js",
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
"start": "TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"dev": "TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles"
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
"seed": "TZ=UTC ts-node -r tsconfig-paths/register src/seeds/index.ts"
},
"dependencies": {
"@types/jest": "^27.0.2",
@ -31,7 +32,6 @@
"jest": "^27.2.4",
"jsonwebtoken": "^8.5.1",
"lodash.clonedeep": "^4.5.0",
"module-alias": "^2.2.2",
"mysql2": "^2.3.0",
"nodemailer": "^6.6.5",
"random-bigint": "^0.0.1",
@ -42,6 +42,7 @@
},
"devDependencies": {
"@types/express": "^4.17.12",
"@types/faker": "^5.5.9",
"@types/jsonwebtoken": "^8.5.2",
"@types/node": "^16.10.3",
"@types/nodemailer": "^6.4.4",
@ -54,18 +55,11 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"faker": "^5.5.3",
"nodemon": "^2.0.7",
"prettier": "^2.3.1",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.14.0",
"typescript": "^4.3.4"
},
"_moduleAliases": {
"@": "./build/src",
"@arg": "./build/src/graphql/arg",
"@dbTools": "../database/build/src",
"@entity": "../database/build/entity",
"@enum": "./build/src/graphql/enum",
"@model": "./build/src/graphql/model",
"@repository": "./build/src/typeorm/repository"
}
}

View File

@ -34,4 +34,5 @@ export enum RIGHTS {
SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL',
DELETE_USER = 'DELETE_USER',
UNDELETE_USER = 'UNDELETE_USER',
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
}

View File

@ -11,10 +11,4 @@ export default class Paginated {
@Field(() => Order, { nullable: true })
order?: Order
@Field(() => Boolean, { nullable: true })
onlyCreations?: boolean
@Field(() => Int, { nullable: true })
userId?: number
}

View File

@ -35,6 +35,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
const userRepository = await getCustomRepository(UserRepository)
try {
const user = await userRepository.findByPubkeyHex(context.pubKey)
context.user = user
const countServerUsers = await ServerUser.count({ email: user.email })
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
} catch {

View File

@ -6,7 +6,7 @@ export enum TransactionTypeId {
RECEIVE = 3,
// This is a virtual property, never occurring on the database
DECAY = 4,
TRANSACTION_LINK = 5,
LINK_SUMMARY = 5,
}
registerEnumType(TransactionTypeId, {

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql'
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql'
import {
getCustomRepository,
IsNull,
@ -19,16 +19,21 @@ import { UserRepository } from '@repository/User'
import CreatePendingCreationArgs from '@arg/CreatePendingCreationArgs'
import UpdatePendingCreationArgs from '@arg/UpdatePendingCreationArgs'
import SearchUsersArgs from '@arg/SearchUsersArgs'
import { Transaction } from '@entity/Transaction'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { Transaction } from '@model/Transaction'
import { TransactionRepository } from '@repository/Transaction'
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 { User as dbUser } from '@entity/User'
import { User } from '@model/User'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import Decimal from 'decimal.js-light'
import { Decay } from '@model/Decay'
import Paginated from '@arg/Paginated'
import { Order } from '@enum/Order'
import { communityUser } from '@/util/communityUser'
// const EMAIL_OPT_IN_REGISTER = 1
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
@ -123,27 +128,26 @@ export class AdminResolver {
@Authorized([RIGHTS.DELETE_USER])
@Mutation(() => Date, { nullable: true })
async deleteUser(@Arg('userId') userId: number, @Ctx() context: any): Promise<Date | null> {
const user = await User.findOne({ id: userId })
const user = await dbUser.findOne({ id: userId })
// user exists ?
if (!user) {
throw new Error(`Could not find user with userId: ${userId}`)
}
// moderator user disabled own account?
const userRepository = getCustomRepository(UserRepository)
const moderatorUser = await userRepository.findByPubkeyHex(context.pubKey)
const moderatorUser = context.user
if (moderatorUser.id === userId) {
throw new Error('Moderator can not delete his own account!')
}
// soft-delete user
await user.softRemove()
const newUser = await User.findOne({ id: userId }, { withDeleted: true })
const newUser = await dbUser.findOne({ id: userId }, { withDeleted: true })
return newUser ? newUser.deletedAt : null
}
@Authorized([RIGHTS.UNDELETE_USER])
@Mutation(() => Date, { nullable: true })
async unDeleteUser(@Arg('userId') userId: number): Promise<Date | null> {
const user = await User.findOne({ id: userId }, { withDeleted: true })
const user = await dbUser.findOne({ id: userId }, { withDeleted: true })
// user exists ?
if (!user) {
throw new Error(`Could not find user with userId: ${userId}`)
@ -158,7 +162,7 @@ export class AdminResolver {
async createPendingCreation(
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
): Promise<number[]> {
const user = await User.findOne({ email }, { withDeleted: true })
const user = await dbUser.findOne({ email }, { withDeleted: true })
if (!user) {
throw new Error(`Could not find user with email: ${email}`)
}
@ -215,7 +219,7 @@ export class AdminResolver {
async updatePendingCreation(
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
): Promise<UpdatePendingCreation> {
const user = await User.findOne({ email }, { withDeleted: true })
const user = await dbUser.findOne({ email }, { withDeleted: true })
if (!user) {
throw new Error(`Could not find user with email: ${email}`)
}
@ -265,7 +269,7 @@ export class AdminResolver {
const userIds = pendingCreations.map((p) => p.userId)
const userCreations = await getUserCreations(userIds)
const users = await User.find({ where: { id: In(userIds) }, withDeleted: true })
const users = await dbUser.find({ where: { id: In(userIds) }, withDeleted: true })
return pendingCreations.map((pendingCreation) => {
const user = users.find((u) => u.id === pendingCreation.userId)
@ -294,12 +298,11 @@ export class AdminResolver {
@Mutation(() => Boolean)
async confirmPendingCreation(@Arg('id') id: number, @Ctx() context: any): Promise<boolean> {
const pendingCreation = await AdminPendingCreation.findOneOrFail(id)
const userRepository = getCustomRepository(UserRepository)
const moderatorUser = await userRepository.findByPubkeyHex(context.pubKey)
const moderatorUser = context.user
if (moderatorUser.id === pendingCreation.userId)
throw new Error('Moderator can not confirm own pending creation')
const user = await User.findOneOrFail({ id: pendingCreation.userId }, { withDeleted: true })
const user = await dbUser.findOneOrFail({ id: pendingCreation.userId }, { withDeleted: true })
if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a creation.')
const creations = await getUserCreation(pendingCreation.userId, false)
@ -321,7 +324,7 @@ export class AdminResolver {
// TODO pending creations decimal
newBalance = newBalance.add(new Decimal(Number(pendingCreation.amount)).toString())
const transaction = new Transaction()
const transaction = new DbTransaction()
transaction.typeId = TransactionTypeId.CREATION
transaction.memo = pendingCreation.memo
transaction.userId = pendingCreation.userId
@ -339,6 +342,27 @@ export class AdminResolver {
return true
}
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
@Query(() => [Transaction])
async creationTransactionList(
@Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Arg('userId', () => Int) userId: number,
): Promise<Transaction[]> {
const offset = (currentPage - 1) * pageSize
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions] = await transactionRepository.findByUserPaged(
userId,
pageSize,
offset,
order,
true,
)
const user = await dbUser.findOneOrFail({ id: userId })
return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
}
}
interface CreationMap {

View File

@ -2,9 +2,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import { getCustomRepository } from '@dbTools/typeorm'
import { Balance } from '@model/Balance'
import { UserRepository } from '@repository/User'
import { calculateDecay } from '@/util/decay'
import { RIGHTS } from '@/auth/RIGHTS'
import { Transaction } from '@entity/Transaction'
@ -16,9 +14,7 @@ export class BalanceResolver {
@Query(() => Balance)
async balance(@Ctx() context: any): Promise<Balance> {
// load user and balance
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
const { user } = context
const now = new Date()
const lastTransaction = await Transaction.findOne(

View File

@ -2,12 +2,10 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Ctx, Authorized, Arg } from 'type-graphql'
import { getCustomRepository } from '@dbTools/typeorm'
import CONFIG from '@/config'
import { GdtEntryList } from '@model/GdtEntryList'
import Paginated from '@arg/Paginated'
import { apiGet } from '@/apis/HttpRequest'
import { UserRepository } from '@repository/User'
import { Order } from '@enum/Order'
import { RIGHTS } from '@/auth/RIGHTS'
@ -22,8 +20,7 @@ export class GdtResolver {
@Ctx() context: any,
): Promise<GdtEntryList> {
// load user
const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const userEntity = context.user
try {
const resultGDT = await apiGet(

View File

@ -2,11 +2,9 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query } from 'type-graphql'
import { getCustomRepository } from '@dbTools/typeorm'
import { TransactionLink } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { User as dbUser } from '@entity/User'
import { UserRepository } from '@repository/User'
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
import Paginated from '@arg/Paginated'
import { calculateBalance } from '@/util/validate'
@ -29,7 +27,7 @@ export const transactionLinkCode = (date: Date): string => {
const CODE_VALID_DAYS_DURATION = 14
const transactionLinkExpireDate = (date: Date): Date => {
export const transactionLinkExpireDate = (date: Date): Date => {
const validUntil = new Date(date)
return new Date(validUntil.setDate(date.getDate() + CODE_VALID_DAYS_DURATION))
}
@ -42,8 +40,7 @@ export class TransactionLinkResolver {
@Args() { amount, memo }: TransactionLinkArgs,
@Ctx() context: any,
): Promise<TransactionLink> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
const { user } = context
const createdDate = new Date()
const validUntil = transactionLinkExpireDate(createdDate)
@ -74,8 +71,7 @@ export class TransactionLinkResolver {
@Authorized([RIGHTS.DELETE_TRANSACTION_LINK])
@Mutation(() => Boolean)
async deleteTransactionLink(@Arg('id') id: number, @Ctx() context: any): Promise<boolean> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
const { user } = context
const transactionLink = await dbTransactionLink.findOne({ id })
if (!transactionLink) {
@ -116,8 +112,7 @@ export class TransactionLinkResolver {
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Ctx() context: any,
): Promise<TransactionLink[]> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
const { user } = context
// const now = new Date()
const transactionLinks = await dbTransactionLink.find({
where: {
@ -137,8 +132,7 @@ export class TransactionLinkResolver {
@Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
@Mutation(() => Boolean)
async redeemTransactionLink(@Arg('id') id: number, @Ctx() context: any): Promise<boolean> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey)
const { user } = context
const transactionLink = await dbTransactionLink.findOneOrFail({ id })
const linkedUser = await dbUser.findOneOrFail({ id: transactionLink.userId })

View File

@ -17,7 +17,6 @@ import Paginated from '@arg/Paginated'
import { Order } from '@enum/Order'
import { UserRepository } from '@repository/User'
import { TransactionRepository } from '@repository/Transaction'
import { TransactionLinkRepository } from '@repository/TransactionLink'
@ -131,22 +130,11 @@ export class TransactionResolver {
@Query(() => TransactionList)
async transactionList(
@Args()
{
currentPage = 1,
pageSize = 25,
order = Order.DESC,
onlyCreations = false,
userId,
}: Paginated,
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Ctx() context: any,
): Promise<TransactionList> {
const now = new Date()
// find user
const userRepository = getCustomRepository(UserRepository)
// TODO: separate those usecases - this is a security issue
const user = userId
? await userRepository.findOneOrFail({ id: userId }, { withDeleted: true })
: await userRepository.findByPubkeyHex(context.pubKey)
const user = context.user
// find current balance
const lastTransaction = await dbTransaction.findOne(
@ -182,7 +170,6 @@ export class TransactionResolver {
pageSize,
offset,
order,
onlyCreations,
)
// find involved users; I am involved
@ -208,7 +195,7 @@ export class TransactionResolver {
await transactionLinkRepository.summary(user.id, now)
// decay & link transactions
if (!onlyCreations && currentPage === 1 && order === Order.DESC) {
if (currentPage === 1 && order === Order.DESC) {
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
@ -256,8 +243,7 @@ export class TransactionResolver {
@Ctx() context: any,
): Promise<boolean> {
// TODO this is subject to replay attacks
const userRepository = getCustomRepository(UserRepository)
const senderUser = await userRepository.findByPubkeyHex(context.pubKey)
const senderUser = context.user
if (senderUser.pubKey.length !== 32) {
throw new Error('invalid sender public key')
}

View File

@ -1,17 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { testEnvironment, createUser, headerPushMock, cleanDB, resetToken } from '@test/helpers'
import { createUserMutation, setPasswordMutation } from '@test/graphql'
import gql from 'graphql-tag'
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { userFactory } from '@/seeds/factory/user'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { login, logout } from '@/seeds/graphql/queries'
import { GraphQLError } from 'graphql'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import CONFIG from '@/config'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
// import { klicktippSignIn } from '@/apis/KlicktippController'
jest.setTimeout(1000000)
// import { klicktippSignIn } from '@/apis/KlicktippController'
jest.mock('@/mailer/sendAccountActivationEmail', () => {
return {
@ -30,27 +31,10 @@ jest.mock('@/apis/KlicktippController', () => {
*/
let mutate: any, query: any, con: any
const loginQuery = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
let testEnv: any
beforeAll(async () => {
const testEnv = await testEnvironment()
testEnv = await testEnvironment()
mutate = testEnv.mutate
query = testEnv.query
con = testEnv.con
@ -77,7 +61,7 @@ describe('UserResolver', () => {
beforeAll(async () => {
jest.clearAllMocks()
result = await mutate({ mutation: createUserMutation, variables })
result = await mutate({ mutation: createUser, variables })
})
afterAll(async () => {
@ -85,7 +69,9 @@ describe('UserResolver', () => {
})
it('returns success', () => {
expect(result).toEqual(expect.objectContaining({ data: { createUser: 'success' } }))
expect(result).toEqual(
expect.objectContaining({ data: { createUser: { id: expect.any(Number) } } }),
)
})
describe('valid input data', () => {
@ -149,7 +135,7 @@ describe('UserResolver', () => {
describe('email already exists', () => {
it('throws an error', async () => {
await expect(mutate({ mutation: createUserMutation, variables })).resolves.toEqual(
await expect(mutate({ mutation: createUser, variables })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('User already exists.')],
}),
@ -160,7 +146,7 @@ describe('UserResolver', () => {
describe('unknown language', () => {
it('sets "de" as default language', async () => {
await mutate({
mutation: createUserMutation,
mutation: createUser,
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' },
})
await expect(User.find()).resolves.toEqual(
@ -177,7 +163,7 @@ describe('UserResolver', () => {
describe('no publisher id', () => {
it('sets publisher id to null', async () => {
await mutate({
mutation: createUserMutation,
mutation: createUser,
variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined },
})
await expect(User.find()).resolves.toEqual(
@ -208,11 +194,11 @@ describe('UserResolver', () => {
let newUser: any
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
const loginEmailOptIn = await LoginEmailOptIn.find()
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: emailOptIn, password: 'Aa12345_' },
})
newUser = await User.find()
@ -252,11 +238,11 @@ describe('UserResolver', () => {
describe('no valid password', () => {
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
const loginEmailOptIn = await LoginEmailOptIn.find()
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: emailOptIn, password: 'not-valid' },
})
})
@ -280,9 +266,9 @@ describe('UserResolver', () => {
describe('no valid optin code', () => {
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: 'not valid', password: 'Aa12345_' },
})
})
@ -303,7 +289,7 @@ describe('UserResolver', () => {
describe('login', () => {
const variables = {
email: 'peter@lustig.de',
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
publisherId: 1234,
}
@ -316,7 +302,7 @@ describe('UserResolver', () => {
describe('no users in database', () => {
beforeAll(async () => {
result = await query({ query: loginQuery, variables })
result = await query({ query: login, variables })
})
it('throws an error', () => {
@ -330,14 +316,8 @@ describe('UserResolver', () => {
describe('user is in database and correct login data', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
result = await query({ query: loginQuery, variables })
await userFactory(testEnv, bibiBloxberg)
result = await query({ query: login, variables })
})
afterAll(async () => {
@ -350,15 +330,16 @@ describe('UserResolver', () => {
data: {
login: {
coinanimation: true,
email: 'peter@lustig.de',
firstName: 'Peter',
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
hasElopage: false,
id: expect.any(Number),
isAdmin: false,
klickTipp: {
newsletterState: false,
},
language: 'de',
lastName: 'Lustig',
lastName: 'Bloxberg',
publisherId: 1234,
},
},
@ -373,13 +354,7 @@ describe('UserResolver', () => {
describe('user is in database and wrong password', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
await userFactory(testEnv, bibiBloxberg)
})
afterAll(async () => {
@ -388,7 +363,7 @@ describe('UserResolver', () => {
it('returns an error', () => {
expect(
query({ query: loginQuery, variables: { ...variables, password: 'wrong' } }),
query({ query: login, variables: { ...variables, password: 'wrong' } }),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
@ -399,16 +374,10 @@ describe('UserResolver', () => {
})
describe('logout', () => {
const logoutQuery = gql`
query {
logout
}
`
describe('unauthenticated', () => {
it('throws an error', async () => {
resetToken()
await expect(query({ query: logoutQuery })).resolves.toEqual(
await expect(query({ query: logout })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
@ -418,19 +387,13 @@ describe('UserResolver', () => {
describe('authenticated', () => {
const variables = {
email: 'peter@lustig.de',
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
}
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
await query({ query: loginQuery, variables })
await userFactory(testEnv, bibiBloxberg)
await query({ query: login, variables })
})
afterAll(async () => {
@ -438,7 +401,7 @@ describe('UserResolver', () => {
})
it('returns true', async () => {
await expect(query({ query: logoutQuery })).resolves.toEqual(
await expect(query({ query: logout })).resolves.toEqual(
expect.objectContaining({
data: { logout: 'true' },
errors: undefined,

View File

@ -14,7 +14,6 @@ import UpdateUserInfosArgs from '@arg/UpdateUserInfosArgs'
import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddleware'
import { UserSettingRepository } from '@repository/UserSettingRepository'
import { Setting } from '@enum/Setting'
import { UserRepository } from '@repository/User'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
@ -208,8 +207,7 @@ export class UserResolver {
@UseMiddleware(klicktippNewsletterStateMiddleware)
async verifyLogin(@Ctx() context: any): Promise<User> {
// TODO refactor and do not have duplicate code with login(see below)
const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const userEntity = context.user
const user = new User(userEntity)
// user.pubkey = userEntity.pubKey.toString('hex')
// Elopage Status & Stored PublisherId
@ -307,10 +305,10 @@ export class UserResolver {
}
@Authorized([RIGHTS.CREATE_USER])
@Mutation(() => String)
@Mutation(() => User)
async createUser(
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
): Promise<string> {
): Promise<User> {
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
// default int publisher_id = 0;
@ -390,7 +388,7 @@ export class UserResolver {
} finally {
await queryRunner.release()
}
return 'success'
return new User(dbUser)
}
// THis is used by the admin only - should we move it to the admin resolver?
@ -589,8 +587,7 @@ export class UserResolver {
}: UpdateUserInfosArgs,
@Ctx() context: any,
): Promise<boolean> {
const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const userEntity = context.user
if (firstName) {
userEntity.firstName = firstName
@ -668,8 +665,7 @@ export class UserResolver {
@Authorized([RIGHTS.HAS_ELOPAGE])
@Query(() => Boolean)
async hasElopage(@Ctx() context: any): Promise<boolean> {
const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey).catch()
const userEntity = context.user
if (!userEntity) {
return false
}

View File

@ -0,0 +1,7 @@
export interface CreationInterface {
email: string
amount: number
memo: string
creationDate: string
confirmed?: boolean
}

View File

@ -0,0 +1,29 @@
import { CreationInterface } from './CreationInterface'
const lastMonth = (date: Date): string => {
return new Date(date.getFullYear(), date.getMonth() - 1, 1).toISOString()
}
export const creations: CreationInterface[] = [
{
email: 'bibi@bloxberg.de',
amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()),
confirmed: true,
},
{
email: 'bob@baumeister.de',
amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()),
confirmed: true,
},
{
email: 'raeuber@hotzenplotz.de',
amount: 1000,
memo: 'Herzlich Willkommen bei Gradido!',
creationDate: lastMonth(new Date()),
confirmed: true,
},
]

View File

@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createPendingCreation, confirmPendingCreation } from '@/seeds/graphql/mutations'
import { login } from '@/seeds/graphql/queries'
import { CreationInterface } from '@/seeds/creation/CreationInterface'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { User } from '@entity/User'
import { AdminPendingCreation } from '@entity/AdminPendingCreation'
// import CONFIG from '@/config/index'
export const creationFactory = async (
client: ApolloServerTestClient,
creation: CreationInterface,
): Promise<void> => {
const { mutate, query } = client
// login as Peter Lustig (admin) and get his user ID
const {
data: {
login: { id },
},
} = await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
await mutate({ mutation: createPendingCreation, variables: { ...creation, moderator: id } })
// get User
const user = await User.findOneOrFail({ where: { email: creation.email } })
if (creation.confirmed) {
const pendingCreation = await AdminPendingCreation.findOneOrFail({
where: { userId: user.id },
order: { created: 'DESC' },
})
await mutate({ mutation: confirmPendingCreation, variables: { id: pendingCreation.id } })
}
}

View File

@ -0,0 +1,43 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { createTransactionLink } from '@/seeds/graphql/mutations'
import { login } from '@/seeds/graphql/queries'
import { TransactionLinkInterface } from '@/seeds/transactionLink/TransactionLinkInterface'
import { transactionLinkExpireDate } from '@/graphql/resolver/TransactionLinkResolver'
import { TransactionLink } from '@entity/TransactionLink'
export const transactionLinkFactory = async (
client: ApolloServerTestClient,
transactionLink: TransactionLinkInterface,
): Promise<void> => {
const { mutate, query } = client
// login
await query({ query: login, variables: { email: transactionLink.email, password: 'Aa12345_' } })
const variables = {
amount: transactionLink.amount,
memo: transactionLink.memo,
}
// get the transaction links's id
const {
data: {
createTransactionLink: { id },
},
} = await mutate({ mutation: createTransactionLink, variables })
if (transactionLink.createdAt || transactionLink.deletedAt) {
const dbTransactionLink = await TransactionLink.findOneOrFail({ id })
if (transactionLink.createdAt) {
dbTransactionLink.createdAt = transactionLink.createdAt
dbTransactionLink.validUntil = transactionLinkExpireDate(transactionLink.createdAt)
await dbTransactionLink.save()
}
if (transactionLink.deletedAt) {
dbTransactionLink.deletedAt = new Date()
await dbTransactionLink.save()
}
}
}

View File

@ -0,0 +1,51 @@
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { User } from '@entity/User'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { ServerUser } from '@entity/ServerUser'
import { UserInterface } from '@/seeds/users/UserInterface'
import { ApolloServerTestClient } from 'apollo-server-testing'
export const userFactory = async (
client: ApolloServerTestClient,
user: UserInterface,
): Promise<void> => {
const { mutate } = client
const {
data: {
createUser: { id },
},
} = await mutate({ mutation: createUser, variables: user })
if (user.emailChecked) {
const optin = await LoginEmailOptIn.findOneOrFail({ userId: id })
await mutate({
mutation: setPassword,
variables: { password: 'Aa12345_', code: optin.verificationCode },
})
}
if (user.createdAt || user.deletedAt || user.isAdmin) {
// get user from database
const dbUser = await User.findOneOrFail({ id })
if (user.createdAt || user.deletedAt) {
if (user.createdAt) dbUser.createdAt = user.createdAt
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
await dbUser.save()
}
if (user.isAdmin) {
const admin = new ServerUser()
admin.username = dbUser.firstName
admin.password = 'please_refactor'
admin.email = dbUser.email
admin.role = 'admin'
admin.activated = 1
admin.lastLogin = new Date()
admin.created = dbUser.createdAt
admin.modified = dbUser.createdAt
await admin.save()
}
}
}

View File

@ -0,0 +1,9 @@
export const GdtEntryType = {
FORM: 'FORM',
CVS: 'CVS',
ELOPAGE: 'ELOPAGE',
ELOPAGE_PUBLISHER: 'ELOPAGE_PUBLISHER',
DIGISTORE: 'DIGISTORE',
CVS2: 'CVS2',
GLOBAL_MODIFICATOR: 'GLOBAL_MODIFICATOR',
}

View File

@ -0,0 +1,100 @@
import gql from 'graphql-tag'
export const subscribeNewsletter = gql`
mutation ($email: String!, $language: String!) {
subscribeNewsletter(email: $email, language: $language)
}
`
export const unsubscribeNewsletter = gql`
mutation ($email: String!) {
unsubscribeNewsletter(email: $email)
}
`
export const setPassword = gql`
mutation ($code: String!, $password: String!) {
setPassword(code: $code, password: $password)
}
`
export const updateUserInfos = gql`
mutation (
$firstName: String
$lastName: String
$password: String
$passwordNew: String
$locale: String
$coinanimation: Boolean
) {
updateUserInfos(
firstName: $firstName
lastName: $lastName
password: $password
passwordNew: $passwordNew
language: $locale
coinanimation: $coinanimation
)
}
`
export const createUser = gql`
mutation (
$firstName: String!
$lastName: String!
$email: String!
$language: String!
$publisherId: Int
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
language: $language
publisherId: $publisherId
) {
id
}
}
`
export const sendCoins = gql`
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
sendCoins(email: $email, amount: $amount, memo: $memo)
}
`
export const createTransactionLink = gql`
mutation ($amount: Decimal!, $memo: String!) {
createTransactionLink(amount: $amount, memo: $memo) {
id
code
}
}
`
// from admin interface
export const createPendingCreation = gql`
mutation (
$email: String!
$amount: Float!
$memo: String!
$creationDate: String!
$moderator: Int!
) {
createPendingCreation(
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
moderator: $moderator
)
}
`
export const confirmPendingCreation = gql`
mutation ($id: Float!) {
confirmPendingCreation(id: $id)
}
`

View File

@ -0,0 +1,145 @@
import gql from 'graphql-tag'
export const login = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
id
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
export const verifyLogin = gql`
query {
verifyLogin {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
export const logout = gql`
query {
logout
}
`
export const transactionsQuery = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 25
$order: Order = DESC
$onlyCreations: Boolean = false
) {
transactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
onlyCreations: $onlyCreations
) {
balanceGDT
count
balance
decayStartBlock
transactions {
id
typeId
amount
balance
balanceDate
memo
linkedUser {
firstName
lastName
}
decay {
decay
start
end
duration
}
}
}
}
`
export const sendResetPasswordEmail = gql`
query ($email: String!) {
sendResetPasswordEmail(email: $email)
}
`
export const listGDTEntriesQuery = gql`
query ($currentPage: Int!, $pageSize: Int!) {
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {
count
gdtEntries {
id
amount
date
comment
gdtEntryType
factor
gdt
}
gdtSum
}
}
`
export const communityInfo = gql`
query {
getCommunityInfo {
name
description
registerUrl
url
}
}
`
export const communities = gql`
query {
communities {
id
name
url
description
registerUrl
}
}
`
export const queryTransactionLink = gql`
query ($code: String!) {
queryTransactionLink(code: $code) {
amount
memo
createdAt
validUntil
user {
firstName
publisherId
}
}
}
`

View File

@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import createServer from '../server/createServer'
import { createTestClient } from 'apollo-server-testing'
import { name, internet, random } from 'faker'
import { users } from './users/index'
import { creations } from './creation/index'
import { transactionLinks } from './transactionLink/index'
import { userFactory } from './factory/user'
import { creationFactory } from './factory/creation'
import { transactionLinkFactory } from './factory/transactionLink'
import { entities } from '@entity/index'
const context = {
token: '',
setHeaders: {
push: (value: { key: string; value: string }): void => {
context.token = value.value
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
forEach: (): void => {},
},
}
export const cleanDB = async () => {
// this only works as lond we do not have foreign key constraints
for (let i = 0; i < entities.length; i++) {
await resetEntity(entities[i])
}
}
const resetEntity = async (entity: any) => {
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((i: any) => i.id)
await entity.delete(ids)
}
}
const run = async () => {
const server = await createServer(context)
const seedClient = createTestClient(server.apollo)
const { con } = server
await cleanDB()
// seed the standard users
for (let i = 0; i < users.length; i++) {
await userFactory(seedClient, users[i])
}
// seed 100 random users
for (let i = 0; i < 100; i++) {
await userFactory(seedClient, {
firstName: name.firstName(),
lastName: name.lastName(),
email: internet.email(),
language: random.boolean() ? 'en' : 'de',
})
}
// create GDD
for (let i = 0; i < creations.length; i++) {
await creationFactory(seedClient, creations[i])
}
// create Transaction Links
for (let i = 0; i < transactionLinks.length; i++) {
await transactionLinkFactory(seedClient, transactionLinks[i])
}
await con.close()
}
run()

View File

@ -0,0 +1,7 @@
export interface TransactionLinkInterface {
email: string
amount: number
memo: string
createdAt?: Date
deletedAt?: boolean
}

View File

@ -0,0 +1,52 @@
import { TransactionLinkInterface } from './TransactionLinkInterface'
export const transactionLinks: TransactionLinkInterface[] = [
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: new Date(2022, 0, 1),
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: `Kein Trick, keine Zauberrei,
bei Gradidio sei dabei!`,
},
{
email: 'bibi@bloxberg.de',
amount: 19.99,
memo: 'Da habe ich mich wohl etwas übernommen.',
deletedAt: true,
},
]

View File

@ -0,0 +1,12 @@
export interface UserInterface {
email?: string
firstName?: string
lastName?: string
// description?: string
createdAt?: Date
emailChecked?: boolean
language?: string
deletedAt?: Date
publisherId?: number
isAdmin?: boolean
}

View File

@ -0,0 +1,11 @@
import { UserInterface } from './UserInterface'
export const bibiBloxberg: UserInterface = {
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
// description: 'Hex Hex',
emailChecked: true,
language: 'de',
publisherId: 1234,
}

View File

@ -0,0 +1,10 @@
import { UserInterface } from './UserInterface'
export const bobBaumeister: UserInterface = {
email: 'bob@baumeister.de',
firstName: 'Bob',
lastName: 'der Baumeister',
// description: 'Können wir das schaffen? Ja, wir schaffen das!',
emailChecked: true,
language: 'de',
}

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const garrickOllivander: UserInterface = {
email: 'garrick@ollivander.com',
firstName: 'Garrick',
lastName: 'Ollivander',
// 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.`,
createdAt: new Date('2022-01-10T10:23:17'),
emailChecked: false,
language: 'en',
}

View File

@ -0,0 +1,15 @@
import { peterLustig } from './peter-lustig'
import { bibiBloxberg } from './bibi-bloxberg'
import { bobBaumeister } from './bob-baumeister'
import { raeuberHotzenplotz } from './raeuber-hotzenplotz'
import { stephenHawking } from './stephen-hawking'
import { garrickOllivander } from './garrick-ollivander'
export const users = [
peterLustig,
bibiBloxberg,
bobBaumeister,
raeuberHotzenplotz,
stephenHawking,
garrickOllivander,
]

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const peterLustig: UserInterface = {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
// description: 'Latzhose und Nickelbrille',
createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true,
language: 'de',
isAdmin: true,
}

View File

@ -0,0 +1,10 @@
import { UserInterface } from './UserInterface'
export const raeuberHotzenplotz: UserInterface = {
email: 'raeuber@hotzenplotz.de',
firstName: 'Räuber',
lastName: 'Hotzenplotz',
// description: 'Pfefferpistole',
emailChecked: true,
language: 'de',
}

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const stephenHawking: UserInterface = {
email: 'stephen@hawking.uk',
firstName: 'Stephen',
lastName: 'Hawking',
// description: 'A Brief History of Time',
emailChecked: true,
createdAt: new Date('1942-01-08T09:17:52'),
deletedAt: new Date('2018-03-14T09:17:52'),
language: 'en',
}

View File

@ -1,5 +1,4 @@
import 'reflect-metadata'
import 'module-alias/register'
import { ApolloServer } from 'apollo-server-express'
import express, { Express } from 'express'

View File

@ -41,7 +41,7 @@ const virtualLinkTransaction = (
id: -2,
userId: -1,
previous: -1,
typeId: TransactionTypeId.TRANSACTION_LINK,
typeId: TransactionTypeId.LINK_SUMMARY,
amount: amount,
balance: balance,
balanceDate: validUntil,

View File

@ -1,25 +0,0 @@
import gql from 'graphql-tag'
export const createUserMutation = gql`
mutation (
$email: String!
$firstName: String!
$lastName: String!
$language: String!
$publisherId: Int
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
language: $language
publisherId: $publisherId
)
}
`
export const setPasswordMutation = gql`
mutation ($code: String!, $password: String!) {
setPassword(code: $code, password: $password)
}
`

View File

@ -4,9 +4,6 @@
import { createTestClient } from 'apollo-server-testing'
import createServer from '../src/server/createServer'
import { initialize } from '@dbTools/helpers'
import { createUserMutation, setPasswordMutation } from './graphql'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import { entities } from '@entity/index'
export const headerPushMock = jest.fn((t) => {
@ -46,19 +43,6 @@ export const resetEntity = async (entity: any) => {
}
}
export const createUser = async (mutate: any, user: any) => {
// resetToken()
await mutate({ mutation: createUserMutation, variables: user })
const dbUser = await User.findOne({ where: { email: user.email } })
if (!dbUser) throw new Error('Ups, no user found')
const optin = await LoginEmailOptIn.findOne({ where: { userId: dbUser.id } })
if (!optin) throw new Error('Ups, no optin found')
await mutate({
mutation: setPasswordMutation,
variables: { password: 'Aa12345_', code: optin.verificationCode },
})
}
export const resetToken = () => {
context.token = ''
}

View File

@ -4,3 +4,4 @@
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.info = () => {}
jest.setTimeout(1000000)

View File

@ -45,16 +45,17 @@
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"@/*": ["./src/*"],
"@arg/*": ["./src/graphql/arg/*"],
"@dbTools/*": ["../database/src/*"],
"@entity/*": ["../database/entity/*"],
"@enum/*": ["./src/graphql/enum/*"],
"@model/*": ["./src/graphql/model/*"],
"@repository/*": ["./src/typeorm/repository/*"],
"@test/*": ["./test/*"]
"@/*": ["src/*"],
"@arg/*": ["src/graphql/arg/*"],
"@enum/*": ["src/graphql/enum/*"],
"@model/*": ["src/graphql/model/*"],
"@repository/*": ["src/typeorm/repository/*"],
"@test/*": ["test/*"],
/* external */
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
@ -82,7 +83,7 @@
{
"path": "../database/tsconfig.json",
// add 'prepend' if you want to include the referenced project in your output file
// "prepend": true,
// "prepend": true
}
]
}

View File

@ -811,6 +811,11 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/faker@^5.5.9":
version "5.5.9"
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.5.9.tgz#588ede92186dc557bff8341d294335d50d255f0c"
integrity sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA==
"@types/fs-capacitor@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e"
@ -2510,6 +2515,11 @@ express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"
faker@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -4092,11 +4102,6 @@ minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
module-alias@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -5232,6 +5237,16 @@ tsconfig-paths@^3.11.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
tsconfig-paths@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz#4fcc48f9ccea8826c41b9ca093479de7f5018976"
integrity sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.1"
minimist "^1.2.0"
strip-bom "^3.0.0"
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"

View File

@ -1,3 +1,4 @@
node_modules
.git
.gitignore
.gitignore
!.eslintignore

View File

@ -1,3 +1,3 @@
node_modules
**/*.min.js
dist
node_modules/
dist/
coverage/

View File

@ -8,9 +8,24 @@ module.exports = {
parserOptions: {
parser: 'babel-eslint',
},
extends: ['standard', 'plugin:vue/essential', 'plugin:prettier/recommended'],
extends: [
'standard',
'plugin:vue/essential',
'plugin:prettier/recommended',
'plugin:@intlify/vue-i18n/recommended',
],
// required to lint *.vue files
plugins: ['vue', 'prettier', 'jest'],
overrides: [
{
files: ['*.json'],
extends: ['plugin:@intlify/vue-i18n/recommended'],
rules: {
// TODO: enable
'@intlify/vue-i18n/no-html-messages': 'off',
},
},
],
// add your custom rules here
rules: {
'no-console': ['error'],
@ -22,6 +37,29 @@ module.exports = {
allowBinding: false,
},
],
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/no-unused-keys': [
'error',
{
src: './src',
extensions: ['.js', '.vue'],
// TODO: remove ignores
ignores: [
'/site.thx./',
'/form./',
'/time./',
'/decay.types./',
'settings.password.resend_subtitle',
'settings.password.reset-password.text',
'settings.password.set',
'settings.password.set-password.text',
'settings.password.subtitle',
'site.login.signin',
],
enableFix: false,
},
],
'@intlify/vue-i18n/no-missing-keys-in-other-locales': 'error',
'prettier/prettier': [
'error',
{
@ -29,4 +67,12 @@ module.exports = {
},
],
},
settings: {
'vue-i18n': {
localeDir: './src/locales/*.json',
// Specify the version of `vue-i18n` you are using.
// If not specified, the message will be parsed twice.
messageSyntaxVersion: '^8.22.4',
},
},
}

1
frontend/.gitignore vendored
View File

@ -5,7 +5,6 @@ dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
test/unit/coverage
package-lock.json
/.env

View File

@ -8,11 +8,10 @@
"build": "vue-cli-service build",
"dev": "yarn run serve",
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue .",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
"test": "TZ=UTC jest --coverage",
"locales": "scripts/missing-keys.sh && scripts/sort.sh"
"locales": "scripts/sort.sh"
},
"dependencies": {
"@babel/core": "^7.13.13",
@ -67,6 +66,7 @@
"vuex-persistedstate": "^4.0.0-beta.3"
},
"devDependencies": {
"@intlify/eslint-plugin-vue-i18n": "^1.4.0",
"@vue/cli-plugin-babel": "^3.7.0",
"@vue/cli-plugin-eslint": "^3.7.0",
"@vue/cli-service": "^3.7.0",

View File

@ -52,10 +52,6 @@
</head>
<body>
<div class="wrapper" id="app">
</div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,36 +0,0 @@
{"state":"success", "statisticdata":
[
{
"i_creation_sum": 29288278.4653,
"i_creation_mon": 68278.4653,
"i_creation_365d": 19288278.4653,
"i_creation_30d": 288278.4653,
"i_creation_14d": 88278.4653,
"i_creation_7d": 3278.4653
},
{
"i_transience_sum": 8278.4653,
"i_transience_mon": 8278.4653,
"i_transience_365d": 8278.4653,
"i_transience_30d": 178.4653,
"i_transience_14d": 78.4653,
"i_transience_7d": 8.4653
},
{
"i_exchange_sum": 23345.2324,
"i_exchange_mon": 2,
"i_exchange_365d": 2,
"i_exchange_30d": 2,
"i_exchange_14d": 2,
"i_exchange_7d": 2
},
{
"i_members_sum": 5398,
"i_members_mon": 234,
"i_members_365d": 2356,
"i_members_30d": 123,
"i_members_14d": 23,
"i_members_7d": 24
}
]
}

View File

@ -1,64 +0,0 @@
{"state":"success", "statisticdata":
[
{
"charts_creation_0_mon": 5635,
"charts_creation_1_mon": 5635,
"charts_creation_2_mon": 5635,
"charts_creation_3_mon": 5635,
"charts_creation_4_mon": 5635,
"charts_creation_5_mon": 5635,
"charts_creation_6_mon": 5635,
"charts_creation_7_mon": 5635,
"charts_creation_8_mon": 5635,
"charts_creation_9_mon": 5635,
"charts_creation_10_mon": 5635,
"charts_creation_11_mon": 5635,
"charts_creation_12_mon": 5635
},
{
"charts_transience_0_mon": 5635,
"charts_transience_1_mon": 5635,
"charts_transience_2_mon": 5635,
"charts_transience_3_mon": 5635,
"charts_transience_4_mon": 5635,
"charts_transience_5_mon": 5635,
"charts_transience_6_mon": 5635,
"charts_transience_7_mon": 5635,
"charts_transience_8_mon": 5635,
"charts_transience_9_mon": 5635,
"charts_transience_10_mon": 5635,
"charts_transience_11_mon": 5635,
"charts_transience_12_mon": 5635
},
{
"charts_exchange_0_mon": 5635,
"charts_exchange_1_mon": 5635,
"charts_exchange_2_mon": 5635,
"charts_exchange_3_mon": 5635,
"charts_exchange_4_mon": 5635,
"charts_exchange_5_mon": 5635,
"charts_exchange_6_mon": 5635,
"charts_exchange_7_mon": 5635,
"charts_exchange_8_mon": 5635,
"charts_exchange_9_mon": 5635,
"charts_exchange_10_mon": 5635,
"charts_exchange_11_mon": 5635,
"charts_exchange_12_mon": 5635
},
{
"charts_members_0_mon": 5635,
"charts_members_1_mon": 5635,
"charts_members_2_mon": 5635,
"charts_members_3_mon": 5635,
"charts_members_4_mon": 5635,
"charts_members_5_mon": 5635,
"charts_members_6_mon": 5635,
"charts_members_7_mon": 5635,
"charts_members_8_mon": 5635,
"charts_members_9_mon": 5635,
"charts_members_10_mon": 5635,
"charts_members_11_mon": 5635,
"charts_members_12_mon": 5635
}
]
}

View File

@ -1,19 +0,0 @@
{"state":"success", "statisticdata":
[
{
"community_entries1_mon": 5635,
"community_entries2_mon": 5635,
"community_entries3_mon": 5635,
"community_entries4_mon": 5635,
"community_entries5_mon": 5635,
"community_entries6_mon": 5635,
"community_entries7_mon": 5635,
"community_entries8_mon": 5635,
"community_entries9_mon": 5635,
"community_entries0_mon": 5635,
"community_entries10_mon": 5635,
"community_entries11_mon": 5635,
"community_entries12_mon": 5635
}
]
}

View File

@ -1,61 +0,0 @@
{"state":"success", "transactions":
[
{
"name": "Jon Tester",
"email": "jon@example.de",
"type": "send",
"transaction_id": 12,
"date": "29-11-2020",
"balance": 7000,
"memo": "Reperatur Waschbecken",
"pubkey": "abcdefghi123456789"
},
{
"name": "Gradido Community",
"email": "gradido@example.de",
"type": "creation",
"transaction_id": 11,
"date": "1-11-2020",
"balance": 10000,
"memo": "Müll gesammelt im Wald",
"pubkey": "abcdefghi123456789"
},
{
"name": "Maria Tester",
"email": "maria@example.de",
"type": "receive",
"transaction_id": 7,
"date": "23-10-2020",
"balance": 5000,
"memo": "Spende an Alice ",
"pubkey": "abcdefghi123456789"
},
{
"name": "Alice Tester",
"email": "alice@example.de",
"type": "receive",
"transaction_id": 5,
"date": "2-8-2020",
"balance": 1000,
"memo": "Bob hat meinen Müll getrennt",
"pubkey": "abcdefghi123456789"
},
{
"name": "Gradido Community",
"email": "gradido@example.de",
"type": "creation",
"transaction_id": 1,
"date": "11-7-2020",
"balance": 10000,
"memo": "Bob hat meinen Müll getrennt",
"pubkey": "abcdefghi123456789"
}
],
"transactionExecutingCount": 8750,
"count": 5
}

View File

@ -1,49 +0,0 @@
{"state":"success", "participation":
[
{
"name": "",
"type": "submitted",
"participation_id": 412,
"date_submitted": "9-12-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "8-12-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"name": "",
"type": "in progress",
"participation_id": 312,
"date_submitted": "2-11-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "2-11-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"name": "",
"type": "confirmed",
"participation_id": 212,
"date_submitted": "20-10-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "20-10-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"name": "",
"type": "rejected",
"participation_id": 142,
"date_submitted": "17-9-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "17-9-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
}
],
"count": 4
}

View File

@ -1,52 +0,0 @@
{"state":"success", "admin0userlist":
[
{
"created": 1578688666,
"disabled": false,
"email": "dervommond@gmail.com",
"email_checked": true,
"first_name": "Max",
"group_alias": "gdd1",
"ident_hash": 2928827813,
"last_name": "Miau",
"public_hex": "2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b",
"role": "admin",
"username": "",
"balance_gdd": 174500,
"balance_gdt": 4500,
"errorCount": 0
},
{
"created": 1578685678,
"disabled": false,
"email": "ttwer@gmail.com",
"email_checked": true,
"first_name": "John",
"group_alias": "gdd1",
"ident_hash": 2928827813,
"last_name": "Doe",
"public_hex": "2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71x",
"role": "user",
"username": "",
"balance_gdd": 144500,
"balance_gdt": 0,
"errorCount": 0
},
{
"created": 1578635671,
"disabled": false,
"email": "test@gmail.com",
"email_checked": true,
"first_name": "Alice",
"group_alias": "gdd1",
"ident_hash": 4928827813,
"last_name": "Seer",
"public_hex": "2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71a",
"role": "user",
"username": "",
"balance_gdd": 444500,
"balance_gdt": 4500,
"errorCount": 0
}
]
}

View File

@ -1,19 +0,0 @@
{"state":"success", "userdata":
[
{
"created": 1578688666,
"disabled": false,
"email": "dervommond@gmail.com",
"email_checked": true,
"first_name": "Max",
"group_alias": "gdd1",
"ident_hash": 2928827813,
"last_name": "Miau",
"public_hex": "2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b",
"role": "admin",
"username": "",
"balance": 174500,
"errorCount": 0
}
]
}

View File

@ -1,45 +0,0 @@
{"state":"success", "participation":
[
{
"type": "submitted",
"participation_id": 412,
"date_submitted": "9-12-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "8-12-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"type": "in progress",
"participation_id": 312,
"date_submitted": "2-11-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "2-11-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"type": "confirmed",
"participation_id": 212,
"date_submitted": "20-10-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "20-10-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
},
{
"type": "rejected",
"participation_id": 142,
"date_submitted": "17-9-2020",
"titel": "Lorem Ipsum panta lore es Tastina sero was. ",
"text": "Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. Lorem Ipsum panta lore es Tastina sero was. ",
"date_participation": "17-9-2020",
"plz_participation": "01099",
"pubkey": "abcdefghi123456789"
}
],
"count": 4
}

View File

@ -1,61 +0,0 @@
{"state":"success", "transactions":
[
{
"name": "Jon Tester",
"email": "jon@example.de",
"type": "send",
"transaction_id": 12,
"date": "29-11-2020",
"balance": 7000,
"memo": "Reperatur Waschbecken",
"pubkey": "abcdefghi123456789"
},
{
"name": "Gradido Community",
"email": "gradido@example.de",
"type": "creation",
"transaction_id": 11,
"date": "1-11-2020",
"balance": 10000,
"memo": "Müll gesammelt im Wald",
"pubkey": "abcdefghi123456789"
},
{
"name": "Maria Tester",
"email": "maria@example.de",
"type": "receive",
"transaction_id": 7,
"date": "23-10-2020",
"balance": 5000,
"memo": "Spende an Alice ",
"pubkey": "abcdefghi123456789"
},
{
"name": "Alice Tester",
"email": "alice@example.de",
"type": "receive",
"transaction_id": 5,
"date": "2-8-2020",
"balance": 1000,
"memo": "Bob hat meinen Müll getrennt",
"pubkey": "abcdefghi123456789"
},
{
"name": "Gradido Community",
"email": "gradido@example.de",
"type": "creation",
"transaction_id": 1,
"date": "11-7-2020",
"balance": 10000,
"memo": "Bob hat meinen Müll getrennt",
"pubkey": "abcdefghi123456789"
}
],
"transactionExecutingCount": 8750,
"count": 5
}

View File

@ -1,17 +0,0 @@
#!/bin/bash
ROOT_DIR=$(dirname "$0")/..
sorting="jq -f $ROOT_DIR/scripts/sort_filter.jq"
english="$sorting $ROOT_DIR/src/locales/en.json"
german="$sorting $ROOT_DIR/src/locales/de.json"
listPaths="jq -c 'path(..)|[.[]|tostring]|join(\".\")'"
diffString="<( $english | $listPaths ) <( $german | $listPaths )"
if eval "diff -q $diffString";
then
: # all good
else
eval "diff -y $diffString | grep '[|<>]'";
printf "\nEnglish and German translation keys do not match, see diff above.\n"
exit 1
fi

View File

@ -11,7 +11,7 @@ describe('ContentFooter', () => {
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
$t: jest.fn((t, options) => (options ? [t, options] : t)),
}
const Wrapper = () => {
@ -33,11 +33,11 @@ describe('ContentFooter', () => {
})
it('renders the copyright year', () => {
expect(wrapper.find('div.copyright').text()).toMatch(/©\s*2[0-9]{3,3}\s+/)
expect(mocks.$t).toBeCalledWith('footer.copyright.year', { year: 2022 })
})
it('renders a link to Gradido-Akademie', () => {
expect(wrapper.find('div.copyright').find('a').text()).toEqual('Gradido-Akademie')
expect(wrapper.find('div.copyright').find('a').text()).toEqual('footer.copyright.link')
})
it('links to the login page when clicked on copyright', () => {
@ -51,7 +51,7 @@ describe('ContentFooter', () => {
it('shows the current version', async () => {
wrapper.setData({ version: 1.23 })
await wrapper.vm.$nextTick()
expect(wrapper.find('div.copyright').findAll('a').at(1).text()).toEqual('App version 1.23')
expect(mocks.$t).toBeCalledWith('footer.app_version', { version: 1.23 })
})
it('links to latest release on GitHub', () => {
@ -64,7 +64,7 @@ describe('ContentFooter', () => {
wrapper.setData({ shortHash: 'ACCEDED' })
wrapper.setData({ hash: 'ACCEDEDC001D00DC001D00DC001D00DC001CAFA' })
await wrapper.vm.$nextTick()
expect(wrapper.find('div.copyright').findAll('a').at(2).text()).toEqual('(ACCEDED)')
expect(mocks.$t).toBeCalledWith('footer.short_hash', { shortHash: 'ACCEDED' })
})
it('links to last release commit', async () => {
@ -78,7 +78,7 @@ describe('ContentFooter', () => {
describe('links to gradido.net', () => {
it('has a link to the legal notice', () => {
expect(wrapper.findAll('a.nav-link').at(0).text()).toEqual('imprint')
expect(wrapper.findAll('a.nav-link').at(0).text()).toEqual('footer.imprint')
})
it('links to the https://gradido.net/en/impressum when locale is en', () => {
@ -88,7 +88,7 @@ describe('ContentFooter', () => {
})
it('has a link to the privacy policy', () => {
expect(wrapper.findAll('a.nav-link').at(1).text()).toEqual('privacy_policy')
expect(wrapper.findAll('a.nav-link').at(1).text()).toEqual('footer.privacy_policy')
})
it('links to the https://gradido.net/en/datenschutz when locale is en', () => {
@ -98,7 +98,7 @@ describe('ContentFooter', () => {
})
it('has a link to the members area', () => {
expect(wrapper.findAll('a.nav-link').at(2).text()).toEqual('members_area')
expect(wrapper.findAll('a.nav-link').at(2).text()).toEqual('navigation.members_area')
})
it('links to the elopage', () => {

View File

@ -3,24 +3,24 @@
<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 }}
{{ $t('footer.copyright.year', { year }) }}
<a
:href="`https://gradido.net/${$i18n.locale}`"
class="font-weight-bold ml-1"
target="_blank"
>
Gradido-Akademie
{{ $t('footer.copyright.link') }}
</a>
|
{{ $t('math.pipe') }}
<a href="https://github.com/gradido/gradido/releases/latest" target="_blank">
App version {{ version }}
{{ $t('footer.app_version', { version }) }}
</a>
<a
v-if="hash"
:href="'https://github.com/gradido/gradido/commit/' + hash"
target="_blank"
>
({{ shortHash }})
{{ $t('footer.short_hash', { shortHash }) }}
</a>
</div>
</b-col>
@ -29,16 +29,16 @@
<b-col>
<b-nav class="nav-footer justify-content-center">
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/impressum/`" target="_blank">
{{ $t('imprint') }}
{{ $t('footer.imprint') }}
</b-nav-item>
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/datenschutz/`" target="_blank">
{{ $t('privacy_policy') }}
{{ $t('footer.privacy_policy') }}
</b-nav-item>
<b-nav-item
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
target="_blank"
>
{{ $t('members_area') }}
{{ $t('navigation.members_area') }}
</b-nav-item>
<b-nav-item
:href="
@ -48,10 +48,10 @@
"
target="_blank"
>
{{ $t('whitepaper') }}
{{ $t('footer.whitepaper') }}
</b-nav-item>
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/contact/`" target="_blank">
{{ $t('site.navbar.support') }}
{{ $t('navigation.support') }}
</b-nav-item>
</b-nav>
</b-col>

View File

@ -0,0 +1,126 @@
import { mount } from '@vue/test-utils'
import CollapseLinksList from './CollapseLinksList'
const localVue = global.localVue
const mocks = {
$i18n: {
locale: 'en',
},
$tc: jest.fn((tc) => tc),
$t: jest.fn((t) => t),
}
const propsData = {
transactionLinks: [
{
amount: '5',
code: 'ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 87,
memo: 'Eene meene Siegerpreis, vor mir steht ein Schokoeis. Hex-hex!',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
{
amount: '6',
code: 'ce28664b5308c17f931c0367',
createdAt: '2022-03-16T14:22:40.000Z',
holdAvailableAmount: '5.13109484759482747111',
id: 86,
memo: 'Eene meene buntes Laub, auf dem Schrank da liegt kein Staub.',
redeemedAt: null,
validUntil: '2022-03-30T14:22:40.000Z',
},
],
transactionLinkCount: 3,
value: 1,
pending: false,
pageSize: 5,
}
describe('CollapseLinksList', () => {
let wrapper
const Wrapper = () => {
return mount(CollapseLinksList, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component div.collapse-links-list', () => {
expect(wrapper.find('div.collapse-links-list').exists()).toBeTruthy()
})
describe('load more links', () => {
beforeEach(async () => {
await wrapper.find('button.test-button-load-more').trigger('click')
})
it('emits input', () => {
expect(wrapper.emitted('input')).toEqual([[2]])
})
})
describe('reset transaction link list', () => {
beforeEach(async () => {
await wrapper
.findComponent({ name: 'TransactionLink' })
.vm.$emit('reset-transaction-link-list')
})
it('emits input ', () => {
expect(wrapper.emitted('input')).toEqual([[0]])
})
})
describe('button text', () => {
describe('one more link to load', () => {
beforeEach(async () => {
await wrapper.setProps({
value: 1,
pending: false,
pageSize: 5,
})
})
it('renders text in singular', () => {
expect(mocks.$tc).toBeCalledWith('link-load', 0)
})
})
describe('less than pageSize links to load', () => {
beforeEach(async () => {
await wrapper.setProps({
value: 1,
pending: false,
pageSize: 5,
transactionLinkCount: 6,
})
})
it('renders text in plural and shows the correct count of links', () => {
expect(mocks.$tc).toBeCalledWith('link-load', 1, { n: 4 })
})
})
describe('more than pageSize links to load', () => {
beforeEach(async () => {
await wrapper.setProps({
value: 1,
pending: false,
pageSize: 5,
transactionLinkCount: 16,
})
})
it('renders text in plural with page size links to load', () => {
expect(mocks.$tc).toBeCalledWith('link-load', 2, { n: 5 })
})
})
})
})
})

View File

@ -1,14 +1,66 @@
<template>
<div class="collapse-links-list">
<div class="d-flex">
<div class="text-center pb-3 gradido-max-width">
<b>{{ $t('links-list.header') }}</b>
<div class="gradido-max-width">
<hr />
<div>
<transaction-link
v-for="item in transactionLinks"
:key="item.id"
v-bind="item"
@reset-transaction-link-list="resetTransactionLinkList"
/>
<div class="mb-3">
<b-button
class="test-button-load-more"
v-if="!pending && transactionLinks.length < transactionLinkCount"
block
variant="outline-primary"
@click="loadMoreLinks"
>
{{ buttonText }}
</b-button>
<div class="text-center">
<b-icon v-if="pending" icon="three-dots" animation="cylon" font-scale="4"></b-icon>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import TransactionLink from '@/components/TransactionLinks/TransactionLink.vue'
export default {
name: 'CollapseLinksList',
components: {
TransactionLink,
},
props: {
transactionLinks: { type: Array, required: true },
transactionLinkCount: {
type: Number,
required: true,
},
value: { type: Number, required: true },
pageSize: { type: Number, default: 5 },
pending: { type: Boolean, default: false },
},
methods: {
resetTransactionLinkList() {
this.$emit('input', 0)
},
loadMoreLinks() {
this.$emit('input', this.value + 1)
},
},
computed: {
buttonText() {
const i = this.transactionLinkCount - this.transactionLinks.length
if (i === 1) return this.$tc('link-load', 0)
if (i <= this.pageSize) return this.$tc('link-load', 1, { n: i })
return this.$tc('link-load', 2, { n: this.pageSize })
},
},
}
</script>

View File

@ -13,8 +13,8 @@
</b-col>
<b-col cols="6">
<div>
{{ (Number(balance) - Number(decay.decay)) | GDD }}
{{ decay.decay | GDD }} =
{{ (Number(balance) - Number(decay)) | GDD }}
{{ decay | GDD }} {{ $t('math.equal') }}
<b>{{ balance | GDD }}</b>
</div>
</b-col>
@ -27,9 +27,11 @@ export default {
props: {
balance: {
type: String,
required: true,
},
decay: {
type: Object,
type: String,
required: true,
},
},
}

View File

@ -4,12 +4,11 @@
<b-col cols="12" class="text-center">
<div>
<div class="display-4">{{ $t('decay.Starting_block_decay') }}</div>
<div>{{ $t('decay.decay_introduced') }} :</div>
<div>{{ $t('decay.decay_introduced') }}</div>
</div>
<div>
<span v-if="decay.start">
{{ $d(new Date(decay.start), 'long') }}
{{ $i18n.locale === 'de' ? 'Uhr' : '' }}
</span>
</div>
</b-col>
@ -28,7 +27,8 @@
</b-row>
<!-- Type-->
<b-row>
<b-col cols="6" class="text-right">{{ $t(`decay.${typeId.toLowerCase()}`) }}</b-col>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
<b-col cols="6" class="text-right">{{ $t(`decay.types.${typeId.toLowerCase()}`) }}</b-col>
<b-col cols="6">{{ amount | GDD }}</b-col>
</b-row>
<!-- Decay-->

View File

@ -15,7 +15,6 @@
<div>
<span>
{{ $d(new Date(decay.start), 'long') }}
{{ $i18n.locale === 'de' ? 'Uhr' : '' }}
</span>
</div>
</b-col>
@ -44,7 +43,8 @@
</b-row>
<!-- Type-->
<b-row>
<b-col cols="6" class="text-right">{{ $t(`decay.${typeId.toLowerCase()}`) }}</b-col>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys-->
<b-col cols="6" class="text-right">{{ $t(`decay.types.${typeId.toLowerCase()}`) }}</b-col>
<b-col cols="6">{{ amount | GDD }}</b-col>
</b-row>
<!-- Decay-->
@ -82,7 +82,8 @@ export default {
const result = []
order.forEach((timeSpan) => {
if (this.duration[timeSpan] > 0) {
const locale = this.$t(`decay.${timeSpan}`)
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
const locale = this.$t(`time.${timeSpan}`)
result.push(`${this.duration[timeSpan]} ${locale}`)
}
})

View File

@ -1,6 +1,6 @@
<template>
<div class="decayinformation-short">
<span v-if="decay.decay">{{ decay.decay | GDD }}</span>
<span v-if="decay">{{ decay | GDD }}</span>
</div>
</template>
<script>
@ -8,7 +8,8 @@ export default {
name: 'DecayInformation-Short',
props: {
decay: {
type: Object,
type: String,
required: true,
},
},
}

View File

@ -1,5 +1,6 @@
<template>
<b-alert show variant="secondary">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-v-html -->
<span class="alert-text" v-html="$t('form.scann_code')"></span>
<b-col v-show="!scan" lg="12" class="text-right">
<a @click="toggle" class="nav-link pointer">
@ -18,6 +19,7 @@
<b-row>
<b-col lg="8">
<b-alert show variant="secondary">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-v-html -->
<span class="alert-text" v-html="$t('form.scann_code')"></span>
</b-alert>
</b-col>

View File

@ -28,12 +28,14 @@
<strong>{{ $t('gdd_per_link.decay-14-day') }}</strong>
</b-col>
<b-col class="text-right borderbottom">
<strong>~ {{ (amount * 0.028 * -1) | GDD }}</strong>
<strong>{{ $t('math.aprox') }} {{ (amount * 0.028 * -1) | GDD }}</strong>
</b-col>
</b-row>
<b-row class="pr-3">
<b-col class="text-right">{{ $t('form.new_balance') }}</b-col>
<b-col class="text-right">~ {{ (balance - amount - amount * 0.028) | GDD }}</b-col>
<b-col class="text-right">
{{ $t('math.aprox') }} {{ (balance - amount - amount * 0.028) | GDD }}
</b-col>
</b-row>
</b-container>

View File

@ -15,7 +15,7 @@
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
<b-input-group id="input-group-2" class="borderbottom" size="lg">
<b-input-group-prepend class="p-2 d-none d-md-block gray-background">
<div class="m-1 mt-2">GDD</div>
<div class="m-1 mt-2">{{ $t('GDD') }}</div>
</b-input-group-prepend>
<div class="p-3">{{ (amount * -1) | GDD }}</div>
@ -27,7 +27,7 @@
<b-input-group-prepend class="d-none d-md-block gray-background">
<b-icon icon="chat-right-text" class="display-4 m-3 mt-4"></b-icon>
</b-input-group-prepend>
<div class="p-3">{{ memo ? memo : '-' }}</div>
<div class="p-3">{{ memo ? memo : $t('em-dash') }}</div>
</b-input-group>
</b-list-group>
</b-col>

View File

@ -76,7 +76,7 @@
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
<b-input-group id="input-group-2" class="border border-default" size="lg">
<b-input-group-prepend class="p-2 d-none d-md-block">
<div class="m-1 mt-2">GDD</div>
<div class="m-1 mt-2">{{ $t('GDD') }}</div>
</b-input-group-prepend>
<b-form-input

View File

@ -19,7 +19,7 @@
>
{{ $t('transaction.receiverDeleted') }}
</div>
<div v-else>({{ errorResult }})</div>
<div v-else>{{ errorResult }}</div>
</div>
<p class="text-center mt-3">
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>

View File

@ -112,7 +112,8 @@ describe('GddTransactionList', () => {
amount: '1',
balance: '31.76099091058520945292',
balanceDate: '2022-02-28T13:55:47',
memo: 'adasd adada',
memo:
'Um den Kessel schlingt den Reihn, Werft die Eingeweid hinein. Kröte du, die Nacht und Tag Unterm kalten Steine lag,',
linkedUser: {
firstName: 'Bibi',
lastName: 'Bloxberg',
@ -124,31 +125,14 @@ describe('GddTransactionList', () => {
duration: 282381,
},
},
{
id: 8,
typeId: 'CREATION',
amount: '1000',
balance: '32.96482231613347376132',
balanceDate: '2022-02-25T07:29:26',
memo: 'asd adada dad',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
decay: {
decay: '-0.03517768386652623868',
start: '2022-02-23T10:55:30',
end: '2022-02-25T07:29:26',
duration: 160436,
},
},
{
id: 6,
typeId: 'RECEIVE',
amount: '10',
balance: '10',
balanceDate: '2022-02-23T10:55:30',
memo: 'asd adaaad adad addad ',
memo:
'Monatlanges Gift sog ein, In den Topf zuerst hinein… (William Shakespeare, Die Hexen aus Macbeth)',
linkedUser: {
firstName: 'Bibi',
lastName: 'Bloxberg',
@ -160,6 +144,24 @@ describe('GddTransactionList', () => {
duration: null,
},
},
{
id: 8,
typeId: 'CREATION',
amount: '1000',
balance: '32.96482231613347376132',
balanceDate: '2022-02-25T07:29:26',
memo: 'Jammern hilft nichts, sondern ich kann selber meinen Teil dazu beitragen.',
linkedUser: {
firstName: 'Gradido',
lastName: 'Akademie',
},
decay: {
decay: '-0.03517768386652623868',
start: '2022-02-23T10:55:30',
end: '2022-02-25T07:29:26',
duration: 160436,
},
},
],
count: 12,
decayStartBlock,
@ -250,7 +252,7 @@ describe('GddTransactionList', () => {
it('shows the message of the transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-message').at(0).text()).toContain(
'adasd adada',
'Um den Kessel schlingt den Reihn, Werft die Eingeweid hinein. Kröte du, die Nacht und Tag Unterm kalten Steine lag,',
)
})
@ -285,7 +287,7 @@ describe('GddTransactionList', () => {
it('has a bi-gift icon', () => {
expect(transaction.findAll('svg').at(1).classes()).toEqual([
'bi-gift',
'bi-arrow-right-circle',
'm-mb-1',
'font2em',
'b-icon',
@ -296,7 +298,7 @@ describe('GddTransactionList', () => {
it('has gradido-global-color-accent color', () => {
expect(transaction.findAll('svg').at(1).classes()).toEqual([
'bi-gift',
'bi-arrow-right-circle',
'm-mb-1',
'font2em',
'b-icon',
@ -314,19 +316,19 @@ describe('GddTransactionList', () => {
it('shows the amount of transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-amount').at(0).text()).toContain(
'1000',
'+ 10 GDD',
)
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('.gdd-transaction-list-item-name').at(0).text()).toContain(
'Gradido Akademie',
'Bibi Bloxberg',
)
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-date').at(0).text()).toContain(
'Fri Feb 25 2022 07:29:26 GMT+0000',
'Wed Feb 23 2022 10:55:30 GMT+0000',
)
})
})
@ -347,12 +349,19 @@ describe('GddTransactionList', () => {
})
it('has a bi-arrow-right-circle icon', () => {
expect(transaction.findAll('svg').at(1).classes()).toContain('bi-arrow-right-circle')
expect(transaction.findAll('svg').at(1).classes()).toEqual([
'bi-gift',
'm-mb-1',
'font2em',
'b-icon',
'bi',
'gradido-global-color-accent',
])
})
it('has gradido-global-color-accent color', () => {
expect(transaction.findAll('svg').at(1).classes()).toEqual([
'bi-arrow-right-circle',
'bi-gift',
'm-mb-1',
'font2em',
'b-icon',
@ -376,19 +385,19 @@ describe('GddTransactionList', () => {
it('shows the name of the recipient', () => {
expect(transaction.findAll('.gdd-transaction-list-item-name').at(0).text()).toContain(
'Bibi Bloxberg',
'Gradido Akademie',
)
})
it('shows the message of the transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-message').at(0).text()).toContain(
'asd adaaad adad addad',
'Jammern hilft nichts, sondern ich kann selber meinen Teil dazu beitragen.',
)
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('.gdd-transaction-list-item-date').at(0).text()).toContain(
'Wed Feb 23 2022 10:55:30 GMT+0000',
'Fri Feb 25 2022 07:29:26 GMT+0000',
)
})
@ -440,7 +449,7 @@ describe('GddTransactionList', () => {
})
it('shows the text "1 / 2"', () => {
expect(paginationButtons.find('p.text-center').text()).toBe('1 / 2')
expect(paginationButtons.find('p.text-center').text()).toBe('1 math.div 2')
})
it('emits update-transactions when next button is clicked', async () => {
@ -452,7 +461,7 @@ describe('GddTransactionList', () => {
it('shows text "2 / 2" when next button is clicked', async () => {
await paginationButtons.find('button.next-page').trigger('click')
expect(paginationButtons.find('p.text-center').text()).toBe('2 / 2')
expect(paginationButtons.find('p.text-center').text()).toBe('2 math.div 2')
})
it('has next-button disabled when next button is clicked', async () => {

View File

@ -42,11 +42,12 @@
/>
</template>
<template #TRANSACTION_LINK>
<transaction-link
<template #LINK_SUMMARY>
<transaction-link-summary
class="list-group-item"
v-bind="transactions[index]"
:transactionLinkCount="transactionLinkCount"
@update-transactions="updateTransactions"
/>
</template>
</transaction-list-item>
@ -71,7 +72,7 @@ import TransactionDecay from '@/components/Transactions/TransactionDecay'
import TransactionSend from '@/components/Transactions/TransactionSend'
import TransactionReceive from '@/components/Transactions/TransactionReceive'
import TransactionCreation from '@/components/Transactions/TransactionCreation'
import TransactionLink from '@/components/Transactions/TransactionLink'
import TransactionLinkSummary from '@/components/Transactions/TransactionLinkSummary'
export default {
name: 'gdd-transaction-list',
@ -82,7 +83,7 @@ export default {
TransactionSend,
TransactionReceive,
TransactionCreation,
TransactionLink,
TransactionLinkSummary,
},
data() {
return {

View File

@ -2,10 +2,12 @@
<div>
<b-list-group>
<b-list-group-item v-if="count > 5">
<!-- eslint-disable @intlify/vue-i18n/no-v-html -->
<router-link
to="/transactions"
v-html="$t('transaction.show_all', { count: count })"
v-html="$t('transaction.show_all', { count })"
></router-link>
<!-- eslint-enable @intlify/vue-i18n/no-v-html -->
</b-list-group-item>
</b-list-group>
</div>

View File

@ -54,31 +54,31 @@ describe('Navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('1234 GDD')
})
it('has first nav-item "overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('overview')
it('has first nav-item "navigation.overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('navigation.overview')
})
it('has first nav-item "send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('send')
it('has first nav-item "navigation.send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toEqual('navigation.send')
})
it('has first nav-item "transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('transactions')
it('has first nav-item "navigation.transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.transactions')
})
it('has first nav-item "my-profil" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('site.navbar.my-profil')
it('has first nav-item "navigation.profile" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.profile')
})
it('has a link to the members area', () => {
expect(wrapper.findAll('.nav-item').at(7).text()).toContain('members_area')
expect(wrapper.findAll('.nav-item').at(7).text()).toContain('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(7).find('a').attributes('href')).toBe(
'https://elopage.com',
)
})
it('has first nav-item "admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('admin_area')
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(8).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('logout')
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(9).text()).toEqual('navigation.logout')
})
})
})

View File

@ -10,10 +10,10 @@
</div>
<b-navbar-nav class="ml-auto" is-nav>
<b-nav-item>{{ pending ? '—' : balance | amount }} GDD</b-nav-item>
<b-nav-item>{{ pending ? $t('em-dash') : balance | amount }} {{ $t('GDD') }}</b-nav-item>
<b-nav-item to="/profile" right class="d-none d-sm-none d-md-none d-lg-flex shadow-lg">
<small>
{{ $store.state.firstName }} {{ $store.state.lastName }},
{{ $store.state.firstName }} {{ $store.state.lastName }}
<b>{{ $store.state.email }}</b>
<b-icon class="ml-3" icon="gear-fill" aria-hidden="true"></b-icon>
</small>
@ -32,40 +32,42 @@
<b-link to="/profile">
<small>
{{ $store.state.firstName }}
{{ $store.state.lastName }},
{{ $store.state.lastName }}
<b>{{ $store.state.email }}</b>
</small>
</b-link>
</div>
<b-nav-item to="/overview" class="mb-3">
<b-icon icon="house" aria-hidden="true"></b-icon>
{{ $t('overview') }}
{{ $t('navigation.overview') }}
</b-nav-item>
<b-nav-item to="/send" class="mb-3">
<b-icon icon="arrow-left-right" aria-hidden="true"></b-icon>
{{ $t('send') }}
{{ $t('navigation.send') }}
</b-nav-item>
<b-nav-item to="/transactions" class="mb-3">
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
{{ $t('transactions') }}
{{ $t('navigation.transactions') }}
</b-nav-item>
<b-nav-item to="/profile" class="mb-3">
<b-icon icon="gear" aria-hidden="true"></b-icon>
{{ $t('site.navbar.my-profil') }}
{{ $t('navigation.profile') }}
</b-nav-item>
<br />
<b-nav-item :href="elopageUri" class="mb-3" target="_blank">
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
{{ $t('members_area') }}
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
{{ $t('navigation.members_area') }}
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
{{ $t('math.exclaim') }}
</b-badge>
</b-nav-item>
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
{{ $t('admin_area') }}
{{ $t('navigation.admin_area') }}
</b-nav-item>
<b-nav-item class="mb-3" @click="$emit('logout')">
<b-icon icon="power" aria-hidden="true"></b-icon>
{{ $t('logout') }}
{{ $t('navigation.logout') }}
</b-nav-item>
</b-nav>
</b-collapse>

View File

@ -36,29 +36,29 @@ describe('Sidebar', () => {
expect(wrapper.findAll('.nav-item')).toHaveLength(7)
})
it('has first nav-item "overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('overview')
it('has first nav-item "navigation.overview" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(0).text()).toEqual('navigation.overview')
})
it('has first nav-item "send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('send')
it('has first nav-item "navigation.send" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(1).text()).toEqual('navigation.send')
})
it('has first nav-item "transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('transactions')
it('has first nav-item "navigation.transactions" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions')
})
it('has first nav-item "my-profil" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('site.navbar.my-profil')
it('has first nav-item "navigation.profile" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('navigation.profile')
})
it('has a link to the members area', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('members_area')
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('navigation.members_area')
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe('#')
})
it('has first nav-item "admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('admin_area')
it('has first nav-item "navigation.admin_area" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toEqual('navigation.admin_area')
})
it('has first nav-item "logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('logout')
it('has first nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(6).text()).toEqual('navigation.logout')
})
})
})

View File

@ -6,35 +6,37 @@
<b-nav vertical class="w-200">
<b-nav-item to="/overview" class="mb-3">
<b-icon icon="house" aria-hidden="true"></b-icon>
{{ $t('overview') }}
{{ $t('navigation.overview') }}
</b-nav-item>
<b-nav-item to="/send" class="mb-3">
<b-icon icon="arrow-left-right" aria-hidden="true"></b-icon>
{{ $t('send') }}
{{ $t('navigation.send') }}
</b-nav-item>
<b-nav-item to="/transactions" class="mb-3">
<b-icon icon="layout-text-sidebar-reverse" aria-hidden="true"></b-icon>
{{ $t('transactions') }}
{{ $t('navigation.transactions') }}
</b-nav-item>
<b-nav-item to="/profile" class="mb-3">
<b-icon icon="gear" aria-hidden="true"></b-icon>
{{ $t('site.navbar.my-profil') }}
{{ $t('navigation.profile') }}
</b-nav-item>
</b-nav>
<hr />
<b-nav vertical class="w-100">
<b-nav-item class="mb-3" :href="elopageUri" target="_blank">
<b-icon icon="link45deg" aria-hidden="true"></b-icon>
{{ $t('members_area') }}
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
{{ $t('navigation.members_area') }}
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">
{{ $t('math.exclaim') }}
</b-badge>
</b-nav-item>
<b-nav-item class="mb-3" v-if="$store.state.isAdmin" @click="$emit('admin')">
<b-icon icon="shield-check" aria-hidden="true"></b-icon>
{{ $t('admin_area') }}
{{ $t('navigation.admin_area') }}
</b-nav-item>
<b-nav-item class="mb-3" @click="$emit('logout')">
<b-icon icon="power" aria-hidden="true"></b-icon>
{{ $t('logout') }}
{{ $t('navigation.logout') }}
</b-nav-item>
</b-nav>
</div>

View File

@ -12,8 +12,12 @@ const propsData = {
describe('PaginationButtons', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
}
const Wrapper = () => {
return mount(PaginationButtons, { localVue, propsData })
return mount(PaginationButtons, { localVue, mocks, propsData })
}
describe('mount', () => {

View File

@ -7,7 +7,7 @@
</b-button>
</b-col>
<b-col cols="3">
<p class="text-center pt-2">{{ value }} / {{ totalPages }}</p>
<p class="text-center pt-2">{{ value }} {{ $t('math.div') }} {{ totalPages }}</p>
</b-col>
<b-col>
<b-button class="next-page" :disabled="!hasNext" @click="currentValue++">

View File

@ -7,6 +7,7 @@ describe('Status', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$n: jest.fn((n) => n),
}
@ -26,7 +27,7 @@ describe('Status', () => {
describe('balance is pending', () => {
it('it displays an en-dash', () => {
expect(wrapper.find('div.gdd-status-div').text()).toEqual(' GDD')
expect(wrapper.find('div.gdd-status-div').text()).toEqual('em-dash GDD')
})
})

View File

@ -1,7 +1,7 @@
<template>
<div class="gdd-status">
<div class="p-0 gdd-status-div">
{{ pending || balance === null ? '—' : $n(balance, 'decimal') }} {{ statusText }}
{{ pending || balance === null ? $t('em-dash') : $n(balance, 'decimal') }} {{ statusText }}
</div>
</div>
</template>

View File

@ -51,7 +51,7 @@
{{ $t('form.date') }}
</b-col>
<b-col cols="6">
{{ $d(new Date(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
{{ $d(new Date(date), 'long') }}
</b-col>
</b-row>

Some files were not shown because too many files have changed in this diff Show More