Merge master in the branch.

This commit is contained in:
elweyn 2021-09-05 10:45:50 +02:00
commit 021952831f
51 changed files with 385 additions and 209 deletions

21
backend/src/auth/auth.ts Normal file
View File

@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AuthChecker } from 'type-graphql'
import decode from '../jwt/decode'
import { apiGet } from '../apis/loginAPI'
import CONFIG from '../config'
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
if (context.token) {
const decoded = decode(context.token)
if (decoded.sessionId && decoded.sessionId !== 0) {
const result = await apiGet(
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
)
context.sessionId = decoded.sessionId
return result.success
}
}
return false
}

View File

@ -17,9 +17,6 @@ export class GdtTransactionInput {
@ArgsType() @ArgsType()
export class GdtTransactionSessionIdInput { export class GdtTransactionSessionIdInput {
@Field(() => Number)
sessionId: number
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
currentPage?: number currentPage?: number

View File

@ -41,9 +41,6 @@ export class ChangePasswordArgs {
@ArgsType() @ArgsType()
export class UpdateUserInfosArgs { export class UpdateUserInfosArgs {
@Field(() => Number)
sessionId!: number
@Field(() => String) @Field(() => String)
email!: string email!: string

View File

@ -2,9 +2,6 @@ import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType() @ArgsType()
export class TransactionListInput { export class TransactionListInput {
@Field(() => Number)
sessionId: number
@Field(() => Int) @Field(() => Int)
firstPage: number firstPage: number
@ -17,9 +14,6 @@ export class TransactionListInput {
@ArgsType() @ArgsType()
export class TransactionSendArgs { export class TransactionSendArgs {
@Field(() => Number)
sessionId: number
@Field(() => String) @Field(() => String)
email: string email: string

View File

@ -15,6 +15,7 @@ export enum GdtEntryType {
@ObjectType() @ObjectType()
export class GdtEntry { export class GdtEntry {
constructor(json: any) { constructor(json: any) {
this.id = json.id
this.amount = json.amount this.amount = json.amount
this.date = json.date this.date = json.date
this.email = json.email this.email = json.email
@ -27,6 +28,9 @@ export class GdtEntry {
this.gdt = json.gdt this.gdt = json.gdt
} }
@Field(() => Number)
id: number
@Field(() => Number) @Field(() => Number)
amount: number amount: number

View File

@ -1,20 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { User } from './User'
// temporaray solution until we have JWT implemented
@ObjectType()
export class LoginResponse {
constructor(json: any) {
this.sessionId = json.session_id
this.user = new User(json.user)
}
@Field(() => Number)
sessionId: number
@Field(() => User)
user: User
}

View File

@ -1,13 +1,17 @@
import { Resolver, Query, /* Mutation, */ Arg } from 'type-graphql' /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config' import CONFIG from '../../config'
import { Balance } from '../models/Balance' import { Balance } from '../models/Balance'
import { apiGet } from '../../apis/HttpRequest' import { apiGet } from '../../apis/HttpRequest'
@Resolver() @Resolver()
export class BalanceResolver { export class BalanceResolver {
@Authorized()
@Query(() => Balance) @Query(() => Balance)
async balance(@Arg('sessionId') sessionId: number): Promise<Balance> { async balance(@Ctx() context: any): Promise<Balance> {
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId) const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + context.sessionId)
if (!result.success) throw new Error(result.data) if (!result.success) throw new Error(result.data)
return new Balance(result.data) return new Balance(result.data)
} }

View File

@ -1,5 +1,7 @@
// import jwt from 'jsonwebtoken' /* eslint-disable @typescript-eslint/no-explicit-any */
import { Resolver, Query, /* Mutation, */ Args } from 'type-graphql' /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config' import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList' import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs' import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
@ -7,14 +9,16 @@ import { apiGet } from '../../apis/HttpRequest'
@Resolver() @Resolver()
export class GdtResolver { export class GdtResolver {
@Authorized()
@Query(() => GdtEntryList) @Query(() => GdtEntryList)
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
async listGDTEntries( async listGDTEntries(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = 'DESC', sessionId }: GdtTransactionSessionIdInput, { currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput,
@Ctx() context: any,
): Promise<GdtEntryList> { ): Promise<GdtEntryList> {
const result = await apiGet( const result = await apiGet(
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${sessionId}`, `${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${context.sessionId}`,
) )
if (!result.success) { if (!result.success) {
throw new Error(result.data) throw new Error(result.data)

View File

@ -1,4 +1,7 @@
import { Resolver, Query, Args } from 'type-graphql' /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Authorized, Ctx } from 'type-graphql'
import CONFIG from '../../config' import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction' import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput' import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
@ -6,23 +9,27 @@ import { apiGet, apiPost } from '../../apis/HttpRequest'
@Resolver() @Resolver()
export class TransactionResolver { export class TransactionResolver {
@Authorized()
@Query(() => TransactionList) @Query(() => TransactionList)
async transactionList( async transactionList(
@Args() { sessionId, firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput, @Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
@Ctx() context: any,
): Promise<TransactionList> { ): Promise<TransactionList> {
const result = await apiGet( const result = await apiGet(
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`, `${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${context.sessionId}`,
) )
if (!result.success) throw new Error(result.data) if (!result.success) throw new Error(result.data)
return new TransactionList(result.data) return new TransactionList(result.data)
} }
@Authorized()
@Query(() => String) @Query(() => String)
async sendCoins( async sendCoins(
@Args() { sessionId, email, amount, memo }: TransactionSendArgs, @Args() { email, amount, memo }: TransactionSendArgs,
@Ctx() context: any,
): Promise<string> { ): Promise<string> {
const payload = { const payload = {
session_id: sessionId, session_id: context.sessionId,
target_email: email, target_email: email,
amount: amount * 10000, amount: amount * 10000,
memo, memo,

View File

@ -1,8 +1,9 @@
// import jwt from 'jsonwebtoken' /* eslint-disable @typescript-eslint/no-explicit-any */
import { Resolver, Query, Args, Arg, UseMiddleware } from 'type-graphql' /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware } from 'type-graphql'
import CONFIG from '../../config' import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse' import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { LoginResponse } from '../models/LoginResponse' import { User } from '../models/User'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode' import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse' import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse' import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
@ -16,13 +17,14 @@ import {
import { apiPost, apiGet } from '../../apis/HttpRequest' import { apiPost, apiGet } from '../../apis/HttpRequest'
import { KlicktippController } from '../../apis/KlicktippController' import { KlicktippController } from '../../apis/KlicktippController'
import { registerMiddleware } from '../../middleware/registerMiddleware' import { registerMiddleware } from '../../middleware/registerMiddleware'
import encode from '../../jwt/encode'
@Resolver() @Resolver()
export class UserResolver { export class UserResolver {
private connector: KlicktippController = new KlicktippController(CONFIG.KLICKTTIPP_API_URL) private connector: KlicktippController = new KlicktippController(CONFIG.KLICKTTIPP_API_URL)
@Query(() => LoginResponse) @Query(() => String)
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<LoginResponse> { async login(@Args() { email, password }: UnsecureLoginArgs): Promise<string> {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
@ -31,21 +33,10 @@ export class UserResolver {
throw new Error(result.data) throw new Error(result.data)
} }
// temporary solution until we have JWT implemented const data = result.data
return new LoginResponse(result.data) const sessionId = data.session_id
delete data.session_id
// create and return the json web token return encode({ sessionId, user: new User(data.user) })
// The expire doesn't help us here. The client needs to track when the token expires on its own,
// since every action prolongs the time the session is valid.
/*
return jwt.sign(
{ result, role: 'todo' },
CONFIG.JWT_SECRET, // * , { expiresIn: CONFIG.JWT_EXPIRES_IN } ,
)
*/
// return (await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', login)).result.data
// const loginResult: LoginResult = await loginAPI.login(data)
// return loginResult.user ? loginResult.user : new User()
} }
@Query(() => LoginViaVerificationCode) @Query(() => LoginViaVerificationCode)
@ -63,9 +54,10 @@ export class UserResolver {
return new LoginViaVerificationCode(result.data) return new LoginViaVerificationCode(result.data)
} }
@Authorized()
@Query(() => String) @Query(() => String)
async logout(@Arg('sessionId') sessionId: number): Promise<string> { async logout(@Ctx() context: any): Promise<string> {
const payload = { session_id: sessionId } const payload = { session_id: context.sessionId }
const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload) const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
if (!result.success) { if (!result.success) {
throw new Error(result.data) throw new Error(result.data)
@ -128,11 +120,11 @@ export class UserResolver {
return 'sucess' return 'sucess'
} }
@Authorized()
@Query(() => UpdateUserInfosResponse) @Query(() => UpdateUserInfosResponse)
async updateUserInfos( async updateUserInfos(
@Args() @Args()
{ {
sessionId,
email, email,
firstName, firstName,
lastName, lastName,
@ -142,9 +134,10 @@ export class UserResolver {
password, password,
passwordNew, passwordNew,
}: UpdateUserInfosArgs, }: UpdateUserInfosArgs,
@Ctx() context: any,
): Promise<UpdateUserInfosResponse> { ): Promise<UpdateUserInfosResponse> {
const payload = { const payload = {
session_id: sessionId, session_id: context.sessionId,
email, email,
update: { update: {
'User.first_name': firstName || undefined, 'User.first_name': firstName || undefined,

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import 'reflect-metadata' import 'reflect-metadata'
import express from 'express' import express from 'express'
import { buildSchema } from 'type-graphql' import { buildSchema } from 'type-graphql'
@ -8,17 +10,30 @@ import connection from './database/connection'
import CONFIG from './config' import CONFIG from './config'
// TODO move to extern // TODO move to extern
// import { BookResolver } from './graphql/resolvers/BookResolver'
import { UserResolver } from './graphql/resolvers/UserResolver' import { UserResolver } from './graphql/resolvers/UserResolver'
import { BalanceResolver } from './graphql/resolvers/BalanceResolver' import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
import { GdtResolver } from './graphql/resolvers/GdtResolver' import { GdtResolver } from './graphql/resolvers/GdtResolver'
import { TransactionResolver } from './graphql/resolvers/TransactionResolver' import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
import { isAuthorized } from './auth/auth'
// TODO implement // TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
const DB_VERSION = '0001-init_db' const DB_VERSION = '0001-init_db'
const context = (req: any) => {
const authorization = req.req.headers.authorization
let token = null
if (authorization) {
token = req.req.headers.authorization.replace(/^Bearer /, '')
}
const context = {
token,
}
return context
}
async function main() { async function main() {
// check for correct database version // check for correct database version
const con = await connection() const con = await connection()
@ -34,6 +49,7 @@ async function main() {
// const connection = await createConnection() // const connection = await createConnection()
const schema = await buildSchema({ const schema = await buildSchema({
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver], resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
authChecker: isAuthorized,
}) })
// Graphiql interface // Graphiql interface
@ -46,7 +62,7 @@ async function main() {
const server = express() const server = express()
// Apollo Server // Apollo Server
const apollo = new ApolloServer({ schema, playground }) const apollo = new ApolloServer({ schema, playground, context })
apollo.applyMiddleware({ app: server }) apollo.applyMiddleware({ app: server })
// Start Server // Start Server

23
backend/src/jwt/decode.ts Normal file
View File

@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import jwt from 'jsonwebtoken'
import CONFIG from '../config/'
export default (token: string): any => {
if (!token) return null
let sessionId = null
const email = null
try {
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
sessionId = decoded.sub
// email = decoded.email
return {
token,
sessionId,
email,
}
} catch (err) {
return null
}
}

18
backend/src/jwt/encode.ts Normal file
View File

@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import jwt from 'jsonwebtoken'
import CONFIG from '../config/'
// Generate an Access Token
export default function encode(data: any): string {
const { user, sessionId } = data
const { email, language, firstName, lastName } = user
const token = jwt.sign({ email, language, firstName, lastName, sessionId }, CONFIG.JWT_SECRET, {
expiresIn: CONFIG.JWT_EXPIRES_IN,
// issuer: CONFIG.GRAPHQL_URI,
// audience: CONFIG.CLIENT_URI,
subject: sessionId.toString(),
})
return token
}

View File

@ -21,7 +21,7 @@ $this->assign('title', __('GDT Kontoübersicht'));
$header = '<h3>' . __('Zur Verfügung: ') . '</h3>'; $header = '<h3>' . __('Zur Verfügung: ') . '</h3>';
if($gdtSum > 0){ if($gdtSum > 0){
$header .= '<h2>'.$this->element('printGDT', ['number' => $gdtSum]).'</h2>'; $header .= '<h2>'.$this->element('printGDT', ['number' => $gdtSum*100.0]).'</h2>';
} }
if($moreEntrysAsShown) { if($moreEntrysAsShown) {
$header .= '<span>'. __('Nur die letzten 100 Einträge werden angezeigt!') . '</span>'; $header .= '<span>'. __('Nur die letzten 100 Einträge werden angezeigt!') . '</span>';
@ -56,7 +56,7 @@ $this->assign('header', $header);
<?= $this->Number->format($entry['factor2']) ?> <?= $this->Number->format($entry['factor2']) ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="cell c3"><?= $this->element('printGDT', ['number' => $entry['gdt']]) ?></div> <div class="cell c3"><?= $this->element('printGDT', ['number' => $entry['gdt']*100.0]) ?></div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
@ -98,7 +98,7 @@ $this->assign('header', $header);
<?= $this->Number->format($gdtEntry['factor2']) ?> <?= $this->Number->format($gdtEntry['factor2']) ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="cell c3"><?= $this->element('printGDT', ['number' => $gdtEntry['gdt']]) ?></div> <div class="cell c3"><?= $this->element('printGDT', ['number' => $gdtEntry['gdt'] * 100.0]) ?></div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>

View File

@ -0,0 +1,53 @@
# Roles
User Roles also handled by blockchain and node servers
My Goal is to save not only the gradido transactions in blockchain but also
who is allowed to create new gradidos,
who allow joining new user to the group,
who allow connect to another group and
how the community decide which one is allowed to do this things.
## Why?
If this would be handled only by community-server everyone could be easly
overwrite this rules by using a modified client to send his transactions direct over
hedera or iota bypassing the community-server rules.
With hedera it is possible to only allow sending messages to a topic with the correct admin key,
but then is the admin the single point of failure. Also must the key saved on server to allow everyone
sending gradidos or the transactions will only be proccessed, when admin is logging in.
If we don't use blockchain technologie at all, we have a big single point of failure.
The Community-Server and everyone who has direct access to server and the admins of course.
But it would be much much simpler of course :)
In summary it is to make sure that the community is in power and no one can take over.
## How?
There is a special type of transactions with which users determine who can determine what.
This transaction control which signatures are neccessary for things like creation and so one.
For this I think different types are needed.
- *one*: The founder of group (or someone other choosen) decide everything, this is default from start
- *some*: a number of user must sign, set absolute count or relative count
- *most*: more than 1/2 or 3/4 must sign
- *all*: all member must sign, default for choose which mode will be used
- *one-council*: one member of council
- *some-council*: absolute or relative number of council members must sign
- *most-council*: more than 1/2 or 3/4 from council members must sign
- *all-council*: all members of council must sign
this configuration can be done for different types of action,
so the voting-mode for creation may differ from voting mode for
add new members to community. Also the council members for different actions
may differ.
Also how to vote for council members is an extra type of action.
## Veto
Especially for *some* and *some-council* maybe also for other types.
The users there could vote but haven't yet and not all need to vote,
can make a Veto with Explanation which reset all existing signs of current vote,
and is needed to sign by all which again vote for the case.
## Summary
With that setup all community consense models should be possible except Democracy.
Democracy needs a secret ballot. The votes on blockchain are open (at least if someone knows which
public-key belongs to which user). A secret ballot on blockchain is really hard. By my last
recherche I haven't found on. But maybe this can do the trick: https://secure.vote/

View File

@ -0,0 +1,42 @@
# Wie Colored Coins in Iota funktionieren
## Schöpfung
- Colored Coins werden bei Iota mit einer speziellen Transaktion erzeugt
- Die Farbe des neuen Coins wird durch den Transaktionshash beschrieben
- Die einmal erzeugte Menge von Colored Coins ist fest
- Um die Menge zu erhöhen müssten neue Colored Coin erzeugt werden und die alten damit ausgetauscht werden (z.B. mittels SmartContract)
## Geldmenge
- Colored Coins basierend auf den normalen Iota-Coins somit werden soviele Iota-Coins benötigt wie man Colored-Coins braucht
- 2.779.530.283.000.000 Iota maximale Coinmenge
- Weltbevölkerung 2021: 7.915.559.780
- Pro Kopf Geldmenge Gradido: 53.476
- Benötigte Iota Coins für Gradido mit 4 Nachkommastellen mit 25% Puffer:
- 7.915.559.780 * 534.760.000 * 1.25 = 5.291.155.935.000.000.000
- Vorhandene Iota coins: 2.779.530.283.000.000
- Es sind nicht genügend Iota Coins vorhanden im die ganze Welt mit Gradido auf Basis von Iota versorgen zu können
## Kosten
- Kurs am 30.08.2021: 1 Miota = 0,84
- Bei Verwendung von 4 Nachkommastellen braucht es 10 Miota für 1.000 Colored Coins (Gradido) also Miota im Wert von 8,40€
- Aktuell (30.08.2021) geschöpfte Gradido Cent: 17.001.990.500
- Notwendige Miota: 17.002.0, Wert: 14.286,73 €
- Solange die Benutzer Kontrolle über ihre Keys haben können sie mit einem regulärem Iota Wallet die Gradidos wieder in Iota umwandeln (um z.B. der Vergänglichkeit zu entgehen)
- Mit 2 Nachkommastellen wird die Vergänglichkeit schon bei 100 Gradido und 1 Stunde ungenau
- 1 Stunde, 100 Gradido, Vergänglichkeit: 0,00576, Gradidos nach einer Stunde: 99,99424 GDD
- 1 Minute, 100 Gradido, Vergänglichkeit: 0,000096, Gradidos nach einer Minute: 99,999904 GDD
## Dust-Protection
- Iota erlaubt bei leeren Adressen nur Transaktionen von mindestens 1 Miota
- Nachdem 1 Miota da ist sind bis zu 10 Transaktione < 1 Miota erlaubt, bevor ein weitere Transaktion mit mindestens 1 Miota eingehen muss
- Bei Verwendung von 4 Nachkommastellen entspricht das 100 GDD, bei 2 Nachkommastellen 10.000 GDD
## Lösung
Wir können nur 3 Nachkommastellen verwenden.
### Kosten
- 0,84 € für 1.000 GDD
- 1.428 Euro für alle bisherigen geschöpften Gradidos
### Dust-Protection
- 1.000 GDD entspricht 1 Miota, die erste Schöpfung muss also zwangsläufig 1k Gradido betragen
- Jeder kann nur maximal 10 Transaktionen < 1k GDD empfangen bevor er die GDD weitergesendet haben muss um neue erhalten zu können oder eine neue Schöpfung von 1k GDD bekommen hat

View File

@ -0,0 +1 @@
<mxfile host="7d619c52-f8a9-4b9d-82fe-1aa45d350dea" modified="2021-08-07T11:49:26.921Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.58.2 Chrome/89.0.4389.128 Electron/12.0.13 Safari/537.36" etag="5tat3B3yItVGc6mcvaQ1" version="12.2.4" pages="1"><diagram id="1SuIEZF2QCnWeueF3Zz_" name="Page-1">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</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -0,0 +1 @@
<mxfile host="11f51089-224b-4c51-8025-2a587ac1ee54" modified="2021-08-08T15:21:15.636Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.58.2 Chrome/89.0.4389.128 Electron/12.0.13 Safari/537.36" etag="Ph0quYraKNcgZ1LpaOqw" version="12.2.4" pages="1"><diagram id="svTVmLC50Oi8Ao2eC_wi" name="Page-1">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</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View File

@ -0,0 +1 @@
<mxfile host="c8c9d8b1-9d45-405b-9350-947d02163aae" modified="2021-08-13T14:14:22.645Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.59.0 Chrome/91.0.4472.124 Electron/13.1.7 Safari/537.36" etag="M5WsFcVCZVYdDRxEgPr7" version="12.2.4" pages="1"><diagram id="4VYGDKFA-XGvTikjH5B0" name="Page-1">zVlLc+MoEP41Os6U9bRz9GMym9qaSmqTqp3sjUjYYoOEF6HYmV+/DQJLCHnieJRkLjK0oNV099cP7IXLYv+Vo23+jWWYesEk23vhyguCaRLAUxKeNWEaN4QNJ1lD8lvCLfmBNXGiqTXJcGUtFIxRQbY2MWVliVNh0RDnbGcvWzNqf3WLNtgh3KaIutS/SSbyhjoLpi39D0w2ufmyn1w0bwpkFuuTVDnK2K5DCr944ZIzJppRsV9iKnVn9NLsuzzy9iAYx6U4aYNW/BOitT7c1fXdXAsnns2JYRcoFyYLkHgriSllNTBZ7HIi8O0WpZK4A3MDLRcFhZkPQ1cgLeMT5gLvOyQt4FfMCiz4MyzRb8MLrSztLWa6a1XvG33mHbXPNA1pa28OnFuFwEDr5Ih+Ekc/4NIZydgdR2WFUkFY6WiLs7rMsGQxeRcNzWwF+ZMTNZSMoaGpoyHXe8psLmGn3AZVFUltFeA9Ed8743upuc+xnq32WpFq8mwmJQj6XS0EI+m53hibebtVzay9N5gTOC3mmnjUDhWreYptfxCIb7BZpjGEMytquNbqmCMesIahcUyRIE92rBkykf7CDSMg8TFnCKKekZvj6E3d0NDj44c9r7roMWp04DBSDnM49Wk+NHNR9td8dbW6/uwFCSokYihoaYEoga8b0kYczNZxNsCMsN2L44r8QA9qgbT0VoqsDhEvvHgl+daCVU2a8ZvPbEoYU7yWrCQQCUT/uSYLJr9eAaJJubmTk9WnaBwkB7OoB+WZC2V/wHmCEaBseHTMQEklcKnyKzxeYZREvXrgMFIUi5FeWj5U8qcgFFeClZDRg0tI2WvCCwiefbuaxLPlLMVV9XJgfUDp40aF4utaUFIa646RknrYCEI34gZDZhoj4poA+ysR97Rg91sHtrCf308NbFEQfp5Mkwswmnr6w6YcP8wFoWO4GzCUDCJtMVEddXwQAPMS0VvBuCpQPxAA8cv+7ydvVJMF0Xv5/0CyNzb8TTDhJOnpudm+Xza8IQzcnsPki9w3+eKfGlY8Um8ZevOIy8zRrADm7SJD3BrCn6woVJbJCIbnN8gTgJRKfkyW6ZMrJpDsHykmaQ6DEqmfDBfS9GoPYKAC78FSN7uaZ4odQEgeAJhzxYsfNh2SlxcsFZEfOD3Ai0Z+QTaSXY7kkzVrHo2ozWJUK0kKpBplpAQLLzun3raHfrHgqQRnj3jJKJPFbSmlCxdrQmmPpEsYIMROQRMca10YaGhNFbRykkn9QIVlNTxjxJfIdsckdPPrJHZh5ffd9qwA45ajxhCgo9LSfvJfLXv1RlOfqkZVc+lajCtjmgXGfGvpEouldIF/cSZNrxtKGFk95WSHaZpj5XfSl1tHcz1LOWrHWRoxj/jLGd2pY7gB8x4vluLYTtzRicnCH6OoDd2i9tXZwvSnbU9633kz3J8OpokXU445sZVyZh+ZYXqZPj67nZzYjKK3aydNRfczk787CCIbBLE/AILZgBnH6BjCETqGzn3L4Xrl3uvergzftZyJg5mLg/BDK60eDpJ+bDoXB3G/YhsRB0P9BmSlkqm8s8w5K0kqHeByxKLCXJ6kuFSXa871SQFVAz3WvgxVEr8EvCjqV8ixC7zJT/zmFcCDaXt13xis/f8j/PI/</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -72,6 +72,7 @@
"vue-good-table": "^2.21.3", "vue-good-table": "^2.21.3",
"vue-i18n": "^8.22.4", "vue-i18n": "^8.22.4",
"vue-jest": "^3.0.7", "vue-jest": "^3.0.7",
"vue-jwt-decode": "^0.1.0",
"vue-loading-overlay": "^3.4.2", "vue-loading-overlay": "^3.4.2",
"vue-moment": "^4.1.0", "vue-moment": "^4.1.0",
"vue-qrcode": "^0.3.5", "vue-qrcode": "^0.3.5",

View File

@ -15,7 +15,6 @@ describe('LanguageSwitch', () => {
let wrapper let wrapper
const state = { const state = {
sessionId: 1234,
email: 'he@ho.he', email: 'he@ho.he',
language: null, language: null,
} }
@ -123,7 +122,6 @@ describe('LanguageSwitch', () => {
expect(updateUserInfosQueryMock).toBeCalledWith( expect(updateUserInfosQueryMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1234,
email: 'he@ho.he', email: 'he@ho.he',
locale: 'en', locale: 'en',
}, },
@ -136,7 +134,6 @@ describe('LanguageSwitch', () => {
expect(updateUserInfosQueryMock).toBeCalledWith( expect(updateUserInfosQueryMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1234,
email: 'he@ho.he', email: 'he@ho.he',
locale: 'de', locale: 'de',
}, },

View File

@ -32,13 +32,13 @@ export default {
localeChanged(locale) localeChanged(locale)
}, },
async saveLocale(locale) { async saveLocale(locale) {
// if (this.$i18n.locale === locale) return
this.setLocale(locale) this.setLocale(locale)
if (this.$store.state.sessionId && this.$store.state.email) { if (this.$store.state.email) {
this.$apollo this.$apollo
.query({ .query({
query: updateUserInfos, query: updateUserInfos,
variables: { variables: {
sessionId: this.$store.state.sessionId,
email: this.$store.state.email, email: this.$store.state.email,
locale: locale, locale: locale,
}, },

View File

@ -2,23 +2,13 @@ import gql from 'graphql-tag'
export const login = gql` export const login = gql`
query($email: String!, $password: String!) { query($email: String!, $password: String!) {
login(email: $email, password: $password) { login(email: $email, password: $password)
sessionId
user {
email
firstName
lastName
language
username
description
}
}
} }
` `
export const logout = gql` export const logout = gql`
query($sessionId: Float!) { query {
logout(sessionId: $sessionId) logout
} }
` `
@ -39,7 +29,6 @@ export const loginViaEmailVerificationCode = gql`
export const updateUserInfos = gql` export const updateUserInfos = gql`
query( query(
$sessionId: Float!
$email: String! $email: String!
$firstName: String $firstName: String
$lastName: String $lastName: String
@ -50,7 +39,6 @@ export const updateUserInfos = gql`
$locale: String $locale: String
) { ) {
updateUserInfos( updateUserInfos(
sessionId: $sessionId
email: $email email: $email
firstName: $firstName firstName: $firstName
lastName: $lastName lastName: $lastName
@ -66,8 +54,8 @@ export const updateUserInfos = gql`
` `
export const transactionsQuery = gql` export const transactionsQuery = gql`
query($sessionId: Float!, $firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") { query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
transactionList(sessionId: $sessionId, firstPage: $firstPage, items: $items, order: $order) { transactionList(firstPage: $firstPage, items: $items, order: $order) {
gdtSum gdtSum
count count
balance balance
@ -115,8 +103,8 @@ export const resgisterUserQuery = gql`
` `
export const sendCoins = gql` export const sendCoins = gql`
query($sessionId: Float!, $email: String!, $amount: Float!, $memo: String!) { query($email: String!, $amount: Float!, $memo: String!) {
sendCoins(sessionId: $sessionId, email: $email, amount: $amount, memo: $memo) sendCoins(email: $email, amount: $amount, memo: $memo)
} }
` `
@ -137,10 +125,11 @@ export const checkUsername = gql`
` `
export const listGDTEntriesQuery = gql` export const listGDTEntriesQuery = gql`
query($currentPage: Int!, $pageSize: Int!, $sessionId: Float!) { query($currentPage: Int!, $pageSize: Int!) {
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize, sessionId: $sessionId) { listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {
count count
gdtEntries { gdtEntries {
id
amount amount
date date
comment comment

View File

@ -18,6 +18,7 @@
"de": "Deutsch", "de": "Deutsch",
"en": "English" "en": "English"
}, },
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
"decay": { "decay": {
"decay": "Vergänglichkeit", "decay": "Vergänglichkeit",
"decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion", "decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion",

View File

@ -18,6 +18,7 @@
"de": "Deutsch", "de": "Deutsch",
"en": "English" "en": "English"
}, },
"select_language": "Please choose a language for the app and newsletter",
"decay": { "decay": {
"decay": "Decay", "decay": "Decay",
"decay_since_last_transaction":"Decay since the last transaction", "decay_since_last_transaction":"Decay since the last transaction",

View File

@ -3,7 +3,7 @@ import DashboardPlugin from './plugins/dashboard-plugin'
import App from './App.vue' import App from './App.vue'
import i18n from './i18n.js' import i18n from './i18n.js'
import { loadAllRules } from './validation-rules' import { loadAllRules } from './validation-rules'
import ApolloClient from 'apollo-boost' import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import CONFIG from './config' import CONFIG from './config'
@ -11,7 +11,21 @@ import { store } from './store/store'
import router from './routes/router' import router from './routes/router'
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
const authLink = new ApolloLink((operation, forward) => {
const token = store.state.token
operation.setContext({
headers: {
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
},
})
return forward(operation)
})
const apolloClient = new ApolloClient({ const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
uri: CONFIG.GRAPHQL_URI, uri: CONFIG.GRAPHQL_URI,
}) })
@ -26,7 +40,7 @@ Vue.config.productionTip = false
loadAllRules(i18n) loadAllRules(i18n)
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.sessionId) { if (to.meta.requiresAuth && !store.state.token) {
next({ path: '/login' }) next({ path: '/login' })
} else { } else {
next() next()

View File

@ -1,6 +1,8 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate' import createPersistedState from 'vuex-persistedstate'
import VueJwtDecode from 'vue-jwt-decode'
Vue.use(Vuex) Vue.use(Vuex)
export const mutations = { export const mutations = {
@ -10,9 +12,6 @@ export const mutations = {
email: (state, email) => { email: (state, email) => {
state.email = email state.email = email
}, },
sessionId: (state, sessionId) => {
state.sessionId = sessionId
},
username: (state, username) => { username: (state, username) => {
state.username = username state.username = username
}, },
@ -25,43 +24,47 @@ export const mutations = {
description: (state, description) => { description: (state, description) => {
state.description = description state.description = description
}, },
token: (state, token) => {
state.token = token
},
} }
export const actions = { export const actions = {
login: ({ dispatch, commit }, data) => { login: ({ dispatch, commit }, token) => {
commit('sessionId', data.sessionId) const decoded = VueJwtDecode.decode(token)
commit('email', data.user.email) commit('token', token)
commit('language', data.user.language) commit('email', decoded.email)
commit('username', data.user.username) commit('language', decoded.language)
commit('firstName', data.user.firstName) commit('username', decoded.username)
commit('lastName', data.user.lastName) commit('firstName', decoded.firstName)
commit('description', data.user.description) commit('lastName', decoded.lastName)
commit('description', decoded.description)
}, },
logout: ({ commit, state }) => { logout: ({ commit, state }) => {
commit('sessionId', null) commit('token', null)
commit('email', null) commit('email', null)
commit('username', '') commit('username', '')
commit('firstName', '') commit('firstName', '')
commit('lastName', '') commit('lastName', '')
commit('description', '') commit('description', '')
sessionStorage.clear() localStorage.clear()
}, },
} }
export const store = new Vuex.Store({ export const store = new Vuex.Store({
plugins: [ plugins: [
createPersistedState({ createPersistedState({
storage: window.sessionStorage, storage: window.localStorage,
}), }),
], ],
state: { state: {
sessionId: null,
email: '', email: '',
language: null, language: null,
firstName: '', firstName: '',
lastName: '', lastName: '',
username: '', username: '',
description: '', description: '',
token: null,
}, },
getters: {}, getters: {},
// Syncronous mutation of the state // Syncronous mutation of the state

View File

@ -1,6 +1,17 @@
import { mutations, actions } from './store' import { mutations, actions } from './store'
import VueJwtDecode from 'vue-jwt-decode'
const { language, email, sessionId, username, firstName, lastName, description } = mutations jest.mock('vue-jwt-decode')
VueJwtDecode.decode.mockReturnValue({
email: 'user@example.org',
language: 'de',
username: 'peter',
firstName: 'Peter',
lastName: 'Lustig',
description: 'Nickelbrille',
})
const { language, email, token, username, firstName, lastName, description } = mutations
const { login, logout } = actions const { login, logout } = actions
describe('Vuex store', () => { describe('Vuex store', () => {
@ -21,11 +32,11 @@ describe('Vuex store', () => {
}) })
}) })
describe('sessionId', () => { describe('token', () => {
it('sets the state of sessionId', () => { it('sets the state of token', () => {
const state = { sessionId: null } const state = { token: null }
sessionId(state, '1234') token(state, '1234')
expect(state.sessionId).toEqual('1234') expect(state.token).toEqual('1234')
}) })
}) })
@ -66,41 +77,31 @@ describe('Vuex store', () => {
describe('login', () => { describe('login', () => {
const commit = jest.fn() const commit = jest.fn()
const state = {} const state = {}
const commitedData = { const commitedData = 'token'
sessionId: 1234,
user: {
email: 'someone@there.is',
language: 'en',
username: 'user',
firstName: 'Peter',
lastName: 'Lustig',
description: 'Nickelbrille',
},
}
it('calls seven commits', () => { it('calls seven commits', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenCalledTimes(7) expect(commit).toHaveBeenCalledTimes(7)
}) })
it('commits sessionId', () => { it('commits token', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234) expect(commit).toHaveBeenNthCalledWith(1, 'token', 'token')
}) })
it('commits email', () => { it('commits email', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is') expect(commit).toHaveBeenNthCalledWith(2, 'email', 'user@example.org')
}) })
it('commits language', () => { it('commits language', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en') expect(commit).toHaveBeenNthCalledWith(3, 'language', 'de')
}) })
it('commits username', () => { it('commits username', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'user') expect(commit).toHaveBeenNthCalledWith(4, 'username', 'peter')
}) })
it('commits firstName', () => { it('commits firstName', () => {
@ -128,9 +129,9 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenCalledTimes(6) expect(commit).toHaveBeenCalledTimes(6)
}) })
it('commits sessionId', () => { it('commits token', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', null) expect(commit).toHaveBeenNthCalledWith(1, 'token', null)
}) })
it('commits email', () => { it('commits email', () => {
@ -159,7 +160,7 @@ describe('Vuex store', () => {
}) })
// how to get this working? // how to get this working?
it.skip('calls sessionStorage.clear()', () => { it.skip('calls localStorage.clear()', () => {
const clearStorageMock = jest.fn() const clearStorageMock = jest.fn()
global.sessionStorage = jest.fn(() => { global.sessionStorage = jest.fn(() => {
return { return {

View File

@ -41,7 +41,6 @@ describe('DashboardLayoutGdd', () => {
}, },
$store: { $store: {
state: { state: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
}, },
dispatch: storeDispatchMock, dispatch: storeDispatchMock,
@ -128,16 +127,17 @@ describe('DashboardLayoutGdd', () => {
describe('logout', () => { describe('logout', () => {
beforeEach(async () => { beforeEach(async () => {
await apolloMock.mockResolvedValue({
data: {
logout: 'success',
},
})
await wrapper.findComponent({ name: 'sidebar' }).vm.$emit('logout') await wrapper.findComponent({ name: 'sidebar' }).vm.$emit('logout')
await flushPromises() await flushPromises()
}) })
it('calls the API', async () => { it('calls the API', async () => {
expect(apolloMock).toBeCalledWith( await expect(apolloMock).toBeCalled()
expect.objectContaining({
variables: { sessionId: 1 },
}),
)
}) })
it('dispatches logout to store', () => { it('dispatches logout to store', () => {
@ -196,7 +196,6 @@ describe('DashboardLayoutGdd', () => {
expect(apolloMock).toBeCalledWith( expect(apolloMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
firstPage: 2, firstPage: 2,
items: 5, items: 5,
}, },

View File

@ -92,7 +92,6 @@ export default {
this.$apollo this.$apollo
.query({ .query({
query: logout, query: logout,
variables: { sessionId: this.$store.state.sessionId },
}) })
.then(() => { .then(() => {
this.$sidebar.displaySidebar(false) this.$sidebar.displaySidebar(false)
@ -111,7 +110,6 @@ export default {
.query({ .query({
query: transactionsQuery, query: transactionsQuery,
variables: { variables: {
sessionId: this.$store.state.sessionId,
firstPage: pagination.firstPage, firstPage: pagination.firstPage,
items: pagination.items, items: pagination.items,
}, },

View File

@ -16,12 +16,12 @@ describe('AccountOverview', () => {
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$n: jest.fn((n) => String(n)),
$store: { $store: {
state: { state: {
sessionId: 1, email: 'sender@example.org',
}, },
}, },
$n: jest.fn((n) => String(n)),
$apollo: { $apollo: {
query: sendMock, query: sendMock,
}, },
@ -93,7 +93,6 @@ describe('AccountOverview', () => {
expect(sendMock).toBeCalledWith( expect(sendMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
amount: 23.45, amount: 23.45,
memo: 'Make the best of it!', memo: 'Make the best of it!',

View File

@ -107,10 +107,7 @@ export default {
this.$apollo this.$apollo
.query({ .query({
query: sendCoins, query: sendCoins,
variables: { variables: this.transactionData,
sessionId: this.$store.state.sessionId,
...this.transactionData,
},
}) })
.then(() => { .then(() => {
this.error = false this.error = false

View File

@ -8,11 +8,6 @@ describe('GddSend', () => {
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$store: {
state: {
sessionId: 1234,
},
},
$i18n: { $i18n: {
locale: jest.fn(() => 'en'), locale: jest.fn(() => 'en'),
}, },

View File

@ -57,11 +57,6 @@ describe('GdtTransactionList', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$n: jest.fn((n) => n), $n: jest.fn((n) => n),
$d: jest.fn((d) => d), $d: jest.fn((d) => d),
$store: {
state: {
sessionId: 1,
},
},
$toasted: { $toasted: {
error: toastErrorMock, error: toastErrorMock,
}, },
@ -89,7 +84,6 @@ describe('GdtTransactionList', () => {
expect(apolloMock).toBeCalledWith( expect(apolloMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
currentPage: 1, currentPage: 1,
pageSize: 25, pageSize: 25,
}, },

View File

@ -163,10 +163,19 @@
import { listGDTEntriesQuery } from '../../../graphql/queries' import { listGDTEntriesQuery } from '../../../graphql/queries'
import PaginationButtons from '../../../components/PaginationButtons' import PaginationButtons from '../../../components/PaginationButtons'
const iconsByType = { function iconByType(typeId) {
1: { icon: 'heart', classes: 'gradido-global-color-accent' }, switch (typeId) {
4: { icon: 'person-check', classes: 'gradido-global-color-accent' }, case 1:
7: { icon: 'gift', classes: 'gradido-global-color-accent' }, case 2:
case 3:
case 5:
case 6:
return { icon: 'heart', classes: 'gradido-global-color-accent' }
case 4:
return { icon: 'person-check', classes: 'gradido-global-color-accent' }
case 7:
return { icon: 'gift', classes: 'gradido-global-color-accent' }
}
} }
export default { export default {
@ -199,7 +208,6 @@ export default {
.query({ .query({
query: listGDTEntriesQuery, query: listGDTEntriesQuery,
variables: { variables: {
sessionId: this.$store.state.sessionId,
currentPage: this.currentPage, currentPage: this.currentPage,
pageSize: this.pageSize, pageSize: this.pageSize,
}, },
@ -216,7 +224,7 @@ export default {
}) })
}, },
getIcon(givenType) { getIcon(givenType) {
const type = iconsByType[givenType] const type = iconByType(givenType)
if (type) if (type)
return { return {
icon: type.icon, icon: type.icon,

View File

@ -6,12 +6,7 @@ const localVue = global.localVue
const loginQueryMock = jest.fn().mockResolvedValue({ const loginQueryMock = jest.fn().mockResolvedValue({
data: { data: {
login: { login: 'token',
sessionId: 1,
user: {
name: 'Peter Lustig',
},
},
}, },
}) })
@ -159,10 +154,7 @@ describe('Login', () => {
describe('login success', () => { describe('login success', () => {
it('dispatches server response to store', () => { it('dispatches server response to store', () => {
expect(mockStoreDispach).toBeCalledWith('login', { expect(mockStoreDispach).toBeCalledWith('login', 'token')
sessionId: 1,
user: { name: 'Peter Lustig' },
})
}) })
it('redirects to overview page', () => { it('redirects to overview page', () => {

View File

@ -85,6 +85,13 @@ describe('Register', () => {
it('has password repeat input fields', () => { it('has password repeat input fields', () => {
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy() expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
}) })
it('has Language selected field', () => {
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')
})
it('has 1 checkbox input fields', () => { it('has 1 checkbox input fields', () => {
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy() expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
@ -126,9 +133,16 @@ describe('Register', () => {
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net') wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456') wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456') wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
wrapper.find('input[name="site.signup.agree"]').setChecked(true) 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('')
})
it('resets the firstName field after clicking the reset button', async () => { it('resets the firstName field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click') await wrapper.find('button.ml-2').trigger('click')
await flushPromises() await flushPromises()
@ -173,6 +187,7 @@ describe('Register', () => {
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net') wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456') wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456') wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
}) })
describe('server sends back error', () => { describe('server sends back error', () => {

View File

@ -84,6 +84,18 @@
:register="register" :register="register"
></input-password-confirmation> ></input-password-confirmation>
<b-row>
<b-col cols="12">
{{ $t('language') }}
<b-form-select
id="selectedLanguage"
v-model="selected"
:options="options"
class="mb-3"
></b-form-select>
</b-col>
</b-row>
<b-row class="my-4"> <b-row class="my-4">
<b-col cols="12"> <b-col cols="12">
<b-form-checkbox <b-form-checkbox
@ -109,7 +121,10 @@
</span> </span>
</b-alert> </b-alert>
<div class="text-center" v-if="namesFilled && emailFilled && form.agree"> <div
class="text-center"
v-if="namesFilled && emailFilled && form.agree && languageFilled"
>
<div class="text-center"> <div class="text-center">
<b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button> <b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button>
<b-button type="submit" variant="primary">{{ $t('signup') }}</b-button> <b-button type="submit" variant="primary">{{ $t('signup') }}</b-button>
@ -147,6 +162,12 @@ export default {
passwordRepeat: '', 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') },
],
submitted: false, submitted: false,
showError: false, showError: false,
messageError: '', messageError: '',
@ -168,8 +189,7 @@ export default {
}, },
agree: false, agree: false,
} }
this.form.password.password = '' this.selected = null
this.form.password.passwordRepeat = ''
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.observer.reset() this.$refs.observer.reset()
}) })
@ -183,7 +203,7 @@ export default {
firstName: this.form.firstname, firstName: this.form.firstname,
lastName: this.form.lastname, lastName: this.form.lastname,
password: this.form.password.password, password: this.form.password.password,
language: this.$store.state.language, language: this.selected,
}, },
}) })
.then(() => { .then(() => {
@ -192,6 +212,7 @@ export default {
this.form.lastname = '' this.form.lastname = ''
this.form.password.password = '' this.form.password.password = ''
this.form.password.passwordRepeat = '' this.form.password.passwordRepeat = ''
this.selected = null
this.$router.push('/thx/register') this.$router.push('/thx/register')
}) })
.catch((error) => { .catch((error) => {
@ -207,6 +228,7 @@ export default {
this.form.lastname = '' this.form.lastname = ''
this.form.password.password = '' this.form.password.password = ''
this.form.password.passwordRepeat = '' this.form.password.passwordRepeat = ''
this.selected = null
}, },
}, },
computed: { computed: {
@ -221,6 +243,9 @@ export default {
emailFilled() { emailFilled() {
return this.form.email !== '' return this.form.email !== ''
}, },
languageFilled() {
return this.selected !== null
},
}, },
} }
</script> </script>

View File

@ -17,7 +17,6 @@ describe('UserCard_FormUserData', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$store: { $store: {
state: { state: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
@ -118,7 +117,6 @@ describe('UserCard_FormUserData', () => {
expect(mockAPIcall).toBeCalledWith( expect(mockAPIcall).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
firstName: 'Petra', firstName: 'Petra',
lastName: 'Lustiger', lastName: 'Lustiger',
@ -167,7 +165,6 @@ describe('UserCard_FormUserData', () => {
expect(mockAPIcall).toBeCalledWith( expect(mockAPIcall).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
firstName: 'Petra', firstName: 'Petra',
lastName: 'Lustiger', lastName: 'Lustiger',

View File

@ -85,7 +85,6 @@ export default {
data() { data() {
return { return {
showUserData: true, showUserData: true,
sessionId: this.$store.state.sessionId,
form: { form: {
firstName: this.$store.state.firstName, firstName: this.$store.state.firstName,
lastName: this.$store.state.lastName, lastName: this.$store.state.lastName,
@ -118,7 +117,6 @@ export default {
.query({ .query({
query: updateUserInfos, query: updateUserInfos,
variables: { variables: {
sessionId: this.$store.state.sessionId,
email: this.$store.state.email, email: this.$store.state.email,
firstName: this.form.firstName, firstName: this.form.firstName,
lastName: this.form.lastName, lastName: this.form.lastName,

View File

@ -14,7 +14,6 @@ describe('UserCard_FormUserMail', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$store: { $store: {
state: { state: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
@ -76,7 +75,6 @@ describe('UserCard_FormUserMail', () => {
expect(mockAPIcall).toHaveBeenCalledWith( expect(mockAPIcall).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
newEmail: 'test@example.org', newEmail: 'test@example.org',
}, },
@ -106,7 +104,6 @@ describe('UserCard_FormUserMail', () => {
expect(mockAPIcall).toHaveBeenCalledWith( expect(mockAPIcall).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
newEmail: 'test@example.org', newEmail: 'test@example.org',
}, },

View File

@ -48,7 +48,6 @@ export default {
.query({ .query({
query: updateUserInfos, query: updateUserInfos,
variables: { variables: {
sessionId: this.$store.state.sessionId,
email: this.$store.state.email, email: this.$store.state.email,
newEmail: this.newEmail, newEmail: this.newEmail,
}, },

View File

@ -17,7 +17,6 @@ describe('UserCard_FormUserPasswort', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$store: { $store: {
state: { state: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
}, },
}, },
@ -175,7 +174,6 @@ describe('UserCard_FormUserPasswort', () => {
expect(changePasswordProfileMock).toHaveBeenCalledWith( expect(changePasswordProfileMock).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
password: '1234', password: '1234',
passwordNew: 'Aa123456', passwordNew: 'Aa123456',

View File

@ -81,7 +81,6 @@ export default {
.query({ .query({
query: updateUserInfos, query: updateUserInfos,
variables: { variables: {
sessionId: this.$store.state.sessionId,
email: this.$store.state.email, email: this.$store.state.email,
password: this.form.password, password: this.form.password,
passwordNew: this.form.newPassword.password, passwordNew: this.form.newPassword.password,

View File

@ -25,7 +25,6 @@ describe('UserCard_FormUsername', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$store: { $store: {
state: { state: {
sessionId: 1,
email: 'user@example.org', email: 'user@example.org',
username: '', username: '',
}, },
@ -111,7 +110,6 @@ describe('UserCard_FormUsername', () => {
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
email: 'user@example.org', email: 'user@example.org',
sessionId: 1,
username: 'username', username: 'username',
}, },
}), }),
@ -151,7 +149,6 @@ describe('UserCard_FormUsername', () => {
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
email: 'user@example.org', email: 'user@example.org',
sessionId: 1,
username: 'username', username: 'username',
}, },
}), }),

View File

@ -90,7 +90,6 @@ export default {
.query({ .query({
query: updateUserInfos, query: updateUserInfos,
variables: { variables: {
sessionId: this.$store.state.sessionId,
email: this.$store.state.email, email: this.$store.state.email,
username: this.form.username, username: this.form.username,
}, },

View File

@ -13,11 +13,6 @@ describe('UserProfileTransactionList', () => {
$i18n: { $i18n: {
locale: jest.fn(() => 'en'), locale: jest.fn(() => 'en'),
}, },
$store: {
state: {
sessionId: 1,
},
},
} }
const stubs = { const stubs = {

View File

@ -13576,6 +13576,13 @@ vue-jest@^3.0.5, vue-jest@^3.0.7:
tsconfig "^7.0.0" tsconfig "^7.0.0"
vue-template-es2015-compiler "^1.6.0" vue-template-es2015-compiler "^1.6.0"
vue-jwt-decode@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/vue-jwt-decode/-/vue-jwt-decode-0.1.0.tgz#f9caf7b9030d5459cc567b1c3117d9d1f291458f"
integrity sha512-4iP0NzYHkAF7G13tYPc/nudk4oNpB8GCVZupc7lekxXok1XKEgefNaGTpDT14g7RKe5H9GaMphPduDj4UVfZwQ==
dependencies:
vue "^2.3.3"
vue-loader@^15.7.0: vue-loader@^15.7.0:
version "15.9.6" version "15.9.6"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.6.tgz#f4bb9ae20c3a8370af3ecf09b8126d38ffdb6b8b" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.6.tgz#f4bb9ae20c3a8370af3ecf09b8126d38ffdb6b8b"
@ -13655,6 +13662,11 @@ vue@^2.2.6, vue@^2.5.17, vue@^2.6.11:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg== integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
vue@^2.3.3:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
vuex-persistedstate@^4.0.0-beta.3: vuex-persistedstate@^4.0.0-beta.3:
version "4.0.0-beta.3" version "4.0.0-beta.3"
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.3.tgz#89dd712de72d28e85cc95467d066002c1405f277" resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.3.tgz#89dd712de72d28e85cc95467d066002c1405f277"