Merge branch 'master' into 585-Coin-animation-on-and-off-switchable

This commit is contained in:
Einhornimmond 2021-10-01 11:39:48 +02:00
commit 161fae9ba2
97 changed files with 1801 additions and 5195 deletions

1
.env.local.dist Normal file
View File

@ -0,0 +1 @@
// for local .env entries

1
.env.shell Normal file
View File

@ -0,0 +1 @@
BUILD_COMMIT="$(git rev-parse HEAD)"

View File

@ -1,6 +1,5 @@
name: gradido test CI
on: [push]
jobs:
@ -417,8 +416,6 @@ jobs:
env:
MARIADB_ALLOW_EMPTY_PASSWORD: 1
MARIADB_USER: root
# ports:
# - 3306:3306
options: --health-cmd="mysqladmin ping"
--health-interval=5s
--health-timeout=5s
@ -427,11 +424,14 @@ jobs:
- name: get mariadb container id
run: echo "::set-output name=id::$(docker container ls | grep mariadb | awk '{ print $1 }')"
id: mariadb_container
- name: show networks
run: echo "$(docker network ls)"
- name: get automatic created network
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
- name: get database migration container id
run: echo "::set-output name=id::$(docker container ls | grep database | awk '{ print $1 }')"
id: database_container
- name: Start Login-Server
run: docker run --network ${{ steps.network.outputs.id }} --name=login-server -d gradido/login_server:with-config
- name: get login-server container id
@ -452,12 +452,13 @@ jobs:
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/community_server.tar
# for debugging login-server
- name: check login-server
run: docker logs ${{ steps.login_server_container.outputs.id }}
- name: check mariadb
run: docker logs ${{ steps.mariadb_container.outputs.id }}
- name: check migration
run: docker logs ${{ steps.database_container.outputs.id }}
##########################################################################
# UNIT TESTS BACKEND COMMUNITY-SERVER #######################################
##########################################################################

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ messages.pot
.skeema
nbproject
.metadata
/.env

4563
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,8 @@
"jsonwebtoken": "^8.5.1",
"mysql2": "^2.3.0",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.1.1"
"type-graphql": "^1.1.1",
"typeorm": "^0.2.37"
},
"devDependencies": {
"@types/express": "^4.17.12",

View File

@ -1,18 +0,0 @@
import { createConnection, Connection } from 'mysql2/promise'
import CONFIG from '../config'
const connection = async (): Promise<Connection> => {
const con = await createConnection({
host: CONFIG.DB_HOST,
port: CONFIG.DB_PORT,
user: CONFIG.DB_USER,
password: CONFIG.DB_PASSWORD,
database: CONFIG.DB_DATABASE,
})
await con.connect()
return con
}
export default connection

View File

@ -9,7 +9,7 @@ export class TransactionListInput {
items: number
@Field(() => String)
order: string
order: 'ASC' | 'DESC'
}
@ArgsType()

View File

@ -1,29 +1,43 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql'
import { Transaction } from '../../typeorm/entity/Transaction'
@ObjectType()
export class Decay {
constructor(json: any) {
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.decayStartBlock = json.decay_start_block
if (json) {
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.decayStartBlock = json.decay_start_block
}
}
static async getDecayStartBlock(): Promise<Transaction | undefined> {
if (!this.decayStartBlockTransaction) {
this.decayStartBlockTransaction = await Transaction.getDecayStartBlock()
}
return this.decayStartBlockTransaction
}
@Field(() => Number)
balance: number
// timestamp in seconds
@Field(() => Int, { nullable: true })
decayStart?: number
decayStart: string
// timestamp in seconds
@Field(() => Int, { nullable: true })
decayEnd?: number
decayEnd: string
@Field(() => String, { nullable: true })
decayDuration?: string
decayDuration?: number
@Field(() => Int, { nullable: true })
decayStartBlock?: number
decayStartBlock?: string
static decayStartBlockTransaction: Transaction | undefined
}

View File

@ -10,18 +10,11 @@ import { Decay } from './Decay'
@ObjectType()
export class Transaction {
constructor(json: any) {
this.type = json.type
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.memo = json.memo
this.transactionId = json.transaction_id
this.name = json.name
this.email = json.email
this.date = json.date
this.decay = json.decay ? new Decay(json.decay) : undefined
constructor() {
this.type = ''
this.balance = 0
this.totalBalance = 0
this.memo = ''
}
@Field(() => String)
@ -30,14 +23,17 @@ export class Transaction {
@Field(() => Number)
balance: number
@Field({ nullable: true })
decayStart?: number
@Field(() => Number)
totalBalance: number
@Field({ nullable: true })
decayEnd?: number
decayStart?: string
@Field({ nullable: true })
decayDuration?: string
decayEnd?: string
@Field({ nullable: true })
decayDuration?: number
@Field(() => String)
memo: string
@ -60,15 +56,12 @@ export class Transaction {
@ObjectType()
export class TransactionList {
constructor(json: any) {
this.gdtSum = Number(json.gdtSum)
this.count = json.count
this.balance = Number(json.balance)
this.decay = Number(json.decay)
this.decayDate = json.decay_date
this.transactions = json.transactions.map((el: any) => {
return new Transaction(el)
})
constructor() {
this.gdtSum = 0
this.count = 0
this.balance = 0
this.decay = 0
this.decayDate = ''
}
@Field(() => Number)

View File

@ -16,6 +16,7 @@ export class User {
this.lastName = json.last_name
this.username = json.username
this.description = json.description
this.pubkey = json.public_hex
this.language = json.language
this.publisherId = json.publisher_id
}
@ -35,6 +36,8 @@ export class User {
@Field(() => String)
description: string
@Field(() => String)
pubkey: string
/*
@Field(() => String)
pubkey: string

View File

@ -5,14 +5,41 @@ import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config'
import { Balance } from '../models/Balance'
import { apiGet } from '../../apis/HttpRequest'
import { User as dbUser } from '../../typeorm/entity/User'
import { Balance as dbBalance } from '../../typeorm/entity/Balance'
import { calculateDecay } from '../../util/decay'
import { roundFloorFrom4 } from '../../util/round'
@Resolver()
export class BalanceResolver {
@Authorized()
@Query(() => Balance)
async balance(@Ctx() context: any): Promise<Balance> {
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + context.sessionId)
// get public key for current logged in user
const result = await apiGet(CONFIG.LOGIN_API_URL + 'login?session_id=' + context.sessionId)
if (!result.success) throw new Error(result.data)
return new Balance(result.data)
// load user and balance
const userEntity = await dbUser.findByPubkeyHex(result.data.user.public_hex)
const balanceEntity = await dbBalance.findByUser(userEntity.id)
let balance: Balance
const now = new Date()
if (balanceEntity) {
balance = new Balance({
balance: roundFloorFrom4(balanceEntity.amount),
decay: roundFloorFrom4(
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
),
decay_date: now.toString(),
})
} else {
balance = new Balance({
balance: 0,
decay: 0,
decay_date: now.toString(),
})
}
return balance
}
}

View File

@ -6,6 +6,7 @@ import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
import { apiGet } from '../../apis/HttpRequest'
import { User as dbUser } from '../../typeorm/entity/User'
@Resolver()
export class GdtResolver {
@ -17,13 +18,20 @@ export class GdtResolver {
{ currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput,
@Ctx() context: any,
): Promise<GdtEntryList> {
const result = await apiGet(
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${context.sessionId}`,
// get public key for current logged in user
const result = await apiGet(CONFIG.LOGIN_API_URL + 'login?session_id=' + context.sessionId)
if (!result.success) throw new Error(result.data)
// load user
const userEntity = await dbUser.findByPubkeyHex(result.data.user.public_hex)
const resultGDT = await apiGet(
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.email}/${currentPage}/${pageSize}/${order}`,
)
if (!result.success) {
if (!resultGDT.success) {
throw new Error(result.data)
}
return new GdtEntryList(result.data)
return new GdtEntryList(resultGDT.data)
}
}

View File

@ -1,11 +1,16 @@
/* 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 { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
import { apiGet, apiPost } from '../../apis/HttpRequest'
import { User as dbUser } from '../../typeorm/entity/User'
import { Balance as dbBalance } from '../../typeorm/entity/Balance'
import listTransactions from './listTransactions'
import { roundFloorFrom4 } from '../../util/round'
import { calculateDecay } from '../../util/decay'
@Resolver()
export class TransactionResolver {
@ -15,15 +20,38 @@ export class TransactionResolver {
@Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
@Ctx() context: any,
): Promise<TransactionList> {
const result = await apiGet(
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${context.sessionId}`,
)
// get public key for current logged in user
const result = await apiGet(CONFIG.LOGIN_API_URL + 'login?session_id=' + context.sessionId)
if (!result.success) throw new Error(result.data)
return new TransactionList(result.data)
// load user
const userEntity = await dbUser.findByPubkeyHex(result.data.user.public_hex)
const transactions = await listTransactions(firstPage, items, order, userEntity)
// get gdt sum
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
email: userEntity.email,
})
if (!resultGDTSum.success) throw new Error(resultGDTSum.data)
transactions.gdtSum = resultGDTSum.data.sum
// get balance
const balanceEntity = await dbBalance.findByUser(userEntity.id)
if (balanceEntity) {
const now = new Date()
transactions.balance = roundFloorFrom4(balanceEntity.amount)
transactions.decay = roundFloorFrom4(
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
)
transactions.decayDate = now.toString()
}
return transactions
}
@Authorized()
@Query(() => String)
@Mutation(() => String)
async sendCoins(
@Args() { email, amount, memo }: TransactionSendArgs,
@Ctx() context: any,

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware } from 'type-graphql'
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
@ -66,8 +66,8 @@ export class UserResolver {
return 'success'
}
@Query(() => String)
async create(
@Mutation(() => String)
async createUser(
@Args() { email, firstName, lastName, password, language }: CreateUserArgs,
): Promise<string> {
const payload = {
@ -104,7 +104,7 @@ export class UserResolver {
return new SendPasswordResetEmailResponse(response.data)
}
@Query(() => String)
@Mutation(() => String)
async resetPassword(
@Args()
{ sessionId, email, password }: ChangePasswordArgs,
@ -122,7 +122,7 @@ export class UserResolver {
}
@Authorized()
@Query(() => UpdateUserInfosResponse)
@Mutation(() => UpdateUserInfosResponse)
async updateUserInfos(
@Args()
{

View File

@ -0,0 +1,19 @@
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

View File

@ -0,0 +1,194 @@
import { User as dbUser } from '../../typeorm/entity/User'
import { TransactionList, Transaction } from '../models/Transaction'
import { UserTransaction as dbUserTransaction } from '../../typeorm/entity/UserTransaction'
import { Transaction as dbTransaction } from '../../typeorm/entity/Transaction'
import { Decay } from '../models/Decay'
import { calculateDecayWithInterval } from '../../util/decay'
import { roundFloorFrom4 } from '../../util/round'
async function calculateAndAddDecayTransactions(
userTransactions: dbUserTransaction[],
user: dbUser,
decay: boolean,
skipFirstTransaction: boolean,
): Promise<Transaction[]> {
const finalTransactions: Transaction[] = []
const transactionIds: number[] = []
const involvedUserIds: number[] = []
userTransactions.forEach((userTransaction: dbUserTransaction) => {
transactionIds.push(userTransaction.transactionId)
})
const transactions = await dbTransaction
.createQueryBuilder('transaction')
.where('transaction.id IN (:...transactions)', { transactions: transactionIds })
.leftJoinAndSelect(
'transaction.transactionSendCoin',
'transactionSendCoin',
// 'transactionSendCoin.transaction_id = transaction.id',
)
.leftJoinAndSelect(
'transaction.transactionCreation',
'transactionCreation',
// 'transactionSendCoin.transaction_id = transaction.id',
)
.getMany()
const transactionIndiced: dbTransaction[] = []
transactions.forEach((transaction: dbTransaction) => {
transactionIndiced[transaction.id] = transaction
if (transaction.transactionTypeId === 2) {
involvedUserIds.push(transaction.transactionSendCoin.userId)
involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId)
}
})
// remove duplicates
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
const involvedUsersUnique = involvedUserIds.filter((v, i, a) => a.indexOf(v) === i)
const userIndiced = await dbUser.getUsersIndiced(involvedUsersUnique)
const decayStartTransaction = await Decay.getDecayStartBlock()
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
const transaction = transactionIndiced[userTransaction.transactionId]
const finalTransaction = new Transaction()
finalTransaction.transactionId = transaction.id
finalTransaction.date = transaction.received.toISOString()
finalTransaction.memo = transaction.memo
finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance)
const prev = i > 0 ? userTransactions[i - 1] : null
if (prev && prev.balance > 0) {
const current = userTransaction
const decay = await calculateDecayWithInterval(
prev.balance,
prev.balanceDate,
current.balanceDate,
)
const balance = prev.balance - decay.balance
if (balance) {
finalTransaction.decay = decay
finalTransaction.decay.balance = roundFloorFrom4(balance)
if (
decayStartTransaction &&
prev.transactionId < decayStartTransaction.id &&
current.transactionId > decayStartTransaction.id
) {
finalTransaction.decay.decayStartBlock = (
decayStartTransaction.received.getTime() / 1000
).toString()
}
}
}
// sender or receiver when user has sended money
// group name if creation
// type: gesendet / empfangen / geschöpft
// transaktion nr / id
// date
// balance
if (userTransaction.transactionTypeId === 1) {
// creation
const creation = transaction.transactionCreation
finalTransaction.name = 'Gradido Akademie'
finalTransaction.type = 'creation'
// finalTransaction.targetDate = creation.targetDate
finalTransaction.balance = roundFloorFrom4(creation.amount)
} else if (userTransaction.transactionTypeId === 2) {
// send coin
const sendCoin = transaction.transactionSendCoin
let otherUser: dbUser | undefined
finalTransaction.balance = roundFloorFrom4(sendCoin.amount)
if (sendCoin.userId === user.id) {
finalTransaction.type = 'send'
otherUser = userIndiced[sendCoin.recipiantUserId]
// finalTransaction.pubkey = sendCoin.recipiantPublic
} else if (sendCoin.recipiantUserId === user.id) {
finalTransaction.type = 'receive'
otherUser = userIndiced[sendCoin.userId]
// finalTransaction.pubkey = sendCoin.senderPublic
} else {
throw new Error('invalid transaction')
}
if (otherUser) {
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
finalTransaction.email = otherUser.email
}
}
if (i > 0 || !skipFirstTransaction) {
finalTransactions.push(finalTransaction)
}
if (i === userTransactions.length - 1 && decay) {
const now = new Date()
const decay = await calculateDecayWithInterval(
userTransaction.balance,
userTransaction.balanceDate,
now.getTime(),
)
const balance = userTransaction.balance - decay.balance
if (balance) {
const decayTransaction = new Transaction()
decayTransaction.type = 'decay'
decayTransaction.balance = roundFloorFrom4(balance)
decayTransaction.decayDuration = decay.decayDuration
decayTransaction.decayStart = decay.decayStart
decayTransaction.decayEnd = decay.decayEnd
finalTransactions.push(decayTransaction)
}
}
}
return finalTransactions
}
export default async function listTransactions(
firstPage: number,
items: number,
order: 'ASC' | 'DESC',
user: dbUser,
): Promise<TransactionList> {
let limit = items
let offset = 0
let skipFirstTransaction = false
if (firstPage > 1) {
offset = (firstPage - 1) * items - 1
limit++
}
if (offset && order === 'ASC') {
offset--
}
let [userTransactions, userTransactionsCount] = await dbUserTransaction.findByUserPaged(
user.id,
limit,
offset,
order,
)
skipFirstTransaction = userTransactionsCount > offset + limit
const decay = !(firstPage > 1)
let transactions: Transaction[] = []
if (userTransactions.length) {
if (order === 'DESC') {
userTransactions = userTransactions.reverse()
}
transactions = await calculateAndAddDecayTransactions(
userTransactions,
user,
decay,
skipFirstTransaction,
)
if (order === 'DESC') {
transactions = transactions.reverse()
}
}
const transactionList = new TransactionList()
transactionList.count = userTransactionsCount
transactionList.transactions = transactions
return transactionList
}

View File

@ -0,0 +1,14 @@
import { GraphQLSchema } from 'graphql'
import { buildSchema } from 'type-graphql'
import resolvers from './resolvers'
import { isAuthorized } from '../auth/auth'
const schema = async (): Promise<GraphQLSchema> => {
return buildSchema({
resolvers: resolvers(),
authChecker: isAuthorized,
})
}
export default schema

View File

@ -2,93 +2,58 @@
import 'reflect-metadata'
import express from 'express'
import cors from 'cors'
import { buildSchema } from 'type-graphql'
import { ApolloServer } from 'apollo-server-express'
import { RowDataPacket } from 'mysql2/promise'
import connection from './database/connection'
// config
import CONFIG from './config'
// TODO move to extern
import { UserResolver } from './graphql/resolvers/UserResolver'
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
import { GdtResolver } from './graphql/resolvers/GdtResolver'
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
import { KlicktippResolver } from './graphql/resolvers/KlicktippResolver'
// database
import connection from './typeorm/connection'
import getDBVersion from './typeorm/getDBVersion'
import { isAuthorized } from './auth/auth'
// server
import cors from './server/cors'
import context from './server/context'
import plugins from './server/plugins'
// graphql
import schema from './graphql/schema'
// TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
const DB_VERSION = '0001-init_db'
const context = (args: any) => {
const authorization = args.req.headers.authorization
let token = null
if (authorization) {
token = authorization.replace(/^Bearer /, '')
}
const context = {
token,
setHeaders: [],
}
return context
}
async function main() {
// check for correct database version
// open mysql connection
const con = await connection()
const [rows] = await con.query(`SELECT * FROM migrations ORDER BY version DESC LIMIT 1;`)
if (
(<RowDataPacket>rows).length === 0 ||
!(<RowDataPacket>rows)[0].fileName ||
(<RowDataPacket>rows)[0].fileName.indexOf(DB_VERSION) === -1
) {
throw new Error(`Wrong database version - the backend requires '${DB_VERSION}'`)
if (!con || !con.isConnected) {
throw new Error(`Couldn't open connection to database`)
}
// const connection = await createConnection()
const schema = await buildSchema({
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver, KlicktippResolver],
authChecker: isAuthorized,
})
// Graphiql interface
let playground = false
if (CONFIG.GRAPHIQL) {
playground = true
// check for correct database version
const dbVersion = await getDBVersion()
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
throw new Error(
`Wrong database version - the backend requires '${DB_VERSION}' but found '${
dbVersion || 'None'
}'`,
)
}
// Express Server
const server = express()
const corsOptions = {
origin: '*',
exposedHeaders: ['token'],
}
server.use(cors(corsOptions))
const plugins = [
{
requestDidStart() {
return {
willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
requestContext.response.http.headers.append(key, value)
})
return requestContext
},
}
},
},
]
// cors
server.use(cors)
// Apollo Server
const apollo = new ApolloServer({ schema, playground, context, plugins })
const apollo = new ApolloServer({
schema: await schema(),
playground: CONFIG.GRAPHIQL,
context,
plugins,
})
apollo.applyMiddleware({ app: server })
// Start Server

View File

@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken'
import CONFIG from '../config/'
export default (token: string): any => {
if (!token) return null
if (!token) return new Error('401 Unauthorized')
let sessionId = null
try {
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
@ -15,6 +15,6 @@ export default (token: string): any => {
sessionId,
}
} catch (err) {
return null
throw new Error('403.13 - Client certificate revoked')
}
}

View File

@ -0,0 +1,14 @@
const context = (args: any) => {
const authorization = args.req.headers.authorization
let token = null
if (authorization) {
token = authorization.replace(/^Bearer /, '')
}
const context = {
token,
setHeaders: [],
}
return context
}
export default context

View File

@ -0,0 +1,8 @@
import cors from 'cors'
const corsOptions = {
origin: '*',
exposedHeaders: ['token'],
}
export default cors(corsOptions)

View File

@ -0,0 +1,17 @@
const plugins = [
{
requestDidStart() {
return {
willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
requestContext.response.http.headers.append(key, value)
})
return requestContext
},
}
},
},
]
export default plugins

View File

@ -0,0 +1,24 @@
import { createConnection, Connection } from 'typeorm'
import CONFIG from '../config'
import path from 'path'
const connection = async (): Promise<Connection | null> => {
let con = null
try {
con = await createConnection({
name: 'default',
type: 'mysql',
host: CONFIG.DB_HOST,
port: CONFIG.DB_PORT,
username: CONFIG.DB_USER,
password: CONFIG.DB_PASSWORD,
database: CONFIG.DB_DATABASE,
entities: [path.join(__dirname, 'entity', '*.{ts,js}')],
synchronize: false,
})
} catch (error) {}
return con
}
export default connection

View File

@ -0,0 +1,23 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('state_balances')
export class Balance extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'state_user_id' })
userId: number
@Column({ type: 'datetime' })
modified: Date
@Column({ name: 'record_date', type: 'datetime' })
recordDate: Date
@Column({ type: 'bigint' })
amount: number
static findByUser(userId: number): Promise<Balance | undefined> {
return this.createQueryBuilder('balance').where('balance.userId = :userId', { userId }).getOne()
}
}

View File

@ -0,0 +1,25 @@
/* import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { User } from "./User"
@Entity()
export class Group {
@PrimaryGeneratedColumn()
id: number;
@Column()
alias: string;
@Column()
name: string;
@Column()
url: string;
@Column()
description: string;
@OneToMany(type => User, user => user.group)
users: User[];
} */

View File

@ -0,0 +1,13 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('migrations')
export class Migration extends BaseEntity {
@PrimaryGeneratedColumn() // This is actually not a primary column
version: number
@Column({ length: 256, nullable: true, default: null })
fileName: string
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
date: Date
}

View File

@ -0,0 +1,45 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { TransactionCreation } from './TransactionCreation'
import { TransactionSendCoin } from './TransactionSendCoin'
@Entity('transactions')
export class Transaction extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'transaction_type_id' })
transactionTypeId: number
@Column({ name: 'tx_hash', type: 'binary', length: 48 })
txHash: Buffer
@Column()
memo: string
@Column({ type: 'timestamp' })
received: Date
@Column({ name: 'blockchain_type_id' })
blockchainTypeId: number
@OneToOne(() => TransactionSendCoin, (transactionSendCoin) => transactionSendCoin.transaction)
transactionSendCoin: TransactionSendCoin
@OneToOne(() => TransactionCreation, (transactionCreation) => transactionCreation.transaction)
transactionCreation: TransactionCreation
static async findByTransactionTypeId(transactionTypeId: number): Promise<Transaction[]> {
return this.createQueryBuilder('transaction')
.where('transaction.transactionTypeId = :transactionTypeId', {
transactionTypeId: transactionTypeId,
})
.getMany()
}
static async getDecayStartBlock(): Promise<Transaction | undefined> {
return this.createQueryBuilder('transaction')
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: 9 })
.orderBy('received', 'ASC')
.getOne()
}
}

View File

@ -0,0 +1,32 @@
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
Timestamp,
OneToOne,
JoinColumn,
} from 'typeorm'
import { Transaction } from './Transaction'
@Entity('transaction_creations')
export class TransactionCreation extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'transaction_id' })
transactionId: number
@Column({ name: 'state_user_id' })
userId: number
@Column()
amount: number
@Column({ name: 'target_date', type: 'timestamp' })
targetDate: Timestamp
@OneToOne(() => Transaction)
@JoinColumn({ name: 'transaction_id' })
transaction: Transaction
}

