Merge branch 'master' into frontend_commit_hash

This commit is contained in:
einhorn_b 2021-09-20 11:40:49 +02:00
commit b196ef78b8
93 changed files with 7259 additions and 1402 deletions

View File

@ -1,3 +1,5 @@
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
<!--
Please take a look at the issue templates at https://github.com/gradido/gradido/issues/new/choose
before submitting a new issue. Following one of the issue templates will ensure maintainers can route your request efficiently.

View File

@ -4,6 +4,7 @@ about: Create a report to help us improve
labels: bug
title: 🐛 [Bug]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
## 🐛 Bugreport
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.-->

View File

@ -1,9 +1,10 @@
---
name: 💥 DevOps ticket
about: Help us manage our deployed App.
about: Help us manage our deployed Software.
labels: devops
title: 💥 [DevOps]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
## 💥 DevOps ticket
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->

View File

@ -4,9 +4,10 @@ about: Define a big development Step
labels: epic
title: 🌟 [EPIC]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
<!-- THIS ISSUE-TYPE IS NOT FOR YOU! -->
<!-- Proceed only if you know what you are doing - go chat with Team Gradido -->
<!-- Visit the Gradido Discord: https://discord.gg/kA3zBAKQDC -->
<!-- Proceed only if you know what you are doing - have a chat with Project's Team first -->
## 🌟 EPIC
<!-- Describe your Epic in detail. Include screenshots and drawings -->

View File

@ -4,6 +4,7 @@ about: Suggest an idea for this project
labels: feature
title: 🚀 [Feature]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
## 🚀 Feature
<!-- Give a short summary of the Feature. Use Screenshots if you want. -->

View File

@ -1,11 +1,13 @@
---
name: 💬 Question
about: If you need help understanding Gradido.
about: If you need help understanding our Software.
labels: question
title: 💬 [Question]
---
<!-- Chat with Team Gradido -->
<!-- If you need an answer right away, visit the Gradido Discord: https://discord.gg/kA3zBAKQDC -->
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
<!-- Question the project's team -->
<!-- If you need an answer right away, consider to take other means of communication with the project's team -->
## 💬 Question
<!-- Describe your Question in detail. Include screenshots and drawings if needed. -->

View File

@ -4,6 +4,7 @@ about: Help us improve our code by refactoring it.
labels: refactor
title: 🔧 [Refactor]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
## 🔧 Refactor ticket
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the problem is.-->

View File

@ -4,9 +4,10 @@ about: Define a Release
labels: release
title: 🏅 [RELEASE]
---
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
<!-- THIS ISSUE-TYPE IS NOT FOR YOU! -->
<!-- Proceed only if you know what you are doing - go chat with Team Gradido -->
<!-- Visit the Gradido Discord: https://discord.gg/kA3zBAKQDC -->
<!-- Proceed only if you know what you are doing - have a chat with Project's Team first -->
## 🏅 RELEASE
<!-- Describe your Release in detail. Include screenshots and drawings -->

View File

@ -1,3 +1,5 @@
<!-- You can find the latest issue templates here https://github.com/ulfgebhardt/issue-templates -->
## 🍰 Pullrequest
<!-- Describe the Pullrequest. Use Screenshots if possible. -->

View File

@ -170,7 +170,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# BUILD NGINX DOCKER IMAGE #############################################
# BUILD NGINX DOCKER IMAGE ###############################################
##########################################################################
- name: nginx | Build `test` image
run: |
@ -182,6 +182,35 @@ jobs:
name: docker-nginx-test
path: /tmp/nginx.tar
##############################################################################
# JOB: LOCALES FRONTEND ######################################################
##############################################################################
locales_frontend:
name: Locales - Frontend
runs-on: ubuntu-latest
needs: [build_test_frontend]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# DOWNLOAD DOCKER IMAGE ##################################################
##########################################################################
- name: Download Docker Image (Frontend)
uses: actions/download-artifact@v2
with:
name: docker-frontend-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/frontend.tar
##########################################################################
# LOCALES FRONTEND #######################################################
##########################################################################
- name: frontend | Locales
run: docker run --rm gradido/frontend:test yarn run locales
##############################################################################
# JOB: LINT FRONTEND #########################################################
##############################################################################
@ -206,7 +235,7 @@ jobs:
- name: Load Docker Image
run: docker load < /tmp/frontend.tar
##########################################################################
# LINT FRONTEND ###########################################################
# LINT FRONTEND ##########################################################
##########################################################################
- name: frontend | Lint
run: docker run --rm gradido/frontend:test yarn run lint
@ -316,7 +345,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 60
min_coverage: 67
token: ${{ github.token }}
##############################################################################

View File

