mirror of
https://github.com/IT4Change/gradido.git
synced 2026-01-20 20:01:31 +00:00
Merge branch 'master' into apollo_saveUserOnCreate
This commit is contained in:
commit
a531b4671f
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -344,7 +344,7 @@ jobs:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 69
|
||||
min_coverage: 73
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
@ -470,7 +470,7 @@ jobs:
|
||||
run: echo "::set-output name=id::$(docker network ls | grep github_network | awk '{ print $1 }')"
|
||||
id: network
|
||||
- name: Start database migration
|
||||
run: docker run --network ${{ steps.network.outputs.id }} --name=database --env NODE_ENV=production --env DB_HOST=mariadb -d gradido/database:production_up
|
||||
run: docker run --network ${{ steps.network.outputs.id }} --name=database --env NODE_ENV=production --env DB_HOST=mariadb --env DB_DATABASE=gradido_community_test -d gradido/database:production_up
|
||||
- name: get database migration container id
|
||||
run: echo "::set-output name=id::$(docker container ls | grep database | awk '{ print $1 }')"
|
||||
id: database_container
|
||||
|
||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
/node_modules/
|
||||
/.env
|
||||
/build/
|
||||
|
||||
package-json.lock
|
||||
coverage
|
||||
# emacs
|
||||
*~
|
||||
7120
backend/package-lock.json
generated
Normal file
7120
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,9 +2,6 @@ import { ArgsType, Field } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export default class UpdateUserInfosArgs {
|
||||
@Field(() => String)
|
||||
email!: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
firstName?: string
|
||||
|
||||
@ -28,4 +25,7 @@ export default class UpdateUserInfosArgs {
|
||||
|
||||
@Field({ nullable: true })
|
||||
passwordNew?: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
coinanimation?: boolean
|
||||
}
|
||||
@ -1,13 +1,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
import decode from '../jwt/decode'
|
||||
import { apiGet } from '../apis/HttpRequest'
|
||||
import CONFIG from '../config'
|
||||
import encode from '../jwt/encode'
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
|
||||
import CONFIG from '../../config'
|
||||
import { apiGet } from '../../apis/HttpRequest'
|
||||
|
||||
import decode from '../../jwt/decode'
|
||||
import encode from '../../jwt/encode'
|
||||
|
||||
const isAuthorized: AuthChecker<any> = async (
|
||||
{ /* root, args, */ context /*, info */ } /*, roles */,
|
||||
) => {
|
||||
if (context.token) {
|
||||
const decoded = decode(context.token)
|
||||
if (decoded.sessionId && decoded.sessionId !== 0) {
|
||||
@ -22,3 +25,5 @@ export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info
|
||||
}
|
||||
throw new Error('401 Unauthorized')
|
||||
}
|
||||
|
||||
export default isAuthorized
|
||||
5
backend/src/graphql/enum/Setting.ts
Normal file
5
backend/src/graphql/enum/Setting.ts
Normal file
@ -0,0 +1,5 @@
|
||||
enum Setting {
|
||||
COIN_ANIMATION = 'coinanimation',
|
||||
}
|
||||
|
||||
export { Setting }
|
||||
@ -69,6 +69,9 @@ export class User {
|
||||
@Field(() => Number)
|
||||
publisherId: number
|
||||
|
||||
@Field(() => Boolean)
|
||||
coinanimation: boolean
|
||||
|
||||
@Field(() => KlickTipp)
|
||||
klickTipp: KlickTipp
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { Balance } from '../models/Balance'
|
||||
import { Balance } from '../model/Balance'
|
||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
@ -4,8 +4,8 @@
|
||||
import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import CONFIG from '../../config'
|
||||
import { GdtEntryList } from '../models/GdtEntryList'
|
||||
import Paginated from '../args/Paginated'
|
||||
import { GdtEntryList } from '../model/GdtEntryList'
|
||||
import Paginated from '../arg/Paginated'
|
||||
import { apiGet } from '../../apis/HttpRequest'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { Order } from '../enum/Order'
|
||||
@ -30,7 +30,6 @@ export class GdtResolver {
|
||||
if (!resultGDT.success) {
|
||||
throw new Error(resultGDT.data)
|
||||
}
|
||||
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ import {
|
||||
unsubscribe,
|
||||
signIn,
|
||||
} from '../../apis/KlicktippController'
|
||||
import SubscribeNewsletterArgs from '../args/SubscribeNewsletterArgs'
|
||||
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
|
||||
|
||||
@Resolver()
|
||||
export class KlicktippResolver {
|
||||
@ -6,11 +6,11 @@ import { getCustomRepository } from 'typeorm'
|
||||
|
||||
import CONFIG from '../../config'
|
||||
|
||||
import { Transaction } from '../models/Transaction'
|
||||
import { TransactionList } from '../models/TransactionList'
|
||||
import { Transaction } from '../model/Transaction'
|
||||
import { TransactionList } from '../model/TransactionList'
|
||||
|
||||
import TransactionSendArgs from '../args/TransactionSendArgs'
|
||||
import Paginated from '../args/Paginated'
|
||||
import TransactionSendArgs from '../arg/TransactionSendArgs'
|
||||
import Paginated from '../arg/Paginated'
|
||||
|
||||
import { Order } from '../enum/Order'
|
||||
|
||||
@ -50,7 +50,7 @@ async function calculateAndAddDecayTransactions(
|
||||
const transactionIndiced: dbTransaction[] = []
|
||||
transactions.forEach((transaction: dbTransaction) => {
|
||||
transactionIndiced[transaction.id] = transaction
|
||||
if (transaction.transactionTypeId === 2) {
|
||||
if (transaction.transactionTypeId === TransactionTypeId.SEND) {
|
||||
involvedUserIds.push(transaction.transactionSendCoin.userId)
|
||||
involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId)
|
||||
}
|
||||
@ -97,7 +97,7 @@ async function calculateAndAddDecayTransactions(
|
||||
}
|
||||
}
|
||||
|
||||
// sender or receiver when user has sended money
|
||||
// sender or receiver when user has sent money
|
||||
// group name if creation
|
||||
// type: gesendet / empfangen / geschöpft
|
||||
// transaktion nr / id
|
||||
@ -227,7 +227,7 @@ export class TransactionResolver {
|
||||
email: userEntity.email,
|
||||
})
|
||||
if (!resultGDTSum.success) throw new Error(resultGDTSum.data)
|
||||
transactions.gdtSum = resultGDTSum.data.sum
|
||||
transactions.gdtSum = resultGDTSum.data.sum || 0
|
||||
|
||||
// get balance
|
||||
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||
@ -4,24 +4,28 @@
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||
import { from_hex as fromHex } from 'libsodium-wrappers'
|
||||
import CONFIG from '../../config'
|
||||
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
|
||||
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
|
||||
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
|
||||
import { User } from '../models/User'
|
||||
import { CheckUsernameResponse } from '../model/CheckUsernameResponse'
|
||||
import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
||||
import { UpdateUserInfosResponse } from '../model/UpdateUserInfosResponse'
|
||||
import { User } from '../model/User'
|
||||
import { User as DbUser } from '../../typeorm/entity/User'
|
||||
import encode from '../../jwt/encode'
|
||||
import ChangePasswordArgs from '../args/ChangePasswordArgs'
|
||||
import CheckUsernameArgs from '../args/CheckUsernameArgs'
|
||||
import CreateUserArgs from '../args/CreateUserArgs'
|
||||
import UnsecureLoginArgs from '../args/UnsecureLoginArgs'
|
||||
import UpdateUserInfosArgs from '../args/UpdateUserInfosArgs'
|
||||
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
||||
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
||||
import CreateUserArgs from '../arg/CreateUserArgs'
|
||||
import UnsecureLoginArgs from '../arg/UnsecureLoginArgs'
|
||||
import UpdateUserInfosArgs from '../arg/UpdateUserInfosArgs'
|
||||
import { apiPost, apiGet } from '../../apis/HttpRequest'
|
||||
import {
|
||||
klicktippRegistrationMiddleware,
|
||||
klicktippNewsletterStateMiddleware,
|
||||
} from '../../middleware/klicktippMiddleware'
|
||||
import { CheckEmailResponse } from '../models/CheckEmailResponse'
|
||||
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||
import { Setting } from '../enum/Setting'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
@ -40,8 +44,19 @@ export class UserResolver {
|
||||
key: 'token',
|
||||
value: encode(result.data.session_id, result.data.user.public_hex),
|
||||
})
|
||||
const user = new User(result.data.user)
|
||||
// read additional settings from settings table
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userEntity = await userRepository.findByPubkeyHex(user.pubkey)
|
||||
|
||||
return new User(result.data.user)
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
return user
|
||||
}
|
||||
|
||||
@Query(() => LoginViaVerificationCode)
|
||||
@ -142,7 +157,6 @@ export class UserResolver {
|
||||
async updateUserInfos(
|
||||
@Args()
|
||||
{
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
description,
|
||||
@ -151,12 +165,12 @@ export class UserResolver {
|
||||
publisherId,
|
||||
password,
|
||||
passwordNew,
|
||||
coinanimation,
|
||||
}: UpdateUserInfosArgs,
|
||||
@Ctx() context: any,
|
||||
): Promise<UpdateUserInfosResponse> {
|
||||
const payload = {
|
||||
session_id: context.sessionId,
|
||||
email,
|
||||
update: {
|
||||
'User.first_name': firstName || undefined,
|
||||
'User.last_name': lastName || undefined,
|
||||
@ -168,9 +182,42 @@ export class UserResolver {
|
||||
'User.password_old': password || undefined,
|
||||
},
|
||||
}
|
||||
let response: UpdateUserInfosResponse | undefined
|
||||
if (
|
||||
firstName ||
|
||||
lastName ||
|
||||
description ||
|
||||
username ||
|
||||
language ||
|
||||
publisherId ||
|
||||
passwordNew ||
|
||||
password
|
||||
) {
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
|
||||
if (!result.success) throw new Error(result.data)
|
||||
return new UpdateUserInfosResponse(result.data)
|
||||
response = new UpdateUserInfosResponse(result.data)
|
||||
}
|
||||
if (coinanimation !== undefined) {
|
||||
// load user and balance
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
userSettingRepository
|
||||
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
response = new UpdateUserInfosResponse({ valid_values: 1 })
|
||||
} else {
|
||||
response.validValues++
|
||||
}
|
||||
}
|
||||
if (!response) {
|
||||
throw new Error('no valid response')
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
@Query(() => CheckUsernameResponse)
|
||||
@ -1,19 +0,0 @@
|
||||
import { UserResolver } from './UserResolver'
|
||||
import { BalanceResolver } from './BalanceResolver'
|
||||
import { GdtResolver } from './GdtResolver'
|
||||
import { TransactionResolver } from './TransactionResolver'
|
||||
import { KlicktippResolver } from './KlicktippResolver'
|
||||
import { NonEmptyArray } from 'type-graphql'
|
||||
|
||||
export { UserResolver, BalanceResolver, GdtResolver, TransactionResolver, KlicktippResolver }
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const resolvers = (): NonEmptyArray<Function> => [
|
||||
UserResolver,
|
||||
BalanceResolver,
|
||||
GdtResolver,
|
||||
TransactionResolver,
|
||||
KlicktippResolver,
|
||||
]
|
||||
|
||||
export default resolvers
|
||||
@ -1,12 +1,12 @@
|
||||
import { GraphQLSchema } from 'graphql'
|
||||
import { buildSchema } from 'type-graphql'
|
||||
import path from 'path'
|
||||
|
||||
import resolvers from './resolvers'
|
||||
import { isAuthorized } from '../auth/auth'
|
||||
import isAuthorized from './directive/isAuthorized'
|
||||
|
||||
const schema = async (): Promise<GraphQLSchema> => {
|
||||
return buildSchema({
|
||||
resolvers: resolvers(),
|
||||
resolvers: [path.join(__dirname, 'resolver', `*.{js,ts}`)],
|
||||
authChecker: isAuthorized,
|
||||
})
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import schema from './graphql/schema'
|
||||
// TODO implement
|
||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||
|
||||
const DB_VERSION = '0001-init_db'
|
||||
const DB_VERSION = '0002-add_settings'
|
||||
|
||||
async function main() {
|
||||
// open mysql connection
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { MiddlewareFn } from 'type-graphql'
|
||||
import { signIn, getKlickTippUser } from '../apis/KlicktippController'
|
||||
import { KlickTipp } from '../graphql/models/KlickTipp'
|
||||
import { KlickTipp } from '../graphql/model/KlickTipp'
|
||||
import CONFIG from '../config/index'
|
||||
|
||||
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||
|
||||
// import { Group } from "./Group"
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
|
||||
import { UserSetting } from './UserSetting'
|
||||
|
||||
// Moriz: I do not like the idea of having two user tables
|
||||
@Entity('state_users')
|
||||
@ -28,4 +27,7 @@ export class User extends BaseEntity {
|
||||
|
||||
@Column()
|
||||
disabled: boolean
|
||||
|
||||
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
|
||||
settings: UserSetting[]
|
||||
}
|
||||
|
||||
20
backend/src/typeorm/entity/UserSetting.ts
Normal file
20
backend/src/typeorm/entity/UserSetting.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'
|
||||
import { User } from './User'
|
||||
|
||||
@Entity()
|
||||
export class UserSetting extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
userId: number
|
||||
|
||||
@ManyToOne(() => User, (user) => user.settings)
|
||||
user: User
|
||||
|
||||
@Column()
|
||||
key: string
|
||||
|
||||
@Column()
|
||||
value: string
|
||||
}
|
||||
@ -1,11 +1,8 @@
|
||||
import { getConnection } from 'typeorm'
|
||||
import { Migration } from './entity/Migration'
|
||||
|
||||
const getDBVersion = async (): Promise<string | null> => {
|
||||
const connection = getConnection()
|
||||
const migrations = connection.getRepository(Migration)
|
||||
try {
|
||||
const dbVersion = await migrations.findOne({ order: { version: 'DESC' } })
|
||||
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||
return dbVersion ? dbVersion.fileName : null
|
||||
} catch (error) {
|
||||
return null
|
||||
|
||||
@ -4,8 +4,6 @@ import { Balance } from '../entity/Balance'
|
||||
@EntityRepository(Balance)
|
||||
export class BalanceRepository extends Repository<Balance> {
|
||||
findByUser(userId: number): Promise<Balance | undefined> {
|
||||
return this.createQueryBuilder('balance')
|
||||
.where('balance.userId = :userId', { userId })
|
||||
.getOneOrFail()
|
||||
return this.createQueryBuilder('balance').where('balance.userId = :userId', { userId }).getOne()
|
||||
}
|
||||
}
|
||||
|
||||
36
backend/src/typeorm/repository/UserSettingRepository.ts
Normal file
36
backend/src/typeorm/repository/UserSettingRepository.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { EntityRepository, Repository } from 'typeorm'
|
||||
import { UserSetting } from '../entity/UserSetting'
|
||||
import { Setting } from '../../graphql/enum/Setting'
|
||||
import { isStringBoolean } from '../../util/validate'
|
||||
|
||||
@EntityRepository(UserSetting)
|
||||
export class UserSettingRepository extends Repository<UserSetting> {
|
||||
async setOrUpdate(userId: number, key: Setting, value: string): Promise<UserSetting> {
|
||||
switch (key) {
|
||||
case Setting.COIN_ANIMATION:
|
||||
if (!isStringBoolean(value)) {
|
||||
throw new Error("coinanimation value isn't boolean")
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error("key isn't defined: " + key)
|
||||
}
|
||||
let entity = await this.findOne({ userId: userId, key: key })
|
||||
|
||||
if (!entity) {
|
||||
entity = new UserSetting()
|
||||
entity.userId = userId
|
||||
entity.key = key
|
||||
}
|
||||
entity.value = value
|
||||
return this.save(entity)
|
||||
}
|
||||
|
||||
async readBoolean(userId: number, key: Setting): Promise<boolean> {
|
||||
const entity = await this.findOne({ userId: userId, key: key })
|
||||
if (!entity || !isStringBoolean(entity.value)) {
|
||||
return true
|
||||
}
|
||||
return entity.value.toLowerCase() === 'true'
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,29 @@
|
||||
import { calculateDecay } from './decay'
|
||||
import { decayFormula, calculateDecay } from './decay'
|
||||
|
||||
describe('utils/decay', () => {
|
||||
describe('decayFormula', () => {
|
||||
it('has base 0.99999997802044727', async () => {
|
||||
const amount = 1.0
|
||||
const seconds = 1
|
||||
expect(await decayFormula(amount, seconds)).toBe(0.99999997802044727)
|
||||
})
|
||||
// Not sure if the following skiped tests make sence!?
|
||||
it.skip('has negative decay?', async () => {
|
||||
const amount = -1.0
|
||||
const seconds = 1
|
||||
expect(await decayFormula(amount, seconds)).toBe(-0.99999997802044727)
|
||||
})
|
||||
it.skip('has correct backward calculation', async () => {
|
||||
const amount = 1.0
|
||||
const seconds = -1
|
||||
expect(await decayFormula(amount, seconds)).toBe(1.0000000219795533)
|
||||
})
|
||||
it.skip('has correct forward calculation', async () => {
|
||||
const amount = 1.000000219795533
|
||||
const seconds = 1
|
||||
expect(await decayFormula(amount, seconds)).toBe(1.0)
|
||||
})
|
||||
})
|
||||
it.skip('has base 0.99999997802044727', async () => {
|
||||
const now = new Date()
|
||||
now.setSeconds(1)
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { getCustomRepository } from 'typeorm'
|
||||
import { Decay } from '../graphql/models/Decay'
|
||||
import { Decay } from '../graphql/model/Decay'
|
||||
import { TransactionRepository } from '../typeorm/repository/Transaction'
|
||||
|
||||
function decayFormula(amount: number, durationInSeconds: number): number {
|
||||
return amount * Math.pow(0.99999997802044727, durationInSeconds)
|
||||
function decayFormula(amount: number, seconds: number): number {
|
||||
return amount * Math.pow(0.99999997802044727, seconds) // This number represents 50% decay a year
|
||||
}
|
||||
|
||||
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
|
||||
@ -58,4 +58,4 @@ async function calculateDecayWithInterval(
|
||||
return result
|
||||
}
|
||||
|
||||
export { calculateDecay, calculateDecayWithInterval }
|
||||
export { decayFormula, calculateDecay, calculateDecayWithInterval }
|
||||
|
||||
9
backend/src/util/validate.ts
Normal file
9
backend/src/util/validate.ts
Normal file
@ -0,0 +1,9 @@
|
||||
function isStringBoolean(value: string): boolean {
|
||||
const lowerValue = value.toLowerCase()
|
||||
if (lowerValue === 'true' || lowerValue === 'false') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export { isStringBoolean }
|
||||
@ -218,7 +218,7 @@
|
||||
|
||||
"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
|
||||
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz"
|
||||
integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.14.5"
|
||||
@ -4802,7 +4802,7 @@ semver-diff@^3.1.1:
|
||||
|
||||
semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
@ -5715,9 +5715,9 @@ yargs@^16.0.0, yargs@^16.2.0:
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.0.1:
|
||||
version "17.2.0"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.2.0.tgz"
|
||||
integrity sha512-UPeZv4h9Xv510ibpt5rdsUNzgD78nMa1rhxxCgvkKiq06hlKCEHJLiJ6Ub8zDg/wR6hedEI6ovnd2vCvJ4nusA==
|
||||
version "17.1.1"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz"
|
||||
integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
|
||||
26
database/migrations/0002-add_settings.ts
Normal file
26
database/migrations/0002-add_settings.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* FIRST MIGRATION
|
||||
*
|
||||
* This migration is special since it takes into account that
|
||||
* the database can be setup already but also may not be.
|
||||
* Therefore you will find all `CREATE TABLE` statements with
|
||||
* a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
|
||||
* downgrade function all `DROP TABLE` with a `IF EXISTS`.
|
||||
* This ensures compatibility for existing or non-existing
|
||||
* databases.
|
||||
*/
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE IF NOT EXISTS \`user_setting\` (
|
||||
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`userId\` int(11) NOT NULL,
|
||||
\`key\` varchar(255) NOT NULL,
|
||||
\`value\` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// write downgrade logic as parameter of queryFn
|
||||
await queryFn(`DROP TABLE IF EXISTS \`user_setting\`;`)
|
||||
}
|
||||
@ -25,12 +25,6 @@ export default async (): Promise<void> => {
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
||||
|
||||
// Create Database `gradido_community_test` for tests
|
||||
await con.query(`
|
||||
CREATE DATABASE IF NOT EXISTS ${CONFIG.DB_DATABASE}_test
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
||||
|
||||
// Check if old migration table is present, delete if needed
|
||||
const [rows] = await con.query(`SHOW TABLES FROM \`${CONFIG.DB_DATABASE}\` LIKE 'migrations';`)
|
||||
if ((<RowDataPacket>rows).length > 0) {
|
||||
|
||||
@ -43,11 +43,13 @@ services:
|
||||
# DATABASE ##############################################
|
||||
########################################################
|
||||
database:
|
||||
# we always run on prouction here since else the service lingers
|
||||
# we always run on production here since else the service lingers
|
||||
# feel free to change this behaviour if it seems useful
|
||||
#image: gradido/database:test_up
|
||||
#build:
|
||||
# target: test_up
|
||||
# Due to problems with the volume caching the built files
|
||||
# we changed this to test build. This keeps the service running.
|
||||
image: gradido/database:test_up
|
||||
build:
|
||||
target: test_up
|
||||
#networks:
|
||||
# - external-net
|
||||
# - internal-net
|
||||
|
||||
@ -222,7 +222,6 @@ with:
|
||||
```json
|
||||
{
|
||||
"session_id": -127182,
|
||||
"email": "max.musterman@gmail.de",
|
||||
"update": {
|
||||
"User.first_name": "Max",
|
||||
"User.last_name" : "Musterman",
|
||||
|
||||
BIN
docu/presentation/wallet-frontend-pages-overview-04-10-2021.odt
Normal file
BIN
docu/presentation/wallet-frontend-pages-overview-04-10-2021.odt
Normal file
Binary file not shown.
BIN
docu/presentation/wallet-frontend-pages-overview-04-10-2021.pdf
Normal file
BIN
docu/presentation/wallet-frontend-pages-overview-04-10-2021.pdf
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="app" class="font-sans text-gray-800">
|
||||
<div class="">
|
||||
<particles-bg type="custom" :config="config" :bg="true" />
|
||||
<particles-bg v-if="$store.state.coinanimation" type="custom" :config="config" :bg="true" />
|
||||
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
</b-row>
|
||||
|
||||
<!-- Message-->
|
||||
<b-row v-if="comment && gdtEntryType !== 7">
|
||||
<b-row v-if="comment && !isGlobalModificator">
|
||||
<b-col cols="6" class="text-right">
|
||||
{{ $t('form.memo') }}
|
||||
</b-col>
|
||||
@ -72,6 +72,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import TransactionCollapse from './TransactionCollapse.vue'
|
||||
import { GdtEntryType } from '../graphql/enums'
|
||||
|
||||
export default {
|
||||
name: 'Transaction',
|
||||
@ -82,42 +83,52 @@ export default {
|
||||
amount: { type: Number },
|
||||
date: { type: String },
|
||||
comment: { type: String },
|
||||
gdtEntryType: { type: Number, default: 1 },
|
||||
gdtEntryType: { type: String, default: GdtEntryType.FORM },
|
||||
factor: { type: Number },
|
||||
gdt: { type: Number },
|
||||
},
|
||||
computed: {
|
||||
isGlobalModificator: function () {
|
||||
return this.gdtEntryType === GdtEntryType.GLOBAL_MODIFICATOR
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getLinesByType(givenType) {
|
||||
if (givenType === 2 || givenType === 3 || givenType === 5 || givenType === 6) givenType = 1
|
||||
|
||||
const linesByType = {
|
||||
1: {
|
||||
icon: 'heart',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.contribution'),
|
||||
descriptiontext: this.$n(this.amount, 'decimal') + ' €',
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
},
|
||||
4: {
|
||||
icon: 'person-check',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.recruited-member'),
|
||||
descriptiontext: '5%',
|
||||
credittext: this.$n(this.amount, 'decimal') + ' GDT',
|
||||
},
|
||||
7: {
|
||||
icon: 'gift',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.gdt-received'),
|
||||
descriptiontext: this.comment,
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
},
|
||||
switch (givenType) {
|
||||
case GdtEntryType.FORM:
|
||||
case GdtEntryType.CVS:
|
||||
case GdtEntryType.ELOPAGE:
|
||||
case GdtEntryType.DIGISTORE:
|
||||
case GdtEntryType.CVS2: {
|
||||
return {
|
||||
icon: 'heart',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.contribution'),
|
||||
descriptiontext: this.$n(this.amount, 'decimal') + ' €',
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
}
|
||||
}
|
||||
case GdtEntryType.ELOPAGE_PUBLISHER: {
|
||||
return {
|
||||
icon: 'person-check',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.recruited-member'),
|
||||
descriptiontext: '5%',
|
||||
credittext: this.$n(this.amount, 'decimal') + ' GDT',
|
||||
}
|
||||
}
|
||||
case GdtEntryType.GLOBAL_MODIFICATOR: {
|
||||
return {
|
||||
icon: 'gift',
|
||||
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||
description: this.$t('gdt.gdt-received'),
|
||||
descriptiontext: this.comment,
|
||||
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error('no lines for this type: ' + givenType)
|
||||
}
|
||||
|
||||
const type = linesByType[givenType]
|
||||
|
||||
if (type) return type
|
||||
throw new Error('no lines for this type: ' + givenType)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import TransactionCollapse from './TransactionCollapse'
|
||||
import { GdtEntryType } from '../graphql/enums'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -15,13 +16,13 @@ describe('TransactionCollapse', () => {
|
||||
return mount(TransactionCollapse, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount with gdtEntryType: 1', () => {
|
||||
describe('mount with gdtEntryType: FORM', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 110,
|
||||
factor: 22,
|
||||
gdtEntryType: 1,
|
||||
gdtEntryType: GdtEntryType.FORM,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
@ -31,8 +32,8 @@ describe('TransactionCollapse', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(1)
|
||||
it('checks the prop gdtEntryType', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe('FORM')
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
@ -60,13 +61,13 @@ describe('TransactionCollapse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('mount with gdtEntryType: 7', () => {
|
||||
describe('mount with gdtEntryType: GLOBAL_MODIFICATOR', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 2200,
|
||||
factor: 22,
|
||||
gdtEntryType: 7,
|
||||
gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
@ -76,8 +77,8 @@ describe('TransactionCollapse', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(7)
|
||||
it('checks the prop gdtEntryType', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe('GLOBAL_MODIFICATOR')
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
@ -105,13 +106,13 @@ describe('TransactionCollapse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('mount with gdtEntryType: 4', () => {
|
||||
describe('mount with gdtEntryType: ELOPAGE_PUBLISHER', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
amount: 100,
|
||||
gdt: 2200,
|
||||
factor: 22,
|
||||
gdtEntryType: 4,
|
||||
gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER,
|
||||
}
|
||||
|
||||
wrapper = Wrapper(propsData)
|
||||
@ -121,8 +122,8 @@ describe('TransactionCollapse', () => {
|
||||
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks the prop gdtEntryType ', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe(4)
|
||||
it('checks the prop gdtEntryType', () => {
|
||||
expect(wrapper.props().gdtEntryType).toBe('ELOPAGE_PUBLISHER')
|
||||
})
|
||||
|
||||
it('renders the component collapse-header', () => {
|
||||
|
||||
@ -23,58 +23,65 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { GdtEntryType } from '../graphql/enums'
|
||||
|
||||
export default {
|
||||
name: 'TransactionCollapse',
|
||||
props: {
|
||||
amount: { type: Number },
|
||||
gdtEntryType: { type: Number, default: 1 },
|
||||
gdtEntryType: { type: String, default: GdtEntryType.FORM },
|
||||
factor: { type: Number },
|
||||
gdt: { type: Number },
|
||||
},
|
||||
methods: {
|
||||
getLinesByType(givenType) {
|
||||
if (givenType === 2 || givenType === 3 || givenType === 5 || givenType === 6) givenType = 1
|
||||
|
||||
const linesByType = {
|
||||
1: {
|
||||
headline: this.$t('gdt.calculation'),
|
||||
first: this.$t('gdt.factor'),
|
||||
firstMath: this.factor + ' GDT pro €',
|
||||
second: this.$t('gdt.formula'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' € * ' +
|
||||
this.factor +
|
||||
' GDT / € = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
},
|
||||
4: {
|
||||
headline: this.$t('gdt.publisher'),
|
||||
first: null,
|
||||
firstMath: null,
|
||||
second: null,
|
||||
secondMath: null,
|
||||
},
|
||||
7: {
|
||||
headline: this.$t('gdt.conversion-gdt-euro'),
|
||||
first: this.$t('gdt.raise'),
|
||||
firstMath: this.factor * 100 + ' % ',
|
||||
second: this.$t('gdt.conversion'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' GDT * ' +
|
||||
this.factor * 100 +
|
||||
' % = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
},
|
||||
switch (givenType) {
|
||||
case GdtEntryType.FORM:
|
||||
case GdtEntryType.CVS:
|
||||
case GdtEntryType.ELOPAGE:
|
||||
case GdtEntryType.DIGISTORE:
|
||||
case GdtEntryType.CVS2: {
|
||||
return {
|
||||
headline: this.$t('gdt.calculation'),
|
||||
first: this.$t('gdt.factor'),
|
||||
firstMath: this.factor + ' GDT pro €',
|
||||
second: this.$t('gdt.formula'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' € * ' +
|
||||
this.factor +
|
||||
' GDT / € = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
}
|
||||
}
|
||||
case GdtEntryType.ELOPAGE_PUBLISHER: {
|
||||
return {
|
||||
headline: this.$t('gdt.publisher'),
|
||||
first: null,
|
||||
firstMath: null,
|
||||
second: null,
|
||||
secondMath: null,
|
||||
}
|
||||
}
|
||||
case GdtEntryType.GLOBAL_MODIFICATOR: {
|
||||
return {
|
||||
headline: this.$t('gdt.conversion-gdt-euro'),
|
||||
first: this.$t('gdt.raise'),
|
||||
firstMath: this.factor * 100 + ' % ',
|
||||
second: this.$t('gdt.conversion'),
|
||||
secondMath:
|
||||
this.$n(this.amount, 'decimal') +
|
||||
' GDT * ' +
|
||||
this.factor * 100 +
|
||||
' % = ' +
|
||||
this.$n(this.gdt, 'decimal') +
|
||||
' GDT',
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error('no additional transaction info for this type: ' + givenType)
|
||||
}
|
||||
|
||||
const type = linesByType[givenType]
|
||||
|
||||
if (type) return type
|
||||
throw new Error('no additional transaction info for this type: ' + givenType)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
9
frontend/src/graphql/enums.js
Normal file
9
frontend/src/graphql/enums.js
Normal file
@ -0,0 +1,9 @@
|
||||
export const GdtEntryType = {
|
||||
FORM: 'FORM',
|
||||
CVS: 'CVS',
|
||||
ELOPAGE: 'ELOPAGE',
|
||||
ELOPAGE_PUBLISHER: 'ELOPAGE_PUBLISHER',
|
||||
DIGISTORE: 'DIGISTORE',
|
||||
CVS2: 'CVS2',
|
||||
GLOBAL_MODIFICATOR: 'GLOBAL_MODIFICATOR',
|
||||
}
|
||||
@ -20,7 +20,6 @@ export const resetPassword = gql`
|
||||
|
||||
export const updateUserInfos = gql`
|
||||
mutation(
|
||||
$email: String!
|
||||
$firstName: String
|
||||
$lastName: String
|
||||
$description: String
|
||||
@ -28,9 +27,9 @@ export const updateUserInfos = gql`
|
||||
$password: String
|
||||
$passwordNew: String
|
||||
$locale: String
|
||||
$coinanimation: Boolean
|
||||
) {
|
||||
updateUserInfos(
|
||||
email: $email
|
||||
firstName: $firstName
|
||||
lastName: $lastName
|
||||
description: $description
|
||||
@ -38,6 +37,7 @@ export const updateUserInfos = gql`
|
||||
password: $password
|
||||
passwordNew: $passwordNew
|
||||
language: $locale
|
||||
coinanimation: $coinanimation
|
||||
) {
|
||||
validValues
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ export const login = gql`
|
||||
lastName
|
||||
language
|
||||
description
|
||||
coinanimation
|
||||
klickTipp {
|
||||
newsletterState
|
||||
}
|
||||
@ -32,7 +33,7 @@ export const loginViaEmailVerificationCode = gql`
|
||||
`
|
||||
|
||||
export const transactionsQuery = gql`
|
||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: String = "DESC") {
|
||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||
gdtSum
|
||||
count
|
||||
|
||||
@ -105,6 +105,11 @@
|
||||
"privacy_policy": "Datenschutzerklärung",
|
||||
"send": "Senden",
|
||||
"settings": {
|
||||
"coinanimation": {
|
||||
"coinanimation": "Münzanimation",
|
||||
"False": "Münzanimation ausgeschaltet",
|
||||
"True": "Münzanimation eingeschaltet"
|
||||
},
|
||||
"language": {
|
||||
"changeLanguage": "Sprache ändern",
|
||||
"de": "Deutsch",
|
||||
|
||||
@ -105,6 +105,11 @@
|
||||
"privacy_policy": "Privacy policy",
|
||||
"send": "Send",
|
||||
"settings": {
|
||||
"coinanimation": {
|
||||
"coinanimation": "Coin animation",
|
||||
"False": "Coin animation disabled",
|
||||
"True": "Coin animation enabled"
|
||||
},
|
||||
"language": {
|
||||
"changeLanguage": "Change language",
|
||||
"de": "Deutsch",
|
||||
|
||||
155
frontend/src/routes/router.test.js
Normal file
155
frontend/src/routes/router.test.js
Normal file
@ -0,0 +1,155 @@
|
||||
import router from './router'
|
||||
import NotFound from '@/views/NotFoundPage.vue'
|
||||
|
||||
describe('router', () => {
|
||||
describe('options', () => {
|
||||
const { options } = router
|
||||
const { scrollBehavior, routes } = options
|
||||
|
||||
it('has "/vue" as base', () => {
|
||||
expect(options).toEqual(
|
||||
expect.objectContaining({
|
||||
base: '/vue',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('has "active" as linkActiveClass', () => {
|
||||
expect(options).toEqual(
|
||||
expect.objectContaining({
|
||||
linkActiveClass: 'active',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('has "history" as mode', () => {
|
||||
expect(options).toEqual(
|
||||
expect.objectContaining({
|
||||
mode: 'history',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('scroll behavior', () => {
|
||||
it('returns save position when given', () => {
|
||||
expect(scrollBehavior({}, {}, 'given')).toBe('given')
|
||||
})
|
||||
|
||||
it('returns selector when hash is given', () => {
|
||||
expect(scrollBehavior({ hash: '#to' }, {})).toEqual({ selector: '#to' })
|
||||
})
|
||||
|
||||
it('returns top left coordinates as default', () => {
|
||||
expect(scrollBehavior({}, {})).toEqual({ x: 0, y: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('register page', () => {
|
||||
it('is not present', () => {
|
||||
expect(routes.find((r) => r.path === '/register')).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('routes', () => {
|
||||
it('has "/login" as default', () => {
|
||||
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
||||
})
|
||||
|
||||
it('has ten routes defined', () => {
|
||||
expect(routes).toHaveLength(10)
|
||||
})
|
||||
|
||||
describe('overview', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/overview').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "Overview" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/overview').component()
|
||||
expect(component.default.name).toBe('Overview')
|
||||
})
|
||||
})
|
||||
|
||||
describe('profile', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/profile').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "UserProfile" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/profile').component()
|
||||
expect(component.default.name).toBe('UserProfile')
|
||||
})
|
||||
})
|
||||
|
||||
describe('transactions', () => {
|
||||
it('requires authorization', () => {
|
||||
expect(routes.find((r) => r.path === '/transactions').meta.requiresAuth).toBeTruthy()
|
||||
})
|
||||
|
||||
it('loads the "UserProfileTransactionList" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/transactions').component()
|
||||
expect(component.default.name).toBe('UserProfileTransactionList')
|
||||
})
|
||||
})
|
||||
|
||||
describe('login', () => {
|
||||
it('loads the "Login" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/login').component()
|
||||
expect(component.default.name).toBe('login')
|
||||
})
|
||||
})
|
||||
|
||||
describe('thx', () => {
|
||||
const thx = routes.find((r) => r.path === '/thx/:comingFrom')
|
||||
|
||||
it('loads the "Thx" component', async () => {
|
||||
const component = await thx.component()
|
||||
expect(component.default.name).toBe('Thx')
|
||||
})
|
||||
|
||||
describe('beforeEnter', () => {
|
||||
const beforeEnter = thx.beforeEnter
|
||||
const next = jest.fn()
|
||||
|
||||
it('redirects to login when not coming from a valid page', () => {
|
||||
beforeEnter({}, { path: '' }, next)
|
||||
expect(next).toBeCalledWith({ path: '/login' })
|
||||
})
|
||||
|
||||
it('enters the page when coming from a valid page', () => {
|
||||
jest.resetAllMocks()
|
||||
beforeEnter({}, { path: '/password' }, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('password', () => {
|
||||
it('loads the "Password" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/password').component()
|
||||
expect(component.default.name).toBe('password')
|
||||
})
|
||||
})
|
||||
|
||||
describe('reset', () => {
|
||||
it('loads the "ResetPassword" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/reset/:optin').component()
|
||||
expect(component.default.name).toBe('ResetPassword')
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkEmail', () => {
|
||||
it('loads the "CheckEmail" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/checkEmail/:optin').component()
|
||||
expect(component.default.name).toBe('CheckEmail')
|
||||
})
|
||||
})
|
||||
|
||||
describe('not found page', () => {
|
||||
it('renders the "NotFound" component', async () => {
|
||||
expect(routes.find((r) => r.path === '*').component).toEqual(NotFound)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -67,6 +67,7 @@ export const store = new Vuex.Store({
|
||||
username: '',
|
||||
description: '',
|
||||
token: null,
|
||||
coinanimation: true,
|
||||
newsletterState: null,
|
||||
},
|
||||
getters: {},
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { GdtEntryType } from '../../../graphql/enums'
|
||||
import GdtTransactionList from './GdtTransactionList'
|
||||
|
||||
const localVue = global.localVue
|
||||
@ -14,7 +15,7 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
factor: 17,
|
||||
comment: '',
|
||||
date: '2021-05-02T17:20:11+00:00',
|
||||
gdtEntryType: 1,
|
||||
gdtEntryType: GdtEntryType.FORM,
|
||||
},
|
||||
{
|
||||
amount: 1810,
|
||||
@ -22,7 +23,7 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
factor: 0.2,
|
||||
comment: 'Dezember 20',
|
||||
date: '2020-12-31T12:00:00+00:00',
|
||||
gdtEntryType: 7,
|
||||
gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR,
|
||||
},
|
||||
{
|
||||
amount: 100,
|
||||
@ -30,7 +31,7 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
factor: 17,
|
||||
comment: '',
|
||||
date: '2020-05-07T17:00:00+00:00',
|
||||
gdtEntryType: 1,
|
||||
gdtEntryType: GdtEntryType.FORM,
|
||||
},
|
||||
{
|
||||
amount: 100,
|
||||
@ -38,7 +39,7 @@ const apolloMock = jest.fn().mockResolvedValue({
|
||||
factor: 22,
|
||||
comment: '',
|
||||
date: '2020-04-10T13:28:00+00:00',
|
||||
gdtEntryType: 4,
|
||||
gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserCardCoinAnimation from './UserCard_CoinAnimation'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn()
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
describe('UserCard_CoinAnimation', () => {
|
||||
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(UserCardCoinAnimation, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#formusercoinanimation').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has an edit BFormCheckbox switch', () => {
|
||||
expect(wrapper.find('.Test-BFormCheckbox').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<b-card
|
||||
id="formusercoinanimation"
|
||||
class="bg-transparent"
|
||||
style="background-color: #ebebeba3 !important; border-radius: 0px"
|
||||
>
|
||||
<div>
|
||||
<b-row class="mb-3">
|
||||
<b-col class="mb-2 col-12">
|
||||
<small>
|
||||
<b>{{ $t('settings.coinanimation.coinanimation') }}</b>
|
||||
</small>
|
||||
</b-col>
|
||||
<b-col class="col-12">
|
||||
<b-form-checkbox
|
||||
class="Test-BFormCheckbox"
|
||||
v-model="CoinAnimationStatus"
|
||||
name="check-button"
|
||||
switch
|
||||
@change="onSubmit"
|
||||
>
|
||||
{{
|
||||
CoinAnimationStatus
|
||||
? $t('settings.coinanimation.True')
|
||||
: $t('settings.coinanimation.False')
|
||||
}}
|
||||
</b-form-checkbox>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import { updateUserInfos } from '../../../graphql/mutations'
|
||||
export default {
|
||||
name: 'FormUserCoinAnimation',
|
||||
data() {
|
||||
return {
|
||||
CoinAnimationStatus: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.CoinAnimationStatus = this.$store.state.coinanimation /* existiert noch nicht im store */
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateUserInfos,
|
||||
variables: {
|
||||
coinanimation: this.CoinAnimationStatus,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.state.coinanimation = this.CoinAnimationStatus
|
||||
this.$toasted.success(
|
||||
this.CoinAnimationStatus
|
||||
? this.$t('settings.coinanimation.True')
|
||||
: this.$t('settings.coinanimation.False'),
|
||||
)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toasted.error(error.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -8,6 +8,8 @@
|
||||
<form-user-language />
|
||||
<hr />
|
||||
<form-user-newsletter />
|
||||
<hr />
|
||||
<form-user-coin-animation />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -16,14 +18,17 @@ import FormUserData from './UserProfile/UserCard_FormUserData.vue'
|
||||
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
|
||||
import FormUserLanguage from './UserProfile/UserCard_Language.vue'
|
||||
import FormUserNewsletter from './UserProfile/UserCard_Newsletter.vue'
|
||||
import FormUserCoinAnimation from './UserProfile/UserCard_CoinAnimation.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserProfile',
|
||||
components: {
|
||||
UserCard,
|
||||
FormUserData,
|
||||
FormUserPasswort,
|
||||
FormUserLanguage,
|
||||
FormUserNewsletter,
|
||||
FormUserCoinAnimation,
|
||||
},
|
||||
props: {
|
||||
balance: { type: Number, default: 0 },
|
||||
|
||||
@ -35,6 +35,9 @@ module.exports = {
|
||||
// 'process.env.PORT': JSON.stringify(process.env.PORT),
|
||||
}),
|
||||
],
|
||||
infrastructureLogging: {
|
||||
level: 'warn', // 'none' | 'error' | 'warn' | 'info' | 'log' | 'verbose'
|
||||
},
|
||||
},
|
||||
css: {
|
||||
// Enable CSS source maps.
|
||||
|
||||
@ -15,7 +15,6 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
|
||||
{
|
||||
/*
|
||||
'session_id' => $session_id,
|
||||
'email' => $email,
|
||||
'update' => ['User.first_name' => 'first_name', 'User.last_name' => 'last_name', 'User.disabled' => 0|1, 'User.language' => 'de']
|
||||
*/
|
||||
// incoming
|
||||
@ -34,8 +33,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
|
||||
/// not available for the given type.
|
||||
/// Throws InvalidAccessException if Var is empty.
|
||||
try {
|
||||
paramJsonObject->get("email").convert(email);
|
||||
|
||||
|
||||
auto session_id_obj = paramJsonObject->get("session_id");
|
||||
if (!session_id_obj.isEmpty()) {
|
||||
session_id_obj.convert(session_id);
|
||||
@ -66,10 +64,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
|
||||
}
|
||||
auto user = mSession->getNewUser();
|
||||
auto user_model = user->getModel();
|
||||
if (user_model->getEmail() != email) {
|
||||
return customStateError("not same", "email don't belong to logged in user");
|
||||
}
|
||||
|
||||
|
||||
Poco::JSON::Object* result = new Poco::JSON::Object;
|
||||
result->set("state", "success");
|
||||
Poco::JSON::Array jsonErrorsArray;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user