View File

@ -0,0 +1,30 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { Transaction } from './Transaction'
@Entity('transaction_send_coins')
export class TransactionSendCoin extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'transaction_id' })
transactionId: number
@Column({ name: 'sender_public_key', type: 'binary', length: 32 })
senderPublic: Buffer
@Column({ name: 'state_user_id' })
userId: number
@Column({ name: 'receiver_public_key', type: 'binary', length: 32 })
recipiantPublic: Buffer
@Column({ name: 'receiver_user_id' })
recipiantUserId: number
@Column()
amount: number
@OneToOne(() => Transaction)
@JoinColumn({ name: 'transaction_id' })
transaction: Transaction
}

View File

@ -0,0 +1,47 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
// import { Group } from "./Group"
@Entity('state_users')
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
// @ManyToOne(type => Group, group => group.users)
// group: Group;
@Column({ type: 'binary', length: 32, name: 'public_key' })
pubkey: Buffer
@Column()
email: string
@Column({ name: 'first_name' })
firstName: string
@Column({ name: 'last_name' })
lastName: string
@Column()
username: string
@Column()
disabled: boolean
static findByPubkeyHex(pubkeyHex: string): Promise<User> {
return this.createQueryBuilder('user')
.where('hex(user.pubkey) = :pubkeyHex', { pubkeyHex })
.getOneOrFail()
}
static async getUsersIndiced(userIds: number[]): Promise<User[]> {
const users = await this.createQueryBuilder('user')
.select(['user.id', 'user.firstName', 'user.lastName', 'user.email'])
.where('user.id IN (:...users)', { users: userIds })
.getMany()
const usersIndiced: User[] = []
users.forEach((value) => {
usersIndiced[value.id] = value
})
return usersIndiced
}
}

View File

@ -0,0 +1,36 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('state_user_transactions')
export class UserTransaction extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'state_user_id' })
userId: number
@Column({ name: 'transaction_id' })
transactionId: number
@Column({ name: 'transaction_type_id' })
transactionTypeId: number
@Column({ name: 'balance', type: 'bigint' })
balance: number
@Column({ name: 'balance_date', type: 'timestamp' })
balanceDate: Date
static findByUserPaged(
userId: number,
limit: number,
offset: number,
order: 'ASC' | 'DESC',
): Promise<[UserTransaction[], number]> {
return this.createQueryBuilder('userTransaction')
.where('userTransaction.userId = :userId', { userId })
.orderBy('userTransaction.balanceDate', order)
.limit(limit)
.offset(offset)
.getManyAndCount()
}
}

