mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'frontend_commit_hash' of github.com:gradido/gradido into frontend_commit_hash
This commit is contained in:
commit
1e792c32e3
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ messages.pot
|
|||||||
.skeema
|
.skeema
|
||||||
nbproject
|
nbproject
|
||||||
.metadata
|
.metadata
|
||||||
|
/.env
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -31,3 +31,6 @@
|
|||||||
[submodule "login_server/src/proto"]
|
[submodule "login_server/src/proto"]
|
||||||
path = login_server/src/proto
|
path = login_server/src/proto
|
||||||
url = https://github.com/gradido/gradido_protocol.git
|
url = https://github.com/gradido/gradido_protocol.git
|
||||||
|
[submodule "login_server/dependencies/protobuf"]
|
||||||
|
path = login_server/dependencies/protobuf
|
||||||
|
url = https://github.com/protocolbuffers/protobuf.git
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"apollo-server-express": "^2.25.2",
|
"apollo-server-express": "^2.25.2",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"class-validator": "^0.13.1",
|
"class-validator": "^0.13.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"graphql": "^15.5.1",
|
"graphql": "^15.5.1",
|
||||||
|
|||||||
23
backend/src/auth/auth.ts
Normal file
23
backend/src/auth/auth.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { AuthChecker } from 'type-graphql'
|
||||||
|
import decode from '../jwt/decode'
|
||||||
|
import { apiGet } from '../apis/loginAPI'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
import encode from '../jwt/encode'
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
|
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
|
||||||
|
if (context.token) {
|
||||||
|
const decoded = decode(context.token)
|
||||||
|
if (decoded.sessionId && decoded.sessionId !== 0) {
|
||||||
|
const result = await apiGet(
|
||||||
|
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
|
||||||
|
)
|
||||||
|
context.sessionId = decoded.sessionId
|
||||||
|
context.setHeaders.push({ key: 'token', value: encode(decoded.sessionId) })
|
||||||
|
return result.success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@ -17,9 +17,6 @@ export class GdtTransactionInput {
|
|||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class GdtTransactionSessionIdInput {
|
export class GdtTransactionSessionIdInput {
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => Int, { nullable: true })
|
@Field(() => Int, { nullable: true })
|
||||||
currentPage?: number
|
currentPage?: number
|
||||||
|
|
||||||
|
|||||||
@ -38,9 +38,6 @@ export class ChangePasswordArgs {
|
|||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class UpdateUserInfosArgs {
|
export class UpdateUserInfosArgs {
|
||||||
@Field(() => Number)
|
|
||||||
sessionId!: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email!: string
|
email!: string
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,6 @@ import { ArgsType, Field, Int } from 'type-graphql'
|
|||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class TransactionListInput {
|
export class TransactionListInput {
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => Int)
|
@Field(() => Int)
|
||||||
firstPage: number
|
firstPage: number
|
||||||
|
|
||||||
@ -17,9 +14,6 @@ export class TransactionListInput {
|
|||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class TransactionSendArgs {
|
export class TransactionSendArgs {
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export enum GdtEntryType {
|
|||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class GdtEntry {
|
export class GdtEntry {
|
||||||
constructor(json: any) {
|
constructor(json: any) {
|
||||||
|
this.id = json.id
|
||||||
this.amount = json.amount
|
this.amount = json.amount
|
||||||
this.date = json.date
|
this.date = json.date
|
||||||
this.email = json.email
|
this.email = json.email
|
||||||
@ -27,6 +28,9 @@ export class GdtEntry {
|
|||||||
this.gdt = json.gdt
|
this.gdt = json.gdt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
id: number
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Number)
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
import { User } from './User'
|
|
||||||
|
|
||||||
// temporaray solution until we have JWT implemented
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class LoginResponse {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.sessionId = json.session_id
|
|
||||||
this.user = new User(json.user)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => User)
|
|
||||||
user: User
|
|
||||||
}
|
|
||||||
@ -1,13 +1,17 @@
|
|||||||
import { Resolver, Query, /* Mutation, */ Arg } from 'type-graphql'
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { Balance } from '../models/Balance'
|
import { Balance } from '../models/Balance'
|
||||||
import { apiGet } from '../../apis/loginAPI'
|
import { apiGet } from '../../apis/loginAPI'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class BalanceResolver {
|
export class BalanceResolver {
|
||||||
|
@Authorized()
|
||||||
@Query(() => Balance)
|
@Query(() => Balance)
|
||||||
async balance(@Arg('sessionId') sessionId: number): Promise<Balance> {
|
async balance(@Ctx() context: any): Promise<Balance> {
|
||||||
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
|
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + context.sessionId)
|
||||||
if (!result.success) throw new Error(result.data)
|
if (!result.success) throw new Error(result.data)
|
||||||
return new Balance(result.data)
|
return new Balance(result.data)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
// import jwt from 'jsonwebtoken'
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Resolver, Query, /* Mutation, */ Args } from 'type-graphql'
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { GdtEntryList } from '../models/GdtEntryList'
|
import { GdtEntryList } from '../models/GdtEntryList'
|
||||||
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
|
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
|
||||||
@ -7,14 +9,16 @@ import { apiGet } from '../../apis/loginAPI'
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class GdtResolver {
|
export class GdtResolver {
|
||||||
|
@Authorized()
|
||||||
@Query(() => GdtEntryList)
|
@Query(() => GdtEntryList)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async listGDTEntries(
|
async listGDTEntries(
|
||||||
@Args()
|
@Args()
|
||||||
{ currentPage = 1, pageSize = 5, order = 'DESC', sessionId }: GdtTransactionSessionIdInput,
|
{ currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput,
|
||||||
|
@Ctx() context: any,
|
||||||
): Promise<GdtEntryList> {
|
): Promise<GdtEntryList> {
|
||||||
const result = await apiGet(
|
const result = await apiGet(
|
||||||
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${sessionId}`,
|
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${context.sessionId}`,
|
||||||
)
|
)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.data)
|
throw new Error(result.data)
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import { Resolver, Query, Args } from 'type-graphql'
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { Resolver, Query, Args, Authorized, Ctx } from 'type-graphql'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { TransactionList } from '../models/Transaction'
|
import { TransactionList } from '../models/Transaction'
|
||||||
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
|
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
|
||||||
@ -6,23 +9,27 @@ import { apiGet, apiPost } from '../../apis/loginAPI'
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class TransactionResolver {
|
export class TransactionResolver {
|
||||||
|
@Authorized()
|
||||||
@Query(() => TransactionList)
|
@Query(() => TransactionList)
|
||||||
async transactionList(
|
async transactionList(
|
||||||
@Args() { sessionId, firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
|
@Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
|
||||||
|
@Ctx() context: any,
|
||||||
): Promise<TransactionList> {
|
): Promise<TransactionList> {
|
||||||
const result = await apiGet(
|
const result = await apiGet(
|
||||||
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
|
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${context.sessionId}`,
|
||||||
)
|
)
|
||||||
if (!result.success) throw new Error(result.data)
|
if (!result.success) throw new Error(result.data)
|
||||||
return new TransactionList(result.data)
|
return new TransactionList(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized()
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async sendCoins(
|
async sendCoins(
|
||||||
@Args() { sessionId, email, amount, memo }: TransactionSendArgs,
|
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||||
|
@Ctx() context: any,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const payload = {
|
const payload = {
|
||||||
session_id: sessionId,
|
session_id: context.sessionId,
|
||||||
target_email: email,
|
target_email: email,
|
||||||
amount: amount * 10000,
|
amount: amount * 10000,
|
||||||
memo,
|
memo,
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
// import jwt from 'jsonwebtoken'
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Resolver, Query, Args, Arg } from 'type-graphql'
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
|
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
|
||||||
import { LoginResponse } from '../models/LoginResponse'
|
|
||||||
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
|
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
|
||||||
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
|
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
|
||||||
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
|
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
|
||||||
|
import { User } from '../models/User'
|
||||||
|
import encode from '../../jwt/encode'
|
||||||
import {
|
import {
|
||||||
ChangePasswordArgs,
|
ChangePasswordArgs,
|
||||||
CheckUsernameArgs,
|
CheckUsernameArgs,
|
||||||
@ -17,8 +20,8 @@ import { apiPost, apiGet } from '../../apis/loginAPI'
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
@Query(() => LoginResponse)
|
@Query(() => User)
|
||||||
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<LoginResponse> {
|
async login(@Args() { email, password }: UnsecureLoginArgs, @Ctx() context: any): Promise<User> {
|
||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||||
|
|
||||||
@ -27,21 +30,9 @@ export class UserResolver {
|
|||||||
throw new Error(result.data)
|
throw new Error(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary solution until we have JWT implemented
|
context.setHeaders.push({ key: 'token', value: encode(result.data.session_id) })
|
||||||
return new LoginResponse(result.data)
|
|
||||||
|
|
||||||
// create and return the json web token
|
return new User(result.data.user)
|
||||||
// The expire doesn't help us here. The client needs to track when the token expires on its own,
|
|
||||||
// since every action prolongs the time the session is valid.
|
|
||||||
/*
|
|
||||||
return jwt.sign(
|
|
||||||
{ result, role: 'todo' },
|
|
||||||
CONFIG.JWT_SECRET, // * , { expiresIn: CONFIG.JWT_EXPIRES_IN } ,
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
// return (await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', login)).result.data
|
|
||||||
// const loginResult: LoginResult = await loginAPI.login(data)
|
|
||||||
// return loginResult.user ? loginResult.user : new User()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => LoginViaVerificationCode)
|
@Query(() => LoginViaVerificationCode)
|
||||||
@ -59,9 +50,10 @@ export class UserResolver {
|
|||||||
return new LoginViaVerificationCode(result.data)
|
return new LoginViaVerificationCode(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized()
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async logout(@Arg('sessionId') sessionId: number): Promise<string> {
|
async logout(@Ctx() context: any): Promise<string> {
|
||||||
const payload = { session_id: sessionId }
|
const payload = { session_id: context.sessionId }
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
|
const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.data)
|
throw new Error(result.data)
|
||||||
@ -115,11 +107,11 @@ export class UserResolver {
|
|||||||
return 'sucess'
|
return 'sucess'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized()
|
||||||
@Query(() => UpdateUserInfosResponse)
|
@Query(() => UpdateUserInfosResponse)
|
||||||
async updateUserInfos(
|
async updateUserInfos(
|
||||||
@Args()
|
@Args()
|
||||||
{
|
{
|
||||||
sessionId,
|
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@ -129,9 +121,10 @@ export class UserResolver {
|
|||||||
password,
|
password,
|
||||||
passwordNew,
|
passwordNew,
|
||||||
}: UpdateUserInfosArgs,
|
}: UpdateUserInfosArgs,
|
||||||
|
@Ctx() context: any,
|
||||||
): Promise<UpdateUserInfosResponse> {
|
): Promise<UpdateUserInfosResponse> {
|
||||||
const payload = {
|
const payload = {
|
||||||
session_id: sessionId,
|
session_id: context.sessionId,
|
||||||
email,
|
email,
|
||||||
update: {
|
update: {
|
||||||
'User.first_name': firstName || undefined,
|
'User.first_name': firstName || undefined,
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import cors from 'cors'
|
||||||
import { buildSchema } from 'type-graphql'
|
import { buildSchema } from 'type-graphql'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import { RowDataPacket } from 'mysql2/promise'
|
import { RowDataPacket } from 'mysql2/promise'
|
||||||
@ -8,17 +11,31 @@ import connection from './database/connection'
|
|||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
// TODO move to extern
|
// TODO move to extern
|
||||||
// import { BookResolver } from './graphql/resolvers/BookResolver'
|
|
||||||
import { UserResolver } from './graphql/resolvers/UserResolver'
|
import { UserResolver } from './graphql/resolvers/UserResolver'
|
||||||
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
|
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
|
||||||
import { GdtResolver } from './graphql/resolvers/GdtResolver'
|
import { GdtResolver } from './graphql/resolvers/GdtResolver'
|
||||||
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
|
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
|
||||||
|
|
||||||
|
import { isAuthorized } from './auth/auth'
|
||||||
|
|
||||||
// TODO implement
|
// TODO implement
|
||||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||||
|
|
||||||
const DB_VERSION = '0001-init_db'
|
const DB_VERSION = '0001-init_db'
|
||||||
|
|
||||||
|
const context = (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() {
|
async function main() {
|
||||||
// check for correct database version
|
// check for correct database version
|
||||||
const con = await connection()
|
const con = await connection()
|
||||||
@ -34,6 +51,7 @@ async function main() {
|
|||||||
// const connection = await createConnection()
|
// const connection = await createConnection()
|
||||||
const schema = await buildSchema({
|
const schema = await buildSchema({
|
||||||
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
|
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
|
||||||
|
authChecker: isAuthorized,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Graphiql interface
|
// Graphiql interface
|
||||||
@ -45,8 +63,31 @@ async function main() {
|
|||||||
// Express Server
|
// Express Server
|
||||||
const server = express()
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// Apollo Server
|
// Apollo Server
|
||||||
const apollo = new ApolloServer({ schema, playground })
|
const apollo = new ApolloServer({ schema, playground, context, plugins })
|
||||||
apollo.applyMiddleware({ app: server })
|
apollo.applyMiddleware({ app: server })
|
||||||
|
|
||||||
// Start Server
|
// Start Server
|
||||||
|
|||||||
20
backend/src/jwt/decode.ts
Normal file
20
backend/src/jwt/decode.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import CONFIG from '../config/'
|
||||||
|
|
||||||
|
export default (token: string): any => {
|
||||||
|
if (!token) return null
|
||||||
|
let sessionId = null
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
|
||||||
|
sessionId = decoded.sub
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
sessionId,
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
14
backend/src/jwt/encode.ts
Normal file
14
backend/src/jwt/encode.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import CONFIG from '../config/'
|
||||||
|
|
||||||
|
// Generate an Access Token
|
||||||
|
export default function encode(sessionId: string): string {
|
||||||
|
const token = jwt.sign({ sessionId }, CONFIG.JWT_SECRET, {
|
||||||
|
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||||
|
subject: sessionId.toString(),
|
||||||
|
})
|
||||||
|
return token
|
||||||
|
}
|
||||||
@ -21,7 +21,7 @@ $this->assign('title', __('GDT Kontoübersicht'));
|
|||||||
$header = '<h3>' . __('Zur Verfügung: ') . '</h3>';
|
$header = '<h3>' . __('Zur Verfügung: ') . '</h3>';
|
||||||
|
|
||||||
if($gdtSum > 0){
|
if($gdtSum > 0){
|
||||||
$header .= '<h2>'.$this->element('printGDT', ['number' => $gdtSum]).'</h2>';
|
$header .= '<h2>'.$this->element('printGDT', ['number' => $gdtSum*100.0]).'</h2>';
|
||||||
}
|
}
|
||||||
if($moreEntrysAsShown) {
|
if($moreEntrysAsShown) {
|
||||||
$header .= '<span>'. __('Nur die letzten 100 Einträge werden angezeigt!') . '</span>';
|
$header .= '<span>'. __('Nur die letzten 100 Einträge werden angezeigt!') . '</span>';
|
||||||
@ -47,8 +47,12 @@ $this->assign('header', $header);
|
|||||||
<div class="cell c3"><?= new FrozenTime($entry['date']) ?></div>
|
<div class="cell c3"><?= new FrozenTime($entry['date']) ?></div>
|
||||||
<div class="cell c0"><?= h($entry['comment']) ?></div>
|
<div class="cell c0"><?= h($entry['comment']) ?></div>
|
||||||
<div class="cell c3">
|
<div class="cell c3">
|
||||||
<?= $this->element('printEuro', ['number' => $entry['amount']]); ?>
|
<?php if(intval($entry['gdt_entry_type_id']) == 7) : ?>
|
||||||
<?php if($entry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $entry['amount2']]) ?>
|
<?= $this->element('printGDT', ['number' => $entry['amount']*100.0]); ?>
|
||||||
|
<?php else : ?>
|
||||||
|
<?= $this->element('printEuro', ['number' => $entry['amount']*100.0]); ?>
|
||||||
|
<?php if($entry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $entry['amount2']*100.0]) ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell c2">
|
<div class="cell c2">
|
||||||
<?= $this->Number->format($entry['factor']) ?>
|
<?= $this->Number->format($entry['factor']) ?>
|
||||||
@ -56,7 +60,7 @@ $this->assign('header', $header);
|
|||||||
<?= $this->Number->format($entry['factor2']) ?>
|
<?= $this->Number->format($entry['factor2']) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell c3"><?= $this->element('printGDT', ['number' => $entry['gdt']]) ?></div>
|
<div class="cell c3"><?= $this->element('printGDT', ['number' => $entry['gdt']*100.0]) ?></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
@ -89,8 +93,12 @@ $this->assign('header', $header);
|
|||||||
<!--<div class="cell c0"><?= h($elopageTransaction['email']) ?></div>-->
|
<!--<div class="cell c0"><?= h($elopageTransaction['email']) ?></div>-->
|
||||||
<div class="cell c3"><?= new FrozenTime($gdtEntry['date']) ?></div>
|
<div class="cell c3"><?= new FrozenTime($gdtEntry['date']) ?></div>
|
||||||
<div class="cell c3">
|
<div class="cell c3">
|
||||||
<?= $this->element('printEuro', ['number' => $gdtEntry['amount']]) ?>
|
<?php if(intval($gdtEntry['gdt_entry_type_id']) == 7) : ?>
|
||||||
<?php if($gdtEntry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $gdtEntry['amount2']]) ?>
|
<?= $this->element('printGDT', ['number' => $gdtEntry['amount']*100.0]); ?>
|
||||||
|
<?php else : ?>
|
||||||
|
<?= $this->element('printEuro', ['number' => $gdtEntry['amount']*100.0]); ?>
|
||||||
|
<?php if($gdtEntry['amount2']) echo ' + ' . $this->element('printEuro', ['number' => $gdtEntry['amount2']*100.0]) ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell c2">
|
<div class="cell c2">
|
||||||
<?= $this->Number->format($gdtEntry['factor']) ?>
|
<?= $this->Number->format($gdtEntry['factor']) ?>
|
||||||
@ -98,7 +106,7 @@ $this->assign('header', $header);
|
|||||||
<?= $this->Number->format($gdtEntry['factor2']) ?>
|
<?= $this->Number->format($gdtEntry['factor2']) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell c3"><?= $this->element('printGDT', ['number' => $gdtEntry['gdt']]) ?></div>
|
<div class="cell c3"><?= $this->element('printGDT', ['number' => $gdtEntry['gdt'] * 100.0]) ?></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -22,7 +22,8 @@ loginServer.db.user = root
|
|||||||
loginServer.db.password =
|
loginServer.db.password =
|
||||||
loginServer.db.port = 3306
|
loginServer.db.port = 3306
|
||||||
|
|
||||||
frontend.checkEmailPath = http://localhost/reset
|
frontend.checkEmailPath = vue/checkEmail
|
||||||
|
frontend.resetPasswordPath = vue/reset
|
||||||
|
|
||||||
email.disable = true
|
email.disable = true
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
# or NPM_BIN Path and NVM_DIR must be adjusted
|
# or NPM_BIN Path and NVM_DIR must be adjusted
|
||||||
|
|
||||||
cd /var/www/html/gradido
|
cd /var/www/html/gradido
|
||||||
eval "echo \"$(cat .env.local)\"" > .env
|
eval "echo \"$(cat .env.shell)\"" > .env
|
||||||
eval "echo \"$(cat .env.shell)\"" >> .env
|
|
||||||
cd frontend
|
cd frontend
|
||||||
|
|
||||||
NPM_BIN=/root/.nvm/versions/node/v12.19.0/bin/npm
|
NPM_BIN=/root/.nvm/versions/node/v12.19.0/bin/npm
|
||||||
|
|||||||
@ -13,7 +13,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- NODE_ENV="development"
|
- NODE_ENV="development"
|
||||||
# - DEBUG=true
|
# - DEBUG=true
|
||||||
- NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/`
|
|
||||||
volumes:
|
volumes:
|
||||||
# This makes sure the docker container has its own node modules.
|
# 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
|
# Therefore it is possible to have a different node version on the host machine
|
||||||
|
|||||||
@ -22,13 +22,12 @@ services:
|
|||||||
# Envs used in Dockerfile
|
# Envs used in Dockerfile
|
||||||
# - DOCKER_WORKDIR="/app"
|
# - DOCKER_WORKDIR="/app"
|
||||||
# - PORT=3000
|
# - PORT=3000
|
||||||
- BUILD_DATE
|
# - BUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||||
- BUILD_VERSION
|
# - BUILD_VERSION="0.0.0.0"
|
||||||
- BUILD_COMMIT
|
# - BUILD_COMMIT="0000000"
|
||||||
- NODE_ENV="production"
|
- NODE_ENV="production"
|
||||||
# Application only envs
|
env_file:
|
||||||
#- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp
|
- ./.env
|
||||||
#env_file:
|
|
||||||
# - ./frontend/.env
|
# - ./frontend/.env
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|||||||
53
docu/Concepts/Snippets/1-Blockchain/Roles.md
Normal file
53
docu/Concepts/Snippets/1-Blockchain/Roles.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Roles
|
||||||
|
User Roles also handled by blockchain and node servers
|
||||||
|
|
||||||
|
My Goal is to save not only the gradido transactions in blockchain but also
|
||||||
|
who is allowed to create new gradidos,
|
||||||
|
who allow joining new user to the group,
|
||||||
|
who allow connect to another group and
|
||||||
|
how the community decide which one is allowed to do this things.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
If this would be handled only by community-server everyone could be easly
|
||||||
|
overwrite this rules by using a modified client to send his transactions direct over
|
||||||
|
hedera or iota bypassing the community-server rules.
|
||||||
|
With hedera it is possible to only allow sending messages to a topic with the correct admin key,
|
||||||
|
but then is the admin the single point of failure. Also must the key saved on server to allow everyone
|
||||||
|
sending gradidos or the transactions will only be proccessed, when admin is logging in.
|
||||||
|
If we don't use blockchain technologie at all, we have a big single point of failure.
|
||||||
|
The Community-Server and everyone who has direct access to server and the admins of course.
|
||||||
|
But it would be much much simpler of course :)
|
||||||
|
|
||||||
|
In summary it is to make sure that the community is in power and no one can take over.
|
||||||
|
|
||||||
|
## How?
|
||||||
|
There is a special type of transactions with which users determine who can determine what.
|
||||||
|
This transaction control which signatures are neccessary for things like creation and so one.
|
||||||
|
For this I think different types are needed.
|
||||||
|
- *one*: The founder of group (or someone other choosen) decide everything, this is default from start
|
||||||
|
- *some*: a number of user must sign, set absolute count or relative count
|
||||||
|
- *most*: more than 1/2 or 3/4 must sign
|
||||||
|
- *all*: all member must sign, default for choose which mode will be used
|
||||||
|
- *one-council*: one member of council
|
||||||
|
- *some-council*: absolute or relative number of council members must sign
|
||||||
|
- *most-council*: more than 1/2 or 3/4 from council members must sign
|
||||||
|
- *all-council*: all members of council must sign
|
||||||
|
|
||||||
|
this configuration can be done for different types of action,
|
||||||
|
so the voting-mode for creation may differ from voting mode for
|
||||||
|
add new members to community. Also the council members for different actions
|
||||||
|
may differ.
|
||||||
|
Also how to vote for council members is an extra type of action.
|
||||||
|
|
||||||
|
## Veto
|
||||||
|
Especially for *some* and *some-council* maybe also for other types.
|
||||||
|
The users there could vote but haven't yet and not all need to vote,
|
||||||
|
can make a Veto with Explanation which reset all existing signs of current vote,
|
||||||
|
and is needed to sign by all which again vote for the case.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
With that setup all community consense models should be possible except Democracy.
|
||||||
|
Democracy needs a secret ballot. The votes on blockchain are open (at least if someone knows which
|
||||||
|
public-key belongs to which user). A secret ballot on blockchain is really hard. By my last
|
||||||
|
recherche I haven't found on. But maybe this can do the trick: https://secure.vote/
|
||||||
|
|
||||||
42
docu/Concepts/Snippets/Iota/ColoredCoins.md
Normal file
42
docu/Concepts/Snippets/Iota/ColoredCoins.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Wie Colored Coins in Iota funktionieren
|
||||||
|
## Schöpfung
|
||||||
|
- Colored Coins werden bei Iota mit einer speziellen Transaktion erzeugt
|
||||||
|
- Die Farbe des neuen Coins wird durch den Transaktionshash beschrieben
|
||||||
|
- Die einmal erzeugte Menge von Colored Coins ist fest
|
||||||
|
- Um die Menge zu erhöhen müssten neue Colored Coin erzeugt werden und die alten damit ausgetauscht werden (z.B. mittels SmartContract)
|
||||||
|
|
||||||
|
## Geldmenge
|
||||||
|
- Colored Coins basierend auf den normalen Iota-Coins somit werden soviele Iota-Coins benötigt wie man Colored-Coins braucht
|
||||||
|
- 2.779.530.283.000.000 Iota maximale Coinmenge
|
||||||
|
- Weltbevölkerung 2021: 7.915.559.780
|
||||||
|
- Pro Kopf Geldmenge Gradido: 53.476
|
||||||
|
- Benötigte Iota Coins für Gradido mit 4 Nachkommastellen mit 25% Puffer:
|
||||||
|
- 7.915.559.780 * 534.760.000 * 1.25 = 5.291.155.935.000.000.000
|
||||||
|
- Vorhandene Iota coins: 2.779.530.283.000.000
|
||||||
|
- Es sind nicht genügend Iota Coins vorhanden im die ganze Welt mit Gradido auf Basis von Iota versorgen zu können
|
||||||
|
|
||||||
|
## Kosten
|
||||||
|
- Kurs am 30.08.2021: 1 Miota = 0,84
|
||||||
|
- Bei Verwendung von 4 Nachkommastellen braucht es 10 Miota für 1.000 Colored Coins (Gradido) also Miota im Wert von 8,40€
|
||||||
|
- Aktuell (30.08.2021) geschöpfte Gradido Cent: 17.001.990.500
|
||||||
|
- Notwendige Miota: 17.002.0, Wert: 14.286,73 €
|
||||||
|
- Solange die Benutzer Kontrolle über ihre Keys haben können sie mit einem regulärem Iota Wallet die Gradidos wieder in Iota umwandeln (um z.B. der Vergänglichkeit zu entgehen)
|
||||||
|
- Mit 2 Nachkommastellen wird die Vergänglichkeit schon bei 100 Gradido und 1 Stunde ungenau
|
||||||
|
- 1 Stunde, 100 Gradido, Vergänglichkeit: 0,00576, Gradidos nach einer Stunde: 99,99424 GDD
|
||||||
|
- 1 Minute, 100 Gradido, Vergänglichkeit: 0,000096, Gradidos nach einer Minute: 99,999904 GDD
|
||||||
|
|
||||||
|
## Dust-Protection
|
||||||
|
- Iota erlaubt bei leeren Adressen nur Transaktionen von mindestens 1 Miota
|
||||||
|
- Nachdem 1 Miota da ist sind bis zu 10 Transaktione < 1 Miota erlaubt, bevor ein weitere Transaktion mit mindestens 1 Miota eingehen muss
|
||||||
|
- Bei Verwendung von 4 Nachkommastellen entspricht das 100 GDD, bei 2 Nachkommastellen 10.000 GDD
|
||||||
|
|
||||||
|
## Lösung
|
||||||
|
Wir können nur 3 Nachkommastellen verwenden.
|
||||||
|
### Kosten
|
||||||
|
- 0,84 € für 1.000 GDD
|
||||||
|
- 1.428 Euro für alle bisherigen geschöpften Gradidos
|
||||||
|
### Dust-Protection
|
||||||
|
- 1.000 GDD entspricht 1 Miota, die erste Schöpfung muss also zwangsläufig 1k Gradido betragen
|
||||||
|
- Jeder kann nur maximal 10 Transaktionen < 1k GDD empfangen bevor er die GDD weitergesendet haben muss um neue erhalten zu können oder eine neue Schöpfung von 1k GDD bekommen hat
|
||||||
|
|
||||||
|
|
||||||
1
docu/Concepts/Snippets/images/classes.drawio
Normal file
1
docu/Concepts/Snippets/images/classes.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="7d619c52-f8a9-4b9d-82fe-1aa45d350dea" modified="2021-08-07T11:49:26.921Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.58.2 Chrome/89.0.4389.128 Electron/12.0.13 Safari/537.36" etag="5tat3B3yItVGc6mcvaQ1" version="12.2.4" pages="1"><diagram id="1SuIEZF2QCnWeueF3Zz_" name="Page-1">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</diagram></mxfile>
|
||||||
BIN
docu/Concepts/Snippets/images/classes.png
Normal file
BIN
docu/Concepts/Snippets/images/classes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
1
docu/Concepts/Snippets/images/example_blockchain.drawio
Normal file
1
docu/Concepts/Snippets/images/example_blockchain.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="11f51089-224b-4c51-8025-2a587ac1ee54" modified="2021-08-08T15:21:15.636Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.58.2 Chrome/89.0.4389.128 Electron/12.0.13 Safari/537.36" etag="Ph0quYraKNcgZ1LpaOqw" version="12.2.4" pages="1"><diagram id="svTVmLC50Oi8Ao2eC_wi" name="Page-1">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</diagram></mxfile>
|
||||||
BIN
docu/Concepts/Snippets/images/example_blockchain.png
Normal file
BIN
docu/Concepts/Snippets/images/example_blockchain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
1
docu/Concepts/Snippets/images/iota.drawio
Normal file
1
docu/Concepts/Snippets/images/iota.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="c8c9d8b1-9d45-405b-9350-947d02163aae" modified="2021-08-13T14:14:22.645Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.59.0 Chrome/91.0.4472.124 Electron/13.1.7 Safari/537.36" etag="M5WsFcVCZVYdDRxEgPr7" version="12.2.4" pages="1"><diagram id="4VYGDKFA-XGvTikjH5B0" name="Page-1">zVlLc+MoEP41Os6U9bRz9GMym9qaSmqTqp3sjUjYYoOEF6HYmV+/DQJLCHnieJRkLjK0oNV099cP7IXLYv+Vo23+jWWYesEk23vhyguCaRLAUxKeNWEaN4QNJ1lD8lvCLfmBNXGiqTXJcGUtFIxRQbY2MWVliVNh0RDnbGcvWzNqf3WLNtgh3KaIutS/SSbyhjoLpi39D0w2ufmyn1w0bwpkFuuTVDnK2K5DCr944ZIzJppRsV9iKnVn9NLsuzzy9iAYx6U4aYNW/BOitT7c1fXdXAsnns2JYRcoFyYLkHgriSllNTBZ7HIi8O0WpZK4A3MDLRcFhZkPQ1cgLeMT5gLvOyQt4FfMCiz4MyzRb8MLrSztLWa6a1XvG33mHbXPNA1pa28OnFuFwEDr5Ih+Ekc/4NIZydgdR2WFUkFY6WiLs7rMsGQxeRcNzWwF+ZMTNZSMoaGpoyHXe8psLmGn3AZVFUltFeA9Ed8743upuc+xnq32WpFq8mwmJQj6XS0EI+m53hibebtVzay9N5gTOC3mmnjUDhWreYptfxCIb7BZpjGEMytquNbqmCMesIahcUyRIE92rBkykf7CDSMg8TFnCKKekZvj6E3d0NDj44c9r7roMWp04DBSDnM49Wk+NHNR9td8dbW6/uwFCSokYihoaYEoga8b0kYczNZxNsCMsN2L44r8QA9qgbT0VoqsDhEvvHgl+daCVU2a8ZvPbEoYU7yWrCQQCUT/uSYLJr9eAaJJubmTk9WnaBwkB7OoB+WZC2V/wHmCEaBseHTMQEklcKnyKzxeYZREvXrgMFIUi5FeWj5U8qcgFFeClZDRg0tI2WvCCwiefbuaxLPlLMVV9XJgfUDp40aF4utaUFIa646RknrYCEI34gZDZhoj4poA+ysR97Rg91sHtrCf308NbFEQfp5Mkwswmnr6w6YcP8wFoWO4GzCUDCJtMVEddXwQAPMS0VvBuCpQPxAA8cv+7ydvVJMF0Xv5/0CyNzb8TTDhJOnpudm+Xza8IQzcnsPki9w3+eKfGlY8Um8ZevOIy8zRrADm7SJD3BrCn6woVJbJCIbnN8gTgJRKfkyW6ZMrJpDsHykmaQ6DEqmfDBfS9GoPYKAC78FSN7uaZ4odQEgeAJhzxYsfNh2SlxcsFZEfOD3Ai0Z+QTaSXY7kkzVrHo2ozWJUK0kKpBplpAQLLzun3raHfrHgqQRnj3jJKJPFbSmlCxdrQmmPpEsYIMROQRMca10YaGhNFbRykkn9QIVlNTxjxJfIdsckdPPrJHZh5ffd9qwA45ajxhCgo9LSfvJfLXv1RlOfqkZVc+lajCtjmgXGfGvpEouldIF/cSZNrxtKGFk95WSHaZpj5XfSl1tHcz1LOWrHWRoxj/jLGd2pY7gB8x4vluLYTtzRicnCH6OoDd2i9tXZwvSnbU9633kz3J8OpokXU445sZVyZh+ZYXqZPj67nZzYjKK3aydNRfczk787CCIbBLE/AILZgBnH6BjCETqGzn3L4Xrl3uvergzftZyJg5mLg/BDK60eDpJ+bDoXB3G/YhsRB0P9BmSlkqm8s8w5K0kqHeByxKLCXJ6kuFSXa871SQFVAz3WvgxVEr8EvCjqV8ixC7zJT/zmFcCDaXt13xis/f8j/PI/</diagram></mxfile>
|
||||||
BIN
docu/Concepts/Snippets/images/iota.png
Normal file
BIN
docu/Concepts/Snippets/images/iota.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@ -2,3 +2,4 @@ LOGIN_API_URL=http://localhost/login_api/
|
|||||||
COMMUNITY_API_URL=http://localhost/api/
|
COMMUNITY_API_URL=http://localhost/api/
|
||||||
ALLOW_REGISTER=true
|
ALLOW_REGISTER=true
|
||||||
GRAPHQL_URI=http://localhost:4000/graphql
|
GRAPHQL_URI=http://localhost:4000/graphql
|
||||||
|
//BUILD_COMMIT=0000000
|
||||||
@ -15,7 +15,6 @@ describe('LanguageSwitch', () => {
|
|||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
sessionId: 1234,
|
|
||||||
email: 'he@ho.he',
|
email: 'he@ho.he',
|
||||||
language: null,
|
language: null,
|
||||||
}
|
}
|
||||||
@ -123,7 +122,6 @@ describe('LanguageSwitch', () => {
|
|||||||
expect(updateUserInfosQueryMock).toBeCalledWith(
|
expect(updateUserInfosQueryMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1234,
|
|
||||||
email: 'he@ho.he',
|
email: 'he@ho.he',
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
},
|
},
|
||||||
@ -136,7 +134,6 @@ describe('LanguageSwitch', () => {
|
|||||||
expect(updateUserInfosQueryMock).toBeCalledWith(
|
expect(updateUserInfosQueryMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1234,
|
|
||||||
email: 'he@ho.he',
|
email: 'he@ho.he',
|
||||||
locale: 'de',
|
locale: 'de',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -32,13 +32,13 @@ export default {
|
|||||||
localeChanged(locale)
|
localeChanged(locale)
|
||||||
},
|
},
|
||||||
async saveLocale(locale) {
|
async saveLocale(locale) {
|
||||||
|
// if (this.$i18n.locale === locale) return
|
||||||
this.setLocale(locale)
|
this.setLocale(locale)
|
||||||
if (this.$store.state.sessionId && this.$store.state.email) {
|
if (this.$store.state.email) {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
query: updateUserInfos,
|
query: updateUserInfos,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
email: this.$store.state.email,
|
email: this.$store.state.email,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
},
|
},
|
||||||
|
|||||||
31
frontend/src/components/Transaction.spec.js
Normal file
31
frontend/src/components/Transaction.spec.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Transaction from './Transaction'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('Transaction', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$i18n: {
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$n: jest.fn((n) => n),
|
||||||
|
$d: jest.fn((d) => d),
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(Transaction, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
124
frontend/src/components/Transaction.vue
Normal file
124
frontend/src/components/Transaction.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="list-group">
|
||||||
|
<div class="list-group-item gdt-transaction-list-item" v-b-toggle="'a' + date + ''">
|
||||||
|
<!-- icon -->
|
||||||
|
<div class="text-right" style="position: absolute">
|
||||||
|
<b-icon
|
||||||
|
:icon="getLinesByType(gdtEntryType).icon"
|
||||||
|
:class="getLinesByType(gdtEntryType).iconclasses"
|
||||||
|
></b-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- collaps Button -->
|
||||||
|
<div class="text-right" style="width: 96%; position: absolute">
|
||||||
|
<b-button class="btn-sm">
|
||||||
|
<b>i</b>
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- type -->
|
||||||
|
<b-row>
|
||||||
|
<div class="col-6 text-right">
|
||||||
|
{{ getLinesByType(gdtEntryType).description }}
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
{{ getLinesByType(gdtEntryType).descriptiontext }}
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<!-- credit -->
|
||||||
|
<b-row>
|
||||||
|
<div class="col-6 text-right">
|
||||||
|
{{ $t('gdt.credit') }}
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
{{ getLinesByType(gdtEntryType).credittext }}
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<!-- Message-->
|
||||||
|
<b-row v-if="comment && gdtEntryType !== 7">
|
||||||
|
<div class="col-6 text-right">
|
||||||
|
{{ $t('form.memo') }}
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
{{ comment }}
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<!-- date-->
|
||||||
|
<b-row class="gdt-list-row text-header">
|
||||||
|
<div class="col-6 text-right">
|
||||||
|
{{ $t('form.date') }}
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import TransactionCollapse from './TransactionCollapse.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Transaction',
|
||||||
|
components: {
|
||||||
|
TransactionCollapse,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
amount: { type: Number },
|
||||||
|
date: { type: String },
|
||||||
|
comment: { type: String },
|
||||||
|
gdtEntryType: { type: Number, default: 1 },
|
||||||
|
factor: { type: Number },
|
||||||
|
gdt: { type: Number },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLinesByType(givenType) {
|
||||||
|
if (givenType === 2 || givenType === 3 || givenType === 5 || givenType === 6) givenType = 1
|
||||||
|
|
||||||
|
const linesByType = {
|
||||||
|
1: {
|
||||||
|
icon: 'heart',
|
||||||
|
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||||
|
description: this.$t('gdt.contribution'),
|
||||||
|
descriptiontext: this.$n(this.amount, 'decimal') + ' €',
|
||||||
|
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
icon: 'person-check',
|
||||||
|
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||||
|
description: this.$t('gdt.recruited-member'),
|
||||||
|
descriptiontext: '5%',
|
||||||
|
credittext: this.$n(this.amount, 'decimal') + ' GDT',
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
icon: 'gift',
|
||||||
|
iconclasses: 'gradido-global-color-accent m-mb-1 font2em',
|
||||||
|
description: this.$t('gdt.gdt-received'),
|
||||||
|
descriptiontext: this.comment,
|
||||||
|
credittext: this.$n(this.gdt, 'decimal') + ' GDT',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = linesByType[givenType]
|
||||||
|
|
||||||
|
if (type) return type
|
||||||
|
throw new Error('no lines for this type: ' + givenType)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
152
frontend/src/components/TransactionCollapse.spec.js
Normal file
152
frontend/src/components/TransactionCollapse.spec.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import TransactionCollapse from './TransactionCollapse'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('TransactionCollapse', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$n: jest.fn((n) => n),
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = (propsData) => {
|
||||||
|
return mount(TransactionCollapse, { localVue, mocks, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount with gdtEntryType: 1', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const propsData = {
|
||||||
|
amount: 100,
|
||||||
|
gdt: 110,
|
||||||
|
factor: 22,
|
||||||
|
gdtEntryType: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper = Wrapper(propsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the prop gdtEntryType ', () => {
|
||||||
|
expect(wrapper.props().gdtEntryType).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-header', () => {
|
||||||
|
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-headline', () => {
|
||||||
|
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.calculation')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-first', () => {
|
||||||
|
expect(wrapper.find('#collapse-first').text()).toBe('gdt.factor')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-second', () => {
|
||||||
|
expect(wrapper.find('#collapse-second').text()).toBe('gdt.formula')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-firstMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-firstMath').text()).toBe('22 GDT pro €')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-secondMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 € * 22 GDT / € = 110 GDT')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount with gdtEntryType: 7', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const propsData = {
|
||||||
|
amount: 100,
|
||||||
|
gdt: 2200,
|
||||||
|
factor: 22,
|
||||||
|
gdtEntryType: 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper = Wrapper(propsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the prop gdtEntryType ', () => {
|
||||||
|
expect(wrapper.props().gdtEntryType).toBe(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-header', () => {
|
||||||
|
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-headline', () => {
|
||||||
|
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.conversion-gdt-euro')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-first', () => {
|
||||||
|
expect(wrapper.find('#collapse-first').text()).toBe('gdt.raise')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-second', () => {
|
||||||
|
expect(wrapper.find('#collapse-second').text()).toBe('gdt.conversion')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-firstMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-firstMath').text()).toBe('2200 %')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-secondMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 GDT * 2200 % = 2200 GDT')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mount with gdtEntryType: 4', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const propsData = {
|
||||||
|
amount: 100,
|
||||||
|
gdt: 2200,
|
||||||
|
factor: 22,
|
||||||
|
gdtEntryType: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper = Wrapper(propsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('div.gdt-transaction-collapse').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the prop gdtEntryType ', () => {
|
||||||
|
expect(wrapper.props().gdtEntryType).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-header', () => {
|
||||||
|
expect(wrapper.find('.gdt-list-collapse-header-text')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-headline', () => {
|
||||||
|
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.publisher')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-first', () => {
|
||||||
|
expect(wrapper.find('#collapse-first').text()).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-second', () => {
|
||||||
|
expect(wrapper.find('#collapse-second').text()).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-firstMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-firstMath').text()).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component collapse-secondMath', () => {
|
||||||
|
expect(wrapper.find('#collapse-secondMath').text()).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
76
frontend/src/components/TransactionCollapse.vue
Normal file
76
frontend/src/components/TransactionCollapse.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gdt-transaction-collapse">
|
||||||
|
<b-row class="gdt-list-collapse-header-text text-center pb-3">
|
||||||
|
<div id="collapse-headline" class="col h4">
|
||||||
|
{{ getLinesByType(gdtEntryType).headline }}
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="gdt-list-collapse-box--all">
|
||||||
|
<div class="col-6 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">
|
||||||
|
<div id="collapse-firstMath">{{ getLinesByType(gdtEntryType).firstMath }}</div>
|
||||||
|
<div id="collapse-secondMath">
|
||||||
|
{{ getLinesByType(gdtEntryType).secondMath }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TransactionCollapse',
|
||||||
|
props: {
|
||||||
|
amount: { type: Number },
|
||||||
|
gdtEntryType: { type: Number, default: 1 },
|
||||||
|
factor: { type: Number },
|
||||||
|
gdt: { type: Number },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLinesByType(givenType) {
|
||||||
|
const linesByType = {
|
||||||
|
1: {
|
||||||
|
headline: this.$t('gdt.calculation'),
|
||||||
|
first: this.$t('gdt.factor'),
|
||||||
|
firstMath: this.factor + ' GDT pro €',
|
||||||
|
second: this.$t('gdt.formula'),
|
||||||
|
secondMath:
|
||||||
|
this.$n(this.amount, 'decimal') +
|
||||||
|
' € * ' +
|
||||||
|
this.factor +
|
||||||
|
' GDT / € = ' +
|
||||||
|
this.$n(this.gdt, 'decimal') +
|
||||||
|
' GDT',
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
headline: this.$t('gdt.publisher'),
|
||||||
|
first: null,
|
||||||
|
firstMath: null,
|
||||||
|
second: null,
|
||||||
|
secondMath: null,
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
headline: this.$t('gdt.conversion-gdt-euro'),
|
||||||
|
first: this.$t('gdt.raise'),
|
||||||
|
firstMath: this.factor * 100 + ' % ',
|
||||||
|
second: this.$t('gdt.conversion'),
|
||||||
|
secondMath:
|
||||||
|
this.$n(this.amount, 'decimal') +
|
||||||
|
' GDT * ' +
|
||||||
|
this.factor * 100 +
|
||||||
|
' % = ' +
|
||||||
|
this.$n(this.gdt, 'decimal') +
|
||||||
|
' GDT',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = linesByType[givenType]
|
||||||
|
|
||||||
|
if (type) return type
|
||||||
|
throw new Error('no additional transaction info for this type: ' + givenType)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,13 +1,20 @@
|
|||||||
// 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
|
// Load Package Details for some default values
|
||||||
const pkg = require('../../package')
|
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 = {
|
const environment = {
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
DEBUG: process.env.NODE_ENV !== 'production' || false,
|
DEBUG: process.env.NODE_ENV !== 'production' || false,
|
||||||
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
||||||
ALLOW_REGISTER: process.env.ALLOW_REGISTER !== 'false',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = {
|
const server = {
|
||||||
@ -16,13 +23,15 @@ const server = {
|
|||||||
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
|
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
const options = {
|
||||||
console.log('hash: %o', process.env.VUE_APP_BUILD_COMMIT)
|
ALLOW_REGISTER: process.env.ALLOW_REGISTER !== 'false',
|
||||||
|
}
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
|
...version,
|
||||||
...environment,
|
...environment,
|
||||||
...server,
|
...server,
|
||||||
APP_VERSION: pkg.version,
|
...options,
|
||||||
COMMIT_HASH:
|
COMMIT_HASH:
|
||||||
process.env.VUE_APP_BUILD_COMMIT === 'undefined'
|
process.env.VUE_APP_BUILD_COMMIT === 'undefined'
|
||||||
? '00000000'
|
? '00000000'
|
||||||
|
|||||||
@ -3,22 +3,19 @@ import gql from 'graphql-tag'
|
|||||||
export const login = gql`
|
export const login = gql`
|
||||||
query($email: String!, $password: String!) {
|
query($email: String!, $password: String!) {
|
||||||
login(email: $email, password: $password) {
|
login(email: $email, password: $password) {
|
||||||
sessionId
|
|
||||||
user {
|
|
||||||
email
|
email
|
||||||
|
username
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
language
|
language
|
||||||
username
|
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export const logout = gql`
|
export const logout = gql`
|
||||||
query($sessionId: Float!) {
|
query {
|
||||||
logout(sessionId: $sessionId)
|
logout
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -39,7 +36,6 @@ export const loginViaEmailVerificationCode = gql`
|
|||||||
|
|
||||||
export const updateUserInfos = gql`
|
export const updateUserInfos = gql`
|
||||||
query(
|
query(
|
||||||
$sessionId: Float!
|
|
||||||
$email: String!
|
$email: String!
|
||||||
$firstName: String
|
$firstName: String
|
||||||
$lastName: String
|
$lastName: String
|
||||||
@ -50,7 +46,6 @@ export const updateUserInfos = gql`
|
|||||||
$locale: String
|
$locale: String
|
||||||
) {
|
) {
|
||||||
updateUserInfos(
|
updateUserInfos(
|
||||||
sessionId: $sessionId
|
|
||||||
email: $email
|
email: $email
|
||||||
firstName: $firstName
|
firstName: $firstName
|
||||||
lastName: $lastName
|
lastName: $lastName
|
||||||
@ -66,8 +61,8 @@ export const updateUserInfos = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query($sessionId: Float!, $firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
|
query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
|
||||||
transactionList(sessionId: $sessionId, firstPage: $firstPage, items: $items, order: $order) {
|
transactionList(firstPage: $firstPage, items: $items, order: $order) {
|
||||||
gdtSum
|
gdtSum
|
||||||
count
|
count
|
||||||
balance
|
balance
|
||||||
@ -103,8 +98,8 @@ export const resgisterUserQuery = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const sendCoins = gql`
|
export const sendCoins = gql`
|
||||||
query($sessionId: Float!, $email: String!, $amount: Float!, $memo: String!) {
|
query($email: String!, $amount: Float!, $memo: String!) {
|
||||||
sendCoins(sessionId: $sessionId, email: $email, amount: $amount, memo: $memo)
|
sendCoins(email: $email, amount: $amount, memo: $memo)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -125,10 +120,11 @@ export const checkUsername = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const listGDTEntriesQuery = gql`
|
export const listGDTEntriesQuery = gql`
|
||||||
query($currentPage: Int!, $pageSize: Int!, $sessionId: Float!) {
|
query($currentPage: Int!, $pageSize: Int!) {
|
||||||
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize, sessionId: $sessionId) {
|
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {
|
||||||
count
|
count
|
||||||
gdtEntries {
|
gdtEntries {
|
||||||
|
id
|
||||||
amount
|
amount
|
||||||
date
|
date
|
||||||
comment
|
comment
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
"en": "English"
|
"en": "English"
|
||||||
},
|
},
|
||||||
|
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
|
||||||
"decay": {
|
"decay": {
|
||||||
"decay": "Vergänglichkeit",
|
"decay": "Vergänglichkeit",
|
||||||
"decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion",
|
"decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion",
|
||||||
@ -136,15 +137,6 @@
|
|||||||
"send_gradido":"Gradido versenden",
|
"send_gradido":"Gradido versenden",
|
||||||
"add_work":"neuer Gemeinschaftsbeitrag"
|
"add_work":"neuer Gemeinschaftsbeitrag"
|
||||||
},
|
},
|
||||||
"profil": {
|
|
||||||
"activity": {
|
|
||||||
"new":"Neue Gemeinschaftsstunden eintragen",
|
|
||||||
"list":"Meine Gemeinschaftsstunden Liste"
|
|
||||||
},
|
|
||||||
"user-data": {
|
|
||||||
"change-success": "Deine Daten wurden gespeichert."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"navbar" : {
|
"navbar" : {
|
||||||
"my-profil":"Mein Profil",
|
"my-profil":"Mein Profil",
|
||||||
"settings":"Einstellungen",
|
"settings":"Einstellungen",
|
||||||
@ -182,8 +174,8 @@
|
|||||||
"formula":"Berechungsformel",
|
"formula":"Berechungsformel",
|
||||||
"no-transactions":"Du hast zur Zeit keine Transaktionen",
|
"no-transactions":"Du hast zur Zeit keine Transaktionen",
|
||||||
"publisher":"Dein geworbenes Mitglied hat einen Beitrag bezahlt",
|
"publisher":"Dein geworbenes Mitglied hat einen Beitrag bezahlt",
|
||||||
"gdt-receive":"Aktion",
|
"action":"Aktion",
|
||||||
"your-share":"Geworbenes Mitglied",
|
"recruited-member":"Geworbenes Mitglied",
|
||||||
"contribution":"Beitrag"
|
"contribution":"Beitrag"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
"en": "English"
|
"en": "English"
|
||||||
},
|
},
|
||||||
|
"select_language": "Please choose a language for the app and newsletter",
|
||||||
"decay": {
|
"decay": {
|
||||||
"decay": "Decay",
|
"decay": "Decay",
|
||||||
"decay_since_last_transaction":"Decay since the last transaction",
|
"decay_since_last_transaction":"Decay since the last transaction",
|
||||||
@ -136,16 +137,6 @@
|
|||||||
"send_gradido":"Send Gradido",
|
"send_gradido":"Send Gradido",
|
||||||
"add_work":"New Community Contribution"
|
"add_work":"New Community Contribution"
|
||||||
},
|
},
|
||||||
"profil": {
|
|
||||||
"transactions":"transactions",
|
|
||||||
"activity": {
|
|
||||||
"new":"Register new community hours",
|
|
||||||
"list":"My Community Hours List"
|
|
||||||
},
|
|
||||||
"user-data": {
|
|
||||||
"change-success": "Your data has been saved."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"navbar" : {
|
"navbar" : {
|
||||||
"my-profil":"My profile",
|
"my-profil":"My profile",
|
||||||
"settings":"Settings",
|
"settings":"Settings",
|
||||||
@ -183,8 +174,8 @@
|
|||||||
"formula": "Calculation formula",
|
"formula": "Calculation formula",
|
||||||
"no-transactions":"You currently have no transactions",
|
"no-transactions":"You currently have no transactions",
|
||||||
"publisher":"A member you referred has paid a contribution",
|
"publisher":"A member you referred has paid a contribution",
|
||||||
"gdt-receive":"GDT receive",
|
"action":"Action",
|
||||||
"your-share":"Your share",
|
"recruited-member":"Recruited Member",
|
||||||
"contribution":"Contribution"
|
"contribution":"Contribution"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import DashboardPlugin from './plugins/dashboard-plugin'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import i18n from './i18n.js'
|
import i18n from './i18n.js'
|
||||||
import { loadAllRules } from './validation-rules'
|
import { loadAllRules } from './validation-rules'
|
||||||
import ApolloClient from 'apollo-boost'
|
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
@ -11,7 +11,25 @@ import { store } from './store/store'
|
|||||||
|
|
||||||
import router from './routes/router'
|
import router from './routes/router'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
|
const token = store.state.token
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return forward(operation).map((response) => {
|
||||||
|
const newToken = operation.getContext().response.headers.get('token')
|
||||||
|
if (newToken) store.commit('token', newToken)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
const apolloClient = new ApolloClient({
|
||||||
|
link: authLink.concat(httpLink),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
uri: CONFIG.GRAPHQL_URI,
|
uri: CONFIG.GRAPHQL_URI,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -26,7 +44,7 @@ Vue.config.productionTip = false
|
|||||||
loadAllRules(i18n)
|
loadAllRules(i18n)
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (to.meta.requiresAuth && !store.state.sessionId) {
|
if (to.meta.requiresAuth && !store.state.token) {
|
||||||
next({ path: '/login' })
|
next({ path: '/login' })
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import createPersistedState from 'vuex-persistedstate'
|
import createPersistedState from 'vuex-persistedstate'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
@ -10,9 +11,6 @@ export const mutations = {
|
|||||||
email: (state, email) => {
|
email: (state, email) => {
|
||||||
state.email = email
|
state.email = email
|
||||||
},
|
},
|
||||||
sessionId: (state, sessionId) => {
|
|
||||||
state.sessionId = sessionId
|
|
||||||
},
|
|
||||||
username: (state, username) => {
|
username: (state, username) => {
|
||||||
state.username = username
|
state.username = username
|
||||||
},
|
},
|
||||||
@ -25,43 +23,45 @@ export const mutations = {
|
|||||||
description: (state, description) => {
|
description: (state, description) => {
|
||||||
state.description = description
|
state.description = description
|
||||||
},
|
},
|
||||||
|
token: (state, token) => {
|
||||||
|
state.token = token
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
login: ({ dispatch, commit }, data) => {
|
login: ({ dispatch, commit }, data) => {
|
||||||
commit('sessionId', data.sessionId)
|
commit('email', data.email)
|
||||||
commit('email', data.user.email)
|
commit('language', data.language)
|
||||||
commit('language', data.user.language)
|
commit('username', data.username)
|
||||||
commit('username', data.user.username)
|
commit('firstName', data.firstName)
|
||||||
commit('firstName', data.user.firstName)
|
commit('lastName', data.lastName)
|
||||||
commit('lastName', data.user.lastName)
|
commit('description', data.description)
|
||||||
commit('description', data.user.description)
|
|
||||||
},
|
},
|
||||||
logout: ({ commit, state }) => {
|
logout: ({ commit, state }) => {
|
||||||
commit('sessionId', null)
|
commit('token', null)
|
||||||
commit('email', null)
|
commit('email', null)
|
||||||
commit('username', '')
|
commit('username', '')
|
||||||
commit('firstName', '')
|
commit('firstName', '')
|
||||||
commit('lastName', '')
|
commit('lastName', '')
|
||||||
commit('description', '')
|
commit('description', '')
|
||||||
sessionStorage.clear()
|
localStorage.clear()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = new Vuex.Store({
|
export const store = new Vuex.Store({
|
||||||
plugins: [
|
plugins: [
|
||||||
createPersistedState({
|
createPersistedState({
|
||||||
storage: window.sessionStorage,
|
storage: window.localStorage,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
state: {
|
state: {
|
||||||
sessionId: null,
|
|
||||||
email: '',
|
email: '',
|
||||||
language: null,
|
language: null,
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
username: '',
|
username: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
token: null,
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
// Syncronous mutation of the state
|
// Syncronous mutation of the state
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { mutations, actions } from './store'
|
import { mutations, actions } from './store'
|
||||||
|
|
||||||
const { language, email, sessionId, username, firstName, lastName, description } = mutations
|
const { language, email, token, username, firstName, lastName, description } = mutations
|
||||||
const { login, logout } = actions
|
const { login, logout } = actions
|
||||||
|
|
||||||
describe('Vuex store', () => {
|
describe('Vuex store', () => {
|
||||||
@ -21,11 +21,11 @@ describe('Vuex store', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sessionId', () => {
|
describe('token', () => {
|
||||||
it('sets the state of sessionId', () => {
|
it('sets the state of token', () => {
|
||||||
const state = { sessionId: null }
|
const state = { token: null }
|
||||||
sessionId(state, '1234')
|
token(state, '1234')
|
||||||
expect(state.sessionId).toEqual('1234')
|
expect(state.token).toEqual('1234')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -67,55 +67,47 @@ describe('Vuex store', () => {
|
|||||||
const commit = jest.fn()
|
const commit = jest.fn()
|
||||||
const state = {}
|
const state = {}
|
||||||
const commitedData = {
|
const commitedData = {
|
||||||
sessionId: 1234,
|
email: 'user@example.org',
|
||||||
user: {
|
language: 'de',
|
||||||
email: 'someone@there.is',
|
username: 'peter',
|
||||||
language: 'en',
|
|
||||||
username: 'user',
|
|
||||||
firstName: 'Peter',
|
firstName: 'Peter',
|
||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
description: 'Nickelbrille',
|
description: 'Nickelbrille',
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('calls seven commits', () => {
|
it('calls seven commits', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenCalledTimes(7)
|
expect(commit).toHaveBeenCalledTimes(6)
|
||||||
})
|
|
||||||
|
|
||||||
it('commits sessionId', () => {
|
|
||||||
login({ commit, state }, commitedData)
|
|
||||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits email', () => {
|
it('commits email', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is')
|
expect(commit).toHaveBeenNthCalledWith(1, 'email', 'user@example.org')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits language', () => {
|
it('commits language', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en')
|
expect(commit).toHaveBeenNthCalledWith(2, 'language', 'de')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits username', () => {
|
it('commits username', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'user')
|
expect(commit).toHaveBeenNthCalledWith(3, 'username', 'peter')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits firstName', () => {
|
it('commits firstName', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(5, 'firstName', 'Peter')
|
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', 'Peter')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits lastName', () => {
|
it('commits lastName', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(6, 'lastName', 'Lustig')
|
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', 'Lustig')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits description', () => {
|
it('commits description', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(7, 'description', 'Nickelbrille')
|
expect(commit).toHaveBeenNthCalledWith(6, 'description', 'Nickelbrille')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -128,9 +120,9 @@ describe('Vuex store', () => {
|
|||||||
expect(commit).toHaveBeenCalledTimes(6)
|
expect(commit).toHaveBeenCalledTimes(6)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits sessionId', () => {
|
it('commits token', () => {
|
||||||
logout({ commit, state })
|
logout({ commit, state })
|
||||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', null)
|
expect(commit).toHaveBeenNthCalledWith(1, 'token', null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits email', () => {
|
it('commits email', () => {
|
||||||
@ -159,7 +151,7 @@ describe('Vuex store', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// how to get this working?
|
// how to get this working?
|
||||||
it.skip('calls sessionStorage.clear()', () => {
|
it.skip('calls localStorage.clear()', () => {
|
||||||
const clearStorageMock = jest.fn()
|
const clearStorageMock = jest.fn()
|
||||||
global.sessionStorage = jest.fn(() => {
|
global.sessionStorage = jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -16,11 +16,11 @@
|
|||||||
App version {{ version }}
|
App version {{ version }}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="shortHash !== '00000000'"
|
v-if="hash"
|
||||||
:href="'https://github.com/gradido/gradido/commit/' + hash"
|
:href="'https://github.com/gradido/gradido/commit/' + hash"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{{ shortHash }}
|
({{ shortHash }})
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
@ -66,8 +66,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
year: new Date().getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
version: CONFIG.APP_VERSION,
|
version: CONFIG.APP_VERSION,
|
||||||
hash: CONFIG.COMMIT_HASH,
|
hash: CONFIG.BUILD_COMMIT,
|
||||||
shortHash: CONFIG.COMMIT_HASH.substr(0, 8),
|
shortHash: CONFIG.BUILD_COMMIT_SHORT,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,6 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
},
|
},
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
},
|
},
|
||||||
dispatch: storeDispatchMock,
|
dispatch: storeDispatchMock,
|
||||||
@ -128,16 +127,17 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
|
|
||||||
describe('logout', () => {
|
describe('logout', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await apolloMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
logout: 'success',
|
||||||
|
},
|
||||||
|
})
|
||||||
await wrapper.findComponent({ name: 'sidebar' }).vm.$emit('logout')
|
await wrapper.findComponent({ name: 'sidebar' }).vm.$emit('logout')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls the API', async () => {
|
it('calls the API', async () => {
|
||||||
expect(apolloMock).toBeCalledWith(
|
await expect(apolloMock).toBeCalled()
|
||||||
expect.objectContaining({
|
|
||||||
variables: { sessionId: 1 },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('dispatches logout to store', () => {
|
it('dispatches logout to store', () => {
|
||||||
@ -196,7 +196,6 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
expect(apolloMock).toBeCalledWith(
|
expect(apolloMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
firstPage: 2,
|
firstPage: 2,
|
||||||
items: 5,
|
items: 5,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -92,7 +92,6 @@ export default {
|
|||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
query: logout,
|
query: logout,
|
||||||
variables: { sessionId: this.$store.state.sessionId },
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$sidebar.displaySidebar(false)
|
this.$sidebar.displaySidebar(false)
|
||||||
@ -111,7 +110,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: transactionsQuery,
|
query: transactionsQuery,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
firstPage: pagination.firstPage,
|
firstPage: pagination.firstPage,
|
||||||
items: pagination.items,
|
items: pagination.items,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,12 +16,12 @@ describe('AccountOverview', () => {
|
|||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
|
$n: jest.fn((n) => String(n)),
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
email: 'sender@example.org',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$n: jest.fn((n) => String(n)),
|
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: sendMock,
|
query: sendMock,
|
||||||
},
|
},
|
||||||
@ -93,7 +93,6 @@ describe('AccountOverview', () => {
|
|||||||
expect(sendMock).toBeCalledWith(
|
expect(sendMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
amount: 23.45,
|
amount: 23.45,
|
||||||
memo: 'Make the best of it!',
|
memo: 'Make the best of it!',
|
||||||
|
|||||||
@ -107,10 +107,7 @@ export default {
|
|||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
query: sendCoins,
|
query: sendCoins,
|
||||||
variables: {
|
variables: this.transactionData,
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
...this.transactionData,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.error = false
|
this.error = false
|
||||||
|
|||||||
@ -8,11 +8,6 @@ describe('GddSend', () => {
|
|||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
sessionId: 1234,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$i18n: {
|
$i18n: {
|
||||||
locale: jest.fn(() => 'en'),
|
locale: jest.fn(() => 'en'),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -57,11 +57,6 @@ describe('GdtTransactionList', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$n: jest.fn((n) => n),
|
$n: jest.fn((n) => n),
|
||||||
$d: jest.fn((d) => d),
|
$d: jest.fn((d) => d),
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
sessionId: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$toasted: {
|
$toasted: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
@ -89,7 +84,6 @@ describe('GdtTransactionList', () => {
|
|||||||
expect(apolloMock).toBeCalledWith(
|
expect(apolloMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 25,
|
pageSize: 25,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,134 +17,14 @@
|
|||||||
} in transactionsGdt"
|
} in transactionsGdt"
|
||||||
:key="transactionId"
|
:key="transactionId"
|
||||||
>
|
>
|
||||||
<div class="list-group-item gdt-transaction-list-item" v-b-toggle="'a' + date + ''">
|
<transaction
|
||||||
<!-- Icon -->
|
:amount="amount"
|
||||||
<div class="text-right" style="position: absolute">
|
:date="date"
|
||||||
<b-icon
|
:comment="comment"
|
||||||
v-if="gdtEntryType"
|
:gdtEntryType="gdtEntryType"
|
||||||
:icon="getIcon(gdtEntryType).icon"
|
:factor="factor"
|
||||||
:class="getIcon(gdtEntryType).class"
|
:gdt="gdt"
|
||||||
></b-icon>
|
></transaction>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Collaps Button -->
|
|
||||||
<div class="text-right" style="width: 96%; position: absolute">
|
|
||||||
<b-button class="btn-sm">
|
|
||||||
<b>i</b>
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Betrag -->
|
|
||||||
|
|
||||||
<!-- 7 nur GDT erhalten -->
|
|
||||||
<b-row v-if="gdtEntryType === 7">
|
|
||||||
<div class="col-6 text-right">
|
|
||||||
<div>{{ $t('gdt.gdt-receive') }}</div>
|
|
||||||
<div>{{ $t('gdt.credit') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div>{{ comment }}</div>
|
|
||||||
<div>{{ $n(gdt, 'decimal') }} GDT</div>
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
<!--4 publisher -->
|
|
||||||
<b-row v-else-if="gdtEntryType === 4">
|
|
||||||
<div class="col-6 text-right">
|
|
||||||
<div>{{ $t('gdt.your-share') }}</div>
|
|
||||||
<div>{{ $t('gdt.credit') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div>5%</div>
|
|
||||||
<div>{{ $n(amount, 'decimal') }} GDT</div>
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
<!-- 1, 2, 3, 5, 6 spenden in euro -->
|
|
||||||
<b-row v-else>
|
|
||||||
<div class="col-6 text-right">
|
|
||||||
<div>{{ $t('gdt.contribution') }}</div>
|
|
||||||
<div>{{ $t('gdt.credit') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div>{{ $n(amount, 'decimal') }} €</div>
|
|
||||||
<div>{{ $n(gdt, 'decimal') }} GDT</div>
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<!-- Betrag ENDE-->
|
|
||||||
|
|
||||||
<!-- Nachricht-->
|
|
||||||
<b-row v-if="comment && gdtEntryType !== 7">
|
|
||||||
<div class="col-6 text-right">
|
|
||||||
{{ $t('form.memo') }}
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
{{ comment }}
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<!-- Datum-->
|
|
||||||
<b-row v-if="date" class="gdt-list-row text-header">
|
|
||||||
<div class="col-6 text-right">
|
|
||||||
{{ $t('form.date') }}
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
{{ $d($moment(date), 'long') }} {{ $i18n.locale === 'de' ? 'Uhr' : '' }}
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Collaps START -->
|
|
||||||
|
|
||||||
<b-collapse v-if="gdtEntryType" :id="'a' + date + ''" class="pb-4">
|
|
||||||
<div style="border: 0px; background-color: #f1f1f1" class="p-2 pb-4 mb-4">
|
|
||||||
<!-- Überschrift -->
|
|
||||||
<b-row class="gdt-list-clooaps-header-text text-center pb-3">
|
|
||||||
<div class="col h4" v-if="gdtEntryType === 7">
|
|
||||||
{{ $t('gdt.conversion-gdt-euro') }}
|
|
||||||
</div>
|
|
||||||
<div class="col h4" v-else-if="gdtEntryType === 4">
|
|
||||||
{{ $t('gdt.publisher') }}
|
|
||||||
</div>
|
|
||||||
<div class="col h4" v-else>{{ $t('gdt.calculation') }}</div>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<!-- 7 nur GDT erhalten -->
|
|
||||||
<b-row class="gdt-list-clooaps-box-7" v-if="gdtEntryType == 7">
|
|
||||||
<div class="col-6 text-right clooaps-col-left">
|
|
||||||
<div>{{ $t('gdt.raise') }}</div>
|
|
||||||
<div>{{ $t('gdt.conversion') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 clooaps-col-right">
|
|
||||||
<div>{{ factor * 100 }} %</div>
|
|
||||||
<div>
|
|
||||||
{{ $n(amount, 'decimal') }} GDT * {{ factor * 100 }} % =
|
|
||||||
{{ $n(gdt, 'decimal') }} GDT
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
<!-- 4 publisher -->
|
|
||||||
<b-row class="gdt-list-clooaps-box-4" v-else-if="gdtEntryType === 4">
|
|
||||||
<div class="col-6 text-right clooaps-col-left"></div>
|
|
||||||
<div class="col-6 clooaps-col-right"></div>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<!-- 1, 2, 3, 5, 6 spenden in euro -->
|
|
||||||
<b-row class="gdt-list-clooaps-box--all" v-else>
|
|
||||||
<div class="col-6 text-right clooaps-col-left">
|
|
||||||
<div>{{ $t('gdt.factor') }}</div>
|
|
||||||
<div>{{ $t('gdt.formula') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 clooaps-col-right">
|
|
||||||
<div>{{ factor }} GDT pro €</div>
|
|
||||||
<div>
|
|
||||||
{{ $n(amount, 'decimal') }} € * {{ factor }} GDT / € =
|
|
||||||
{{ $n(gdt, 'decimal') }} GDT
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-row>
|
|
||||||
</div>
|
|
||||||
</b-collapse>
|
|
||||||
<!-- Collaps ENDE -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pagination-buttons
|
<pagination-buttons
|
||||||
@ -162,17 +42,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import { listGDTEntriesQuery } from '../../../graphql/queries'
|
import { listGDTEntriesQuery } from '../../../graphql/queries'
|
||||||
import PaginationButtons from '../../../components/PaginationButtons'
|
import PaginationButtons from '../../../components/PaginationButtons'
|
||||||
|
import Transaction from '../../../components/Transaction.vue'
|
||||||
const iconsByType = {
|
|
||||||
1: { icon: 'heart', classes: 'gradido-global-color-accent' },
|
|
||||||
4: { icon: 'person-check', classes: 'gradido-global-color-accent' },
|
|
||||||
7: { icon: 'gift', classes: 'gradido-global-color-accent' },
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'gdt-transaction-list',
|
name: 'gdt-transaction-list',
|
||||||
components: {
|
components: {
|
||||||
PaginationButtons,
|
PaginationButtons,
|
||||||
|
Transaction,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -199,7 +75,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: listGDTEntriesQuery,
|
query: listGDTEntriesQuery,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
currentPage: this.currentPage,
|
currentPage: this.currentPage,
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
},
|
},
|
||||||
@ -215,18 +90,6 @@ export default {
|
|||||||
this.$toasted.error(error.message)
|
this.$toasted.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getIcon(givenType) {
|
|
||||||
const type = iconsByType[givenType]
|
|
||||||
if (type)
|
|
||||||
return {
|
|
||||||
icon: type.icon,
|
|
||||||
class: type.classes + ' m-mb-1 font2em',
|
|
||||||
}
|
|
||||||
this.throwError('no icon to given type: ' + givenType)
|
|
||||||
},
|
|
||||||
throwError(msg) {
|
|
||||||
throw new Error(msg)
|
|
||||||
},
|
|
||||||
showNext() {
|
showNext() {
|
||||||
this.currentPage++
|
this.currentPage++
|
||||||
this.updateGdt()
|
this.updateGdt()
|
||||||
|
|||||||
@ -6,12 +6,7 @@ const localVue = global.localVue
|
|||||||
|
|
||||||
const loginQueryMock = jest.fn().mockResolvedValue({
|
const loginQueryMock = jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
login: {
|
login: 'token',
|
||||||
sessionId: 1,
|
|
||||||
user: {
|
|
||||||
name: 'Peter Lustig',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -159,10 +154,7 @@ describe('Login', () => {
|
|||||||
|
|
||||||
describe('login success', () => {
|
describe('login success', () => {
|
||||||
it('dispatches server response to store', () => {
|
it('dispatches server response to store', () => {
|
||||||
expect(mockStoreDispach).toBeCalledWith('login', {
|
expect(mockStoreDispach).toBeCalledWith('login', 'token')
|
||||||
sessionId: 1,
|
|
||||||
user: { name: 'Peter Lustig' },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('redirects to overview page', () => {
|
it('redirects to overview page', () => {
|
||||||
|
|||||||
@ -80,6 +80,13 @@ describe('Register', () => {
|
|||||||
it('has password repeat input fields', () => {
|
it('has password repeat input fields', () => {
|
||||||
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
|
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
it('has Language selected field', () => {
|
||||||
|
expect(wrapper.find('#selectedLanguage').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
it('selected Language value de', async () => {
|
||||||
|
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||||
|
expect(wrapper.find('#selectedLanguage').element.value).toBe('de')
|
||||||
|
})
|
||||||
|
|
||||||
it('has 1 checkbox input fields', () => {
|
it('has 1 checkbox input fields', () => {
|
||||||
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
|
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
|
||||||
@ -121,9 +128,16 @@ describe('Register', () => {
|
|||||||
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
||||||
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
||||||
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
||||||
|
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||||
wrapper.find('input[name="site.signup.agree"]').setChecked(true)
|
wrapper.find('input[name="site.signup.agree"]').setChecked(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('reset selected value language', async () => {
|
||||||
|
await wrapper.find('button.ml-2').trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.find('#selectedLanguage').element.value).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
it('resets the firstName field after clicking the reset button', async () => {
|
it('resets the firstName field after clicking the reset button', async () => {
|
||||||
await wrapper.find('button.ml-2').trigger('click')
|
await wrapper.find('button.ml-2').trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
@ -168,6 +182,7 @@ describe('Register', () => {
|
|||||||
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
||||||
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
wrapper.find('input[name="form.password"]').setValue('Aa123456')
|
||||||
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456')
|
||||||
|
wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('server sends back error', () => {
|
describe('server sends back error', () => {
|
||||||
@ -214,6 +229,7 @@ describe('Register', () => {
|
|||||||
firstName: 'Max',
|
firstName: 'Max',
|
||||||
lastName: 'Mustermann',
|
lastName: 'Mustermann',
|
||||||
password: 'Aa123456',
|
password: 'Aa123456',
|
||||||
|
language: 'de',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -84,6 +84,18 @@
|
|||||||
:register="register"
|
:register="register"
|
||||||
></input-password-confirmation>
|
></input-password-confirmation>
|
||||||
|
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="12">
|
||||||
|
{{ $t('language') }}
|
||||||
|
<b-form-select
|
||||||
|
id="selectedLanguage"
|
||||||
|
v-model="selected"
|
||||||
|
:options="options"
|
||||||
|
class="mb-3"
|
||||||
|
></b-form-select>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
<b-row class="my-4">
|
<b-row class="my-4">
|
||||||
<b-col cols="12">
|
<b-col cols="12">
|
||||||
<b-form-checkbox
|
<b-form-checkbox
|
||||||
@ -109,7 +121,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</b-alert>
|
</b-alert>
|
||||||
|
|
||||||
<div class="text-center" v-if="namesFilled && emailFilled && form.agree">
|
<div
|
||||||
|
class="text-center"
|
||||||
|
v-if="namesFilled && emailFilled && form.agree && languageFilled"
|
||||||
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button>
|
<b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button>
|
||||||
<b-button type="submit" variant="primary">{{ $t('signup') }}</b-button>
|
<b-button type="submit" variant="primary">{{ $t('signup') }}</b-button>
|
||||||
@ -147,6 +162,12 @@ export default {
|
|||||||
passwordRepeat: '',
|
passwordRepeat: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
selected: null,
|
||||||
|
options: [
|
||||||
|
{ value: null, text: this.$t('select_language') },
|
||||||
|
{ value: 'de', text: this.$t('languages.de') },
|
||||||
|
{ value: 'en', text: this.$t('languages.en') },
|
||||||
|
],
|
||||||
submitted: false,
|
submitted: false,
|
||||||
showError: false,
|
showError: false,
|
||||||
messageError: '',
|
messageError: '',
|
||||||
@ -168,8 +189,7 @@ export default {
|
|||||||
},
|
},
|
||||||
agree: false,
|
agree: false,
|
||||||
}
|
}
|
||||||
this.form.password.password = ''
|
this.selected = null
|
||||||
this.form.password.passwordRepeat = ''
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.observer.reset()
|
this.$refs.observer.reset()
|
||||||
})
|
})
|
||||||
@ -183,6 +203,7 @@ export default {
|
|||||||
firstName: this.form.firstname,
|
firstName: this.form.firstname,
|
||||||
lastName: this.form.lastname,
|
lastName: this.form.lastname,
|
||||||
password: this.form.password.password,
|
password: this.form.password.password,
|
||||||
|
language: this.selected,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -191,6 +212,7 @@ export default {
|
|||||||
this.form.lastname = ''
|
this.form.lastname = ''
|
||||||
this.form.password.password = ''
|
this.form.password.password = ''
|
||||||
this.form.password.passwordRepeat = ''
|
this.form.password.passwordRepeat = ''
|
||||||
|
this.selected = null
|
||||||
this.$router.push('/thx/register')
|
this.$router.push('/thx/register')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -206,6 +228,7 @@ export default {
|
|||||||
this.form.lastname = ''
|
this.form.lastname = ''
|
||||||
this.form.password.password = ''
|
this.form.password.password = ''
|
||||||
this.form.password.passwordRepeat = ''
|
this.form.password.passwordRepeat = ''
|
||||||
|
this.selected = null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -220,6 +243,9 @@ export default {
|
|||||||
emailFilled() {
|
emailFilled() {
|
||||||
return this.form.email !== ''
|
return this.form.email !== ''
|
||||||
},
|
},
|
||||||
|
languageFilled() {
|
||||||
|
return this.selected !== null
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -17,7 +17,6 @@ describe('UserCard_FormUserData', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
firstName: 'Peter',
|
firstName: 'Peter',
|
||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
@ -118,7 +117,6 @@ describe('UserCard_FormUserData', () => {
|
|||||||
expect(mockAPIcall).toBeCalledWith(
|
expect(mockAPIcall).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
firstName: 'Petra',
|
firstName: 'Petra',
|
||||||
lastName: 'Lustiger',
|
lastName: 'Lustiger',
|
||||||
@ -167,7 +165,6 @@ describe('UserCard_FormUserData', () => {
|
|||||||
expect(mockAPIcall).toBeCalledWith(
|
expect(mockAPIcall).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
firstName: 'Petra',
|
firstName: 'Petra',
|
||||||
lastName: 'Lustiger',
|
lastName: 'Lustiger',
|
||||||
|
|||||||
@ -85,7 +85,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showUserData: true,
|
showUserData: true,
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
form: {
|
form: {
|
||||||
firstName: this.$store.state.firstName,
|
firstName: this.$store.state.firstName,
|
||||||
lastName: this.$store.state.lastName,
|
lastName: this.$store.state.lastName,
|
||||||
@ -118,7 +117,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: updateUserInfos,
|
query: updateUserInfos,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
email: this.$store.state.email,
|
email: this.$store.state.email,
|
||||||
firstName: this.form.firstName,
|
firstName: this.form.firstName,
|
||||||
lastName: this.form.lastName,
|
lastName: this.form.lastName,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ describe('UserCard_FormUserMail', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
firstName: 'Peter',
|
firstName: 'Peter',
|
||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
@ -76,7 +75,6 @@ describe('UserCard_FormUserMail', () => {
|
|||||||
expect(mockAPIcall).toHaveBeenCalledWith(
|
expect(mockAPIcall).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
newEmail: 'test@example.org',
|
newEmail: 'test@example.org',
|
||||||
},
|
},
|
||||||
@ -106,7 +104,6 @@ describe('UserCard_FormUserMail', () => {
|
|||||||
expect(mockAPIcall).toHaveBeenCalledWith(
|
expect(mockAPIcall).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
newEmail: 'test@example.org',
|
newEmail: 'test@example.org',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: updateUserInfos,
|
query: updateUserInfos,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
email: this.$store.state.email,
|
email: this.$store.state.email,
|
||||||
newEmail: this.newEmail,
|
newEmail: this.newEmail,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,7 +17,6 @@ describe('UserCard_FormUserPasswort', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -175,7 +174,6 @@ describe('UserCard_FormUserPasswort', () => {
|
|||||||
expect(changePasswordProfileMock).toHaveBeenCalledWith(
|
expect(changePasswordProfileMock).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
password: '1234',
|
password: '1234',
|
||||||
passwordNew: 'Aa123456',
|
passwordNew: 'Aa123456',
|
||||||
|
|||||||
@ -81,7 +81,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: updateUserInfos,
|
query: updateUserInfos,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
email: this.$store.state.email,
|
email: this.$store.state.email,
|
||||||
password: this.form.password,
|
password: this.form.password,
|
||||||
passwordNew: this.form.newPassword.password,
|
passwordNew: this.form.newPassword.password,
|
||||||
|
|||||||
@ -25,7 +25,6 @@ describe('UserCard_FormUsername', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
username: '',
|
username: '',
|
||||||
},
|
},
|
||||||
@ -111,7 +110,6 @@ describe('UserCard_FormUsername', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
sessionId: 1,
|
|
||||||
username: 'username',
|
username: 'username',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -151,7 +149,6 @@ describe('UserCard_FormUsername', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
sessionId: 1,
|
|
||||||
username: 'username',
|
username: 'username',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -90,7 +90,6 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: updateUserInfos,
|
query: updateUserInfos,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.$store.state.sessionId,
|
|
||||||
email: this.$store.state.email,
|
email: this.$store.state.email,
|
||||||
username: this.form.username,
|
username: this.form.username,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,11 +13,6 @@ describe('UserProfileTransactionList', () => {
|
|||||||
$i18n: {
|
$i18n: {
|
||||||
locale: jest.fn(() => 'en'),
|
locale: jest.fn(() => 'en'),
|
||||||
},
|
},
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
sessionId: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dotenv = require('dotenv-webpack')
|
const webpack = require('webpack')
|
||||||
|
const Dotenv = require('dotenv-webpack')
|
||||||
|
|
||||||
process.env.VUE_APP_BUILD_COMMIT = process.env.BUILD_COMMIT
|
process.env.VUE_APP_BUILD_COMMIT = process.env.BUILD_COMMIT
|
||||||
|
|
||||||
@ -25,8 +26,17 @@ module.exports = {
|
|||||||
assets: path.join(__dirname, 'src/assets'),
|
assets: path.join(__dirname, 'src/assets'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line new-cap
|
plugins: [
|
||||||
plugins: [new dotenv()],
|
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: {
|
css: {
|
||||||
// Enable CSS source maps.
|
// Enable CSS source maps.
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
sudo apt install libsodium-dev
|
|
||||||
|
|
||||||
# get dependencies
|
|
||||||
git submodule update --init --recursive
|
|
||||||
|
|
||||||
|
|
||||||
cd dependencies/mariadb-connector-c
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DWITH_SSL=OFF ..
|
|
||||||
cd ../../../
|
|
||||||
|
|
||||||
|
|
||||||
# get more dependencies with conan (need conan from https://conan.io/)
|
|
||||||
mkdir build && cd build
|
|
||||||
# // not used anymore
|
|
||||||
# conan remote add inexor https://api.bintray.com/conan/inexorgame/inexor-conan
|
|
||||||
# not needed, but bincrafter
|
|
||||||
# conan install .. -s build_type=Debug
|
|
||||||
conan install ..
|
|
||||||
|
|
||||||
# build Makefile with cmake
|
|
||||||
cmake ..
|
|
||||||
|
|
||||||
make grpc
|
|
||||||
# under windows build at least release for protoc.exe and grpc c++ plugin
|
|
||||||
cd ../
|
|
||||||
./unix_parse_proto.sh
|
|
||||||
cd build
|
|
||||||
make
|
|
||||||
|
|
||||||
147
login_server/README.md
Normal file
147
login_server/README.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# Build Login-Server yourself
|
||||||
|
## Linux (Ubuntu) Packets
|
||||||
|
install build essentials
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y gcovr build-essential gettext libcurl4-openssl-dev libssl-dev libsodium-dev libboost-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
CMake is used for build file generation and the Login-Server needs at least version v3.18.2
|
||||||
|
You can build and install it from source.
|
||||||
|
The Version in apt is sadly to old.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Kitware/CMake.git --branch v3.18.2
|
||||||
|
cd CMake
|
||||||
|
./bootstrap --parallel=$(nproc) && make -j$(nproc) && sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
## dependencies
|
||||||
|
load git submodules if you haven't done it yet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
## build tools
|
||||||
|
build protoc and page compiler needed for generating some additional code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd scripts
|
||||||
|
./prepare_build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## build
|
||||||
|
build login-server in debug mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd scripts
|
||||||
|
./build_debug.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## multilanguage text
|
||||||
|
Login-Server uses gettext translations found after build in src/LOCALE
|
||||||
|
On Linux Login-Server expect the *.po files in folder /etc/grd_login/LOCALE
|
||||||
|
on windows next to Binary in Folder LOCALE.
|
||||||
|
So please copy them over by yourself on first run or after change.
|
||||||
|
|
||||||
|
If you like to update some translations your find a messages.pot in src/LOCALE.
|
||||||
|
Use it together with poedit and don't forget to copy over *.po files after change to /etc/grd_login/LOCALE
|
||||||
|
To update messages.pot run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/compile_pot.sh
|
||||||
|
```
|
||||||
|
This will be also called by ./scripts/build_debug.sh
|
||||||
|
|
||||||
|
## database
|
||||||
|
Login-Server needs a db to run, it is tested with mariadb
|
||||||
|
table definitions are found in folder ./skeema/gradido_login
|
||||||
|
Currently at least one group must be present in table groups.
|
||||||
|
For example:
|
||||||
|
```sql
|
||||||
|
INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `host`, `home`, `description`) VALUES
|
||||||
|
(1, 'docker', 'docker gradido group', 'localhost', 'localhost', '/', 'gradido test group for docker with blockchain db');
|
||||||
|
```
|
||||||
|
|
||||||
|
## configuration
|
||||||
|
Login-Server needs a configuration file to able to run.
|
||||||
|
On Linux it expect it to find the file /etc/grd_login/grd_login.properties
|
||||||
|
and /etc/grd_login/grd_login_test.properties for unittest
|
||||||
|
|
||||||
|
Example configuration (ini-format)
|
||||||
|
```ini
|
||||||
|
# Port for Web-Interface
|
||||||
|
HTTPServer.port = 1200
|
||||||
|
# Port for json-Interface (used by new backend)
|
||||||
|
JSONServer.port = 1201
|
||||||
|
# default group id for new users, if no group was choosen
|
||||||
|
Gradido.group_id = 1
|
||||||
|
|
||||||
|
# currently not used
|
||||||
|
crypto.server_admin_public = f909a866baec97c5460b8d7a93b72d3d4d20cc45d9f15d78bd83944eb9286b7f
|
||||||
|
# Server admin Passphrase
|
||||||
|
# nerve execute merit pool talk hockey basic win cargo spin disagree ethics swear price purchase say clutch decrease slow half forest reform cheese able
|
||||||
|
#
|
||||||
|
|
||||||
|
# TODO: auto-generate in docker build step
|
||||||
|
# expect valid hex 32 character long (16 Byte)
|
||||||
|
# salt for hashing user password, should be moved into db generated and saved per user, used for hardening against hash-tables
|
||||||
|
crypto.server_key = a51ef8ac7ef1abf162fb7a65261acd7a
|
||||||
|
|
||||||
|
# TODO: auto-generate in docker build step
|
||||||
|
# salt for hashing user encryption key, expect valid hex, as long as you like, used in sha512
|
||||||
|
crypto.app_secret = 21ffbbc616fe
|
||||||
|
|
||||||
|
# for url forwarding to old frontend, path of community server
|
||||||
|
phpServer.url = http://localhost/
|
||||||
|
# host for community server api calls
|
||||||
|
phpServer.host = localhost
|
||||||
|
# port for community server api calls
|
||||||
|
phpServer.port = 80
|
||||||
|
|
||||||
|
# Path for Login-Server Web-Interface used for link-generation
|
||||||
|
loginServer.path = http://localhost/account
|
||||||
|
# default language for new users and if no one is logged in
|
||||||
|
loginServer.default_locale = de
|
||||||
|
|
||||||
|
# db setup tested with mariadb, should also work with mysql
|
||||||
|
loginServer.db.host = localhost
|
||||||
|
loginServer.db.name = gradido_login
|
||||||
|
loginServer.db.user = root
|
||||||
|
loginServer.db.password =
|
||||||
|
loginServer.db.port = 3306
|
||||||
|
|
||||||
|
# check email path for new frontend for link generation in emails
|
||||||
|
frontend.checkEmailPath = http://localhost/vue/reset
|
||||||
|
|
||||||
|
# disable email all together
|
||||||
|
email.disable = true
|
||||||
|
|
||||||
|
# setup email smtp server for sending emails
|
||||||
|
#email.username =
|
||||||
|
#email.sender =
|
||||||
|
#email.admin_receiver =
|
||||||
|
#email.password =
|
||||||
|
#email.smtp.url =
|
||||||
|
#email.smtp.port =
|
||||||
|
|
||||||
|
# server setup types: test, staging or production
|
||||||
|
# used mainly to decide if using http or https for links
|
||||||
|
# test use http and staging and production uses https
|
||||||
|
ServerSetupType=test
|
||||||
|
dev.default_group = docker
|
||||||
|
|
||||||
|
# Session timeout in minutes
|
||||||
|
session.timeout = 15
|
||||||
|
|
||||||
|
# Disabling security features for faster develop and testing
|
||||||
|
unsecure.allow_passwort_via_json_request = 1
|
||||||
|
unsecure.allow_auto_sign_transactions = 1
|
||||||
|
unsecure.allow_cors_all = 1
|
||||||
|
|
||||||
|
# default disable, passwords must contain a number, a lower character, a high character, special character, and be at least 8 characters long
|
||||||
|
unsecure.allow_all_passwords = 1
|
||||||
|
|
||||||
|
```
|
||||||
1
login_server/dependencies/protobuf
Submodule
1
login_server/dependencies/protobuf
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 0b8d13a1d4cd9be16ed8a2230577aa9c296aa1ca
|
||||||
@ -1,12 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd ../scripts
|
|
||||||
|
|
||||||
chmod +x compile_pot.sh
|
chmod +x compile_pot.sh
|
||||||
|
./compile_pot.sh
|
||||||
|
|
||||||
cd ../build
|
cd ../build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Debug ..
|
cmake -DCMAKE_BUILD_TYPE=Debug ..
|
||||||
./compile_pot.sh
|
|
||||||
make -j$(nproc) Gradido_LoginServer
|
make -j$(nproc) Gradido_LoginServer
|
||||||
|
|
||||||
chmod +x ./bin/Gradido_LoginServer
|
chmod +x ./bin/Gradido_LoginServer
|
||||||
|
|||||||
@ -9,7 +9,7 @@ fi
|
|||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DWITH_SSL=OFF ..
|
cmake -DWITH_SSL=OFF ..
|
||||||
cd ../../
|
cd ../../../
|
||||||
|
|
||||||
if [ ! -d "./build" ] ; then
|
if [ ! -d "./build" ] ; then
|
||||||
mkdir build
|
mkdir build
|
||||||
|
|||||||
8
login_server/scripts/unittest_coverage.sh
Normal file
8
login_server/scripts/unittest_coverage.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd ../build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Debug -DCOLLECT_COVERAGE_DATA=ON -DCOVERAGE_TOOL=gcovr .. && \
|
||||||
|
make -j$(nproc) Gradido_LoginServer_Test
|
||||||
|
make coverage
|
||||||
|
|
||||||
|
|
||||||
@ -105,12 +105,13 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
|
|||||||
return stateSuccess();
|
return stateSuccess();
|
||||||
}
|
}
|
||||||
auto receiver_user_id = receiver_user->getModel()->getID();
|
auto receiver_user_id = receiver_user->getModel()->getID();
|
||||||
std::string checkEmailUrl = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath;
|
std::string linkInEmail = "";
|
||||||
if (emailVerificationCodeType == model::table::EMAIL_OPT_IN_RESET_PASSWORD)
|
if (emailVerificationCodeType == model::table::EMAIL_OPT_IN_RESET_PASSWORD)
|
||||||
{
|
{
|
||||||
|
linkInEmail = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_resetPasswordPath;
|
||||||
session = sm->getNewSession();
|
session = sm->getNewSession();
|
||||||
if (emailType == model::EMAIL_USER_RESET_PASSWORD) {
|
if (emailType == model::EMAIL_USER_RESET_PASSWORD) {
|
||||||
auto r = session->sendResetPasswordEmail(receiver_user, true, checkEmailUrl);
|
auto r = session->sendResetPasswordEmail(receiver_user, true, linkInEmail);
|
||||||
if (1 == r) {
|
if (1 == r) {
|
||||||
return stateWarning("email already sended");
|
return stateWarning("email already sended");
|
||||||
}
|
}
|
||||||
@ -120,7 +121,7 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
|
|||||||
}
|
}
|
||||||
else if (emailType == model::EMAIL_CUSTOM_TEXT) {
|
else if (emailType == model::EMAIL_CUSTOM_TEXT) {
|
||||||
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, model::table::EMAIL_OPT_IN_RESET_PASSWORD);
|
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, model::table::EMAIL_OPT_IN_RESET_PASSWORD);
|
||||||
email_verification_code_object->setBaseUrl(checkEmailUrl);
|
email_verification_code_object->setBaseUrl(linkInEmail);
|
||||||
auto email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);
|
auto email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);
|
||||||
em->addEmail(email);
|
em->addEmail(email);
|
||||||
}
|
}
|
||||||
@ -131,12 +132,13 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
linkInEmail = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath;
|
||||||
if (session->getNewUser()->getModel()->getRole() != model::table::ROLE_ADMIN) {
|
if (session->getNewUser()->getModel()->getRole() != model::table::ROLE_ADMIN) {
|
||||||
return stateError("admin needed");
|
return stateError("admin needed");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, emailVerificationCodeType);
|
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, emailVerificationCodeType);
|
||||||
email_verification_code_object->setBaseUrl(checkEmailUrl);
|
email_verification_code_object->setBaseUrl(linkInEmail);
|
||||||
model::Email* email = nullptr;
|
model::Email* email = nullptr;
|
||||||
if (emailType == model::EMAIL_CUSTOM_TEXT) {
|
if (emailType == model::EMAIL_CUSTOM_TEXT) {
|
||||||
email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);
|
email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);
|
||||||
|
|||||||
@ -51,6 +51,7 @@ namespace ServerConfig {
|
|||||||
std::string g_php_serverPath;
|
std::string g_php_serverPath;
|
||||||
std::string g_php_serverHost;
|
std::string g_php_serverHost;
|
||||||
std::string g_frontend_checkEmailPath;
|
std::string g_frontend_checkEmailPath;
|
||||||
|
std::string g_frontend_resetPasswordPath;
|
||||||
int g_phpServerPort;
|
int g_phpServerPort;
|
||||||
Poco::Mutex g_TimeMutex;
|
Poco::Mutex g_TimeMutex;
|
||||||
int g_FakeLoginSleepTime = 820;
|
int g_FakeLoginSleepTime = 820;
|
||||||
@ -238,8 +239,9 @@ namespace ServerConfig {
|
|||||||
if ("" != app_secret_string) {
|
if ("" != app_secret_string) {
|
||||||
g_CryptoAppSecret = DataTypeConverter::hexToBin(app_secret_string);
|
g_CryptoAppSecret = DataTypeConverter::hexToBin(app_secret_string);
|
||||||
}
|
}
|
||||||
std::string defaultCheckEmailPath = g_serverPath + "/checkEmail";
|
std::string defaultCheckEmailPath = "/account/checkEmail";
|
||||||
g_frontend_checkEmailPath = cfg.getString("frontend.checkEmailPath", defaultCheckEmailPath);
|
g_frontend_checkEmailPath = cfg.getString("frontend.checkEmailPath", defaultCheckEmailPath);
|
||||||
|
g_frontend_resetPasswordPath = cfg.getString("frontend.resetPasswordPath", defaultCheckEmailPath);
|
||||||
//g_CryptoAppSecret
|
//g_CryptoAppSecret
|
||||||
|
|
||||||
// unsecure flags
|
// unsecure flags
|
||||||
|
|||||||
@ -67,6 +67,7 @@ namespace ServerConfig {
|
|||||||
extern std::string g_php_serverPath;
|
extern std::string g_php_serverPath;
|
||||||
extern std::string g_php_serverHost;
|
extern std::string g_php_serverHost;
|
||||||
extern std::string g_frontend_checkEmailPath;
|
extern std::string g_frontend_checkEmailPath;
|
||||||
|
extern std::string g_frontend_resetPasswordPath;
|
||||||
extern int g_phpServerPort;
|
extern int g_phpServerPort;
|
||||||
extern Poco::Mutex g_TimeMutex;
|
extern Poco::Mutex g_TimeMutex;
|
||||||
extern int g_FakeLoginSleepTime;
|
extern int g_FakeLoginSleepTime;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user