@ -39,4 +39,3 @@ git submodule update --recursive --init
## Useful Links
- [Gradido.net](https://gradido.net/)
- [Discord](https://discord.gg/kA3zBAKQDC)

View File

@ -9,4 +9,9 @@ DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
DB_DATABASE=gradido_community
#KLICKTIPP_USER=
#KLICKTIPP_PASSWORD=
#KLICKTIPP_APIKEY_DE=
#KLICKTIPP_APIKEY_EN=
#KLICKTIPP=true

View File

@ -4,8 +4,35 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.3.1](https://github.com/gradido/gradido/compare/1.3.1...1.3.1)
#### [1.4.0](https://github.com/gradido/gradido/compare/1.3.1...1.4.0)
- Integration of the KlicktippAPI to the User management [`#753`](https://github.com/gradido/gradido/pull/753)
- feat: Locale Management [`#809`](https://github.com/gradido/gradido/pull/809)
- feat: Increase Coverage Test Frontend [`#812`](https://github.com/gradido/gradido/pull/812)
- My thoughts to jwt [`#800`](https://github.com/gradido/gradido/pull/800)
- Remove discord link [`#808`](https://github.com/gradido/gradido/pull/808)
- refactor: Pagination Buttons [`#806`](https://github.com/gradido/gradido/pull/806)
- add new components selectLanguage and Usercard_Language [`#798`](https://github.com/gradido/gradido/pull/798)
- gdt transaction with arrays and without slots [`#793`](https://github.com/gradido/gradido/pull/793)
- feat: New JWT in Every Authenticated Response [`#797`](https://github.com/gradido/gradido/pull/797)
- fix euro after comma gdt view [`#799`](https://github.com/gradido/gradido/pull/799)
- different path for checkEmail and resetPassword [`#796`](https://github.com/gradido/gradido/pull/796)
- fix old frontend wrong display of event gdt [`#795`](https://github.com/gradido/gradido/pull/795)
- update scripts and doc for login-server setup without docker [`#783`](https://github.com/gradido/gradido/pull/783)
- fix: Flaky Test for Logout [`#792`](https://github.com/gradido/gradido/pull/792)
- Analyse Iota Colored Coins [`#779`](https://github.com/gradido/gradido/pull/779)
- Fix Bug in displaying GDT in Old frontend [`#788`](https://github.com/gradido/gradido/pull/788)
- feat: JSON Web Token for Authentification [`#777`](https://github.com/gradido/gradido/pull/777)
- select language during registration [`#778`](https://github.com/gradido/gradido/pull/778)
- Fix missing gdt id [`#782`](https://github.com/gradido/gradido/pull/782)
- Remove Migrations from community server [`#776`](https://github.com/gradido/gradido/pull/776)
- fix_database_migrations [`#775`](https://github.com/gradido/gradido/pull/775)
- decay with the value 0 is no longer displayed [`#773`](https://github.com/gradido/gradido/pull/773)
- database_migrations [`#770`](https://github.com/gradido/gradido/pull/770)
- backend_version [`#756`](https://github.com/gradido/gradido/pull/756)
- issue_type_release [`#769`](https://github.com/gradido/gradido/pull/769)
- some_docu [`#771`](https://github.com/gradido/gradido/pull/771)
- database_reachable_in_testmode [`#768`](https://github.com/gradido/gradido/pull/768)
- logo changed, old logos and icons deleted [`#734`](https://github.com/gradido/gradido/pull/734)
- change default value of communty url [`#755`](https://github.com/gradido/gradido/pull/755)
- feat: Testing Tabs of TransactionLists [`#737`](https://github.com/gradido/gradido/pull/737)
@ -14,9 +41,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- feat: Vue Apollo Client [`#701`](https://github.com/gradido/gradido/pull/701)
- change text from Geld to Gradidos [`#711`](https://github.com/gradido/gradido/pull/711)
- fix fix [`#728`](https://github.com/gradido/gradido/pull/728)
- generate updated pot file and update po file [`1569845`](https://github.com/gradido/gradido/commit/15698459f8be9d5111757393f5f3b4558e60c7b1)
- Implementation of the FormUserMail Tests. And review changes. [`421aba2`](https://github.com/gradido/gradido/commit/421aba22ff1620c534d6ab8b6592b5129e275265)
- test for GDT transaction list [`bfe8069`](https://github.com/gradido/gradido/commit/bfe806988e309b88d3f8f3f3aa0cd9ca86319300)
- sort locales [`ec12a28`](https://github.com/gradido/gradido/commit/ec12a28f81577d530f58b42b7f8c2c7d20dffd64)
- feat: Unify and Sort Locales [`aba4f4d`](https://github.com/gradido/gradido/commit/aba4f4d20e0a13016e3528a1c5c30c111eb3a9f1)
- feat: Increase Coverage [`3c061bc`](https://github.com/gradido/gradido/commit/3c061bcb8d1a3a47442ed6a351e1428e15b314aa)
#### [1.3.1](https://github.com/gradido/gradido/compare/1.3.0...1.3.1)

4563
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "gradido-backend",
"version": "1.3.1",
"version": "1.4.0",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { KlicktippConnector } from './klicktippConnector'
import CONFIG from '../config'
const klicktippConnector = new KlicktippConnector()
export const signIn = async (
email: string,
language: string,
firstName?: string,
lastName?: string,
): Promise<boolean> => {
const fields = {
fieldFirstName: firstName,
fieldLastName: lastName,
}
const apiKey = language === 'de' ? CONFIG.KLICKTIPP_APIKEY_DE : CONFIG.KLICKTIPP_APIKEY_EN
const result = await klicktippConnector.signin(apiKey, email, fields)
return result
}
export const signout = async (email: string, language: string): Promise<boolean> => {
const apiKey = language === 'de' ? CONFIG.KLICKTIPP_APIKEY_DE : CONFIG.KLICKTIPP_APIKEY_EN
const result = await klicktippConnector.signoff(apiKey, email)
return result
}
export const unsubscribe = async (email: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.unsubscribe(email)
}
throw new Error(`Could not unsubscribe ${email}`)
}
export const getKlickTippUser = async (email: string): Promise<any> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
const subscriberId = await klicktippConnector.subscriberSearch(email)
const result = await klicktippConnector.subscriberGet(subscriberId)
return result
}
return false
}
export const loginKlicktippUser = async (): Promise<boolean> => {
return await klicktippConnector.login(CONFIG.KLICKTIPP_USER, CONFIG.KLICKTIPP_PASSWORD)
}
export const logoutKlicktippUser = async (): Promise<boolean> => {
return await klicktippConnector.logout()
}
export const untagUser = async (email: string, tagId: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.untag(email, tagId)
}
return false
}
export const tagUser = async (email: string, tagIds: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tag(email, tagIds)
}
return false
}
export const getKlicktippTagMap = async () => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tagIndex()
}
return ''
}

View File

@ -0,0 +1,620 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import axios, { AxiosRequestConfig, Method } from 'axios'
export class KlicktippConnector {
private baseURL: string
private sessionName: string
private sessionId: string
private error: string
constructor(service?: string) {
this.baseURL = service !== undefined ? service : 'https://api.klicktipp.com'
this.sessionName = ''
this.sessionId = ''
}
/**
* Get last error
*
* @return string an error description of the last error
*/
getLastError(): string {
const result = this.error
return result
}
/**
* login
*
* @param username The login name of the user to login.
* @param password The password of the user.
* @return TRUE on success
*/
async login(username: string, password: string): Promise<boolean> {
if (!(username.length > 0 && password.length > 0)) {
throw new Error('Klicktipp Login failed: Illegal Arguments')
}
const res = await this.httpRequest('/account/login', 'POST', { username, password }, false)
if (!res.isAxiosError) {
this.sessionId = res.data.sessid
this.sessionName = res.data.session_name
return true
}
throw new Error(`Klicktipp Login failed: ${res.response.statusText}`)
}
/**
* Logs out the user currently logged in.
*
* @return TRUE on success
*/
async logout(): Promise<boolean> {
const res = await this.httpRequest('/account/logout', 'POST')
if (!res.isAxiosError) {
this.sessionId = ''
this.sessionName = ''
return true
}
throw new Error(`Klicktipp Logout failed: ${res.response.statusText}`)
}
/**
* Get all subscription processes (lists) of the logged in user. Requires to be logged in.
*
* @return A associative obeject <list id> => <list name>
*/
async subscriptionProcessIndex(): Promise<any> {
const res = await this.httpRequest('/list', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process index failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) definition. Requires to be logged in.
*
* @param listid The id of the subscription process
*
* @return An object representing the Klicktipp subscription process.
*/
async subscriptionProcessGet(listid: string): Promise<any> {
if (!listid || listid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${listid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process get failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) redirection url for given subscription.
*
* @param listid The id of the subscription process.
* @param email The email address of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async subscriptionProcessRedirect(listid: string, email: string): Promise<any> {
if (!listid || listid === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = { listid, email }
const res = await this.httpRequest('/list/redirect', 'POST', data)
if (!res.isAxiosError) {
return res.data
}
throw new Error(
`Klicktipp Subscription process get redirection url failed: ${res.response.statusText}`,
)
}
/**
* Get all manual tags of the logged in user. Requires to be logged in.
*
* @return A associative object <tag id> => <tag name>
*/
async tagIndex(): Promise<any> {
const res = await this.httpRequest('/tag', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag index failed: ${res.response.statusText}`)
}
/**
* Get a tag definition. Requires to be logged in.
*
* @param tagid The tag id.
*
* @return An object representing the Klicktipp tag object.
*/
async tagGet(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag get failed: ${res.response.statusText}`)
}
/**
* Create a new manual tag. Requires to be logged in.
*
* @param name The name of the tag.
* @param text (optional) An additional description of the tag.
*
* @return The id of the newly created tag or false if failed.
*/
async tagCreate(name: string, text?: string): Promise<boolean> {
if (!name || name === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name,
text: text !== undefined ? text : '',
}
const res = await this.httpRequest('/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag creation failed: ${res.response.statusText}`)
}
/**
* Updates a tag. Requires to be logged in.
*
* @param tagid The tag id used to identify which tag to modify.
* @param name (optional) The new tag name. Set empty to leave it unchanged.
* @param text (optional) The new tag description. Set empty to leave it unchanged.
*
* @return TRUE on success
*/
async tagUpdate(tagid: string, name?: string, text?: string): Promise<boolean> {
if (!tagid || tagid === '' || (name === '' && text === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name: name !== undefined ? name : '',
text: text !== undefined ? text : '',
}
const res = await this.httpRequest(`/tag/${tagid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag update failed: ${res.response.statusText}`)
}
/**
* Deletes a tag. Requires to be logged in.
*
* @param tagid The user id of the user to delete.
*
* @return TRUE on success
*/
async tagDelete(tagid: string): Promise<boolean> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'DELETE')
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag deletion failed: ${res.response.statusText}`)
}
/**
* Get all contact fields of the logged in user. Requires to be logged in.
*
* @return A associative object <field id> => <field name>
*/
async fieldIndex(): Promise<any> {
const res = await this.httpRequest('/field', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Field index failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param listid (optional) The id subscription process.
* @param tagid (optional) The id of the manual tag the subscriber will be tagged with.
* @param fields (optional) Additional fields of the subscriber.
*
* @return An object representing the Klicktipp subscriber object.
*/
async subscribe(
email: string,
listid?: number,
tagid?: number,
fields?: any,
smsnumber?: string,
): Promise<any> {
if ((!email || email === '') && smsnumber === '') {
throw new Error('Illegal Arguments')
}
// subscribe
const data = {
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
listid: listid !== undefined ? listid : 0,
tagid: tagid !== undefined ? tagid : 0,
}
const res = await this.httpRequest('/subscriber', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async unsubscribe(email: string): Promise<boolean> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { email }
const res = await this.httpRequest('/subscriber/unsubscribe', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
/**
* Tag an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param tagids an array of the manual tag(s) the subscriber will be tagged with.
*
* @return TRUE on success
*/
async tag(email: string, tagids: string): Promise<boolean> {
if (!email || email === '' || !tagids || tagids === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// tag
const data = {
email,
tagids,
}
const res = await this.httpRequest('/subscriber/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tagging failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires to be logged in.
*
* @param mixed $email The email address of the subscriber.
* @param mixed $tagid The id of the manual tag that will be removed from the subscriber.
*
* @return TRUE on success.
*/
async untag(email: string, tagid: string): Promise<boolean> {
if (!email || email === '' || !tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
email,
tagid,
}
const res = await this.httpRequest('/subscriber/untag', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Resend an autoresponder for an email address. Requires to be logged in.
*
* @param email A valid email address
* @param autoresponder An id of the autoresponder
*
* @return TRUE on success
*/
async resend(email: string, autoresponder: string): Promise<boolean> {
if (!email || email === '' || !autoresponder || autoresponder === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// resend/reset autoresponder
const data = { email, autoresponder }
const res = await this.httpRequest('/subscriber/resend', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Resend failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers. Requires to be logged in.
*
* @return An array of subscriber ids.
*/
async subscriberIndex(): Promise<[string]> {
const res = await this.httpRequest('/subscriber', 'GET', undefined, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber index failed: ${res.response.statusText}`)
}
/**
* Get subscriber information. Requires to be logged in.
*
* @param subscriberid The subscriber id.
*
* @return An object representing the Klicktipp subscriber.
*/
async subscriberGet(subscriberid: string): Promise<any> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber get failed: ${res.response.statusText}`)
}
/**
* Get a subscriber id by email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return The id of the subscriber. Use subscriber_get to get subscriber details.
*/
async subscriberSearch(email: string): Promise<any> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { email }
const res = await this.httpRequest('/subscriber/search', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber search failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers tagged with the given tag id. Requires to be logged in.
*
* @param tagid The id of the tag.
*
* @return An array with id -> subscription date of the tagged subscribers. Use subscriber_get to get subscriber details.
*/
async subscriberTagged(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { tagid }
const res = await this.httpRequest('/subscriber/tagged', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp subscriber tagged failed: ${res.response.statusText}`)
}
/**
* Updates a subscriber. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
* @param fields (optional) The fields of the subscriber to update
* @param newemail (optional) The new email of the subscriber to update
*
* @return TRUE on success
*/
async subscriberUpdate(
subscriberid: string,
fields?: any,
newemail?: string,
newsmsnumber?: string,
): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = {
fields: fields !== undefined ? fields : {},
newemail: newemail !== undefined ? newemail : '',
newsmsnumber: newsmsnumber !== undefined ? newsmsnumber : '',
}
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber update failed: ${res.response.statusText}`)
}
/**
* Delete a subscribe. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
*
* @return TRUE on success.
*/
async subscriberDelete(subscriberid: string): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// delete
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'DELETE', {}, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber deletion failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
* @param fields (optional) Additional fields of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async signin(apikey: string, email: string, fields?: any, smsnumber?: string): Promise<boolean> {
if (!apikey || apikey === '' || ((!email || email === '') && smsnumber === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
apikey,
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
}
const res = await this.httpRequest('/subscriber/signin', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signout(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// untag
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signout', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signoff(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signoff', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
async httpRequest(path: string, method?: Method, data?: any, usesession?: boolean): Promise<any> {
if (method === undefined) {
method = 'GET'
}
const options: AxiosRequestConfig = {
baseURL: this.baseURL,
method,
url: path,
data,
headers: {
'Content-Type': 'application/json',
Content: 'application/json',
Cookie:
usesession && this.sessionName !== '' ? `${this.sessionName}=${this.sessionId}` : '',
},
}
return axios(options)
.then((res) => res)
.catch((error) => error)
}
}

View File

@ -2,7 +2,7 @@
import { AuthChecker } from 'type-graphql'
import decode from '../jwt/decode'
import { apiGet } from '../apis/loginAPI'
import { apiGet } from '../apis/HttpRequest'
import CONFIG from '../config'
import encode from '../jwt/encode'

View File

@ -21,9 +21,18 @@ const database = {
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
}
const klicktipp = {
KLICKTIPP: process.env.KLICKTIPP === 'true' || false,
KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL || 'https://api.klicktipp.com',
KLICKTIPP_USER: process.env.KLICKTIPP_USER || 'gradido_test',
KLICKTIPP_PASSWORD: process.env.KLICKTIPP_PASSWORD || 'secret321',
KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE || 'SomeFakeKeyDE',
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN',
}
// This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET
const CONFIG = { ...server, ...database }
const CONFIG = { ...server, ...database, ...klicktipp }
export default CONFIG

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class SubscribeNewsletterArguments {
@Field(() => String)
email: string
@Field(() => String)
language: string
}

View File

@ -22,6 +22,9 @@ export class CreateUserArgs {
@Field(() => String)
password: string
@Field(() => String)
language: string
}
@ArgsType()
@ -56,6 +59,9 @@ export class UpdateUserInfosArgs {
@Field({ nullable: true })
language?: string
@Field({ nullable: true })
publisherId?: number
@Field({ nullable: true })
password?: string

View File

@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class CheckEmailResponse {
constructor(json: any) {
this.sessionId = json.session_id
this.email = json.user.email
this.language = json.user.language
this.firstName = json.user.first_name
this.lastName = json.user.last_name
}
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
language: string
}

View File

@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class KlickTipp {
constructor(json: any) {
this.newsletterState = json.status === 'Subscribed'
}
@Field(() => Boolean)
newsletterState: boolean
}

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { KlickTipp } from './KlickTipp'
@ObjectType()
export class User {
@ -16,6 +17,7 @@ export class User {
this.username = json.username
this.description = json.description
this.language = json.language
this.publisherId = json.publisher_id
}
@Field(() => String)
@ -59,9 +61,11 @@ export class User {
/* I suggest to have a group as type here
@Field(() => ID)
groupId: number
// what is puvlisherId?
@Field(() => ID)
*/
// what is publisherId?
@Field(() => Number)
publisherId: number
*/
@Field(() => KlickTipp)
klickTipp: KlickTipp
}

View File

@ -4,7 +4,7 @@
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config'
import { Balance } from '../models/Balance'
import { apiGet } from '../../apis/loginAPI'
import { apiGet } from '../../apis/HttpRequest'
@Resolver()
export class BalanceResolver {

View File

@ -5,7 +5,7 @@ import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
import { apiGet } from '../../apis/loginAPI'
import { apiGet } from '../../apis/HttpRequest'
@Resolver()
export class GdtResolver {

View File

@ -0,0 +1,40 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql'
import {
getKlickTippUser,
getKlicktippTagMap,
unsubscribe,
signIn,
} from '../../apis/KlicktippController'
import { SubscribeNewsletterArguments } from '../inputs/KlickTippInputs'
@Resolver()
export class KlicktippResolver {
@Authorized()
@Query(() => String)
async getKlicktippUser(@Arg('email') email: string): Promise<string> {
return await getKlickTippUser(email)
}
@Authorized()
@Query(() => String)
async getKlicktippTagMap(): Promise<string> {
return await getKlicktippTagMap()
}
@Authorized()
@Mutation(() => Boolean)
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
return await unsubscribe(email)
}
@Authorized()
@Mutation(() => Boolean)
async subscribeNewsletter(
@Args() { email, language }: SubscribeNewsletterArguments,
): Promise<boolean> {
return await signIn(email, language)
}
}

View File

@ -5,7 +5,7 @@ import { Resolver, Query, Args, Authorized, Ctx } from 'type-graphql'
import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
import { apiGet, apiPost } from '../../apis/loginAPI'
import { apiGet, apiPost } from '../../apis/HttpRequest'
@Resolver()
export class TransactionResolver {

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql'
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware } from 'type-graphql'
import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
@ -16,11 +16,16 @@ import {
UnsecureLoginArgs,
UpdateUserInfosArgs,
} from '../inputs/LoginUserInput'
import { apiPost, apiGet } from '../../apis/loginAPI'
import { apiPost, apiGet } from '../../apis/HttpRequest'
import {
klicktippRegistrationMiddleware,
klicktippNewsletterStateMiddleware,
} from '../../middleware/klicktippMiddleware'
import { CheckEmailResponse } from '../models/CheckEmailResponse'
@Resolver()
export class UserResolver {
@Query(() => User)
@UseMiddleware(klicktippNewsletterStateMiddleware)
async login(@Args() { email, password }: UnsecureLoginArgs, @Ctx() context: any): Promise<User> {
email = email.trim().toLowerCase()
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
@ -62,7 +67,9 @@ export class UserResolver {
}
@Query(() => String)
async create(@Args() { email, firstName, lastName, password }: CreateUserArgs): Promise<string> {
async create(
@Args() { email, firstName, lastName, password, language }: CreateUserArgs,
): Promise<string> {
const payload = {
email,
first_name: firstName,
@ -70,11 +77,14 @@ export class UserResolver {
password,
emailType: 2,
login_after_register: true,
language: language,
publisher_id: 0,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createUser', payload)
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@ -88,7 +98,9 @@ export class UserResolver {
email_verification_code_type: 'resetPassword',
}
const response = await apiPost(CONFIG.LOGIN_API_URL + 'sendEmail', payload)
if (!response.success) throw new Error(response.data)
if (!response.success) {
throw new Error(response.data)
}
return new SendPasswordResetEmailResponse(response.data)
}
@ -103,8 +115,10 @@ export class UserResolver {
password,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
if (!result.success) throw new Error(result.data)
return 'sucess'
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@Authorized()
@ -118,6 +132,7 @@ export class UserResolver {
description,
username,
language,
publisherId,
password,
passwordNew,
}: UpdateUserInfosArgs,
@ -132,6 +147,7 @@ export class UserResolver {
'User.description': description || undefined,
'User.username': username || undefined,
'User.language': language || undefined,
'User.publisher_id': publisherId || undefined,
'User.password': passwordNew || undefined,
'User.password_old': password || undefined,
},
@ -151,4 +167,16 @@ export class UserResolver {
if (!response.success) throw new Error(response.data)
return new CheckUsernameResponse(response.data)
}
@Query(() => CheckEmailResponse)
@UseMiddleware(klicktippRegistrationMiddleware)
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
const result = await apiGet(
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
)
if (!result.success) {
throw new Error(result.data)
}
return new CheckEmailResponse(result.data)
}
}

View File

@ -15,6 +15,7 @@ import { UserResolver } from './graphql/resolvers/UserResolver'
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
import { GdtResolver } from './graphql/resolvers/GdtResolver'
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
import { KlicktippResolver } from './graphql/resolvers/KlicktippResolver'
import { isAuthorized } from './auth/auth'
@ -50,7 +51,7 @@ async function main() {
// const connection = await createConnection()
const schema = await buildSchema({
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver, KlicktippResolver],
authChecker: isAuthorized,
})

View File

@ -0,0 +1,34 @@
import { MiddlewareFn } from 'type-graphql'
import { signIn, getKlickTippUser } from '../apis/KlicktippController'
import { KlickTipp } from '../graphql/models/KlickTipp'
import CONFIG from '../config/index'
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
// Only for demo
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
{ root, args, context, info },
next,
) => {
// Do Something here before resolver is called
const result = await next()
// Do Something here after resolver is completed
await signIn(result.email, result.language, result.firstName, result.lastName)
return result
}
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
{ root, args, context, info },
next,
) => {
const result = await next()
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
if (CONFIG.KLICKTIPP) {
const klickTippUser = await getKlickTippUser(result.email)
if (klickTippUser) {
klickTipp = new KlickTipp(klickTippUser)
}
}
result.klickTipp = klickTipp
return result
}

View File

@ -4,7 +4,7 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */

View File

@ -0,0 +1,28 @@
# Authorization and Private Keys
## Keys
For creating transactions ed25519 keys are used for signing.
As long the user is the only controlling the private key he is the only one
how can sign transactions on his behalf.
It is a core concept of all crypto currencies and important for the concept,
that the user has full control over his data.
Usually crypto currencies like bitcoin or iota save the keys on local system,
maybe additional protected with a password which is used to encrypt the keys.
## Gradido
Gradido should be easy to use, so we must offer a solution for everyone not that fit
with computer, as easy to use like paypal.
For that role we have the Login-Server.
It stores the private keys of the user encrypted with there email and password.
Additional it stores the passphrase which can be used to generate the private key,
encryted with server admin public key. So only the server admin can access the keys
with his private key. [not done yet]
It is needed for passwort reset if a user has forgetten his password.
But for the entire concept Login-Server isn't the only way to store the private keys.
For users which has more experience with computer and especially with crypto currencies
it should be a way to keep there private keys by themselfs.
For example a Desktop- or Handy-App which store the keys locally maybe additional encrypted.
Maybe it is possible to use Stronghold from iota for that.
With that the user don't need to use the Login-Server.

View File

@ -0,0 +1,29 @@
# How JWT could be used for authorization with and without Login-Server
## What we need
The only encrypted data in db are the private key.
Every other data could be accessed without login, depending on frontend and backend code.
So we need only a way to prove the backend that we have access to the private key.
## JWT
JWT is perfect for that.
We can use JWT to store the public key of the user as UUID for finding his data in db,
signing it with the private key. So even if the backend is running in multiple instances,
on every request is it possible to check the JWT token, that the signature is signed with
the private key, belonging to the public key.
The only thing the backend cannot do with that is signing a transaction.
That can only be done by the Login-Server or a Desktop or Handy-App storing the private key locally.
With that we have universal way for authorization against the backend.
We could additional store if we like to sign transactions local or with Login-Server and the Login-Server url.
## JWT and Login-Server
Login-Server uses Poco version 1.9.4 but unfortunately Poco only introduces jwt from version 1.10.
And Updating to 1.10 needs some work because some things have changed in Poco 1.10.
## JWT signature algorithms
In JWT standard ed25519 don't seemd to play a role.
We must find out if we can use the ed25519 keys together with one of the signature algorithms
in JWT standard or we must use **crypto_sign_verify_detached** from libsodium even it is nonstandard
to verify signature created with ed25519 keys and libsodiums **crypto_sign_detached** function.

View File

@ -0,0 +1,16 @@
# Session Id Authorization
## Login-Server
With every login, the Login-Server creates a session with a random id,
storing it in memory. For Login email and password are needed.
From email and an additional app-secret (**crypto.app_secret** in Login-Server config) a sha512 hash will be genereted, named **hash512_salt**.
With sodium function *crypto_pwhash* with **hash512_salt** and user password a secret encryption key will be calculated.
*crypto_pwhash* uses argon2 algorithmus to have a CPU hard calculation. Currently it is configured for < 0.5s.
So it is harder to use brute-force attacks to guess the password. Even if someone gets hands on the data saved in db.
With sodium function *crypto_shorthash* a hash will be calculated from the secret encryption key and server crypto key (**crypto.server_key** in Login-Server config, hex encoded, 16 Bytes, 32 Character hex encoded)
and compared against saved hash in db. If they identical user has successfull logged in.
The secret encryption key will be stored in memory together with the user session and client ip from which login call came.
The session_id will be returned.
The session will be hold in memory for 15 minutes default, can be changed in Login-Server config field **session.timeout**

View File

@ -151,7 +151,7 @@ with:
{
"email":"max.musterman@gmail.de",
"first_name":"Max",
"last_name":"Musterman",
"last_name":"Musterman" ,
"username": "Maxilein",
"description": "Tischler",
"emailType": 2,
@ -231,7 +231,8 @@ with:
"User.disabled": 0,
"User.language": "de",
"User.password": "1234",
"User.password_old": "4321"
"User.password_old": "4321",
"User.publisher_id": "1"
}
}
```
@ -304,7 +305,8 @@ with:
"user.description",
"user.disabled",
"user.email_checked",
"user.language"
"user.language",
"user.publisher_id"
]
}
```
@ -342,6 +344,7 @@ Return only the fields which are defined in request
is in db only saved in state_users so if we delete this entry, validating all transactions is no longer possible. Disabled User cannot login and cannot receive transactions.
- `email_checked`: If user has clicked on link in verification email (register), can only transfer gradidos if email_checked is 1
- `language`: Language Key for User, currently 'de' or 'en'
- `publisher_id`: elopage publisher ip
- `errors`: array of strings if error occure
## Login by Email Verification Code

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -72,6 +72,9 @@ RUN yarn run build
##################################################################################
FROM build as test
# Install Additional Software
RUN apk add --no-cache bash jq
# Run command
CMD /bin/sh -c "yarn run dev"

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "1.3.1",
"version": "1.4.0",
"private": true,
"scripts": {
"start": "node run/server.js",
@ -9,7 +9,8 @@
"lint": "eslint --ext .js,.vue .",
"dev": "yarn run serve",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
"test": "jest --coverage"
"test": "jest --coverage",
"locales": "scripts/missing-keys.sh && scripts/sort.sh"
},
"dependencies": {
"@babel/core": "^7.13.13",
@ -89,9 +90,9 @@
"@vue/eslint-config-prettier": "^4.0.1",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.0",
"dotenv-webpack": "^6.0.4",
"node-sass": "^4.12.0",
"sass-loader": "^7.1.0",
"dotenv-webpack": "^7.0.3",
"node-sass": "^6.0.1",
"sass-loader": "^10",
"vue-template-compiler": "^2.6.11"
},
"postcss": {

View File

@ -0,0 +1,17 @@
#!/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

25
frontend/scripts/sort.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
ROOT_DIR=$(dirname "$0")/..
tmp=$(mktemp)
exit_code=0
for locale_file in $ROOT_DIR/src/locales/*.json
do
jq -f $(dirname "$0")/sort_filter.jq $locale_file > "$tmp"
if [ "$*" == "--fix" ]
then
mv "$tmp" $locale_file
else
if diff -q "$tmp" $locale_file > /dev/null ;
then
: # all good
else
exit_code=$?
echo "$(basename -- $locale_file) is not sorted by keys"
fi
fi
done
exit $exit_code

View File

@ -0,0 +1,13 @@
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def keys_sort_by(f):
to_entries | sort_by(.key|f ) | from_entries;
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)

View File

@ -2,29 +2,29 @@
// Components
//
@import "custom/alert";
@import "custom/avatar";
@import "custom/buttons";
@import "custom/card";
@import "custom/chart";
@import "custom/close";
@import "custom/content";
@import "custom/custom-forms";
@import "custom/dropdown";
@import "custom/footer";
@import "custom/forms";
@import "custom/header";
@import "custom/icons";
@import "custom/input-group";
@import "custom/list-group";
@import "custom/map";
@import "custom/mask";
@import "custom/modal";
@import "custom/nav";
@import "custom/navbar";
@import "custom/pagination";
@import "custom/popover";
@import "custom/progress";
@import "custom/separator";
@import "custom/tables";
@import "custom/type";
@import "alert";
@import "avatar";
@import "buttons";
@import "card";
@import "chart";
@import "close";
@import "content";
@import "custom-forms";
@import "dropdown";
@import "footer";
@import "forms";
@import "header";
@import "icons";
@import "input-group";
@import "list-group";
@import "map";
@import "mask";
@import "modal";
@import "nav";
@import "navbar";
@import "pagination";
@import "popover";
@import "progress";
@import "separator";
@import "tables";
@import "type";

View File

@ -1,79 +0,0 @@
<template>
<div id="accordion" role="tablist" aria-multiselectable="true" class="accordion">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'collapse',
props: {
animationDuration: {
type: Number,
default: 250,
description: 'Collapse animation duration',
},
multipleActive: {
type: Boolean,
default: true,
description: 'Whether you can have multiple collapse items opened at the same time',
},
activeIndex: {
type: Number,
default: -1,
description: 'Active collapse item index',
},
},
provide() {
return {
animationDuration: this.animationDuration,
multipleActive: this.multipleActive,
addItem: this.addItem,
removeItem: this.removeItem,
deactivateAll: this.deactivateAll,
}
},
data() {
return {
items: [],
}
},
methods: {
addItem(item) {
const index = this.$slots.default.indexOf(item.$vnode)
if (index !== -1) {
this.items.splice(index, 0, item)
}
},
removeItem(item) {
const items = this.items
const index = items.indexOf(item)
if (index > -1) {
items.splice(index, 1)
}
},
deactivateAll() {
this.items.forEach((item) => {
item.active = false
})
},
activateItem() {
if (this.activeIndex !== -1) {
this.items[this.activeIndex].active = true
}
},
},
mounted() {
this.$nextTick(() => {
this.activateItem()
})
},
watch: {
activeIndex() {
this.activateItem()
},
},
}
</script>
<style scoped></style>

View File

@ -1,91 +0,0 @@
<template>
<b-card no-body>
<b-card-header role="tab" class="card-header" :aria-expanded="active">
<a
data-toggle="collapse"
data-parent="#accordion"
:href="`#${itemId}`"
@click.prevent="activate"
:aria-controls="`content-${itemId}`"
>
<slot name="title">{{ title }}</slot>
<i class="tim-icons icon-minimal-down"></i>
</a>
</b-card-header>
<collapse-transition :duration="animationDuration">
<div
v-show="active"
:id="`content-${itemId}`"
role="tabpanel"
:aria-labelledby="title"
class="collapsed"
>
<div class="card-body"><slot></slot></div>
</div>
</collapse-transition>
</b-card>
</template>
<script>
import { CollapseTransition } from 'vue2-transitions'
export default {
name: 'collapse-item',
components: {
CollapseTransition,
},
props: {
title: {
type: String,
default: '',
description: 'Collapse item title',
},
id: String,
},
inject: {
animationDuration: {
default: 250,
},
multipleActive: {
default: false,
},
addItem: {
default: () => {},
},
removeItem: {
default: () => {},
},
deactivateAll: {
default: () => {},
},
},
computed: {
itemId() {
return this.id || this.title
},
},
data() {
return {
active: false,
}
},
methods: {
activate() {
const wasActive = this.active
if (!this.multipleActive) {
this.deactivateAll()
}
this.active = !wasActive
},
},
mounted() {
this.addItem(this)
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
}
this.removeItem(this)
},
}
</script>
<style></style>

View File

@ -0,0 +1,40 @@
<template>
<div class="language-switch-select">
<b-form-select
v-model="selected"
:options="options"
class="selectedLanguage mb-3"
></b-form-select>
</div>
</template>
<script>
export default {
name: 'LanguageSwitch',
data() {
return {
selected: null,
options: [
{ value: null, text: this.$t('select_language') },
{ value: 'de', text: this.$t('languages.de') },
{ value: 'en', text: this.$t('languages.en') },
],
}
},
props: {
language: { type: String },
},
created() {
this.selected = this.$store.state.language
},
computed: {
languageObject() {
return this.selected
},
},
watch: {
selected() {
this.$emit('update-language', this.languageObject)
},
},
}
</script>

View File

@ -3,11 +3,17 @@ import PaginationButtons from './PaginationButtons'
const localVue = global.localVue
const propsData = {
totalRows: 42,
perPage: 12,
value: 1,
}
describe('PaginationButtons', () => {
let wrapper
const Wrapper = () => {
return mount(PaginationButtons, { localVue })
return mount(PaginationButtons, { localVue, propsData })
}
describe('mount', () => {
@ -19,34 +25,20 @@ describe('PaginationButtons', () => {
expect(wrapper.find('div.pagination-buttons').exists()).toBeTruthy()
})
it('has previous page button disabled by default', () => {
expect(wrapper.find('button.previous-page').attributes('disabled')).toBe('disabled')
})
it('has bext page button disabled by default', () => {
expect(wrapper.find('button.next-page').attributes('disabled')).toBe('disabled')
})
it('shows the text "1 / 1" by default"', () => {
expect(wrapper.find('p.text-center').text()).toBe('1 / 1')
})
describe('with active buttons', () => {
beforeEach(async () => {
await wrapper.setProps({
hasNext: true,
hasPrevious: true,
})
})
it('emits show-previous when previous page button is clicked', () => {
wrapper.find('button.previous-page').trigger('click')
expect(wrapper.emitted('show-previous')).toBeTruthy()
})
it('emits show-next when next page button is clicked', () => {
it('emits input next page button is clicked', async () => {
wrapper.find('button.next-page').trigger('click')
expect(wrapper.emitted('show-next')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([2])
})
it('emits input when previous page button is clicked', async () => {
wrapper.setProps({ value: 2 })
wrapper.setData({ currentValue: 2 })
await wrapper.vm.$nextTick()
wrapper.find('button.previous-page').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([1])
})
})
})

View File

@ -1,16 +1,16 @@
<template>
<div class="pagination-buttons">
<div class="pagination-buttons" v-if="totalRows > perPage">
<b-row class="m-4">
<b-col class="text-right">
<b-button class="previous-page" :disabled="!hasPrevious" @click="$emit('show-previous')">
<b-button class="previous-page" :disabled="!hasPrevious" @click="currentValue--">
<b-icon icon="chevron-left" variant="primary"></b-icon>
</b-button>
</b-col>
<b-col cols="3">
<p class="text-center pt-2">{{ currentPage }} / {{ totalPages }}</p>
<p class="text-center pt-2">{{ value }} / {{ totalPages }}</p>
</b-col>
<b-col>
<b-button class="next-page" :disabled="!hasNext" @click="$emit('show-next')">
<b-button class="next-page" :disabled="!hasNext" @click="currentValue++">
<b-icon icon="chevron-right" variant="primary"></b-icon>
</b-button>
</b-col>
@ -21,10 +21,33 @@
export default {
name: 'PaginationButtons',
props: {
hasNext: { type: Boolean, default: false },
hasPrevious: { type: Boolean, default: false },
totalPages: { type: Number, default: 1 },
currentPage: { type: Number, default: 1 },
totalRows: { required: true },
perPage: { type: Number, required: true },
value: { type: Number, required: true },
},
data() {
return {
currentValue: { type: Number, default: 1 },
}
},
computed: {
hasNext() {
return this.value * this.perPage < this.totalRows
},
hasPrevious() {
return this.value > 1
},
totalPages() {
return Math.ceil(this.totalRows / this.perPage)
},
},
created() {
this.currentValue = this.value
},
watch: {
currentValue() {
if (this.currentValue !== this.value) this.$emit('input', this.currentValue)
},
},
}
</script>

View File

@ -113,12 +113,6 @@ describe('SideBar', () => {
expect(wrapper.emitted('logout')).toEqual([[]])
})
})
describe('language-switch', () => {
it('has a language-switch button', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
})
})
})
})

View File

@ -64,23 +64,18 @@
</a>
</li>
</ul>
<div class="mt-5 ml-4">
<language-switch />
</div>
</div>
</div>
</nav>
</template>
<script>
import NavbarToggleButton from '@/components/NavbarToggleButton'
import LanguageSwitch from '@/components/LanguageSwitch.vue'
import VueQrcode from 'vue-qrcode'
export default {
name: 'sidebar',
components: {
NavbarToggleButton,
LanguageSwitch,
VueQrcode,
},
props: {

View File

@ -1,8 +1,5 @@
import NavbarToggleButton from './Navbar/NavbarToggleButton'
import Collapse from './Collapse/Collapse.vue'
import CollapseItem from './Collapse/CollapseItem.vue'
import SidebarPlugin from './SidebarPlugin'
export { SidebarPlugin, NavbarToggleButton, Collapse, CollapseItem }
export { SidebarPlugin, NavbarToggleButton }

View File

@ -0,0 +1,13 @@
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)
}
`

View File

@ -9,6 +9,9 @@ export const login = gql`
lastName
language
description
klickTipp {
newsletterState
}
}
}
`
@ -92,8 +95,20 @@ export const transactionsQuery = gql`
`
export const resgisterUserQuery = gql`
query($firstName: String!, $lastName: String!, $email: String!, $password: String!) {
create(email: $email, firstName: $firstName, lastName: $lastName, password: $password)
query(
$firstName: String!
$lastName: String!
$email: String!
$password: String!
$language: String!
) {
create(
email: $email
firstName: $firstName
lastName: $lastName
password: $password
language: $language
)
}
`
@ -136,3 +151,12 @@ export const listGDTEntriesQuery = gql`
}
}
`
export const checkEmailQuery = gql`
query($optin: String!) {
checkEmail(optin: $optin) {
email
sessionId
}
}
`

View File

@ -1,181 +1,193 @@
{
"message": "hallo gradido !!",
"welcome":"Willkommen!",
"back": "Zurück",
"community": "Gemeinschaft",
"logout":"Abmelden",
"login":"Anmeldung",
"signup": "Registrieren",
"reset": "Passwort zurücksetzen",
"imprint":"Impressum",
"privacy_policy":"Datenschutzerklärung",
"members_area": "Mitgliederbereich",
"whitepaper": "Whitepaper",
"back":"Zurück",
"send":"Senden",
"transactions":"Transaktionen",
"language":"Sprache",
"languages":{
"de": "Deutsch",
"en": "English"
"communitys": {
"form": {
"date_period": "Datum / Zeitraum",
"hours": "Stunden",
"hours_report": "Stundenbericht",
"more_hours": "weitere Stunden",
"submit": "Einreichen"
}
},
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
"decay": {
"calculation_decay": "Berechnung der Vergänglichkeit",
"created": "Geschöpft",
"days": "Tage",
"decay": "Vergänglichkeit",
"decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion",
"calculation_decay":"Berechnung der Vergänglichkeit",
"Starting_block_decay":"Startblock Vergänglichkeit",
"decay_introduced":"Die Vergänglichkeit wurde Eingeführt am ",
"decayStart": " - Startblock für Vergänglichkeit am: ",
"last_transaction":"Letzte Transaktion",
"past_time":"Vergangene Zeit",
"since_introduction":"seit Einführung der Vergänglichkeit",
"year":"Jahre",
"months":"Monate",
"days":"Tage",
"hours":"Stunden",
"minutes":"Minuten",
"seconds":"Sekunden",
"received":"Empfangen",
"sent":"Gesendet",
"created":"Geschöpft",
"fromCommunity":"Aus der Gemeinschaft",
"toCommunity":"An die Gemeinschaft",
"noDecay": "Keine Vergänglichkeit"
},
"decay_introduced": "Die Vergänglichkeit wurde Eingeführt am ",
"decay_since_last_transaction": "Vergänglichkeit seit der letzten Transaktion",
"fromCommunity": "Aus der Gemeinschaft",
"hours": "Stunden",
"last_transaction": "Letzte Transaktion",
"minutes": "Minuten",
"months": "Monate",
"noDecay": "Keine Vergänglichkeit",
"past_time": "Vergangene Zeit",
"received": "Empfangen",
"seconds": "Sekunden",
"sent": "Gesendet",
"since_introduction": "seit Einführung der Vergänglichkeit",
"Starting_block_decay": "Startblock Vergänglichkeit",
"toCommunity": "An die Gemeinschaft",
"year": "Jahre"
},
"error": {
"change-password": "Fehler beim Ändern des Passworts",
"error": "Fehler",
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!"
},
"form": {
"amount": "Betrag",
"at": "am",
"cancel": "Abbrechen",
"reset": "Zurücksetzen",
"close": "schließen",
"edit": "bearbeiten",
"save": "speichern",
"recipient":"Empfänger",
"sender":"Absender",
"username":"Username",
"firstname":"Vorname",
"lastname":"Nachname",
"description": "Beschreibung",
"email":"E-Mail",
"email_repeat":"eMail wiederholen",
"password":"Passwort",
"passwordRepeat":"Passwort wiederholen",
"password_old":"altes Passwort",
"password_new":"neues Passwort",
"password_new_repeat":"neues Passwort wiederholen",
"change": "ändern",
"change-password": "Passwort ändern",
"change-name": "Name ändern",
"amount":"Betrag",
"memo":"Nachricht",
"message":"Nachricht",
"date":"Datum",
"from":"von",
"to":"bis",
"to1":"an",
"at":"am",
"time":"Zeit",
"send_now":"Jetzt senden",
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
"max_gdd_info":"Maximale anzahl GDD zum versenden erreicht!",
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
"thx":"Danke",
"sorry":"Entschuldigung",
"send_transaction_success":"Deine Transaktion wurde erfolgreich ausgeführt",
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
"change-password": "Passwort ändern",
"changeLanguage": "Sprache ändern",
"change_username_info": "Einmal gespeichert, kann der Username ncht mehr geändert werden!",
"close": "schließen",
"date": "Datum",
"description": "Beschreibung",
"edit": "bearbeiten",
"email": "E-Mail",
"email_repeat": "eMail wiederholen",
"firstname": "Vorname",
"from": "von",
"lastname": "Nachname",
"max_gdd_info": "Maximale anzahl GDD zum versenden erreicht!",
"memo": "Nachricht",
"message": "Nachricht",
"password": "Passwort",
"passwordRepeat": "Passwort wiederholen",
"password_new": "neues Passwort",
"password_new_repeat": "neues Passwort wiederholen",
"password_old": "altes Passwort",
"recipient": "Empfänger",
"reset": "Zurücksetzen",
"save": "speichern",
"scann_code": "<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
"sender": "Absender",
"send_check": "Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
"send_now": "Jetzt senden",
"send_transaction_error": "Leider konnte die Transaktion nicht ausgeführt werden!",
"send_transaction_success": "Deine Transaktion wurde erfolgreich ausgeführt",
"sorry": "Entschuldigung",
"thx": "Danke",
"time": "Zeit",
"to": "bis",
"to1": "an",
"username": "Username",
"validation": {
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
"usernmae-unique": "Der Username ist bereits vergeben.",
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen."
},
"change_username_info": "Einmal gespeichert, kann der Username ncht mehr geändert werden!"
},
"error": {
"error":"Fehler",
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
"change-password": "Fehler beim Ändern des Passworts"
},
"transaction":{
"show_all":"Alle <strong>{count}</strong> Transaktionen ansehen",
"nullTransactions":"Du hast noch keine Transaktionen auf deinem Konto.",
"more": "mehr",
"receiverNotFound":"Empfänger nicht gefunden",
"gdd-text":"Gradido Transaktionen",
"gdt-text":"Gradido Transform Transaktionen"
},
"site": {
"login": {
"community":"Tausend Dank, weil du bei uns bist!",
"remember":"Passwort merken",
"signin":"Anmelden",
"forgot_pwd":"Passwort vergessen?",
"new_wallet":"Neues Konto erstellen"
},
"signup": {
"title": "Erstelle dein Gradido-Konto",
"subtitle": "Werde Teil der Gemeinschaft!",
"agree":"Ich stimme der <a href='https://gradido.net/de/datenschutz/' target='_blank' >Datenschutzerklärung</a> zu.",
"lowercase":"Ein Kleinbuchstabe erforderlich.",
"uppercase":"Ein Großbuchstabe erforderlich.",
"minimum":"Mindestens 8 Zeichen.",
"one_number":"Eine Zahl erforderlich.",
"dont_match":"Die Passwörter stimmen nicht überein."
},
"password": {
"title": "Passwort zurücksetzen",
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen.",
"send_now": "Jetzt senden"
},
"thx": {
"title": "Danke!",
"email": "Wir haben dir eine eMail gesendet.",
"reset": "Dein Passwort wurde geändert.",
"register": "Du bist jetzt regisriert."
},
"overview":{
"account_overview":"Kontoübersicht",
"since_last_month": "seid letzten Monat",
"send_gradido":"Gradido versenden",
"add_work":"neuer Gemeinschaftsbeitrag"
},
"navbar" : {
"my-profil":"Mein Profil",
"settings":"Einstellungen",
"activity":"Aktivität",
"support":"Support"
},
"404" : {
"ooops" : "Ooops!",
"text" : "Seite nicht gefunden. Aber keine Sorge, wir haben noch viele andere Seiten zum Erkunden",
"back" : "Zurück zur Übersicht!"
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen.",
"usernmae-unique": "Der Username ist bereits vergeben."
}
},
"communitys":{
"form":{
"hours":"Stunden",
"date_period":"Datum / Zeitraum",
"more_hours":"weitere Stunden",
"submit":"Einreichen",
"hours_report":"Stundenbericht"
}
},
"reset-password": {
"title": "Passwort zurücksetzen",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst.",
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support."
},
"gdt": {
"gdt-received":"Gradido Transform (GDT) erhalten",
"factor":"Faktor",
"raise": "Erhöhung",
"action": "Aktion",
"calculation": "Berechnung der Gradido Transform",
"contribution": "Beitrag",
"conversion": "Umrechnung",
"conversion-gdt-euro": "Umrechnung Euro / Gradido Transform (GDT)",
"credit": "Gutschrift",
"conversion-gdt-euro":"Umrechnung Euro / Gradido Transform (GDT)",
"calculation":"Berechnung der Gradido Transform",
"conversion":"Umrechnung",
"formula":"Berechungsformel",
"no-transactions":"Du hast zur Zeit keine Transaktionen",
"publisher":"Dein geworbenes Mitglied hat einen Beitrag bezahlt",
"action":"Aktion",
"recruited-member":"Geworbenes Mitglied",
"contribution":"Beitrag"
}
"factor": "Faktor",
"formula": "Berechungsformel",
"gdt-received": "Gradido Transform (GDT) erhalten",
"no-transactions": "Du hast zur Zeit keine Transaktionen",
"publisher": "Dein geworbenes Mitglied hat einen Beitrag bezahlt",
"raise": "Erhöhung",
"recruited-member": "Geworbenes Mitglied"
},
"imprint": "Impressum",
"language": "Sprache",
"languages": {
"de": "Deutsch",
"en": "English"
},
"login": "Anmeldung",
"logout": "Abmelden",
"members_area": "Mitgliederbereich",
"message": "hallo gradido !!",
"privacy_policy": "Datenschutzerklärung",
"reset": "Passwort zurücksetzen",
"reset-password": {
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst.",
"title": "Passwort zurücksetzen"
},
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
"send": "Senden",
"setting": {
"changeNewsletter": "Newsletter Status ändern",
"newsletter": "Newsletter",
"newsletterFalse": "Du bist aus Newslettersystem ausgetragen.",
"newsletterTrue": "Du bist im Newslettersystem eingetraten."
},
"signup": "Registrieren",
"site": {
"404": {
"back": "Zurück zur Übersicht!",
"ooops": "Ooops!",
"text": "Seite nicht gefunden. Aber keine Sorge, wir haben noch viele andere Seiten zum Erkunden"
},
"checkEmail": {
"errorText": "Email konnte nicht verifiziert werden.",
"title": "Email wird verifiziert"
},
"login": {
"community": "Tausend Dank, weil du bei uns bist!",
"forgot_pwd": "Passwort vergessen?",
"new_wallet": "Neues Konto erstellen",
"remember": "Passwort merken",
"signin": "Anmelden"
},
"navbar": {
"activity": "Aktivität",
"my-profil": "Mein Profil",
"settings": "Einstellungen",
"support": "Support"
},
"overview": {
"account_overview": "Kontoübersicht",
"add_work": "neuer Gemeinschaftsbeitrag",
"send_gradido": "Gradido versenden",
"since_last_month": "seid letzten Monat"
},
"password": {
"send_now": "Jetzt senden",
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen.",
"title": "Passwort zurücksetzen"
},
"signup": {
"agree": "Ich stimme der <a href='https://gradido.net/de/datenschutz/' target='_blank' >Datenschutzerklärung</a> zu.",
"dont_match": "Die Passwörter stimmen nicht überein.",
"lowercase": "Ein Kleinbuchstabe erforderlich.",
"minimum": "Mindestens 8 Zeichen.",
"one_number": "Eine Zahl erforderlich.",
"subtitle": "Werde Teil der Gemeinschaft!",
"title": "Erstelle dein Gradido-Konto",
"uppercase": "Ein Großbuchstabe erforderlich."
},
"thx": {
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
"email": "Wir haben dir eine eMail gesendet.",
"register": "Du bist jetzt regisriert.",
"reset": "Dein Passwort wurde geändert.",
"title": "Danke!"
}
},
"transaction": {
"gdd-text": "Gradido Transaktionen",
"gdt-text": "Gradido Transform Transaktionen",
"more": "mehr",
"nullTransactions": "Du hast noch keine Transaktionen auf deinem Konto.",
"receiverNotFound": "Empfänger nicht gefunden",
"show_all": "Alle <strong>{count}</strong> Transaktionen ansehen"
},
"transactions": "Transaktionen",
"welcome": "Willkommen!",
"whitepaper": "Whitepaper"
}

View File

@ -1,181 +1,193 @@
{
"message": "hello gradido !!",
"welcome":"Welcome!",
"back": "Back",
"community": "Community",
"logout":"Logout",
"login":"Login",
"signup": "Sign up",
"reset": "Reset password",
"imprint":"Legal notice",
"privacy_policy":"Privacy policy",
"members_area": "Member's area",
"whitepaper": "Whitepaper",
"back":"Back",
"send":"Send",
"transactions":"Transactions",
"language":"Language",
"languages":{
"de": "Deutsch",
"en": "English"
"communitys": {
"form": {
"date_period": "Date / Period",
"hours": "hours",
"hours_report": "Hourly report",
"more_hours": "more hours",
"submit": "submit"
}
},
"select_language": "Please choose a language for the app and newsletter",
"decay": {
"decay": "Decay",
"decay_since_last_transaction":"Decay since the last transaction",
"calculation_decay": "Calculation of Decay",
"Starting_block_decay": "Starting Block Decay",
"decay_introduced": "Decay was Introduced on",
"decayStart": " - Starting block for decay at: ",
"last_transaction": "Last transaction:",
"past_time": "Past time",
"since_introduction": "Since the introduction of Decay",
"year": "Years",
"months": "Months",
"created": "Created",
"days": "Days",
"decay": "Decay",
"decayStart": " - Starting block for decay at: ",
"decay_introduced": "Decay was Introduced on",
"decay_since_last_transaction": "Decay since the last transaction",
"fromCommunity": "From the community",
"hours": "Hours",
"last_transaction": "Last transaction:",
"minutes": "Minutes",
"months": "Months",
"noDecay": "No Decay",
"past_time": "Past time",
"received": "Received",
"seconds": "Seconds",
"received":"Received",
"sent":"Sent",
"created":"Created",
"fromCommunity":"From the community",
"toCommunity":"To the community",
"noDecay": "No Decay"
"sent": "Sent",
"since_introduction": "Since the introduction of Decay",
"Starting_block_decay": "Starting Block Decay",
"toCommunity": "To the community",
"year": "Years"
},
"error": {
"change-password": "Error while changing password",
"error": "Error",
"no-account": "Unfortunately we could not find an account to the given data!"
},
"form": {
"cancel":"Cancel",
"reset": "Reset",
"close":"Close",
"edit": "Edit",
"save": "save",
"recipient":"Recipient",
"sender":"Sender",
"username":"Username",
"firstname":"Firstname",
"lastname":"Lastname",
"description": "Description",
"email":"Email",
"email_repeat":"Repeat Email",
"password":"Password",
"passwordRepeat":"Repeat password",
"password_old":"Old password",
"password_new":"New password",
"password_new_repeat":"Repeat new password",
"amount": "Amount",
"at": "at",
"cancel": "Cancel",
"change": "change",
"change-password": "Change password",
"change-name": "Change name",
"amount":"Amount",
"memo":"Message",
"message":"Message",
"date":"Date",
"from":"from",
"to":"to",
"to1":"to",
"at":"at",
"time":"Time",
"send_now":"Send now",
"scann_code":"<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
"max_gdd_info":"Maximum number of GDDs to be sent has been reached!",
"send_check":"Confirm your payment. Please check all data again!",
"thx":"Thank you",
"sorry":"Sorry",
"send_transaction_success":"Your transaction was successfully completed",
"send_transaction_error":"Unfortunately, the transaction could not be executed!",
"change-password": "Change password",
"changeLanguage": "Change language",
"change_username_info": "Once saved, the username cannot be changed again!",
"close": "Close",
"date": "Date",
"description": "Description",
"edit": "Edit",
"email": "Email",
"email_repeat": "Repeat Email",
"firstname": "Firstname",
"from": "from",
"lastname": "Lastname",
"max_gdd_info": "Maximum number of GDDs to be sent has been reached!",
"memo": "Message",
"message": "Message",
"password": "Password",
"passwordRepeat": "Repeat password",
"password_new": "New password",
"password_new_repeat": "Repeat new password",
"password_old": "Old password",
"recipient": "Recipient",
"reset": "Reset",
"save": "save",
"scann_code": "<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
"sender": "Sender",
"send_check": "Confirm your payment. Please check all data again!",
"send_now": "Send now",
"send_transaction_error": "Unfortunately, the transaction could not be executed!",
"send_transaction_success": "Your transaction was successfully completed",
"sorry": "Sorry",
"thx": "Thank you",
"time": "Time",
"to": "to",
"to1": "to",
"username": "Username",
"validation": {
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits",
"is-not": "You cannot send Gradidos to yourself",
"usernmae-unique": "The username is already taken.",
"usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters."
},
"change_username_info": "Once saved, the username cannot be changed again!"
},
"error": {
"error":"Error",
"no-account": "Unfortunately we could not find an account to the given data!",
"change-password": "Error while changing password"
},
"transaction":{
"show_all":"View all <strong>{count}</strong> transactions.",
"nullTransactions":"You don't have any transactions on your account yet.",
"more": "more",
"receiverNotFound":"Recipient not found",
"gdd-text":"Gradido Transactions",
"gdt-text":"Gradido Transform Transactions"
},
"site": {
"login": {
"community":"A thousand thanks for being with us!",
"remember":"Remember password",
"signin":"Sign in",
"forgot_pwd":"Forgot password?",
"new_wallet":"Create new account"
},
"signup": {
"title": "Create your Gradido account",
"subtitle": "Become a part of the community!",
"agree":"I agree to the <a href='https://gradido.net/en/datenschutz/' target='_blank' > privacy policy</a>.",
"lowercase":"One lowercase letter required.",
"uppercase":"One uppercase letter required.",
"minimum":"8 characters minimum.",
"one_number":"One number required.",
"dont_match":"Passwords don't match."
},
"password": {
"title": "Reset password",
"subtitle": "If you have forgotten your password, you can reset it here.",
"send_now": "Send now"
},
"thx": {
"title": "Thank you!",
"email": "We have sent you an email.",
"reset": "Your password has been changed.",
"register": "You are registred now."
},
"overview":{
"account_overview":"Account overview",
"since_last_month": "since last month",
"send_gradido":"Send Gradido",
"add_work":"New Community Contribution"
},
"navbar" : {
"my-profil":"My profile",
"settings":"Settings",
"activity":"Activity",
"support":"Support"
},
"404" : {
"ooops" : "Ooops!",
"text" : "Page not found. Do not worry though, we have plenty of other pages to explore",
"back" : "Back to dashboard!"
}
},
"communitys":{
"form":{
"hours":"hours",
"date_period":"Date / Period",
"more_hours":"more hours",
"submit":"submit",
"hours_report":"Hourly report"
"usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters.",
"usernmae-unique": "The username is already taken."
}
},
"reset-password": {
"title": "Reset Password",
"text": "Now you can save a new password to login to the Gradido-App in the future.",
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support."
},
"gdt": {
"gdt-received": "Gradido Transform (GDT) received",
"factor": "Factor",
"raise": "Increase",
"credit": "Credit",
"conversion-gdt-euro": "Conversion Euro / Gradido Transform (GDT)",
"action": "Action",
"calculation": "Calculation of Gradido Transform",
"contribution": "Contribution",
"conversion": "Conversion",
"conversion-gdt-euro": "Conversion Euro / Gradido Transform (GDT)",
"credit": "Credit",
"factor": "Factor",
"formula": "Calculation formula",
"no-transactions":"You currently have no transactions",
"publisher":"A member you referred has paid a contribution",
"action":"Action",
"recruited-member":"Recruited Member",
"contribution":"Contribution"
}
"gdt-received": "Gradido Transform (GDT) received",
"no-transactions": "You currently have no transactions",
"publisher": "A member you referred has paid a contribution",
"raise": "Increase",
"recruited-member": "Recruited Member"
},
"imprint": "Legal notice",
"language": "Language",
"languages": {
"de": "Deutsch",
"en": "English"
},
"login": "Login",
"logout": "Logout",
"members_area": "Member's area",
"message": "hello gradido !!",
"privacy_policy": "Privacy policy",
"reset": "Reset password",
"reset-password": {
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
"text": "Now you can save a new password to login to the Gradido-App in the future.",
"title": "Reset Password"
},
"select_language": "Please choose a language for the app and newsletter",
"send": "Send",
"setting": {
"changeNewsletter": "Newsletter status change",
"newsletter": "Newsletter",
"newsletterFalse": "You are unsubscribed from newsletter system.",
"newsletterTrue": "You are subscribed to newsletter system."
},
"signup": "Sign up",
"site": {
"404": {
"back": "Back to dashboard!",
"ooops": "Ooops!",
"text": "Page not found. Do not worry though, we have plenty of other pages to explore"
},
"checkEmail": {
"errorText": "Could not verify the email.",
"title": "Verifing email"
},
"login": {
"community": "A thousand thanks for being with us!",
"forgot_pwd": "Forgot password?",
"new_wallet": "Create new account",
"remember": "Remember password",
"signin": "Sign in"
},
"navbar": {
"activity": "Activity",
"my-profil": "My profile",
"settings": "Settings",
"support": "Support"
},
"overview": {
"account_overview": "Account overview",
"add_work": "New Community Contribution",
"send_gradido": "Send Gradido",
"since_last_month": "since last month"
},
"password": {
"send_now": "Send now",
"subtitle": "If you have forgotten your password, you can reset it here.",
"title": "Reset password"
},
"signup": {
"agree": "I agree to the <a href='https://gradido.net/en/datenschutz/' target='_blank' > privacy policy</a>.",
"dont_match": "Passwords don't match.",
"lowercase": "One lowercase letter required.",
"minimum": "8 characters minimum.",
"one_number": "One number required.",
"subtitle": "Become a part of the community!",
"title": "Create your Gradido account",
"uppercase": "One uppercase letter required."
},
"thx": {
"checkEmail": "Your email has been successfully verified.",
"email": "We have sent you an email.",
"register": "You are registred now.",
"reset": "Your password has been changed.",
"title": "Thank you!"
}
},
"transaction": {
"gdd-text": "Gradido Transactions",
"gdt-text": "Gradido Transform Transactions",
"more": "more",
"nullTransactions": "You don't have any transactions on your account yet.",
"receiverNotFound": "Recipient not found",
"show_all": "View all <strong>{count}</strong> transactions."
},
"transactions": "Transactions",
"welcome": "Welcome!",
"whitepaper": "Whitepaper"
}

View File

@ -52,6 +52,10 @@ const routes = [
path: '/reset/:optin',
component: () => import('../views/Pages/ResetPassword.vue'),
},
{
path: '/checkEmail/:optin',
component: () => import('../views/Pages/CheckEmail.vue'),
},
{ path: '*', component: NotFound },
]

View File

@ -26,6 +26,9 @@ export const mutations = {
token: (state, token) => {
state.token = token
},
newsletterState: (state, newsletterState) => {
state.newsletterState = newsletterState
},
}
export const actions = {
@ -36,6 +39,7 @@ export const actions = {
commit('firstName', data.firstName)
commit('lastName', data.lastName)
commit('description', data.description)
commit('newsletterState', data.klickTipp.newsletterState)
},
logout: ({ commit, state }) => {
commit('token', null)
@ -44,6 +48,7 @@ export const actions = {
commit('firstName', '')
commit('lastName', '')
commit('description', '')
commit('newsletterState', null)
localStorage.clear()
},
}
@ -62,6 +67,7 @@ export const store = new Vuex.Store({
username: '',
description: '',
token: null,
newsletterState: null,
},
getters: {},
// Syncronous mutation of the state

View File

@ -1,6 +1,15 @@
import { mutations, actions } from './store'
const { language, email, token, username, firstName, lastName, description } = mutations
const {
language,
email,
token,
username,
firstName,
lastName,
description,
newsletterState,
} = mutations
const { login, logout } = actions
describe('Vuex store', () => {
@ -60,6 +69,14 @@ describe('Vuex store', () => {
expect(state.description).toEqual('Nickelbrille')
})
})
describe('newsletterState', () => {
it('sets the state of newsletterState', () => {
const state = { newsletterState: null }
newsletterState(state, true)
expect(state.newsletterState).toEqual(true)
})
})
})
describe('actions', () => {
@ -73,11 +90,14 @@ describe('Vuex store', () => {
firstName: 'Peter',
lastName: 'Lustig',
description: 'Nickelbrille',
klickTipp: {
newsletterState: true,
},
}
it('calls seven commits', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenCalledTimes(6)
expect(commit).toHaveBeenCalledTimes(7)
})
it('commits email', () => {
@ -109,6 +129,11 @@ describe('Vuex store', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(6, 'description', 'Nickelbrille')
})
it('commits newsletterState', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(7, 'newsletterState', true)
})
})
describe('logout', () => {
@ -117,7 +142,7 @@ describe('Vuex store', () => {
it('calls six commits', () => {
logout({ commit, state })
expect(commit).toHaveBeenCalledTimes(6)
expect(commit).toHaveBeenCalledTimes(7)
})
it('commits token', () => {
@ -150,6 +175,11 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(6, 'description', '')
})
it('commits newsletterState', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(7, 'newsletterState', null)
})
// how to get this working?
it.skip('calls localStorage.clear()', () => {
const clearStorageMock = jest.fn()

View File

@ -117,10 +117,6 @@ describe('DashboardLayoutGdd', () => {
)
})
it('has a locale switch', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
it('has a logout button', () => {
expect(wrapper.findAll('ul').at(3).text()).toBe('logout')
})

View File

@ -6,6 +6,8 @@ sendMock.mockResolvedValue('success')
const localVue = global.localVue
window.scrollTo = jest.fn()
describe('AccountOverview', () => {
let wrapper

View File

@ -1,183 +0,0 @@
<template>
<div class="pt-4 pb-4">
<b-tabs content-class="mt-3" class="display-4" fill>
<b-tab :title="names.thisMonth" active>
<b-row>
<b-col lg="3">
<b-input :label="$t('communitys.form.hours')">
<b-form-input
type="number"
size="lg"
placeholder="23"
style="font-size: xx-large; padding-left: 5px"
/>
</b-input>
<b-input :label="$t('communitys.form.date_period')">
<flat-pickr
class="form-control"
v-model="date"
:config="config"
style="font-size: 0.5em; padding-left: 5px"
></flat-pickr>
</b-input>
</b-col>
<b-col lg="9">
<b-input :label="$t('communitys.form.hours_report')">
<textarea
class="form-control"
rows="5"
@focus="textFocus"
style="font-size: x-large; padding-left: 20px"
></textarea>
</b-input>
</b-col>
</b-row>
<b-row>
<div ref="mydiv"></div>
</b-row>
<b-row>
<b-col md="6">
<b-button @click.prevent="newWorkForm" variant="warning">
+ {{ $t('communitys.form.more_hours') }}
</b-button>
</b-col>
<b-col md="6" class="text-right">
<b-button variant="success" @click.prevent="submitForm2">
{{ $t('communitys.form.submit') }}
</b-button>
</b-col>
</b-row>
</b-tab>
<b-tab :title="names.lastMonth"></b-tab>
<b-tab :title="names.beforLastMonth"></b-tab>
</b-tabs>
</div>
</template>
<script>
export default {
name: 'GDDAddWork2',
data() {
return {
date: null,
config: {
altInput: false,
dateFormat: 'd-m-Y',
minDate: this.$moment().startOf('month').format('DD.MM.YYYY'),
maxDate: this.$moment().format('DD.MM.YYYY'),
mode: 'range',
},
index: 0,
form: [],
stundenSumme: 0,
messages: [],
submitted: false,
days: {
thisMonth: this.$moment().month(this.$moment().month()).daysInMonth(),
lastMonth: this.$moment()
.month(this.$moment().month() - 1)
.daysInMonth(),
beforLastMonth: this.$moment()
.month(this.$moment().month() - 2)
.daysInMonth(),
},
names: {
thisMonth: this.$moment().month(this.$moment().month()).format('MMMM'),
lastMonth: this.$moment()
.month(this.$moment().month() - 1)
.format('MMMM'),
beforLastMonth: this.$moment()
.month(this.$moment().month() - 2)
.format('MMMM'),
},
formular: null,
}
},
created() {},
watch: {
$form: function () {
this.stunden(this.form)
},
},
mounted() {},
methods: {
stunden(hour, i, mon) {
let n = 0
this.stundenSumme = 0
for (n; n < this.form.length; n++) {
if (this.form[n] > 0) {
this.stundenSumme += parseInt(this.form[n])
}
}
this.messages.push({
id: this.index,
MonthsNumber: mon,
DaysNumber: i,
HoursNumber: hour,
DestinationText: '',
TextDecoded: '',
})
this.index++
},
addNewMessage: function () {
this.messages.push({
DaysNumber: '',
TextDecoded: '',
})
},
deleteNewMessage: function (event) {
this.form.splice(event, null)
this.messages.splice(this.index, 1)
this.index--
},
submitForm: function (e) {
// console.log('submitForm')
this.messages = [{ DaysNumber: '', TextDecoded: '' }]
this.submitted = true
},
textFocus() {
// console.log('textFocus TODO')
},
newWorkForm() {
this.formular = `
<b-col lg="3">
<b-input label="Stunden">
<b-form-input
type="number"
size="lg"
placeholder="0"
style="font-size: xx-large; padding-left: 20px"
/>
</b-input>
<b-input label="Datum / Zeitraum">
<flat-pickr
class="form-control"
v-model="date"
:config="config"
style="font-size: xx-large; padding-left: 20px"
></flat-pickr>
</b-input>
</b-col>
<b-col lg="9">
<b-input label="Arbeitsreport">
<textarea
class="form-control"
rows="5"
@focus="textFocus"
style="font-size: x-large; padding-left: 20px"
></textarea>
</b-input>
</b-col>
`
// console.log('newWorkForm TODO')
const myElement = this.$refs.mydiv
myElement.append(this.formular)
this.$compile(myElement)
this.formular = null
},
},
}
</script>

View File

@ -84,13 +84,10 @@
</div>
</div>
<pagination-buttons
v-if="showPagination && transactionCount > pageSize"
:has-next="hasNext"
:has-previous="hasPrevious"
:total-pages="totalPages"
:current-page="currentPage"
@show-next="showNext"
@show-previous="showPrevious"
v-if="showPagination"
v-model="currentPage"
:per-page="pageSize"
:total-rows="transactionCount"
></pagination-buttons>
<div v-if="transactions.length === 0" class="mt-4 text-center">
<span>{{ $t('transaction.nullTransactions') }}</span>
@ -128,29 +125,13 @@ export default {
transactionCount: { type: Number, default: 0 },
showPagination: { type: Boolean, default: false },
},
watch: {
timestamp: {
immediate: true,
handler: 'updateTransactions',
},
},
computed: {
hasNext() {
return this.currentPage * this.pageSize < this.transactionCount
},
hasPrevious() {
return this.currentPage > 1
},
totalPages() {
return Math.ceil(this.transactionCount / this.pageSize)
},
},
methods: {
updateTransactions() {
this.$emit('update-transactions', {
firstPage: this.currentPage,
items: this.pageSize,
})
window.scrollTo(0, 0)
},
getProperties(givenType) {
const type = iconsByType[givenType]
@ -165,15 +146,14 @@ export default {
throwError(msg) {
throw new Error(msg)
},
showNext() {
this.currentPage++
},
watch: {
currentPage() {
this.updateTransactions()
window.scrollTo(0, 0)
},
showPrevious() {
this.currentPage--
this.updateTransactions()
window.scrollTo(0, 0)
timestamp: {
immediate: true,
handler: 'updateTransactions',
},
},
}

View File

@ -1,83 +0,0 @@
<template>
<div>
<b-list-group>
<b-list-group-item v-for="item in items" :key="item.id">
<div class="d-flex w-100 justify-content-between" @click="toogle(item)">
<b-icon
v-if="item.status == 'submitted'"
icon="clock-history"
class="m-1"
font-scale="2"
style="color: orange"
></b-icon>
<b-icon v-else icon="check2-all" class="m-1" font-scale="2" style="color: green"></b-icon>
<h2 class="text-muted">
<small>{{ item.datel }}</small>
- {{ item.text }}
</h2>
</div>
</b-list-group-item>
</b-list-group>
<hr />
<b-icon icon="clock-history" class="m-1" font-scale="2" style="color: orange"></b-icon>
Wartet auf Bestätigung |
<b-icon icon="check2-all" class="m-1" font-scale="2" style="color: green"></b-icon>
bestätigt
</div>
</template>
<script>
export default {
name: 'GddWorkTable',
data() {
return {
form: [],
items: [
{
id: 1,
text: 'Zwei Säcke Plastikmüll im Wald gesammelt',
datel: '12.12.2020 14:04',
status: 'submitted',
},
{
id: 2,
text: 'Frau Schmidt bei der Gartenarbeit geholfen.',
datel: '22.06.2020 22:23',
status: 'submitted',
},
{
id: 3,
text: 'Ein online Kurs für nachhaltiges Mülltrennen erstellt',
datel: '15.04.2020 12:55',
status: 'confirmed',
},
{
id: 4,
text: 'Gradido bei meinen Freunden vorgestellt',
datel: '10.03.2020 18:20',
status: 'confirmed',
},
],
}
},
methods: {
rowClass(item, type) {
if (!item || type !== 'row') return
if (item.status === 'received') return 'table-success'
if (item.status === 'sent') return 'table-warning'
if (item.status === 'earned') return 'table-primary'
},
toogle(item) {
// eslint-disable-next-line no-unused-vars
const temp =
'<b-collapse visible v-bind:id="item.id">xxx <small class="text-muted">porta</small></b-collapse>'
},
},
}
</script>
<style>
.el-table .cell {
padding-left: 0px;
padding-right: 0px;
}
</style>

View File

@ -46,6 +46,9 @@ const apolloMock = jest.fn().mockResolvedValue({
})
const toastErrorMock = jest.fn()
const windowScrollToMock = jest.fn()
window.scrollTo = windowScrollToMock
describe('GdtTransactionList', () => {
let wrapper
@ -90,6 +93,10 @@ describe('GdtTransactionList', () => {
}),
)
})
it('scrolls to (0, 0) after API call', () => {
expect(windowScrollToMock).toBeCalledWith(0, 0)
})
})
describe('server returns error', () => {
@ -105,5 +112,21 @@ describe('GdtTransactionList', () => {
expect(toastErrorMock).toBeCalledWith('Ouch!')
})
})
describe('change of currentPage', () => {
it('calls the API after currentPage changes', async () => {
jest.clearAllMocks()
wrapper.setData({ currentPage: 2 })
await wrapper.vm.$nextTick()
expect(apolloMock).toBeCalledWith(
expect.objectContaining({
variables: {
currentPage: 2,
pageSize: 25,
},
}),
)
})
})
})
})

View File

@ -28,13 +28,9 @@
</div>
</div>
<pagination-buttons
v-if="transactionGdtCount > pageSize"
:has-next="hasNext"
:has-previous="hasPrevious"
:total-pages="totalPages"
:current-page="currentPage"
@show-next="showNext"
@show-previous="showPrevious"
v-model="currentPage"
:per-page="pageSize"
:total-rows="transactionGdtCount"
></pagination-buttons>
</div>
</template>
@ -52,23 +48,12 @@ export default {
},
data() {
return {
transactionsGdt: { default: () => [] },
transactionsGdt: [],
transactionGdtCount: { type: Number, default: 0 },
currentPage: 1,
pageSize: 25,
}
},
computed: {
hasNext() {
return this.currentPage * this.pageSize < this.transactionGdtCount
},
hasPrevious() {
return this.currentPage > 1
},
totalPages() {
return Math.ceil(this.transactionGdtCount / this.pageSize)
},
},
methods: {
async updateGdt() {
this.$apollo
@ -85,25 +70,21 @@ export default {
} = result
this.transactionsGdt = listGDTEntries.gdtEntries
this.transactionGdtCount = listGDTEntries.count
window.scrollTo(0, 0)
})
.catch((error) => {
this.$toasted.error(error.message)
})
},
showNext() {
this.currentPage++
this.updateGdt()
window.scrollTo(0, 0)
},
showPrevious() {
this.currentPage--
this.updateGdt()
window.scrollTo(0, 0)
},
},
mounted() {
this.updateGdt()
},
watch: {
currentPage() {
this.updateGdt()
},
},
}
</script>
<style>

View File

@ -0,0 +1,105 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import CheckEmail from './CheckEmail'
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockRejectedValue({ message: 'error' })
const toasterMock = jest.fn()
const routerPushMock = jest.fn()
describe('CheckEmail', () => {
let wrapper
const mocks = {
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
$route: {
params: {
optin: '123',
},
},
$toasted: {
error: toasterMock,
},
$router: {
push: routerPushMock,
},
$loading: {
show: jest.fn(() => {
return { hide: jest.fn() }
}),
},
$apollo: {
query: apolloQueryMock,
},
}
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(CheckEmail, { localVue, mocks, stubs })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('calls the checkEmail when created', async () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({ variables: { optin: '123' } }),
)
})
describe('No valid optin', () => {
it('toasts an error when no valid optin is given', () => {
expect(toasterMock).toHaveBeenCalledWith('error')
})
it('has a message suggesting to contact the support', () => {
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
expect(wrapper.find('div.header').text()).toContain('checkEmail.errorText')
})
})
describe('is authenticated', () => {
beforeEach(() => {
apolloQueryMock.mockResolvedValue({
data: {
checkEmail: {
sessionId: 1,
email: 'user@example.org',
language: 'de',
},
},
})
})
it.skip('Has sessionId from API call', async () => {
await wrapper.vm.$nextTick()
expect(wrapper.vm.sessionId).toBe(1)
})
describe('Register header', () => {
it('has a welcome message', async () => {
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
})
})
describe('links', () => {
it('has a link "Back"', async () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
})
it('links to /login when clicking "Back"', async () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/Login')
})
})
})
})
})

View File

@ -0,0 +1,72 @@
<template>
<div class="checkemail-form">
<b-container>
<div class="header p-4" ref="header">
<div class="header-body text-center mb-7">
<b-row class="justify-content-center">
<b-col xl="5" lg="6" md="8" class="px-2">
<h1>{{ $t('checkEmail.title') }}</h1>
<div class="pb-4" v-if="!pending">
<span v-if="!authenticated">
{{ $t('checkEmail.errorText') }}
</span>
</div>
</b-col>
</b-row>
</div>
</div>
</b-container>
<b-container class="mt--8 p-1">
<b-row>
<b-col class="text-center py-lg-4">
<router-link to="/Login" class="mt-3">{{ $t('back') }}</router-link>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import { checkEmailQuery } from '../../graphql/queries'
export default {
name: 'CheckEmail',
data() {
return {
authenticated: false,
sessionId: null,
email: null,
pending: true,
}
},
methods: {
async authenticate() {
const loader = this.$loading.show({
container: this.$refs.header,
})
const optin = this.$route.params.optin
this.$apollo
.query({
query: checkEmailQuery,
variables: {
optin: optin,
},
})
.then((result) => {
this.authenticated = true
this.sessionId = result.data.checkEmail.sessionId
this.email = result.data.checkEmail.email
this.$router.push('/thx/checkEmail')
})
.catch((error) => {
this.$toasted.error(error.message)
})
loader.hide()
this.pending = false
},
},
mounted() {
this.authenticate()
},
}
</script>
<style></style>

View File

@ -22,6 +22,11 @@ describe('Register', () => {
$apollo: {
query: resgisterUserQueryMock,
},
$store: {
state: {
language: null,
},
},
}
const stubs = {
@ -38,7 +43,7 @@ describe('Register', () => {
})
it('renders the Register form', () => {
expect(wrapper.find('div.register-form').exists()).toBeTruthy()
expect(wrapper.find('div#registerform').exists()).toBeTruthy()
})
describe('Register header', () => {
@ -81,11 +86,11 @@ describe('Register', () => {
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
})
it('has Language selected field', () => {
expect(wrapper.find('#selectedLanguage').exists()).toBeTruthy()
expect(wrapper.find('.selectedLanguage').exists()).toBeTruthy()
})
it('selected Language value de', async () => {
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
expect(wrapper.find('#selectedLanguage').element.value).toBe('de')
wrapper.find('.selectedLanguage').findAll('option').at(1).setSelected()
expect(wrapper.find('.selectedLanguage').element.value).toBe('de')
})
it('has 1 checkbox input fields', () => {
@ -128,14 +133,14 @@ describe('Register', () => {
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
wrapper.find('input[name="site.signup.agree"]').setChecked(true)
})
it('reset selected value language', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('#selectedLanguage').element.value).toBe('')
expect(wrapper.find('.language-switch-select').element.value).toBe(undefined)
})
it('resets the firstName field after clicking the reset button', async () => {
@ -182,7 +187,7 @@ describe('Register', () => {
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
})
describe('server sends back error', () => {

View File

@ -1,5 +1,5 @@
<template>
<div class="register-form">
<div id="registerform">
<!-- Header -->
<div class="header p-4">
<b-container class="container">
@ -87,12 +87,7 @@
<b-row>
<b-col cols="12">
{{ $t('language') }}
<b-form-select
id="selectedLanguage"
v-model="selected"
:options="options"
class="mb-3"
></b-form-select>
<language-switch-select @update-language="updateLanguage" />
</b-col>
</b-row>
@ -145,10 +140,11 @@
<script>
import InputEmail from '../../components/Inputs/InputEmail.vue'
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
import { resgisterUserQuery } from '../../graphql/queries'
export default {
components: { InputPasswordConfirmation, InputEmail },
components: { InputPasswordConfirmation, InputEmail, LanguageSwitchSelect },
name: 'register',
data() {
return {
@ -162,12 +158,7 @@ export default {
passwordRepeat: '',
},
},
selected: null,
options: [
{ value: null, text: this.$t('select_language') },
{ value: 'de', text: this.$t('languages.de') },
{ value: 'en', text: this.$t('languages.en') },
],
language: '',
submitted: false,
showError: false,
messageError: '',
@ -175,6 +166,9 @@ export default {
}
},
methods: {
updateLanguage(e) {
this.language = e
},
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null
},
@ -189,7 +183,7 @@ export default {
},
agree: false,
}
this.selected = null
this.language = ''
this.$nextTick(() => {
this.$refs.observer.reset()
})
@ -203,7 +197,7 @@ export default {
firstName: this.form.firstname,
lastName: this.form.lastname,
password: this.form.password.password,
language: this.selected,
language: this.language,
},
})
.then(() => {
@ -212,7 +206,7 @@ export default {
this.form.lastname = ''
this.form.password.password = ''
this.form.password.passwordRepeat = ''
this.selected = null
this.language = ''
this.$router.push('/thx/register')
})
.catch((error) => {
@ -228,7 +222,7 @@ export default {
this.form.lastname = ''
this.form.password.password = ''
this.form.password.passwordRepeat = ''
this.selected = null
this.language = ''
},
},
computed: {
@ -244,7 +238,7 @@ export default {
return this.form.email !== ''
},
languageFilled() {
return this.selected !== null
return this.language !== null && this.language !== ''
},
},
}

View File

@ -1,7 +1,11 @@
<template>
<b-card class="bg-transparent">
<div class="w-100 text-center">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
<vue-qrcode
v-if="$store.state.email"
:value="$store.state.email"
type="image/png"
></vue-qrcode>
</div>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">

View File

@ -1,81 +1,75 @@
<template>
<div class="userdata_form" fluid="sm">
<div
id="userdata_form"
class="bg-transparent pt-3 pb-3"
style="background-color: #ebebeba3 !important"
>
<b-container>
<b-row class="text-right">
<b-col class="mb-3">
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
<b-card id="userdata_form" class="bg-transparent" style="background-color: #ebebeba3 !important">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
<b-icon v-if="showUserData" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</div>
<b-icon v-if="showUserData" class="pointer" icon="pencil">
{{ $t('form.change') }}
</b-icon>
<b-icon v-else class="pointer" icon="x-circle" variant="danger"></b-icon>
</a>
<div>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-3">
<b-col class="col-12">
<small>
<b>{{ $t('form.firstname') }}</b>
</small>
</b-col>
<b-col v-if="showUserData" class="col-12">
{{ form.firstName }}
</b-col>
<b-col v-else class="col-12">
<b-input type="text" v-model="form.firstName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-12">
<small>
<b>{{ $t('form.lastname') }}</b>
</small>
</b-col>
<b-col v-if="showUserData" class="col-12">
{{ form.lastName }}
</b-col>
<b-col v-else class="col-12">
<b-input type="text" v-model="form.lastName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3" v-show="false">
<b-col class="col-12">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-12">
{{ form.description }}
</b-col>
<b-col v-else class="col-12">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
</b-container>
<b-container>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-3">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.firstname') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.firstName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-input type="text" v-model="form.firstName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.lastname') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.lastName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-input type="text" v-model="form.lastName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3" v-show="false">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.description }}
</b-col>
<b-col v-else class="col-sm-10 col-md-9">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
<b-row class="text-right" v-if="!showUserData">
<b-col>
<div class="text-right" ref="submitButton">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</b-container>
<b-row class="text-right" v-if="!showUserData">
<b-col>
<div class="text-right" ref="submitButton">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</div>
</div>
</b-card>
</template>
<script>
import { updateUserInfos } from '../../../graphql/queries'

View File

@ -1,48 +1,43 @@
<template>
<div
id="change_pwd"
class="bg-transparent pt-3 pb-3"
style="background-color: #ebebeba3 !important"
>
<b-container>
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="!editPassword ? (editPassword = !editPassword) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
<b-icon v-if="!editPassword" class="pointer ml-3" icon="pencil" />
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</div>
<div v-if="editPassword">
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-2">
<b-col>
<input-password
:label="$t('form.password_old')"
:placeholder="$t('form.password_old')"
v-model="form.password"
></input-password>
</b-col>
</b-row>
<input-password-confirmation v-model="form.newPassword" :register="register" />
<b-row class="text-right">
<b-col>
<div class="text-right">
<b-button type="submit" variant="primary" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</validation-observer>
</div>
</b-container>
</div>
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showPassword ? (showPassword = !showPassword) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
<b-icon v-if="showPassword" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</div>
<div v-if="!showPassword">
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-2">
<b-col>
<input-password
:label="$t('form.password_old')"
:placeholder="$t('form.password_old')"
v-model="form.password"
></input-password>
</b-col>
</b-row>
<input-password-confirmation v-model="form.newPassword" :register="register" />
<b-row class="text-right">
<b-col>
<div class="text-right">
<b-button type="submit" variant="primary" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</validation-observer>
</div>
</b-card>
</template>
<script>
import InputPassword from '../../../components/Inputs/InputPassword'
@ -57,7 +52,7 @@ export default {
},
data() {
return {
editPassword: false,
showPassword: true,
email: null,
form: {
password: '',
@ -71,7 +66,7 @@ export default {
},
methods: {
cancelEdit() {
this.editPassword = false
this.showPassword = true
this.form.password = ''
this.form.passwordNew = ''
this.form.passwordNewRepeat = ''

View File

@ -0,0 +1,49 @@
import { mount } from '@vue/test-utils'
import UserCardLanguage from './UserCard_Language'
const localVue = global.localVue
const mockAPIcall = jest.fn()
const toastErrorMock = jest.fn()
const toastSuccessMock = jest.fn()
const storeCommitMock = jest.fn()
describe('UserCard_Language', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$store: {
state: {
language: 'de',
},
commit: storeCommitMock,
},
$toasted: {
success: toastSuccessMock,
error: toastErrorMock,
},
$apollo: {
query: mockAPIcall,
},
}
const Wrapper = () => {
return mount(UserCardLanguage, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div#formuserlanguage').exists()).toBeTruthy()
})
it('has an edit icon', () => {
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
})
})
})

View File

@ -0,0 +1,108 @@
<template>
<b-card
id="formuserlanguage"
class="bg-transparent"
style="background-color: #ebebeba3 !important"
>
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showLanguage ? (showLanguage = !showLanguage) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.changeLanguage') }}</span>
<b-icon v-if="showLanguage" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</div>
<div v-if="showLanguage">
<b-row class="mb-3">
<b-col class="col-12">
<small>
<b>{{ $t('language') }}</b>
</small>
</b-col>
<b-col class="col-12">{{ $store.state.language }}</b-col>
</b-row>
</div>
<div v-else>
<div>
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-2">
<b-col class="col-12">
<small>
<b>{{ $t('language') }}</b>
</small>
</b-col>
<b-col class="col-12">
<language-switch-select @update-language="updateLanguage" :language="language" />
</b-col>
</b-row>
<b-row class="text-right">
<b-col>
<div class="text-right" ref="submitButton">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</div>
</div>
</b-card>
</template>
<script>
import LanguageSwitchSelect from '../../../components/LanguageSwitchSelect.vue'
import { updateUserInfos } from '../../../graphql/queries'
export default {
name: 'FormUserLanguage',
components: { LanguageSwitchSelect },
data() {
return {
showLanguage: true,
language: '',
loading: true,
}
},
methods: {
updateLanguage(e) {
this.language = e
if (this.language !== this.$store.state.language) {
this.loading = false
} else {
this.loading = true
}
},
cancelEdit() {
this.showLanguage = true
},
async onSubmit() {
this.$apollo
.query({
query: updateUserInfos,
variables: {
language: this.$store.state.language,
},
})
.then(() => {
this.cancelEdit()
})
.catch((error) => {
this.$toasted.error(error.message)
})
},
},
}
</script>

View File

@ -0,0 +1,98 @@
import { mount } from '@vue/test-utils'
import UserCardNewsletter from './UserCard_Newsletter'
import { unsubscribeNewsletter } from '../../../graphql/mutations'
const localVue = global.localVue
const mockAPIcall = jest.fn()
const toastErrorMock = jest.fn()
const toastSuccessMock = jest.fn()
const storeCommitMock = jest.fn()
const newsletterStateMock = jest.fn().mockReturnValue(true)
describe('UserCard_Newsletter', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$store: {
state: {
language: 'de',
email: 'peter@lustig.de',
newsletterState: newsletterStateMock,
},
commit: storeCommitMock,
},
$toasted: {
success: toastSuccessMock,
error: toastErrorMock,
},
$apollo: {
mutate: mockAPIcall,
},
}
const Wrapper = () => {
return mount(UserCardNewsletter, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div#formusernewsletter').exists()).toBeTruthy()
})
it('has an edit BFormCheckbox switch', () => {
expect(wrapper.find('.Test-BFormCheckbox').exists()).toBeTruthy()
})
describe('unsubscribe with sucess', () => {
beforeEach(() => {
mockAPIcall.mockResolvedValue({
data: {
unsubscribeNewsletter: true,
},
})
wrapper.find('input').trigger('change')
})
it('calls the unsubscribe mutation', () => {
expect(mockAPIcall).toBeCalledWith({
mutation: unsubscribeNewsletter,
variables: {
email: 'peter@lustig.de',
},
})
})
it('updates the store', () => {
expect(storeCommitMock).toBeCalledWith('newsletterState', false)
})
it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('setting.newsletterFalse')
})
})
describe('unsubscribe with server error', () => {
beforeEach(() => {
mockAPIcall.mockRejectedValue({
message: 'Ouch',
})
wrapper.find('input').trigger('change')
})
it('resets the newsletterState', () => {
expect(wrapper.vm.newsletterState).toBeTruthy()
})
it('toasts an error message', () => {
expect(toastErrorMock).toBeCalledWith('Ouch')
})
})
})
})

View File

@ -0,0 +1,64 @@
<template>
<b-card
id="formusernewsletter"
class="bg-transparent"
style="background-color: #ebebeba3 !important"
>
<div>
<b-row class="mb-3">
<b-col class="mb-2 col-12">
<small>
<b>{{ $t('setting.newsletter') }}</b>
</small>
</b-col>
<b-col class="col-12">
<b-form-checkbox
class="Test-BFormCheckbox"
v-model="newsletterState"
name="check-button"
switch
@change="onSubmit"
>
{{ newsletterState ? $t('setting.newsletterTrue') : $t('setting.newsletterFalse') }}
</b-form-checkbox>
</b-col>
</b-row>
</div>
</b-card>
</template>
<script>
import { subscribeNewsletter, unsubscribeNewsletter } from '../../../graphql/mutations'
export default {
name: 'FormUserNewsletter',
data() {
return {
newsletterState: this.$store.state.newsletterState,
}
},
methods: {
async onSubmit() {
this.$apollo
.mutate({
mutation: this.newsletterState ? subscribeNewsletter : unsubscribeNewsletter,
variables: {
email: this.$store.state.email,
language: this.newsletterState ? this.$store.state.language : undefined,
},
})
.then(() => {
this.$store.commit('newsletterState', this.newsletterState)
this.$toasted.success(
this.newsletterState
? this.$t('setting.newsletterTrue')
: this.$t('setting.newsletterFalse'),
)
})
.catch((error) => {
this.newsletterState = this.$store.state.newsletterState
this.$toasted.error(error.message)
})
},
},
}
</script>

View File

@ -23,16 +23,16 @@ describe('UserProfileOverview', () => {
expect(wrapper.findComponent({ name: 'UserCard' }).exists()).toBeTruthy()
})
it('has a user data form', () => {
it('has a user first and last name form', () => {
expect(wrapper.findComponent({ name: 'FormUserData' }).exists()).toBeTruthy()
})
// it('has a user name form', () => {
// expect(wrapper.findComponent({ name: 'FormUsername' }).exists()).toBeTruthy()
// })
it('has a user password form', () => {
it('has a user change password form', () => {
expect(wrapper.findComponent({ name: 'FormUserPasswort' }).exists()).toBeTruthy()
})
it('has a user change language form', () => {
expect(wrapper.findComponent({ name: 'FormUserLanguage' }).exists()).toBeTruthy()
})
})
})

View File

@ -1,24 +1,29 @@
<template>
<div fluid="sm">
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
<p><form-user-data /></p>
<!--<form-username />-->
<form-user-data />
<hr />
<p><form-user-passwort /></p>
<form-user-passwort />
<hr />
<form-user-language />
<hr />
<form-user-newsletter />
</div>
</template>
<script>
import UserCard from './UserProfile/UserCard.vue'
import FormUserData from './UserProfile/UserCard_FormUserData.vue'
// import FormUsername from './UserProfile/UserCard_FormUsername.vue'
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
import FormUserLanguage from './UserProfile/UserCard_Language.vue'
import FormUserNewsletter from './UserProfile/UserCard_Newsletter.vue'
export default {
components: {
UserCard,
FormUserData,
// FormUsername,
FormUserPasswort,
FormUserLanguage,
FormUserNewsletter,
},
props: {
balance: { type: Number, default: 0 },

View File

@ -3,6 +3,8 @@ import UserProfileTransactionList from './UserProfileTransactionList'
const localVue = global.localVue
window.scrollTo = jest.fn()
describe('UserProfileTransactionList', () => {
let wrapper

View File

@ -31,6 +31,11 @@ const textFields = {
button: 'site.login.signin',
linkTo: '/overview',
},
checkEmail: {
subtitle: 'site.thx.checkEmail',
button: 'login',
linkTo: '/login',
},
}
export default {

View File

@ -1796,6 +1796,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/json-schema@^7.0.8":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@ -1806,6 +1811,11 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/minimist@^1.2.0":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
"@types/node@*":
version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
@ -2495,7 +2505,7 @@ ajv@^5.2.3, ajv@^5.3.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -2752,11 +2762,6 @@ array-equal@^1.0.0:
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@ -2809,6 +2814,11 @@ array.prototype.flat@^1.2.3:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.1"
arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asn1.js@^5.2.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@ -3293,13 +3303,6 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
block-stream@*:
version "0.0.9"
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
dependencies:
inherits "~2.0.0"
bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@ -3711,18 +3714,14 @@ camel-case@3.0.x:
no-case "^2.2.0"
upper-case "^1.1.1"
camelcase-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
camelcase-keys@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==
dependencies:
camelcase "^2.0.0"
map-obj "^1.0.0"
camelcase@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
camelcase "^5.3.1"
map-obj "^4.0.0"
quick-lru "^4.0.1"
camelcase@^4.1.0:
version "4.1.0"
@ -3876,6 +3875,11 @@ chownr@^1.0.1, chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
chrome-trace-event@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@ -4012,15 +4016,6 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
dependencies:
is-plain-object "^2.0.4"
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clone@2.x, clone@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
@ -4376,14 +4371,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-spawn@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI=
dependencies:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@ -4404,7 +4391,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -4629,13 +4616,6 @@ current-script-polyfill@^1.0.0:
resolved "https://registry.yarnpkg.com/current-script-polyfill/-/current-script-polyfill-1.0.0.tgz#f31cf7e4f3e218b0726e738ca92a02d3488ef615"
integrity sha1-8xz35PPiGLBybnOMqSoC00iO9hU=
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
dependencies:
array-find-index "^1.0.1"
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@ -4997,7 +4977,15 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "2.1.2"
decamelize@^1.1.2, decamelize@^1.2.0:
decamelize-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=
dependencies:
decamelize "^1.1.0"
map-obj "^1.0.0"
decamelize@^1.1.0, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@ -5315,10 +5303,10 @@ dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"
dotenv-defaults@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-2.0.1.tgz#ea6f9632b3b5cc55e48b736760def5561f1cb7c0"
integrity sha512-ugFCyBF7ILuwpmznduHPQZBMucHHJ8T4OBManTEVjemxCm2+nqifSuW2lD2SNKdiKSH1E324kZSdJ8M04b4I/A==
dotenv-defaults@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz#6b3ec2e4319aafb70940abda72d3856770ee77ac"
integrity sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==
dependencies:
dotenv "^8.2.0"
@ -5327,12 +5315,12 @@ dotenv-expand@^5.1.0:
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
dotenv-webpack@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-6.0.4.tgz#4874045d408598e45a95519d3cc71017c91c9104"
integrity sha512-WiTPNLanDNJ1O8AvgkBpsbarw78a4PMYG2EfJcQoxTHFWy+ji213HR+3f4PhWB1RBumiD9cbiuC3SNxJXbBp9g==
dotenv-webpack@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz#f50ec3c7083a69ec6076e110566720003b7b107b"
integrity sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==
dependencies:
dotenv-defaults "^2.0.1"
dotenv-defaults "^2.0.2"
dotenv@^7.0.0:
version "7.0.0"
@ -5485,6 +5473,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@ -6639,6 +6632,13 @@ fs-extra@^8.1.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
dependencies:
minipass "^3.0.0"
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@ -6667,16 +6667,6 @@ fsevents@^2.1.2, fsevents@~2.3.1:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
fstream@^1.0.0, fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
dependencies:
graceful-fs "^4.1.2"
inherits "~2.0.0"
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -6916,6 +6906,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
graceful-fs@^4.2.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graphql-tag@^2.4.2:
version "2.12.5"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f"
@ -6959,6 +6954,11 @@ har-validator@~5.1.3:
ajv "^6.12.3"
har-schema "^2.0.0"
hard-rejection@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
harmony-reflect@^1.4.6:
version "1.6.1"
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9"
@ -7102,6 +7102,13 @@ hosted-git-info@^2.1.4:
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
hosted-git-info@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961"
integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==
dependencies:
lru-cache "^6.0.0"
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
@ -7381,17 +7388,10 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
in-publish@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c"
integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==
indent-string@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
dependencies:
repeating "^2.0.0"
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
indexes-of@^1.0.1:
version "1.0.1"
@ -7411,7 +7411,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -7591,6 +7591,13 @@ is-core-module@^2.2.0:
dependencies:
has "^1.0.3"
is-core-module@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19"
integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==
dependencies:
has "^1.0.3"
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@ -7655,11 +7662,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-finite@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
@ -7742,7 +7744,7 @@ is-path-inside@^2.1.0:
dependencies:
path-is-inside "^1.0.2"
is-plain-obj@^1.0.0:
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
@ -7806,11 +7808,6 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
is-valid-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
@ -8995,7 +8992,7 @@ kind-of@^5.0.0:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
kind-of@^6.0.0, kind-of@^6.0.2:
kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@ -9005,6 +9002,11 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
klona@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
launch-editor-middleware@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157"
@ -9051,17 +9053,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
pify "^2.0.0"
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
load-json-file@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
@ -9105,7 +9096,7 @@ loader-utils@^0.2.16:
json5 "^0.5.0"
object-assign "^4.0.1"
loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@ -9240,14 +9231,6 @@ loose-envify@^1.0.0, loose-envify@^1.2.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
dependencies:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
@ -9321,11 +9304,16 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
map-obj@^1.0.0, map-obj@^1.0.1:
map-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
map-obj@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7"
integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@ -9373,21 +9361,23 @@ memory-fs@^0.5.0:
errno "^0.1.3"
readable-stream "^2.0.1"
meow@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==
dependencies:
camelcase-keys "^2.0.0"
decamelize "^1.1.2"
loud-rejection "^1.0.0"
map-obj "^1.0.1"
minimist "^1.1.3"
normalize-package-data "^2.3.4"
object-assign "^4.0.1"
read-pkg-up "^1.0.1"
redent "^1.0.0"
trim-newlines "^1.0.0"
"@types/minimist" "^1.2.0"
camelcase-keys "^6.2.2"
decamelize "^1.2.0"
decamelize-keys "^1.1.0"
hard-rejection "^2.1.0"
minimist-options "4.1.0"
normalize-package-data "^3.0.0"
read-pkg-up "^7.0.1"
redent "^3.0.0"
trim-newlines "^3.0.0"
type-fest "^0.18.0"
yargs-parser "^20.2.3"
merge-descriptors@1.0.1:
version "1.0.1"
@ -9490,6 +9480,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-css-extract-plugin@^0.8.0:
version "0.8.2"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz#a875e169beb27c88af77dd962771c9eedc3da161"
@ -9517,11 +9512,35 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
dependencies:
brace-expansion "^1.1.7"
minimist-options@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
dependencies:
arrify "^1.0.1"
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minipass@^3.0.0:
version "3.1.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732"
integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==
dependencies:
yallist "^4.0.0"
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
mississippi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
@ -9562,14 +9581,14 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
mkdirp@0.x, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.4:
mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@ -9677,7 +9696,7 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@ -9730,23 +9749,21 @@ node-forge@^0.10.0:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
node-gyp@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
node-gyp@^7.1.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae"
integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==
dependencies:
fstream "^1.0.0"
glob "^7.0.3"
graceful-fs "^4.1.2"
mkdirp "^0.5.0"
nopt "2 || 3"
npmlog "0 || 1 || 2 || 3 || 4"
osenv "0"
request "^2.87.0"
rimraf "2"
semver "~5.3.0"
tar "^2.0.0"
which "1"
env-paths "^2.2.0"
glob "^7.1.4"
graceful-fs "^4.2.3"
nopt "^5.0.0"
npmlog "^4.1.2"
request "^2.88.2"
rimraf "^3.0.2"
semver "^7.3.2"
tar "^6.0.2"
which "^2.0.2"
node-int64@^0.4.0:
version "0.4.0"
@ -9824,36 +9841,27 @@ node-releases@^1.1.70:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
node-sass@^4.12.0:
version "4.14.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5"
integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==
node-sass@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-6.0.1.tgz#cad1ccd0ce63e35c7181f545d8b986f3a9a887fe"
integrity sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==
dependencies:
async-foreach "^0.1.3"
chalk "^1.1.1"
cross-spawn "^3.0.0"
cross-spawn "^7.0.3"
gaze "^1.0.0"
get-stdin "^4.0.1"
glob "^7.0.3"
in-publish "^2.0.0"
lodash "^4.17.15"
meow "^3.7.0"
mkdirp "^0.5.1"
meow "^9.0.0"
nan "^2.13.2"
node-gyp "^3.8.0"
node-gyp "^7.1.0"
npmlog "^4.0.0"
request "^2.88.0"
sass-graph "2.2.5"
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
"nopt@2 || 3":
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
dependencies:
abbrev "1"
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
@ -9861,7 +9869,7 @@ nopt@^5.0.0:
dependencies:
abbrev "1"
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0:
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
@ -9871,6 +9879,16 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-package-data@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
dependencies:
hosted-git-info "^4.0.1"
is-core-module "^2.5.0"
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
normalize-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379"
@ -9927,7 +9945,7 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0:
npmlog@^4.0.0, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@ -10173,24 +10191,11 @@ os-browserify@^0.3.0:
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
osenv@0:
version "0.1.5"
resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
dependencies:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
p-each-series@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
@ -10452,15 +10457,6 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
dependencies:
graceful-fs "^4.1.2"
pify "^2.0.0"
pinkie-promise "^2.0.0"
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@ -11195,6 +11191,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
quill-delta@^3.6.2:
version "3.6.3"
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
@ -11273,14 +11274,6 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
dependencies:
find-up "^1.0.0"
read-pkg "^1.0.0"
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@ -11306,15 +11299,6 @@ read-pkg-up@^7.0.1:
read-pkg "^5.2.0"
type-fest "^0.8.1"
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
dependencies:
load-json-file "^1.0.0"
normalize-package-data "^2.3.2"
path-type "^1.0.0"
read-pkg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
@ -11388,13 +11372,13 @@ realpath-native@^1.1.0:
dependencies:
util.promisify "^1.0.0"
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
dependencies:
indent-string "^2.1.0"
strip-indent "^1.0.1"
indent-string "^4.0.0"
strip-indent "^3.0.0"
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
@ -11506,13 +11490,6 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
dependencies:
is-finite "^1.0.0"
request-promise-core@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
@ -11678,7 +11655,7 @@ rgba-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@ -11802,16 +11779,16 @@ sass-graph@2.2.5:
scss-tokenizer "^0.2.3"
yargs "^13.3.2"
sass-loader@^7.1.0:
version "7.3.1"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f"
integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==
sass-loader@^10:
version "10.2.0"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716"
integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==
dependencies:
clone-deep "^4.0.1"
loader-utils "^1.0.1"
neo-async "^2.5.0"
pify "^4.0.1"
semver "^6.3.0"
klona "^2.0.4"
loader-utils "^2.0.0"
neo-async "^2.6.2"
schema-utils "^3.0.0"
semver "^7.3.2"
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
@ -11850,6 +11827,15 @@ schema-utils@^2.6.5:
ajv "^6.12.4"
ajv-keywords "^3.5.2"
schema-utils@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
dependencies:
"@types/json-schema" "^7.0.8"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@ -11895,7 +11881,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.2.1:
semver@^7.2.1, semver@^7.3.4:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@ -11909,11 +11895,6 @@ semver@^7.3.2:
dependencies:
lru-cache "^6.0.0"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@ -12006,13 +11987,6 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
shallow-clone@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
dependencies:
kind-of "^6.0.2"
shallow-copy@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170"
@ -12566,13 +12540,6 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
dependencies:
is-utf8 "^0.2.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@ -12593,18 +12560,18 @@ strip-final-newline@^2.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
dependencies:
get-stdin "^4.0.1"
strip-indent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
strip-indent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
dependencies:
min-indent "^1.0.0"
strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@ -12726,14 +12693,17 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
tar@^6.0.2:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
dependencies:
block-stream "*"
fstream "^1.0.12"
inherits "2"
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
terminal-link@^2.0.0:
version "2.1.1"
@ -12985,10 +12955,10 @@ tr46@^2.0.2:
dependencies:
punycode "^2.1.1"
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
trim-newlines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
"true-case-path@^1.0.2":
version "1.0.3"
@ -13103,6 +13073,11 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
type-fest@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
@ -13951,7 +13926,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which@1, which@^1.2.9, which@^1.3.0:
which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@ -14134,6 +14109,11 @@ yargs-parser@^20.2.2:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
yargs-parser@^20.2.3:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs@^13.2.2, yargs@^13.2.4, yargs@^13.3.0, yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"

View File

@ -15,6 +15,7 @@ CREATE TABLE `users` (
`language` varchar(4) NOT NULL DEFAULT 'de',
`disabled` tinyint DEFAULT '0',
`group_id` int unsigned DEFAULT 0,
`publisher_id` int DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -6,6 +6,7 @@
#include "../SingletonManager/EmailManager.h"
#include "../SingletonManager/SessionManager.h"
#include "../SingletonManager/LanguageManager.h"
#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
@ -17,9 +18,12 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
std::string password;
std::string username;
std::string description;
std::string language;
bool login_after_register = false;
int emailType;
int group_id = 1;
int publisher_id = 0;
bool group_was_not_set = false;
auto em = EmailManager::getInstance();
@ -40,18 +44,26 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
paramJsonObject->get("emailType").convert(emailType);
auto group_id_obj = paramJsonObject->get("group_id");
auto publisher_id_obj = paramJsonObject->get("publisher_id");
auto username_obj = paramJsonObject->get("username");
auto description_obj = paramJsonObject->get("description");
auto language_obj = paramJsonObject->get("language");
if(!group_id_obj.isEmpty()) {
group_id_obj.convert(group_id);
}
if (!publisher_id_obj.isEmpty()) {
publisher_id_obj.convert(publisher_id);
}
if (!username_obj.isEmpty()) {
username_obj.convert(username);
}
if (!description_obj.isEmpty()) {
description_obj.convert(description);
}
if (!language_obj.isEmpty()) {
language_obj.convert(language);
}
if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
paramJsonObject->get("password").convert(password);
}
@ -96,15 +108,21 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
group_was_not_set = true;
}
user = controller::User::create(email, first_name, last_name, group_id);
auto user_model = user->getModel();
if (username.size() > 3) {
if (user->isUsernameAlreadyUsed(username)) {
return stateError("username already in use");
}
user->getModel()->setUsername(username);
user_model->setUsername(username);
}
if (description.size() > 3) {
user->getModel()->setDescription(description);
user_model->setDescription(description);
}
if (LanguageManager::languageFromString(language) != LANG_NULL) {
user_model->setLanguageKey(language);
}
user_model->setPublisherId(publisher_id);
auto userModel = user->getModel();
Session* session = nullptr;

View File

@ -140,6 +140,9 @@ Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
else if (parameterString == "user.language") {
jsonUser.set("language", user_model->getLanguageKey());
}
else if (parameterString == "user.publisher_id") {
jsonUser.set("publisher_id", user_model->getPublisherId());
}
}
catch (Poco::Exception& ex) {
jsonErrorsArray.add("ask parameter invalid");

View File

@ -170,10 +170,20 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
}
}
else if ("User.publisher_id" == name) {
if (value.isInteger()) {
int publisher_id = 0;
value.convert(publisher_id);
user_model->setPublisherId(publisher_id);
extractet_values++;
}
else {
jsonErrorsArray.add("User.publisher_id isn't a valid integer");
}
}
else if ("User.password" == name && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS)
{
std::string str_val = validateString(value, "User.password", jsonErrorsArray);
if (str_val.size() > 0)
{
if (!user->hasPassword()) {

View File

@ -15,12 +15,12 @@ namespace model {
namespace table {
User::User()
: mPasswordHashed(0), mEmailChecked(false), mLanguageKey("de"), mDisabled(false), mRole(ROLE_NOT_LOADED)
: mPasswordHashed(0), mEmailChecked(false), mLanguageKey("de"), mDisabled(false), mPublisherId(0), mRole(ROLE_NOT_LOADED)
{
}
User::User(const std::string& email, const std::string& first_name, const std::string& last_name, int group_id, Poco::UInt64 passwordHashed/* = 0*/, std::string languageKey/* = "de"*/)
: mFirstName(first_name), mLastName(last_name), mPasswordHashed(passwordHashed), mEmailChecked(false), mLanguageKey(languageKey), mDisabled(false), mGroupId(group_id), mRole(ROLE_NOT_LOADED)
: mFirstName(first_name), mLastName(last_name), mPasswordHashed(passwordHashed), mEmailChecked(false), mLanguageKey(languageKey), mDisabled(false), mGroupId(group_id), mPublisherId(0), mRole(ROLE_NOT_LOADED)
{
setEmail(email);
@ -29,9 +29,9 @@ namespace model {
User::User(UserTuple tuple)
: ModelBase(tuple.get<0>()),
mFirstName(tuple.get<1>()), mLastName(tuple.get<2>()), mEmail(tuple.get<3>()), mUsername(tuple.get<4>()),
mDescription(tuple.get<5>()),
mPublicKey(tuple.get<6>()), mCreated(tuple.get<7>()), mEmailChecked(tuple.get<8>()), mDisabled(tuple.get<9>()), mGroupId(tuple.get<10>()),
mPasswordHashed(0), mLanguageKey("de"), mRole(ROLE_NOT_LOADED)
mDescription(tuple.get<5>()), mPublicKey(tuple.get<6>()), mCreated(tuple.get<7>()),
mEmailChecked(tuple.get<8>()), mDisabled(tuple.get<9>()), mGroupId(tuple.get<10>()), mPublisherId(tuple.get<11>()),
mPasswordHashed(0), mLanguageKey("de"), mRole(ROLE_NOT_LOADED)
{
}
@ -83,12 +83,13 @@ namespace model {
if (mPasswordHashed) {
insert << "INSERT INTO users (email, first_name, last_name, username, description, password, email_hash, language, group_id) VALUES(?,?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), bind(mPasswordHashed), use(mEmailHash), use(mLanguageKey), use(mGroupId);
insert << "INSERT INTO users (email, first_name, last_name, username, description, password, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), bind(mPasswordHashed), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId);
}
else {
insert << "INSERT INTO users (email, first_name, last_name, username, description, email_hash, language, group_id) VALUES(?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mEmailHash), use(mLanguageKey), use(mGroupId);
insert << "INSERT INTO users (email, first_name, last_name, username, description, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId);
}
return insert;
@ -101,13 +102,14 @@ namespace model {
_fieldName = getTableName() + std::string(".id");
}
Poco::Data::Statement select(session);
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, username, description, password, pubkey, privkey, email_hash, created, email_checked, language, disabled, group_id, user_roles.role_id "
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, username, description, password, pubkey, privkey, email_hash, created, email_checked, language, disabled, group_id, publisher_id, user_roles.role_id "
<< " FROM " << getTableName()
<< " LEFT JOIN user_roles ON " << getTableName() << ".id = user_roles.user_id "
<< " WHERE " << _fieldName << " = ?" ,
into(mID), into(mEmail), into(mFirstName), into(mLastName), into(mUsername), into(mDescription), into(mPasswordHashed),
into(mPublicKey), into(mPrivateKey), into(mEmailHash), into(mCreated), into(mEmailChecked),
into(mLanguageKey), into(mDisabled), into(mGroupId), into(mRole);
into(mLanguageKey), into(mDisabled), into(mGroupId), into(mPublisherId), into(mRole);
return select;
@ -117,9 +119,8 @@ namespace model {
{
Poco::Data::Statement select(session);
// typedef Poco::Tuple<std::string, std::string, std::string, Poco::Nullable<Poco::Data::BLOB>, int> UserTuple;
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id FROM " << getTableName()
<< " where " << fieldName << " LIKE ?";
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id, publisher_id FROM " << getTableName()
<< " where " << fieldName << " LIKE ?";
return select;
}
@ -133,8 +134,8 @@ namespace model {
}
// typedef Poco::Tuple<std::string, std::string, std::string, Poco::Nullable<Poco::Data::BLOB>, int> UserTuple;
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id FROM " << getTableName()
<< " where " << fieldNames[0] << " LIKE ?";
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id, publisher_id FROM " << getTableName()
<< " where " << fieldNames[0] << " LIKE ?";
if (conditionType == MYSQL_CONDITION_AND) {
for (int i = 1; i < fieldNames.size(); i++) {
select << " AND " << fieldNames[i] << " LIKE ? ";
@ -245,9 +246,8 @@ namespace model {
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement update(session);
update << "UPDATE users SET first_name = ?, last_name = ?, username = ?, description = ?, disabled = ?, language = ? where id = ?;",
use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mDisabled), use(mLanguageKey), use(mID);
update << "UPDATE users SET first_name = ?, last_name = ?, username = ?, description = ?, disabled = ?, language = ?, publisher_id = ? where id = ?;",
use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mDisabled), use(mLanguageKey), use(mPublisherId), use(mID);
try {
return update.execute();
@ -317,6 +317,7 @@ namespace model {
ss << "language key: " << mLanguageKey << std::endl;
ss << "disabled: " << mDisabled << std::endl;
ss << "group id: " << std::to_string(mGroupId) << std::endl;
ss << "publisher id: " << std::to_string(mPublisherId) << std::endl;
mm->releaseMemory(pubkeyHex);
mm->releaseMemory(privkeyHex);
@ -355,7 +356,8 @@ namespace model {
ss << "language key: " << mLanguageKey << "<br>";
ss << "role: " << UserRole::typeToString(getRole()) << "<br>";
ss << "disabled: " << mDisabled << "<br>";
ss << "group_id: " << std::to_string(mGroupId) << std::endl;
ss << "group_id: " << std::to_string(mGroupId) << "<br>";
ss << "publisher_id" << std::to_string(mPublisherId) << "<br>";
mm->releaseMemory(pubkeyHex);
mm->releaseMemory(email_hash);
@ -429,6 +431,7 @@ namespace model {
userObj.set("ident_hash", DRMakeStringHash(mEmail.data(), mEmail.size()));
userObj.set("language", mLanguageKey);
userObj.set("disabled", mDisabled);
userObj.set("publisher_id", mPublisherId);
try {
userObj.set("role", UserRole::typeToString(getRole()));
}

View File

@ -30,7 +30,20 @@ namespace model {
USER_FIELDS_LANGUAGE
};
typedef Poco::Tuple<int, std::string, std::string, std::string, std::string, std::string, Poco::Nullable<Poco::Data::BLOB>, Poco::DateTime, int, int, int> UserTuple;
typedef Poco::Tuple<
int, // id
std::string, // first name
std::string, // last name
std::string, // email
std::string, // username
std::string, // description
Poco::Nullable<Poco::Data::BLOB>, // public key
Poco::DateTime, // created
int, // check email
int, // disabled
int, // group_id
int // publisher_id
> UserTuple;
class User : public ModelBase
{
@ -63,6 +76,7 @@ namespace model {
inline std::string getNameWithEmailHtml() const { SHARED_LOCK; return mFirstName + "&nbsp;" + mLastName + "&nbsp;&lt;" + mEmail + "&gt;"; }
inline const Poco::UInt64 getPasswordHashed() const { SHARED_LOCK; return mPasswordHashed; }
inline int getGroupId() const { SHARED_LOCK; return mGroupId; }
inline int getPublisherId() const { SHARED_LOCK; return mPublisherId; }
inline RoleType getRole() const { SHARED_LOCK; if (mRole.isNull()) return ROLE_NONE; return static_cast<RoleType>(mRole.value()); }
inline const unsigned char* getPublicKey() const { SHARED_LOCK; if (mPublicKey.isNull()) return nullptr; return mPublicKey.value().content().data(); }
MemoryBin* getPublicKeyCopy() const;
@ -93,6 +107,7 @@ namespace model {
inline void setLanguageKey(const std::string& languageKey) { UNIQUE_LOCK; mLanguageKey = languageKey; }
inline void setDisabled(bool disabled) { UNIQUE_LOCK; mDisabled = disabled; }
inline void setGroupId(int groupId) { UNIQUE_LOCK; mGroupId = groupId; }
inline void setPublisherId(int publisherId) { UNIQUE_LOCK; mPublisherId = publisherId; }
Poco::JSON::Object getJson();
@ -126,6 +141,7 @@ namespace model {
bool mDisabled;
int mGroupId;
int mPublisherId;
// from neighbor tables
Poco::Nullable<int> mRole;

View File

@ -1,4 +1,4 @@
FROM nginx:latest
FROM nginx:1.21.0
WORKDIR /var/www/cakephp

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "1.3.1",
"version": "1.4.0",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",

View File

@ -1,21 +1,18 @@
#########################################################################################################
# Build skeema
#########################################################################################################
FROM golang:1.14.4 as skeema_build
FROM golang:1.17.1 as skeema_build
RUN go get -d -v github.com/skeema/skeema
WORKDIR /go/src/github.com/skeema/skeema
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /go/bin/skeema .
RUN go install github.com/skeema/skeema@v1.5.3
#########################################################################################################
# Run skeema
#########################################################################################################
FROM alpine:3.13.5 as skeema_run
FROM skeema_build as skeema_run
ENV DOCKER_WORKDIR="/skeema"
# copy skeema
COPY --from=skeema_build /go/bin/skeema /usr/bin/
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}