View File

@ -0,0 +1,15 @@
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' } })
return dbVersion ? dbVersion.fileName : null
} catch (error) {
return null
}
}
export default getDBVersion

55
backend/src/util/decay.ts Normal file
View File

@ -0,0 +1,55 @@
import { Decay } from '../graphql/models/Decay'
function decayFormula(amount: number, durationInSeconds: number): number {
return amount * Math.pow(0.99999997802044727, durationInSeconds)
}
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
// load decay start block
const decayStartBlock = await Decay.getDecayStartBlock()
// if decay hasn't started yet we return input amount
if (!decayStartBlock) return amount
const decayDuration = (to.getTime() - from.getTime()) / 1000
return decayFormula(amount, decayDuration)
}
async function calculateDecayWithInterval(
amount: number,
from: number | Date,
to: number | Date,
): Promise<Decay> {
const decayStartBlock = await Decay.getDecayStartBlock()
const result = new Decay(undefined)
result.balance = amount
const fromMillis = typeof from === 'number' ? from : from.getTime()
const toMillis = typeof to === 'number' ? to : to.getTime()
result.decayStart = (fromMillis / 1000).toString()
result.decayEnd = (toMillis / 1000).toString()
// (amount, from.getTime(), to.getTime())
// if no decay start block exist or decay startet after end date
if (!decayStartBlock || decayStartBlock.received.getTime() > toMillis) {
return result
}
const decayStartBlockMillis = decayStartBlock.received.getTime()
// if decay start date is before start date we calculate decay for full duration
if (decayStartBlockMillis < fromMillis) {
result.decayDuration = toMillis - fromMillis
}
// if decay start in between start date and end date we caculcate decay from decay start time to end date
else {
result.decayDuration = toMillis - decayStartBlockMillis
result.decayStart = (decayStartBlockMillis / 1000).toString()
}
// js use timestamp in milliseconds but we calculate with seconds
result.decayDuration /= 1000
result.balance = decayFormula(amount, result.decayDuration)
return result
}
export { calculateDecay, calculateDecayWithInterval }

17
backend/src/util/round.ts Normal file
View File

@ -0,0 +1,17 @@
function roundCeilFrom4(decimal: number): number {
return Math.ceil(decimal / 100) / 100
}
function roundFloorFrom4(decimal: number): number {
return Math.floor(decimal / 100) / 100
}
function roundCeilFrom2(decimal: number): number {
return Math.ceil(decimal / 100)
}
function roundFloorFrom2(decimal: number): number {
return Math.floor(decimal / 100)
}
export { roundCeilFrom4, roundFloorFrom4, roundCeilFrom2, roundFloorFrom2 }

View File

@ -166,6 +166,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sqltools/formatter@^1.2.2":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
"@szmarczak/http-timer@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
@ -396,6 +401,11 @@
dependencies:
"@types/node" "*"
"@types/zen-observable@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==
"@typescript-eslint/eslint-plugin@^4.28.0":
version "4.28.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz#1a66f03b264844387beb7dc85e1f1d403bd1803f"
@ -527,6 +537,11 @@ ansi-colors@^4.1.1:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
@ -537,6 +552,11 @@ ansi-regex@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@ -551,6 +571,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@ -709,6 +734,11 @@ apollo-utilities@^1.0.1, apollo-utilities@^1.3.0:
ts-invariant "^0.4.0"
tslib "^1.10.0"
app-root-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad"
integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
@ -721,6 +751,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@ -780,6 +815,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@ -840,6 +880,14 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
busboy@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
@ -883,6 +931,17 @@ camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chalk@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
dependencies:
ansi-styles "^2.2.1"
escape-string-regexp "^1.0.2"
has-ansi "^2.0.0"
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -908,6 +967,14 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@^3.2.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
@ -942,6 +1009,27 @@ cli-boxes@^2.2.0:
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
cli-highlight@^2.1.11:
version "2.1.11"
resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf"
integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==
dependencies:
chalk "^4.0.0"
highlight.js "^10.7.1"
mz "^2.4.0"
parse5 "^5.1.1"
parse5-htmlparser2-tree-adapter "^6.0.0"
yargs "^16.0.0"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@ -1169,6 +1257,11 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -1253,6 +1346,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-goat@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
@ -1263,7 +1361,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@^1.0.5:
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@ -1557,6 +1655,11 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
figlet@^1.1.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.2.tgz#dda34ff233c9a48e36fcff6741aeb5bafe49b634"
integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -1658,6 +1761,11 @@ generate-function@^2.3.1:
dependencies:
is-property "^1.0.2"
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
@ -1794,6 +1902,13 @@ graphql@^15.5.1:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad"
integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
dependencies:
ansi-regex "^2.0.0"
has-bigints@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@ -1826,6 +1941,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
highlight.js@^10.7.1:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@ -1883,6 +2003,11 @@ iconv-lite@^0.6.2:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
@ -2115,6 +2240,13 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
@ -2394,6 +2526,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -2428,6 +2565,15 @@ mysql2@^2.3.0:
seq-queue "^0.0.5"
sqlstring "^2.3.2"
mz@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
named-placeholders@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8"
@ -2493,7 +2639,7 @@ normalize-url@^4.1.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
object-assign@^4:
object-assign@^4, object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -2608,6 +2754,11 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parent-require@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977"
integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@ -2616,6 +2767,23 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse5-htmlparser2-tree-adapter@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
dependencies:
parse5 "^6.0.1"
parse5@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
parse5@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parseurl@^1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@ -2830,6 +2998,11 @@ registry-url@^5.0.0:
dependencies:
rc "^1.2.8"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
@ -2894,6 +3067,11 @@ safe-buffer@^5.0.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver-diff@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
@ -3094,6 +3272,13 @@ string.prototype.trimstart@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@ -3134,6 +3319,11 @@ subscriptions-transport-ws@^0.9.19:
symbol-observable "^1.0.4"
ws "^5.2.0 || ^6.0.0 || ^7.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -3175,6 +3365,20 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.1"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
dependencies:
any-promise "^1.0.0"
to-readable-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
@ -3295,6 +3499,29 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typeorm@^0.2.37:
version "0.2.37"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.37.tgz#1a5e59216077640694d27c04c99ed3f968d15dc8"
integrity sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==
dependencies:
"@sqltools/formatter" "^1.2.2"
app-root-path "^3.0.0"
buffer "^6.0.3"
chalk "^4.1.0"
cli-highlight "^2.1.11"
debug "^4.3.1"
dotenv "^8.2.0"
glob "^7.1.6"
js-yaml "^4.0.0"
mkdirp "^1.0.4"
reflect-metadata "^0.1.13"
sha.js "^2.4.11"
tslib "^2.1.0"
xml2js "^0.4.23"
yargonaut "^1.1.4"
yargs "^17.0.1"
zen-observable-ts "^1.0.0"
typescript@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc"
@ -3441,6 +3668,15 @@ word-wrap@^1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@ -3466,6 +3702,19 @@ xdg-basedir@^4.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xml2js@^0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xss@^1.0.8:
version "1.0.9"
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.9.tgz#3ffd565571ff60d2e40db7f3b80b4677bec770d2"
@ -3474,6 +3723,11 @@ xss@^1.0.8:
commander "^2.20.3"
cssfilter "0.0.10"
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
@ -3484,6 +3738,46 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargonaut@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c"
integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==
dependencies:
chalk "^1.1.1"
figlet "^1.1.1"
parent-require "^1.0.0"
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs@^16.0.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.0.1:
version "17.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.0.tgz#ec529632b2cb9044f3927f4b45f9cc4ae2535653"
integrity sha512-UPeZv4h9Xv510ibpt5rdsUNzgD78nMa1rhxxCgvkKiq06hlKCEHJLiJ6Ub8zDg/wR6hedEI6ovnd2vCvJ4nusA==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
@ -3497,7 +3791,15 @@ zen-observable-ts@^0.8.21:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable@^0.8.0:
zen-observable-ts@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83"
integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==
dependencies:
"@types/zen-observable" "0.8.3"
zen-observable "0.8.15"
zen-observable@0.8.15, zen-observable@^0.8.0:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==

2
database/build/.env.dist Normal file
View File

@ -0,0 +1,2 @@
// For production you need to put your env file in here.
// Please copy the dist file from the root folder in here and rename it to .env

View File

@ -25,6 +25,12 @@ 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) {

View File

@ -2,7 +2,10 @@
# For that to work, node v12.19.0 needs to be installed with nvm for root
# or NPM_BIN Path and NVM_DIR must be adjusted
cd /var/www/html/gradido/frontend
cd /var/www/html/gradido
eval "echo \"$(cat .env.shell)\"" > .env
export BUILD_COMMIT="$(git rev-parse HEAD)"
cd frontend
NPM_BIN=/root/.nvm/versions/node/v12.19.0/bin/npm

View File

@ -13,7 +13,6 @@ services:
environment:
- NODE_ENV="development"
# - DEBUG=true
- NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/`
volumes:
# This makes sure the docker container has its own node modules.
# Therefore it is possible to have a different node version on the host machine
@ -140,11 +139,13 @@ services:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_run
target: skeema_dev_run
depends_on:
- mariadb
networks:
- internal-net
volumes:
- ./login_server/skeema/gradido_login:/skeema/gradido_login
volumes:
frontend_node_modules:

View File

@ -22,14 +22,13 @@ services:
# Envs used in Dockerfile
# - DOCKER_WORKDIR="/app"
# - PORT=3000
- BUILD_DATE
- BUILD_VERSION
- BUILD_COMMIT
# - BUILD_DATE="1970-01-01T00:00:00.00Z"
# - BUILD_VERSION="0.0.0.0"
# - BUILD_COMMIT="0000000"
- NODE_ENV="production"
# Application only envs
#- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp
#env_file:
# - ./frontend/.env
# env_file:
# - ./.env
# - ./frontend/.env
#########################################################
## MARIADB ##############################################
@ -157,6 +156,19 @@ services:
volumes:
- ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
#########################################################
## skeema for updating dbs if changes happend ###########
#########################################################
skeema:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_run
depends_on:
- mariadb
networks:
- internal-net
#########################################################
## GRADIDO NODE v1 ######################################
#########################################################

View File

@ -0,0 +1,103 @@
<mxfile host="65bd71144e">
<diagram id="ZvjvITeOyjQP4YKfGS0G" name="Page-1">
<mxGraphModel dx="923" dy="562" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="29" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="660" y="135" width="120" height="215" as="geometry"/>
</mxCell>
<mxCell id="28" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="540" y="135" width="120" height="215" as="geometry"/>
</mxCell>
<mxCell id="27" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="420" y="135" width="120" height="215" as="geometry"/>
</mxCell>
<mxCell id="26" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="300" y="135" width="120" height="215" as="geometry"/>
</mxCell>
<mxCell id="25" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fa6800;strokeColor=#C73500;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="180" y="135" width="120" height="215" as="geometry"/>
</mxCell>
<mxCell id="24" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e51400;strokeColor=#B20000;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="70" y="135" width="110" height="215" as="geometry"/>
</mxCell>
<mxCell id="5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fillColor=#008a00;strokeColor=#005700;jumpSize=6;endSize=6;startSize=6;strokeWidth=3;" edge="1" parent="1" source="2" target="3">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#008a00;strokeColor=#005700;strokeWidth=3;" edge="1" parent="1" source="2" target="4">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="2" value="1.5.0&lt;br&gt;Register" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="80" y="200" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="3" target="7">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;fillColor=#60a917;strokeColor=#2D7600;" edge="1" parent="1" source="3" target="10">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="3" value="1.6.0&lt;br&gt;Kubernetes" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="200" y="150" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;fillColor=#60a917;strokeColor=#2D7600;" edge="1" parent="1" source="4" target="7">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;fillColor=#60a917;strokeColor=#2D7600;" edge="1" parent="1" source="4" target="10">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="4" value="1.6.0&lt;br&gt;Overview&lt;br&gt;Page" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="200" y="250" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;fillColor=#e3c800;strokeColor=#B09500;" edge="1" parent="1" source="7" target="15">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="7" value="1.7.0&lt;br&gt;Federation" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="320" y="150" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;fillColor=#e3c800;strokeColor=#B09500;" edge="1" parent="1" source="10" target="15">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="10" value="1.7.0&lt;br&gt;Statistics(?)" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="320" y="250" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;fillColor=#f0a30a;strokeColor=#BD7000;strokeWidth=3;" edge="1" parent="1" source="15" target="18">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="1.8.0&lt;br&gt;Activities" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="440" y="200" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;fillColor=#fa6800;strokeColor=#C73500;strokeWidth=3;" edge="1" parent="1" source="18" target="19">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="18" value="1.X.0&lt;br&gt;???" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="560" y="200" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="19" value="Von A nach B überweisen&amp;nbsp;" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#2A2A2A;strokeColor=#F0F0F0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="680" y="200" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="30" value="The background color symbolizes how far away we are from the goal. The arrow color symbolizes how sure we can be that this will be the course of action" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="70" y="640" width="330" height="50" as="geometry"/>
</mxCell>
<mxCell id="31" value="Our current release" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="70" y="360" width="110" height="240" as="geometry"/>
</mxCell>
<mxCell id="32" value="The Release 1.6.0 will make sure we can actually deploy multiple instances of our Software.&lt;br&gt;Additionally we will create the Overview Page in the Frontend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="185" y="360" width="110" height="240" as="geometry"/>
</mxCell>
<mxCell id="33" value="The Release 1.7.0 will focus on the Federation. Maybe we will be able to send Coins at this stage, but its likely this will be done in the next steps. Here we most likely get to know the other communties only.&lt;br&gt;In the Frontend we consider to show some statisticts with the amount of known Communities" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="305" y="361" width="110" height="239" as="geometry"/>
</mxCell>
<mxCell id="35" value="The Release 1.8.0 will implement Activities in order to allow other communities to actually create GDD." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="425" y="360" width="110" height="240" as="geometry"/>
</mxCell>
<mxCell id="36" value="More Steps might be needed to have the usable full functionallity of sending from A to B." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="545" y="360" width="110" height="240" as="geometry"/>
</mxCell>
<mxCell id="37" value="Be aware that we might decide to split some Releases into two. Especially the Federation Release might need a followup Release in oder to actually send coins, since this will not be part of the Federation itself." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#F0F0F0;" vertex="1" parent="1">
<mxGeometry x="70" y="710" width="330" height="50" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,4 +1,3 @@
LOGIN_API_URL=http://localhost/login_api/
COMMUNITY_API_URL=http://localhost/api/
ALLOW_REGISTER=true
GRAPHQL_URI=http://localhost:4000/graphql
//BUILD_COMMIT=0000000

View File

@ -1,69 +1,114 @@
<template>
<div>
<div class="decayinformation">
<span v-if="decaytyp === 'short'">
<div v-if="decay.balance > 0">
<span v-if="decay.balance > 0">
{{ decay ? ' -' + decay.balance + ' ' + decayStartBlockTextShort : '' }}
</div>
<div v-else>
</span>
<span v-else>
{{ $t('decay.noDecay') }}
</div>
</span>
</span>
<div v-if="decaytyp === 'new'">
<b-list-group style="border: 0px">
<b-list-group-item style="border: 0px; background-color: #f1f1f1">
<div class="d-flex" v-if="!decay.decayStartBlock">
<div style="width: 100%" class="text-center pb-3">
<b-icon icon="droplet-half" height="12" class="mb-2" />
<b>{{ $t('decay.calculation_decay') }}</b>
</div>
</div>
<div class="d-flex" v-if="!decay.decayStartBlock">
<div style="width: 100%" class="text-center pb-3">
<b-icon icon="droplet-half" height="12" class="mb-2" />
<b>{{ $t('decay.calculation_decay') }}</b>
</div>
</div>
<div class="d-flex">
<div style="width: 40%" class="text-right pr-3 mr-2">
<div v-if="!decay.decayStartBlock">{{ $t('decay.last_transaction') }}</div>
</div>
<div style="width: 60%">
<div v-if="decay.decayStartBlock > 0">
<div class="display-4">{{ $t('decay.Starting_block_decay') }}</div>
<div>
{{ $t('decay.decay_introduced') }} :
{{ $d($moment.unix(decay.decayStart), 'long') }}
</div>
</div>
<div>
<span v-if="!decay.decayStart">
{{ $d($moment.unix(decay.decayStart), 'long') }}
{{ $i18n.locale === 'de' ? 'Uhr' : '' }}
</span>
</div>
<b-row>
<b-col cols="6" class="text-right">
<div v-if="!decay.decayStartBlock">{{ $t('decay.last_transaction') }}</div>
</b-col>
<b-col cols="6">
<div v-if="decay.decayStartBlock > 0">
<div class="display-4">{{ $t('decay.Starting_block_decay') }}</div>
<div>
{{ $t('decay.decay_introduced') }} :
{{ $d($moment.unix(decay.decayStart), 'long') }}
</div>
</div>
<div>
<span v-if="decay.decayStart">
{{ $d($moment.unix(decay.decayStart), 'long') }}
{{ $i18n.locale === 'de' ? 'Uhr' : '' }}
</span>
</div>
</b-col>
</b-row>
<b-row>
<b-col cols="6" class="text-right">
<div v-if="!decay.decayStartBlock">{{ $t('decay.past_time') }}</div>
</b-col>
<b-col cols="6">
<div v-if="decay.decayStartBlock > 0">{{ $t('decay.since_introduction') }}</div>
<span v-if="duration">
<span v-if="duration.years > 0">{{ duration.years }} {{ $t('decay.year') }},</span>
<span v-if="duration.months > 0">{{ duration.months }} {{ $t('decay.months') }},</span>
<span v-if="duration.days > 0">{{ duration.days }} {{ $t('decay.days') }},</span>
<span v-if="duration.hours > 0">{{ duration.hours }} {{ $t('decay.hours') }},</span>
<span v-if="duration.minutes > 0">
{{ duration.minutes }} {{ $t('decay.minutes') }},
</span>
<span v-if="duration.seconds > 0">
{{ duration.seconds }} {{ $t('decay.seconds') }}
</span>
</span>
</b-col>
</b-row>
<div class="d-flex">
<div style="width: 40%" class="text-right pr-3 mr-2">
<div v-if="!decay.decayStartBlock">{{ $t('decay.past_time') }}</div>
<div v-if="decay.balance > 0">
<!-- Decay-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div>- {{ decay.balance }}</div>
</b-col>
</b-row>
<hr class="mt-2 mb-2" />
<b-row>
<b-col class="text-center pt-3 pb-2">
<b>{{ $t('decay.calculation_total') }}</b>
</b-col>
</b-row>
<!-- Type-->
<b-row>
<b-col cols="6" class="text-right">
<div v-if="type === 'send'">{{ $t('decay.sent') }}</div>
<div v-if="type === 'receive'">{{ $t('decay.received') }}</div>
</b-col>
<b-col cols="6">
<div v-if="type === 'send'">- {{ balance }}</div>
<div v-if="type === 'receive'">+ {{ balance }}</div>
</b-col>
</b-row>
<!-- Decay-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.decay') }}</div>
</b-col>
<b-col cols="6">
<div>- {{ decay.balance }}</div>
</b-col>
</b-row>
<!-- Total-->
<b-row>
<b-col cols="6" class="text-right">
<div>{{ $t('decay.total') }}</div>
</b-col>
<b-col cols="6">
<div v-if="type === 'send'">
<b>- {{ parseInt(balance) + decay.balance }}</b>
</div>
<div style="width: 60%">
<div v-if="decay.decayStartBlock > 0">{{ $t('decay.since_introduction') }}</div>
<span v-if="duration">
<span v-if="duration.years > 0">{{ duration.years }} {{ $t('decay.year') }},</span>
<span v-if="duration.months > 0">
{{ duration.months }} {{ $t('decay.months') }},
</span>
<span v-if="duration.days > 0">{{ duration.days }} {{ $t('decay.days') }},</span>
<span v-if="duration.hours > 0">{{ duration.hours }} {{ $t('decay.hours') }},</span>
<span v-if="duration.minutes > 0">
{{ duration.minutes }} {{ $t('decay.minutes') }},
</span>
<span v-if="duration.seconds > 0">
{{ duration.seconds }} {{ $t('decay.seconds') }}
</span>
</span>
<div v-if="type === 'receive'">
<b>{{ parseInt(balance) - decay.balance }}</b>
</div>
</div>
</b-list-group-item>
</b-list-group>
</b-col>
</b-row>
</div>
</div>
</div>
</template>
@ -71,6 +116,8 @@
export default {
name: 'DecayInformation',
props: {
balance: { type: Number },
type: { type: String, default: '' },
decay: {
balance: '',
decayDuration: '',

View File

@ -9,6 +9,8 @@
containsUppercaseCharacter: true,
containsNumericCharacter: true,
atLeastEightCharactera: true,
atLeastOneSpecialCharater: true,
noWhitespaceCharacters: true,
}"
:label="register ? $t('form.password') : $t('form.password_new')"
:showAllErrors="true"

View File

@ -3,7 +3,7 @@ import LanguageSwitch from './LanguageSwitch'
const localVue = global.localVue
const updateUserInfosQueryMock = jest.fn().mockResolvedValue({
const updateUserInfosMutationMock = jest.fn().mockResolvedValue({
data: {
updateUserInfos: {
validValues: 1,
@ -28,7 +28,7 @@ describe('LanguageSwitch', () => {
locale: 'en',
},
$apollo: {
query: updateUserInfosQueryMock,
mutate: updateUserInfosMutationMock,
},
}
@ -119,7 +119,7 @@ describe('LanguageSwitch', () => {
describe('calls the API', () => {
it("with locale 'en'", () => {
wrapper.findAll('li').at(0).find('a').trigger('click')
expect(updateUserInfosQueryMock).toBeCalledWith(
expect(updateUserInfosMutationMock).toBeCalledWith(
expect.objectContaining({
variables: {
email: 'he@ho.he',
@ -131,7 +131,7 @@ describe('LanguageSwitch', () => {
it("with locale 'de'", () => {
wrapper.findAll('li').at(1).find('a').trigger('click')
expect(updateUserInfosQueryMock).toBeCalledWith(
expect(updateUserInfosMutationMock).toBeCalledWith(
expect.objectContaining({
variables: {
email: 'he@ho.he',

View File

@ -14,7 +14,7 @@
<script>
import { localeChanged } from 'vee-validate'
import locales from '../locales/'
import { updateUserInfos } from '../graphql/queries'
import { updateUserInfos } from '../graphql/mutations'
export default {
name: 'LanguageSwitch',
@ -36,8 +36,8 @@ export default {
this.setLocale(locale)
if (this.$store.state.email) {
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
email: this.$store.state.email,
locale: locale,

View File

@ -14,7 +14,6 @@ export default {
return {
selected: null,
options: [
{ value: null, text: this.$t('select_language') },
{ value: 'de', text: this.$t('languages.de') },
{ value: 'en', text: this.$t('languages.en') },
],

View File

@ -19,54 +19,54 @@
<!-- type -->
<b-row>
<div class="col-6 text-right">
<b-col cols="6" class="text-right">
{{ getLinesByType(gdtEntryType).description }}
</div>
<div class="col-6">
</b-col>
<b-col cols="6">
{{ getLinesByType(gdtEntryType).descriptiontext }}
</div>
</b-col>
</b-row>
<!-- credit -->
<b-row>
<div class="col-6 text-right">
<b-col cols="6" class="text-right">
{{ $t('gdt.credit') }}
</div>
<div class="col-6">
</b-col>
<b-col cols="6">
{{ getLinesByType(gdtEntryType).credittext }}
</div>
</b-col>
</b-row>
<!-- Message-->
<b-row v-if="comment && gdtEntryType !== 7">
<div class="col-6 text-right">
<b-col cols="6" class="text-right">
{{ $t('form.memo') }}
</div>
<div class="col-6">
</b-col>
<b-col cols="6">
{{ comment }}
</div>
</b-col>
</b-row>
<!-- date-->
<b-row class="gdt-list-row text-header">
<div class="col-6 text-right">
<b-col cols="6" class="text-right">
{{ $t('form.date') }}
</div>
<div class="col-6">
</b-col>
<b-col cols="6">
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
</div>
</b-col>
</b-row>
</div>
<!-- collaps trancaction info-->
<b-collapse :id="'a' + date + ''" class="pb-4">
<transaction-collapse
:amount="amount"
:gdtEntryType="gdtEntryType"
:factor="factor"
:gdt="gdt"
></transaction-collapse>
</b-collapse>
<!-- collaps trancaction info-->
<b-collapse :id="'a' + date + ''" class="mt-2 pb-4">
<transaction-collapse
:amount="amount"
:gdtEntryType="gdtEntryType"
:factor="factor"
:gdt="gdt"
></transaction-collapse>
</b-collapse>
</div>
</div>
</div>
</template>

View File

@ -1,21 +1,24 @@
<template>
<div class="gdt-transaction-collapse">
<div
class="gdt-transaction-collapse p-2 pt-4 pb-4 mb-4"
style="border: 0px; background-color: #f1f1f1"
>
<b-row class="gdt-list-collapse-header-text text-center pb-3">
<div id="collapse-headline" class="col h4">
{{ getLinesByType(gdtEntryType).headline }}
</div>
<b-col id="collapse-headline">
<b>{{ getLinesByType(gdtEntryType).headline }}</b>
</b-col>
</b-row>
<b-row class="gdt-list-collapse-box--all">
<div class="col-6 text-right collapse-col-left">
<b-col cols="6" class="text-right collapse-col-left">
<div id="collapse-first">{{ getLinesByType(gdtEntryType).first }}</div>
<div id="collapse-second">{{ getLinesByType(gdtEntryType).second }}</div>
</div>
<div class="col-6 collapse-col-right">
</b-col>
<b-col cols="6" class="collapse-col-right">
<div id="collapse-firstMath">{{ getLinesByType(gdtEntryType).firstMath }}</div>
<div id="collapse-secondMath">
{{ getLinesByType(gdtEntryType).secondMath }}
</div>
</div>
</b-col>
</b-row>
</div>
</template>
@ -30,6 +33,8 @@ export default {
},
methods: {
getLinesByType(givenType) {
if (givenType === 2 || givenType === 3 || givenType === 5 || givenType === 6) givenType = 1
const linesByType = {
1: {
headline: this.$t('gdt.calculation'),

View File

@ -1,25 +1,35 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env).
// The whole contents is exposed to the client
// Load Package Details for some default values
const pkg = require('../../package')
const version = {
APP_VERSION: pkg.version,
BUILD_COMMIT: process.env.BUILD_COMMIT || null,
// self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT || '0000000').substr(0, 7),
}
const environment = {
NODE_ENV: process.env.NODE_ENV,
DEBUG: process.env.NODE_ENV !== 'production' || false,
PRODUCTION: process.env.NODE_ENV === 'production' || false,
ALLOW_REGISTER: process.env.ALLOW_REGISTER !== 'false',
}
const server = {
LOGIN_API_URL: process.env.LOGIN_API_URL || 'http://localhost/login_api/',
COMMUNITY_API_URL: process.env.COMMUNITY_API_URL || 'http://localhost/api/',
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
}
const options = {
ALLOW_REGISTER: process.env.ALLOW_REGISTER !== 'false',
}
const CONFIG = {
...version,
...environment,
...server,
APP_VERSION: pkg.version,
...options,
}
export default CONFIG

View File

@ -11,3 +11,59 @@ export const unsubscribeNewsletter = gql`
unsubscribeNewsletter(email: $email)
}
`
export const resetPassword = gql`
mutation($sessionId: Float!, $email: String!, $password: String!) {
resetPassword(sessionId: $sessionId, email: $email, password: $password)
}
`
export const updateUserInfos = gql`
mutation(
$email: String!
$firstName: String
$lastName: String
$description: String
$username: String
$password: String
$passwordNew: String
$locale: String
) {
updateUserInfos(
email: $email
firstName: $firstName
lastName: $lastName
description: $description
username: $username
password: $password
passwordNew: $passwordNew
language: $locale
) {
validValues
}
}
`
export const registerUser = gql`
mutation(
$firstName: String!
$lastName: String!
$email: String!
$password: String!
$language: String!
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
password: $password
language: $language
)
}
`
export const sendCoins = gql`
mutation($email: String!, $amount: Float!, $memo: String!) {
sendCoins(email: $email, amount: $amount, memo: $memo)
}
`

View File

@ -22,12 +22,6 @@ export const logout = gql`
}
`
export const resetPassword = gql`
query($sessionId: Float!, $email: String!, $password: String!) {
resetPassword(sessionId: $sessionId, email: $email, password: $password)
}
`
export const loginViaEmailVerificationCode = gql`
query($optin: String!) {
loginViaEmailVerificationCode(optin: $optin) {
@ -37,32 +31,6 @@ export const loginViaEmailVerificationCode = gql`
}
`
export const updateUserInfos = gql`
query(
$email: String!
$firstName: String
$lastName: String
$description: String
$username: String
$password: String
$passwordNew: String
$locale: String
) {
updateUserInfos(
email: $email
firstName: $firstName
lastName: $lastName
description: $description
username: $username
password: $password
passwordNew: $passwordNew
language: $locale
) {
validValues
}
}
`
export const transactionsQuery = gql`
query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
transactionList(firstPage: $firstPage, items: $items, order: $order) {
@ -94,30 +62,6 @@ export const transactionsQuery = gql`
}
`
export const resgisterUserQuery = gql`
query(
$firstName: String!
$lastName: String!
$email: String!
$password: String!
$language: String!
) {
create(
email: $email
firstName: $firstName
lastName: $lastName
password: $password
language: $language
)
}
`
export const sendCoins = gql`
query($email: String!, $amount: Float!, $memo: String!) {
sendCoins(email: $email, amount: $amount, memo: $memo)
}
`
export const sendResetPasswordEmail = gql`
query($email: String!) {
sendResetPasswordEmail(email: $email) {

View File

@ -12,6 +12,7 @@
},
"decay": {
"calculation_decay": "Berechnung der Vergänglichkeit",
"calculation_total": "Berechnung der Gesamtsumme",
"created": "Geschöpft",
"days": "Tage",
"decay": "Vergänglichkeit",
@ -31,32 +32,27 @@
"since_introduction": "seit Einführung der Vergänglichkeit",
"Starting_block_decay": "Startblock Vergänglichkeit",
"toCommunity": "An die Gemeinschaft",
"total": "Gesamt",
"year": "Jahre"
},
"error": {
"change-password": "Fehler beim Ändern des Passworts",
"error": "Fehler",
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!"
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
"session-expired": "Sitzung abgelaufen!"
},
"form": {
"amount": "Betrag",
"at": "am",
"cancel": "Abbrechen",
"change": "ändern",
"change-name": "Name ändern",
"change-password": "Passwort ändern",
"changeLanguage": "Sprache ändern",
"change_username_info": "Einmal gespeichert, kann der Username ncht mehr geändert werden!",
"close": "schließen",
"date": "Datum",
"description": "Beschreibung",
"edit": "bearbeiten",
"email": "E-Mail",
"email_repeat": "eMail wiederholen",
"firstname": "Vorname",
"from": "von",
"lastname": "Nachname",
"max_gdd_info": "Maximale anzahl GDD zum versenden erreicht!",
"memo": "Nachricht",
"message": "Nachricht",
"password": "Passwort",
@ -78,7 +74,6 @@
"time": "Zeit",
"to": "bis",
"to1": "an",
"username": "Username",
"validation": {
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
@ -103,28 +98,40 @@
},
"imprint": "Impressum",
"language": "Sprache",
"languages": {
"de": "Deutsch",
"en": "English"
},
"login": "Anmeldung",
"logout": "Abmelden",
"members_area": "Mitgliederbereich",
"message": "hallo gradido !!",
"privacy_policy": "Datenschutzerklärung",
"reset": "Passwort zurücksetzen",
"reset-password": {
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst.",
"title": "Passwort zurücksetzen"
},
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
"send": "Senden",
"setting": {
"changeNewsletter": "Newsletter Status ändern",
"newsletter": "Newsletter",
"newsletterFalse": "Du bist aus Newslettersystem ausgetragen.",
"newsletterTrue": "Du bist im Newslettersystem eingetraten."
"settings": {
"language": {
"changeLanguage": "Sprache ändern",
"de": "Deutsch",
"en": "English",
"select_language": "Bitte wähle eine Sprache.",
"success": "Deine Sprache wurde erfolgreich geändert."
},
"name": {
"change-name": "Name ändern",
"change-success": "Dein Name wurde erfolgreich geändert."
},
"newsletter": {
"newsletter": "Newsletter",
"newsletterFalse": "Du bist aus Newslettersystem ausgetragen.",
"newsletterTrue": "Du bist im Newslettersystem eingetraten."
},
"password": {
"change-password": "Passwort ändern",
"forgot_pwd": "Passwort vergessen?",
"reset": "Passwort zurücksetzen",
"reset-password": {
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
},
"send_now": "Jetzt senden",
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
}
},
"signup": "Registrieren",
"site": {
@ -139,34 +146,21 @@
},
"login": {
"community": "Tausend Dank, weil du bei uns bist!",
"forgot_pwd": "Passwort vergessen?",
"new_wallet": "Neues Konto erstellen",
"remember": "Passwort merken",
"signin": "Anmelden"
},
"navbar": {
"activity": "Aktivität",
"my-profil": "Mein Profil",
"settings": "Einstellungen",
"support": "Support"
},
"overview": {
"account_overview": "Kontoübersicht",
"add_work": "neuer Gemeinschaftsbeitrag",
"send_gradido": "Gradido versenden",
"since_last_month": "seid letzten Monat"
},
"password": {
"send_now": "Jetzt senden",
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen.",
"title": "Passwort zurücksetzen"
},
"signup": {
"agree": "Ich stimme der <a href='https://gradido.net/de/datenschutz/' target='_blank' >Datenschutzerklärung</a> zu.",
"dont_match": "Die Passwörter stimmen nicht überein.",
"lowercase": "Ein Kleinbuchstabe erforderlich.",
"minimum": "Mindestens 8 Zeichen.",
"no-whitespace": "Keine Leerzeichen und Tabulatoren",
"one_number": "Eine Zahl erforderlich.",
"special-char": "Ein Sonderzeichen erforderlich (z.B. _ oder ä)",
"subtitle": "Werde Teil der Gemeinschaft!",
"title": "Erstelle dein Gradido-Konto",
"uppercase": "Ein Großbuchstabe erforderlich."
@ -188,6 +182,5 @@
"show_all": "Alle <strong>{count}</strong> Transaktionen ansehen"
},
"transactions": "Transaktionen",
"welcome": "Willkommen!",
"whitepaper": "Whitepaper"
}

View File

@ -12,6 +12,7 @@
},
"decay": {
"calculation_decay": "Calculation of Decay",
"calculation_total": "Calculation of the grand total",
"created": "Created",
"days": "Days",
"decay": "Decay",
@ -31,32 +32,27 @@
"since_introduction": "Since the introduction of Decay",
"Starting_block_decay": "Starting Block Decay",
"toCommunity": "To the community",
"total": "Total",
"year": "Years"
},
"error": {
"change-password": "Error while changing password",
"error": "Error",
"no-account": "Unfortunately we could not find an account to the given data!"
"no-account": "Unfortunately we could not find an account to the given data!",
"session-expired": "The session expired"
},
"form": {
"amount": "Amount",
"at": "at",
"cancel": "Cancel",
"change": "change",
"change-name": "Change name",
"change-password": "Change password",
"changeLanguage": "Change language",
"change_username_info": "Once saved, the username cannot be changed again!",
"close": "Close",
"date": "Date",
"description": "Description",
"edit": "Edit",
"email": "Email",
"email_repeat": "Repeat Email",
"firstname": "Firstname",
"from": "from",
"lastname": "Lastname",
"max_gdd_info": "Maximum number of GDDs to be sent has been reached!",
"memo": "Message",
"message": "Message",
"password": "Password",
@ -78,7 +74,6 @@
"time": "Time",
"to": "to",
"to1": "to",
"username": "Username",
"validation": {
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits",
"is-not": "You cannot send Gradidos to yourself",
@ -103,28 +98,40 @@
},
"imprint": "Legal notice",
"language": "Language",
"languages": {
"de": "Deutsch",
"en": "English"
},
"login": "Login",
"logout": "Logout",
"members_area": "Member's area",
"message": "hello gradido !!",
"privacy_policy": "Privacy policy",
"reset": "Reset password",
"reset-password": {
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
"text": "Now you can save a new password to login to the Gradido-App in the future.",
"title": "Reset Password"
},
"select_language": "Please choose a language for the app and newsletter",
"send": "Send",
"setting": {
"changeNewsletter": "Newsletter status change",
"newsletter": "Newsletter",
"newsletterFalse": "You are unsubscribed from newsletter system.",
"newsletterTrue": "You are subscribed to newsletter system."
"settings": {
"language": {
"changeLanguage": "Change language",
"de": "Deutsch",
"en": "English",
"select_language": "Please choose a language.",
"success": "Your language has been successfully updated."
},
"name": {
"change-name": "Change name",
"change-success": "Your name has been successfully changed."
},
"newsletter": {
"newsletter": "Newsletter",
"newsletterFalse": "You are unsubscribed from newsletter system.",
"newsletterTrue": "You are subscribed to newsletter system."
},
"password": {
"change-password": "Change password",
"forgot_pwd": "Forgot password?",
"reset": "Reset password",
"reset-password": {
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
"text": "Now you can save a new password to login to the Gradido-App in the future."
},
"send_now": "Send now",
"subtitle": "If you have forgotten your password, you can reset it here."
}
},
"signup": "Sign up",
"site": {
@ -139,34 +146,21 @@
},
"login": {
"community": "A thousand thanks for being with us!",
"forgot_pwd": "Forgot password?",
"new_wallet": "Create new account",
"remember": "Remember password",
"signin": "Sign in"
},
"navbar": {
"activity": "Activity",
"my-profil": "My profile",
"settings": "Settings",
"support": "Support"
},
"overview": {
"account_overview": "Account overview",
"add_work": "New Community Contribution",
"send_gradido": "Send Gradido",
"since_last_month": "since last month"
},
"password": {
"send_now": "Send now",
"subtitle": "If you have forgotten your password, you can reset it here.",
"title": "Reset password"
},
"signup": {
"agree": "I agree to the <a href='https://gradido.net/en/datenschutz/' target='_blank' > privacy policy</a>.",
"dont_match": "Passwords don't match.",
"lowercase": "One lowercase letter required.",
"minimum": "8 characters minimum.",
"no-whitespace": "No white spaces and tabs",
"one_number": "One number required.",
"special-char": "One special character required (e.g. _ or ä)",
"subtitle": "Become a part of the community!",
"title": "Create your Gradido account",
"uppercase": "One uppercase letter required."
@ -188,6 +182,5 @@
"show_all": "View all <strong>{count}</strong> transactions."
},
"transactions": "Transactions",
"welcome": "Welcome!",
"whitepaper": "Whitepaper"
}

View File

@ -21,6 +21,12 @@ const authLink = new ApolloLink((operation, forward) => {
},
})
return forward(operation).map((response) => {
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
response.errors[0].message = i18n.t('error.session-expired')
store.dispatch('logout', null)
if (router.currentRoute.path !== '/login') router.push('/login')
return response
}
const newToken = operation.getContext().response.headers.get('token')
if (newToken) store.commit('token', newToken)
return response

View File

@ -112,6 +112,20 @@ export const loadAllRules = (i18nCallback) => {
message: (_, values) => i18nCallback.t('site.signup.minimum', values),
})
extend('atLeastOneSpecialCharater', {
validate(value) {
return !!value.match(/[^a-zA-Z0-9]/)
},
message: (_, values) => i18nCallback.t('site.signup.special-char', values),
})
extend('noWhitespaceCharacters', {
validate(value) {
return !!value.match(/[^ \t\n\r]/)
},
message: (_, values) => i18nCallback.t('site.signup.no-whitespace', values),
})
extend('samePassword', {
validate(value, [pwd]) {
return value === pwd

View File

@ -59,6 +59,21 @@ describe('ContentFooter', () => {
'https://github.com/gradido/gradido/releases/latest',
)
})
it('has last commit hash', async () => {
wrapper.setData({ shortHash: 'ACCEDED' })
wrapper.setData({ hash: 'ACCEDEDC001D00DC001D00DC001D00DC001CAFA' })
await wrapper.vm.$nextTick()
expect(wrapper.find('div.copyright').findAll('a').at(2).text()).toEqual('(ACCEDED)')
})
it('links to last release commit', async () => {
wrapper.setData({ hash: 'ACCEDEDC001D00DC001D00DC001D00DC001CAFA' })
await wrapper.vm.$nextTick()
expect(wrapper.find('div.copyright').findAll('a').at(2).attributes('href')).toEqual(
'https://github.com/gradido/gradido/commit/ACCEDEDC001D00DC001D00DC001D00DC001CAFA',
)
})
})
describe('links to gradido.net', () => {

View File

@ -15,6 +15,13 @@
<a href="https://github.com/gradido/gradido/releases/latest" target="_blank">
App version {{ version }}
</a>
<a
v-if="hash"
:href="'https://github.com/gradido/gradido/commit/' + hash"
target="_blank"
>
({{ shortHash }})
</a>
</div>
</b-col>
</b-row>
@ -59,6 +66,8 @@ export default {
return {
year: new Date().getFullYear(),
version: CONFIG.APP_VERSION,
hash: CONFIG.BUILD_COMMIT,
shortHash: CONFIG.BUILD_COMMIT_SHORT,
}
},
}

View File

@ -25,7 +25,7 @@ describe('AccountOverview', () => {
},
},
$apollo: {
query: sendMock,
mutate: sendMock,
},
}

View File

@ -51,7 +51,7 @@ import GddTransactionListFooter from './AccountOverview/GddTransactionListFooter
import TransactionForm from './AccountOverview/GddSend/TransactionForm.vue'
import TransactionConfirmation from './AccountOverview/GddSend/TransactionConfirmation.vue'
import TransactionResult from './AccountOverview/GddSend/TransactionResult.vue'
import { sendCoins } from '../../graphql/queries.js'
import { sendCoins } from '../../graphql/mutations.js'
const EMPTY_TRANSACTION_DATA = {
email: '',
@ -105,8 +105,8 @@ export default {
async sendTransaction() {
this.loading = true
this.$apollo
.query({
query: sendCoins,
.mutate({
mutation: sendCoins,
variables: this.transactionData,
})
.then(() => {

View File

@ -54,7 +54,7 @@ describe('GddTransactionList', () => {
await wrapper.setProps({
transactions: [
{
balance: '19.93',
balance: 19.93,
date: '2021-05-25T17:38:13+00:00',
memo: 'Alles Gute zum Geburtstag',
name: 'Bob der Baumeister',
@ -63,7 +63,7 @@ describe('GddTransactionList', () => {
decay: { balance: '0.5' },
},
{
balance: '1000',
balance: 1000,
date: '2021-04-29T15:34:49+00:00',
memo: 'Gut das du da bist!',
name: 'Gradido Akademie',
@ -71,7 +71,7 @@ describe('GddTransactionList', () => {
type: 'creation',
},
{
balance: '314.98',
balance: 314.98,
date: '2021-04-29T17:26:40+00:00',
memo: 'Für das Fahrrad!',
name: 'Jan Ulrich',

View File

@ -18,13 +18,13 @@
</b-button>
</div>
<b-row>
<div>
<!-- ICON -->
<div class="col-1 gdd-transaction-list-item-icon">
<b-icon :icon="getProperties(type).icon" :class="getProperties(type).class" />
</div>
<div class="col col-11">
<div>
<!-- Betrag / Name Email -->
<b-row>
<div class="col-5 text-right">
@ -70,13 +70,19 @@
</div>
</b-row>
</div>
</b-row>
</div>
<!-- Collaps Start -->
<b-collapse v-if="type != 'decay'" class="pb-4" :id="'a' + date + ''">
<div style="border: 0px; background-color: #f1f1f1" class="p-2 pb-4 mb-4">
<decay-information v-if="decay" decaytyp="new" :decay="decay" />
<decay-information
v-if="decay"
decaytyp="new"
:balance="balance"
:decay="decay"
:type="type"
/>
</div>
</b-collapse>

View File

@ -1,6 +1,6 @@
<template>
<div class="gdt-transaction-list">
<div class="list-group">
<div class="list-group" style="background-color: #fff">
<div v-if="transactionGdtCount === 0">
{{ $t('gdt.no-transactions') }}
</div>

View File

@ -39,11 +39,11 @@ describe('ForgotPassword', () => {
})
it('has a title', () => {
expect(wrapper.find('h1').text()).toEqual('site.password.title')
expect(wrapper.find('h1').text()).toEqual('settings.password.reset')
})
it('has a subtitle', () => {
expect(wrapper.find('p.text-lead').text()).toEqual('site.password.subtitle')
expect(wrapper.find('p.text-lead').text()).toEqual('settings.password.subtitle')
})
describe('back button', () => {

View File

@ -5,8 +5,8 @@
<div class="header-body text-center mb-7">
<b-row class="justify-content-center">
<b-col xl="5" lg="6" md="8" class="px-2">
<h1>{{ $t('site.password.title') }}</h1>
<p class="text-lead">{{ $t('site.password.subtitle') }}</p>
<h1>{{ $t('settings.password.reset') }}</h1>
<p class="text-lead">{{ $t('settings.password.subtitle') }}</p>
</b-col>
</b-row>
</div>
@ -22,7 +22,7 @@
<input-email v-model="form.email"></input-email>
<div class="text-center">
<b-button type="submit" variant="primary">
{{ $t('site.password.send_now') }}
{{ $t('settings.password.send_now') }}
</b-button>
</div>
</b-form>

View File

@ -71,7 +71,7 @@ describe('Login', () => {
describe('links', () => {
it('has a link "Forgot Password?"', () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual(
'site.login.forgot_pwd',
'settings.password.forgot_pwd',
)
})

View File

@ -40,7 +40,7 @@
<b-row class="mt-3">
<b-col cols="6">
<router-link to="/password">
{{ $t('site.login.forgot_pwd') }}
{{ $t('settings.password.forgot_pwd') }}
</router-link>
</b-col>
<b-col cols="6" class="text-right" v-show="allowRegister">

View File

@ -5,7 +5,7 @@ import Register from './Register'
const localVue = global.localVue
const resgisterUserQueryMock = jest.fn()
const registerUserMutationMock = jest.fn()
const routerPushMock = jest.fn()
describe('Register', () => {
@ -20,11 +20,12 @@ describe('Register', () => {
push: routerPushMock,
},
$apollo: {
query: resgisterUserQueryMock,
mutate: registerUserMutationMock,
},
$store: {
state: {
language: null,
email: 'peter@lustig.de',
language: 'en',
},
},
}
@ -54,11 +55,11 @@ describe('Register', () => {
describe('links', () => {
it('has a link "Back"', () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
expect(wrapper.find('.test-button-back').text()).toEqual('back')
})
it('links to /login when clicking "Back"', () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
expect(wrapper.find('.test-button-back').props().to).toBe('/login')
})
})
@ -88,17 +89,17 @@ describe('Register', () => {
it('has Language selected field', () => {
expect(wrapper.find('.selectedLanguage').exists()).toBeTruthy()
})
it('selected Language value de', async () => {
it('selects Language value en', async () => {
wrapper.find('.selectedLanguage').findAll('option').at(1).setSelected()
expect(wrapper.find('.selectedLanguage').element.value).toBe('de')
expect(wrapper.find('.selectedLanguage').element.value).toBe('en')
})
it('has 1 checkbox input fields', () => {
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
})
it('has no submit button when not completely filled', () => {
expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
it('has disabled submit button when not completely filled', () => {
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
})
it('displays a message that Email is required', async () => {
@ -126,73 +127,23 @@ describe('Register', () => {
})
})
describe('resetForm', () => {
beforeEach(() => {
wrapper.find('#registerFirstname').setValue('Max')
wrapper.find('#registerLastname').setValue('Mustermann')
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
wrapper.find('input[name="site.signup.agree"]').setChecked(true)
})
it('reset selected value language', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('.language-switch-select').element.value).toBe(undefined)
})
it('resets the firstName field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('#registerFirstname').element.value).toBe('')
})
it('resets the lastName field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('#registerLastname').element.value).toBe('')
})
it('resets the email field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('#Email-input-field').element.value).toBe('')
})
it.skip('resets the password field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('input[name="form.password"]').element.value).toBe('')
})
it.skip('resets the passwordRepeat field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('input[name="form.passwordRepeat"]').element.value).toBe('')
})
it('resets the firstName field after clicking the reset button', async () => {
await wrapper.find('button.ml-2').trigger('click')
await flushPromises()
expect(wrapper.find('input[name="site.signup.agree"]').props.checked).not.toBeTruthy()
})
})
describe('API calls', () => {
beforeEach(() => {
wrapper.find('#registerFirstname').setValue('Max')
wrapper.find('#registerLastname').setValue('Mustermann')
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
wrapper.find('input[name="form.password"]').setValue('Aa123456')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
wrapper.find('input[name="form.password"]').setValue('Aa123456_')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456_')
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
})
it('has enabled submit button when completely filled', () => {
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
})
describe('server sends back error', () => {
beforeEach(async () => {
resgisterUserQueryMock.mockRejectedValue({ message: 'Ouch!' })
registerUserMutationMock.mockRejectedValue({ message: 'Ouch!' })
await wrapper.find('form').trigger('submit')
await flushPromises()
})
@ -217,7 +168,7 @@ describe('Register', () => {
describe('server sends back success', () => {
beforeEach(() => {
resgisterUserQueryMock.mockResolvedValue({
registerUserMutationMock.mockResolvedValue({
data: {
create: 'success',
},
@ -227,14 +178,14 @@ describe('Register', () => {
it('routes to "/thx/register"', async () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(resgisterUserQueryMock).toBeCalledWith(
expect(registerUserMutationMock).toBeCalledWith(
expect.objectContaining({
variables: {
email: 'max.mustermann@gradido.net',
firstName: 'Max',
lastName: 'Mustermann',
password: 'Aa123456',
language: 'de',
password: 'Aa123456_',
language: 'en',
},
}),
)

View File

@ -116,13 +116,19 @@
</span>
</b-alert>
<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 type="submit" variant="primary">{{ $t('signup') }}</b-button>
<b-button class="ml-2 test-button-back" to="/login">
{{ $t('back') }}
</b-button>
<b-button
:disabled="!(namesFilled && emailFilled && form.agree && languageFilled)"
type="submit"
variant="primary"
>
{{ $t('signup') }}
</b-button>
</div>
</div>
</b-form>
@ -131,9 +137,6 @@
</b-card>
</b-col>
</b-row>
<div class="text-center py-lg-4">
<router-link to="/login" class="mt-3">{{ $t('back') }}</router-link>
</div>
</b-container>
</div>
</template>
@ -141,7 +144,7 @@
import InputEmail from '../../components/Inputs/InputEmail.vue'
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
import { resgisterUserQuery } from '../../graphql/queries'
import { registerUser } from '../../graphql/mutations'
export default {
components: { InputPasswordConfirmation, InputEmail, LanguageSwitchSelect },
@ -172,26 +175,10 @@ export default {
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null
},
resetForm() {
this.form = {
firstname: '',
lastname: '',
email: '',
password: {
password: '',
passwordRepeat: '',
},
agree: false,
}
this.language = ''
this.$nextTick(() => {
this.$refs.observer.reset()
})
},
async onSubmit() {
this.$apollo
.query({
query: resgisterUserQuery,
.mutate({
mutation: registerUser,
variables: {
email: this.form.email,
firstName: this.form.firstname,
@ -238,7 +225,7 @@ export default {
return this.form.email !== ''
},
languageFilled() {
return this.language !== null && this.language !== ''
return !!this.language
},
},
}

View File

@ -7,6 +7,7 @@ import flushPromises from 'flush-promises'
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockRejectedValue({ message: 'error' })
const apolloMutationMock = jest.fn()
const toasterMock = jest.fn()
const routerPushMock = jest.fn()
@ -36,6 +37,7 @@ describe('ResetPassword', () => {
}),
},
$apollo: {
mutate: apolloMutationMock,
query: apolloQueryMock,
},
}
@ -69,8 +71,10 @@ describe('ResetPassword', () => {
})
it('has a message suggesting to contact the support', () => {
expect(wrapper.find('div.header').text()).toContain('reset-password.title')
expect(wrapper.find('div.header').text()).toContain('reset-password.not-authenticated')
expect(wrapper.find('div.header').text()).toContain('settings.password.reset')
expect(wrapper.find('div.header').text()).toContain(
'settings.password.reset-password.not-authenticated',
)
})
})
@ -97,8 +101,10 @@ describe('ResetPassword', () => {
describe('Register header', () => {
it('has a welcome message', async () => {
expect(wrapper.find('div.header').text()).toContain('reset-password.title')
expect(wrapper.find('div.header').text()).toContain('reset-password.text')
expect(wrapper.find('div.header').text()).toContain('settings.password.reset')
expect(wrapper.find('div.header').text()).toContain(
'settings.password.reset-password.text',
)
})
})
@ -138,15 +144,15 @@ describe('ResetPassword', () => {
beforeEach(async () => {
await wrapper.setData({ authenticated: true, sessionId: 1 })
await wrapper.vm.$nextTick()
await wrapper.findAll('input').at(0).setValue('Aa123456')
await wrapper.findAll('input').at(1).setValue('Aa123456')
await wrapper.findAll('input').at(0).setValue('Aa123456_')
await wrapper.findAll('input').at(1).setValue('Aa123456_')
await flushPromises()
await wrapper.find('form').trigger('submit')
})
describe('server response with error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({ message: 'error' })
apolloMutationMock.mockRejectedValue({ message: 'error' })
})
it('toasts an error message', () => {
expect(toasterMock).toHaveBeenCalledWith('error')
@ -155,19 +161,19 @@ describe('ResetPassword', () => {
describe('server response with success', () => {
beforeEach(() => {
apolloQueryMock.mockResolvedValue({
apolloMutationMock.mockResolvedValue({
data: {
resetPassword: 'success',
},
})
})
it('calls the API', () => {
expect(apolloQueryMock).toBeCalledWith(
expect(apolloMutationMock).toBeCalledWith(
expect.objectContaining({
variables: {
sessionId: 1,
email: 'user@example.org',
password: 'Aa123456',
password: 'Aa123456_',
},
}),
)

View File

@ -5,13 +5,13 @@
<div class="header-body text-center mb-7">
<b-row class="justify-content-center">
<b-col xl="5" lg="6" md="8" class="px-2">
<h1>{{ $t('reset-password.title') }}</h1>
<h1>{{ $t('settings.password.reset') }}</h1>
<div class="pb-4" v-if="!pending">
<span v-if="authenticated">
{{ $t('reset-password.text') }}
{{ $t('settings.password.reset-password.text') }}
</span>
<span v-else>
{{ $t('reset-password.not-authenticated') }}
{{ $t('settings.password.reset-password.not-authenticated') }}
</span>
</div>
</b-col>
@ -29,7 +29,7 @@
<input-password-confirmation v-model="form" :register="register" />
<div class="text-center">
<b-button type="submit" variant="primary" class="mt-4">
{{ $t('reset') }}
{{ $t('settings.password.reset') }}
</b-button>
</div>
</b-form>
@ -48,7 +48,8 @@
</template>
<script>
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation'
import { resetPassword, loginViaEmailVerificationCode } from '../../graphql/queries'
import { loginViaEmailVerificationCode } from '../../graphql/queries'
import { resetPassword } from '../../graphql/mutations'
export default {
name: 'ResetPassword',
@ -71,8 +72,8 @@ export default {
methods: {
async onSubmit() {
this.$apollo
.query({
query: resetPassword,
.mutate({
mutation: resetPassword,
variables: {
sessionId: this.sessionId,
email: this.email,

View File

@ -1,5 +1,5 @@
<template>
<b-card class="bg-transparent">
<b-card class="bg-transparent border-0">
<div class="w-100 text-center">
<vue-qrcode
v-if="$store.state.email"

View File

@ -29,7 +29,7 @@ describe('UserCard_FormUserData', () => {
error: toastErrorMock,
},
$apollo: {
query: mockAPIcall,
mutate: mockAPIcall,
},
}
@ -139,7 +139,7 @@ describe('UserCard_FormUserData', () => {
})
it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('site.profil.user-data.change-success')
expect(toastSuccessMock).toBeCalledWith('settings.name.change-success')
})
it('has an edit button again', () => {

View File

@ -1,10 +1,10 @@
<template>
<b-card id="userdata_form" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-card id="userdata_form" class="card-border-radius card-background-gray">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
<span class="pointer mr-3">{{ $t('settings.name.change-name') }}</span>
<b-icon v-if="showUserData" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
@ -72,7 +72,7 @@
</b-card>
</template>
<script>
import { updateUserInfos } from '../../../graphql/queries'
import { updateUserInfos } from '../../../graphql/mutations'
export default {
name: 'FormUserData',
@ -108,8 +108,8 @@ export default {
async onSubmit(event) {
event.preventDefault()
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
email: this.$store.state.email,
firstName: this.form.firstName,
@ -122,7 +122,7 @@ export default {
this.$store.commit('lastName', this.form.lastName)
this.$store.commit('description', this.form.description)
this.showUserData = true
this.$toasted.success(this.$t('site.profil.user-data.change-success'))
this.$toasted.success(this.$t('settings.name.change-success'))
})
.catch((error) => {
this.$toasted.error(error.message)

View File

@ -21,7 +21,7 @@ describe('UserCard_FormUserMail', () => {
},
},
$apollo: {
query: mockAPIcall,
mutate: mockAPIcall,
},
}

View File

@ -32,7 +32,7 @@
</b-card>
</template>
<script>
import { updateUserInfos } from '../../../graphql/queries'
import { updateUserInfos } from '../../../graphql/mutations'
export default {
name: 'FormUserMail',
@ -45,8 +45,8 @@ export default {
methods: {
async onSubmit() {
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
email: this.$store.state.email,
newEmail: this.newEmail,

View File

@ -25,7 +25,7 @@ describe('UserCard_FormUserPasswort', () => {
error: toastErrorMock,
},
$apollo: {
query: changePasswordProfileMock,
mutate: changePasswordProfileMock,
},
}
@ -47,7 +47,7 @@ describe('UserCard_FormUserPasswort', () => {
})
it('has a change password button with text "form.change-password"', () => {
expect(wrapper.find('a').text()).toEqual('form.change-password')
expect(wrapper.find('a').text()).toEqual('settings.password.change-password')
})
it('has a change password button with a pencil icon', () => {
@ -105,12 +105,14 @@ describe('UserCard_FormUserPasswort', () => {
describe('validation', () => {
it('displays all password requirements', () => {
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(5)
expect(feedbackArray).toHaveLength(7)
expect(feedbackArray.at(0).text()).toBe('validations.messages.required')
expect(feedbackArray.at(1).text()).toBe('site.signup.lowercase')
expect(feedbackArray.at(2).text()).toBe('site.signup.uppercase')
expect(feedbackArray.at(3).text()).toBe('site.signup.one_number')
expect(feedbackArray.at(4).text()).toBe('site.signup.minimum')
expect(feedbackArray.at(5).text()).toBe('site.signup.special-char')
expect(feedbackArray.at(6).text()).toBe('site.signup.no-whitespace')
})
it('removes first message when a character is given', async () => {
@ -125,7 +127,7 @@ describe('UserCard_FormUserPasswort', () => {
await wrapper.findAll('input').at(1).setValue('a')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(3)
expect(feedbackArray).toHaveLength(4)
expect(feedbackArray.at(0).text()).toBe('site.signup.uppercase')
})
@ -133,7 +135,7 @@ describe('UserCard_FormUserPasswort', () => {
await wrapper.findAll('input').at(1).setValue('Aa')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(2)
expect(feedbackArray).toHaveLength(3)
expect(feedbackArray.at(0).text()).toBe('site.signup.one_number')
})
@ -141,14 +143,22 @@ describe('UserCard_FormUserPasswort', () => {
await wrapper.findAll('input').at(1).setValue('Aa1')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(1)
expect(feedbackArray).toHaveLength(2)
expect(feedbackArray.at(0).text()).toBe('site.signup.minimum')
})
it('removes all messages when all rules are fulfilled', async () => {
it('removes the first five messages when a eight lowercase, uppercase and numeric characters are given', async () => {
await wrapper.findAll('input').at(1).setValue('Aa123456')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(1)
expect(feedbackArray.at(0).text()).toBe('site.signup.special-char')
})
it('removes all messages when a eight lowercase, uppercase and numeric characters are given', async () => {
await wrapper.findAll('input').at(1).setValue('Aa123456_')
await flushPromises()
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
expect(feedbackArray).toHaveLength(0)
})
})
@ -164,8 +174,8 @@ describe('UserCard_FormUserPasswort', () => {
},
})
await form.findAll('input').at(0).setValue('1234')
await form.findAll('input').at(1).setValue('Aa123456')
await form.findAll('input').at(2).setValue('Aa123456')
await form.findAll('input').at(1).setValue('Aa123456_')
await form.findAll('input').at(2).setValue('Aa123456_')
await form.trigger('submit')
await flushPromises()
})
@ -176,7 +186,7 @@ describe('UserCard_FormUserPasswort', () => {
variables: {
email: 'user@example.org',
password: '1234',
passwordNew: 'Aa123456',
passwordNew: 'Aa123456_',
},
}),
)
@ -197,8 +207,8 @@ describe('UserCard_FormUserPasswort', () => {
message: 'error',
})
await form.findAll('input').at(0).setValue('1234')
await form.findAll('input').at(1).setValue('Aa123456')
await form.findAll('input').at(2).setValue('Aa123456')
await form.findAll('input').at(1).setValue('Aa123456_')
await form.findAll('input').at(2).setValue('Aa123456_')
await form.trigger('submit')
await flushPromises()
})

View File

@ -1,10 +1,10 @@
<template>
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-card id="change_pwd" class="card-border-radius card-background-gray">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showPassword ? (showPassword = !showPassword) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
<span class="pointer mr-3">{{ $t('settings.password.change-password') }}</span>
<b-icon v-if="showPassword" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
@ -42,7 +42,7 @@
<script>
import InputPassword from '../../../components/Inputs/InputPassword'
import InputPasswordConfirmation from '../../../components/Inputs/InputPasswordConfirmation'
import { updateUserInfos } from '../../../graphql/queries'
import { updateUserInfos } from '../../../graphql/mutations'
export default {
name: 'FormUserPasswort',
@ -73,8 +73,8 @@ export default {
},
async onSubmit() {
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
email: this.$store.state.email,
password: this.form.password,

View File

@ -35,7 +35,7 @@ describe('UserCard_FormUsername', () => {
error: toastErrorMock,
},
$apollo: {
query: mockAPIcall,
mutate: mockAPIcall,
},
}
@ -125,7 +125,7 @@ describe('UserCard_FormUsername', () => {
})
it('toasts an success message', () => {
expect(toastSuccessMock).toBeCalledWith('site.profil.user-data.change-success')
expect(toastSuccessMock).toBeCalledWith('settings.name.change-success')
})
it('has no edit button anymore', () => {

View File

@ -67,7 +67,7 @@
</b-card>
</template>
<script>
import { updateUserInfos } from '../../../graphql/queries'
import { updateUserInfos } from '../../../graphql/mutations'
export default {
name: 'FormUsername',
@ -87,8 +87,8 @@ export default {
},
async onSubmit() {
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
email: this.$store.state.email,
username: this.form.username,
@ -98,7 +98,7 @@ export default {
this.$store.commit('username', this.form.username)
this.username = this.form.username
this.showUsername = true
this.$toasted.success(this.$t('site.profil.user-data.change-success'))
this.$toasted.success(this.$t('settings.name.change-success'))
})
.catch((error) => {
this.$toasted.error(error.message)

View File

@ -25,7 +25,7 @@ describe('UserCard_Language', () => {
error: toastErrorMock,
},
$apollo: {
query: mockAPIcall,
mutate: mockAPIcall,
},
}

View File

@ -1,14 +1,10 @@
<template>
<b-card
id="formuserlanguage"
class="bg-transparent"
style="background-color: #ebebeba3 !important"
>
<b-card id="formuserlanguage" class="card-border-radius card-background-gray">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a @click="showLanguage ? (showLanguage = !showLanguage) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.changeLanguage') }}</span>
<span class="pointer mr-3">{{ $t('settings.language.changeLanguage') }}</span>
<b-icon v-if="showLanguage" class="pointer ml-3" icon="pencil"></b-icon>
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
@ -23,13 +19,13 @@
<b>{{ $t('language') }}</b>
</small>
</b-col>
<b-col class="col-12">{{ $store.state.language }}</b-col>
<b-col class="col-12">{{ $t(buildTagFromLanguageString()) }}</b-col>
</b-row>
</div>
<div v-else>
<div>
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-form @submit.stop.prevent="onSubmit">
<b-row class="mb-2">
<b-col class="col-12">
<small>
@ -46,7 +42,6 @@
<div class="text-right" ref="submitButton">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
@ -62,8 +57,9 @@
</b-card>
</template>
<script>
import { localeChanged } from 'vee-validate'
import LanguageSwitchSelect from '../../../components/LanguageSwitchSelect.vue'
import { updateUserInfos } from '../../../graphql/queries'
import { updateUserInfos } from '../../../graphql/mutations'
export default {
name: 'FormUserLanguage',
@ -87,22 +83,31 @@ export default {
cancelEdit() {
this.showLanguage = true
},
async onSubmit() {
this.$apollo
.query({
query: updateUserInfos,
.mutate({
mutation: updateUserInfos,
variables: {
language: this.$store.state.language,
email: this.$store.state.email,
locale: this.language,
},
})
.then(() => {
this.$store.commit('language', this.language)
this.$i18n.locale = this.language
localeChanged(this.language)
this.cancelEdit()
this.$toasted.success(this.$t('settings.language.success'))
})
.catch((error) => {
this.language = this.$store.state.language
this.$toasted.error(error.message)
})
},
buildTagFromLanguageString() {
return 'languages.' + this.$store.state.language
},
},
}
</script>

View File

@ -74,7 +74,7 @@ describe('UserCard_Newsletter', () => {
})
it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('setting.newsletterFalse')
expect(toastSuccessMock).toBeCalledWith('settings.newsletter.newsletterFalse')
})
})

View File

@ -1,14 +1,10 @@
<template>
<b-card
id="formusernewsletter"
class="bg-transparent"
style="background-color: #ebebeba3 !important"
>
<b-card id="formusernewsletter" class="card-border-radius card-background-gray">
<div>
<b-row class="mb-3">
<b-col class="mb-2 col-12">
<small>
<b>{{ $t('setting.newsletter') }}</b>
<b>{{ $t('settings.newsletter.newsletter') }}</b>
</small>
</b-col>
<b-col class="col-12">
@ -19,7 +15,11 @@
switch
@change="onSubmit"
>
{{ newsletterState ? $t('setting.newsletterTrue') : $t('setting.newsletterFalse') }}
{{
newsletterState
? $t('settings.newsletter.newsletterTrue')
: $t('settings.newsletter.newsletterFalse')
}}
</b-form-checkbox>
</b-col>
</b-row>
@ -50,8 +50,8 @@ export default {
this.$store.commit('newsletterState', this.newsletterState)
this.$toasted.success(
this.newsletterState
? this.$t('setting.newsletterTrue')
: this.$t('setting.newsletterFalse'),
? this.$t('settings.newsletter.newsletterTrue')
: this.$t('settings.newsletter.newsletterFalse'),
)
})
.catch((error) => {

View File

@ -35,4 +35,16 @@ export default {
},
}
</script>
<style></style>
<style>
.card-border-radius {
border-radius: 0px 5px 5px 0px !important;
}
@media screen and (max-width: 1235px) {
.card-border-radius {
border-radius: 0px !important;
}
}
.card-background-gray {
background-color: #ebebeba3 !important;
}
</style>

View File

@ -1,6 +1,7 @@
import { createLocalVue } from '@vue/test-utils'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import Vuex from 'vuex'
import Vue from 'vue'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
import * as rules from 'vee-validate/dist/rules'
@ -47,3 +48,8 @@ global.localVue.component('validation-provider', ValidationProvider)
global.localVue.component('validation-observer', ValidationObserver)
global.localVue.directive('click-outside', clickOutside)
global.localVue.directive('focus', focus)
// throw errors for vue warnings to force the programmers to take care about warnings
Vue.config.warnHandler = (w) => {
throw new Error(w)
}

View File

@ -1,5 +1,6 @@
const path = require('path')
const dotenv = require('dotenv-webpack')
const webpack = require('webpack')
const Dotenv = require('dotenv-webpack')
// vue.config.js
module.exports = {
@ -23,8 +24,17 @@ module.exports = {
assets: path.join(__dirname, 'src/assets'),
},
},
// eslint-disable-next-line new-cap
plugins: [new dotenv()],
plugins: [
new Dotenv(),
new webpack.DefinePlugin({
// Those are Environment Variables transmitted via Docker
// 'process.env.DOCKER_WORKDIR': JSON.stringify(process.env.DOCKER_WORKDIR),
// 'process.env.BUILD_DATE': JSON.stringify(process.env.BUILD_DATE),
// 'process.env.BUILD_VERSION': JSON.stringify(process.env.BUILD_VERSION),
'process.env.BUILD_COMMIT': JSON.stringify(process.env.BUILD_COMMIT),
// 'process.env.PORT': JSON.stringify(process.env.PORT),
}),
],
},
css: {
// Enable CSS source maps.

Binary file not shown.

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-06-21 13:37+0200\n"
"PO-Revision-Date: 2021-06-21 13:38+0200\n"
"POT-Creation-Date: 2021-09-23 17:56+0200\n"
"PO-Revision-Date: 2021-09-27 13:31+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de_DE\n"
@ -455,11 +455,10 @@ msgstr "Gradido: Passwort zurücksetzen"
#: src/cpp/SingletonManager/SessionManager.cpp:604
msgid ""
"Please enter a valid password with at least 8 characters, upper and lower "
"case letters, at least one number and one special character (@$!%*?&+-_)!"
"case letters, at least one number and one special character!"
msgstr ""
"Bitte gebe ein gültiges Password ein mit mindestens 8 Zeichen, Groß- und "
"Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen (@$!%*?&+-_) "
"ein!"
"Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen!"
#: src/cpp/SingletonManager/SessionManager.cpp:610
msgid "Your password is to short!"
@ -478,8 +477,8 @@ msgid "Your password does not contain any number!"
msgstr "Dein Passwort enthält keine Zahlen!"
#: src/cpp/SingletonManager/SessionManager.cpp:630
msgid "Your password does not contain special characters (@$!%*?&+-)!"
msgstr "Dein Passwort enthält keine Sonderzeichen (@$!%*?&+-)!"
msgid "Your password does not contain special characters!"
msgstr "Dein Passwort enthält keine Sonderzeichen!"
#~ msgid "Account"
#~ msgstr "Konto"

View File

@ -57,6 +57,7 @@ namespace ServerConfig {
int g_FakeLoginSleepTime = 820;
std::string g_versionString = "";
bool g_disableEmail = false;
bool g_resendUnfinishedTransactionOnStart = false;
ServerSetupType g_ServerSetupType = SERVER_TYPE_PRODUCTION;
std::string g_devDefaultGroup = "";
std::string g_gRPCRelayServerFullURL;
@ -259,23 +260,7 @@ namespace ServerConfig {
g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_ALLOW_ALL_PASSWORDS);
}
g_gRPCRelayServerFullURL = cfg.getString("grpc.server", "");
// unsecure flags
//g_AllowUnsecureFlags
if (cfg.getInt("unsecure.allow_passwort_via_json_request", 0) == 1) {
g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_PASSWORD_REQUESTS);
}
if (cfg.getInt("unsecure.allow_auto_sign_transactions", 0) == 1) {
g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_AUTO_SIGN_TRANSACTIONS);
}
if (cfg.getInt("unsecure.allow_cors_all", 0) == 1) {
g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_CORS_ALL);
}
if (cfg.getInt("unsecure.allow_all_passwords", 0) == 1) {
g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_ALLOW_ALL_PASSWORDS);
}
g_resendUnfinishedTransactionOnStart = cfg.getBool("dev.resend_unfinished_transactions_on_start", false);
return true;
}

View File

@ -73,6 +73,7 @@ namespace ServerConfig {
extern int g_FakeLoginSleepTime;
extern std::string g_versionString;
extern bool g_disableEmail;
extern bool g_resendUnfinishedTransactionOnStart;
extern ServerSetupType g_ServerSetupType;
extern std::string g_devDefaultGroup;
extern std::string g_gRPCRelayServerFullURL;

View File

@ -46,22 +46,22 @@ bool SessionManager::init()
case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[^<>&;]{2,}$"); break;
case VALIDATE_USERNAME: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z][a-zA-Z0-9_-]*$"); break;
case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break;
case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-_])[A-Za-z0-9@$!%*?&+-_]{8,}$"); break;
case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9 \\t\\n\\r]).{8,}$"); break;
case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break;
case VALIDATE_GROUP_ALIAS: mValidations[i] = new Poco::RegularExpression("^[a-z0-9-]{3,120}"); break;
case VALIDATE_HEDERA_ID: mValidations[i] = new Poco::RegularExpression("^[0-9]*\.[0-9]*\.[0-9]\.$"); break;
case VALIDATE_HAS_NUMBER: mValidations[i] = new Poco::RegularExpression(".*[0-9].*"); break;
case VALIDATE_HAS_NUMBER: mValidations[i] = new Poco::RegularExpression("[0-9]"); break;
case VALIDATE_ONLY_INTEGER: mValidations[i] = new Poco::RegularExpression("^[0-9]*$"); break;
case VALIDATE_ONLY_DECIMAL: mValidations[i] = new Poco::RegularExpression("^[0-9]*(\.|,)[0-9]*$"); break;
case VALIDATE_ONLY_HEX: mValidations[i] = new Poco::RegularExpression("^(0x)?[a-fA-F0-9]*$"); break;
//case VALIDATE_ONLY_URL: mValidations[i] = new Poco::RegularExpression("^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}$"); break;
case VALIDATE_ONLY_URL: mValidations[i] = new Poco::RegularExpression("^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\/?"); break;
case VALIDATE_HAS_SPECIAL_CHARACTER: mValidations[i] = new Poco::RegularExpression(".*[@$!%*?&+-].*"); break;
case VALIDATE_HAS_SPECIAL_CHARACTER: mValidations[i] = new Poco::RegularExpression("[^a-zA-Z0-9 \\t\\n\\r]"); break;
case VALIDATE_HAS_UPPERCASE_LETTER:
mValidations[i] = new Poco::RegularExpression(".*[A-Z].*");
mValidations[i] = new Poco::RegularExpression("[A-Z]");
ServerConfig::g_ServerKeySeed->put(i, DRRandom::r64());
break;
case VALIDATE_HAS_LOWERCASE_LETTER: mValidations[i] = new Poco::RegularExpression(".*[a-z].*"); break;
case VALIDATE_HAS_LOWERCASE_LETTER: mValidations[i] = new Poco::RegularExpression("[a-z]"); break;
default: printf("[SessionManager::%s] unknown validation type\n", __FUNCTION__);
}
}
@ -601,7 +601,7 @@ bool SessionManager::checkPwdValidation(const std::string& pwd, NotificationList
if (!isValid(pwd, VALIDATE_PASSWORD)) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character (@$!%*?&+-_)!")));
lang->gettext("Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!")));
// @$!%*?&+-
if (pwd.size() < 8) {
@ -627,7 +627,7 @@ bool SessionManager::checkPwdValidation(const std::string& pwd, NotificationList
else if (!isValid(pwd, VALIDATE_HAS_SPECIAL_CHARACTER)) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Your password does not contain special characters (@$!%*?&+-)!")));
lang->gettext("Your password does not contain special characters!")));
}
return false;

View File

@ -204,7 +204,7 @@ namespace model {
}
}
// try not finished but signed transactions again
if (!finished) {
if (!finished && ServerConfig::g_resendUnfinishedTransactionOnStart) {
transaction->ifEnoughSignsProceed(nullptr);
}

View File

@ -85,7 +85,7 @@ enum PageState {
<form method="POST">
<p>
Bitte denke dir ein sicheres Passwort aus, das mindestens 8 Zeichen lang ist, einen Klein- und einen Gro&szlig;buchstaben enth&auml;lt,
eine Zahl und eines der folgenden Sonderzeichen: @$!%*?&+-
eine Zahl und ein Sonderzeichen.
</p>
<label class="form-label" for="register-password">Passwort</label>
<input class="form-control" id="register-password" type="password" name="register-password"/>

View File

@ -7,7 +7,22 @@ WORKDIR /go/src/github.com/skeema/skeema
RUN go install github.com/skeema/skeema@v1.5.3
#########################################################################################################
# Run skeema
# Run skeema for dev (dynamic)
#########################################################################################################
FROM skeema_build as skeema_dev_run
ENV DOCKER_WORKDIR="/skeema"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
COPY ./skeema/.skeema .
COPY ./mariadb/.skeema.login .
CMD cp .skeema.login ./gradido_login/.skeema && skeema push --allow-unsafe && rm ./gradido_login/.skeema
#########################################################################################################
# Run skeema
#########################################################################################################
FROM skeema_build as skeema_run
@ -20,4 +35,5 @@ COPY ./skeema/.skeema .
COPY ./login_server/skeema/ .
COPY ./mariadb/.skeema.login ./gradido_login/.skeema
CMD skeema push --allow-unsafe
CMD skeema push --allow-unsafe