Merge branch 'master' into 650-View-for-the-GDT-Transaction-List

This commit is contained in:
Moriz Wahl 2021-08-10 12:50:29 +02:00
commit 281a6beadd
52 changed files with 5135 additions and 264 deletions

View File

@ -7,7 +7,7 @@ on:
jobs:
##############################################################################
# JOB: DOCKER BUILD COMMUNITY NEO4J ##########################################
# JOB: DOCKER BUILD PRODUCTION FRONTEND ######################################
##############################################################################
build_production_frontend:
name: Docker Build Production - Frontend
@ -43,6 +43,43 @@ jobs:
name: docker-frontend-production
path: /tmp/frontend.tar
##############################################################################
# JOB: DOCKER BUILD PRODUCTION BACKEND #######################################
##############################################################################
build_production_backend:
name: Docker Build Production - Backend
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# SET ENVS ###############################################################
##########################################################################
- name: ENV - VERSION
run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
- name: ENV - BUILD_DATE
run: echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
- name: ENV - BUILD_VERSION
run: echo "BUILD_VERSION=${VERSION}.${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: ENV - BUILD_COMMIT
run: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
##########################################################################
# BACKEND ################################################################
##########################################################################
- name: Backend | Build `production` image
run: |
docker build --target production -t "gradido/backend:latest" -t "gradido/backend:production" -t "gradido/backend:${VERSION}" -t "gradido/backend:${BUILD_VERSION}" backend/
docker save "gradido/backend" > /tmp/backend.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: docker-backend-production
path: /tmp/backend.tar
##############################################################################
# JOB: DOCKER BUILD PRODUCTION LOGIN SERVER ##################################
##############################################################################
@ -199,7 +236,7 @@ jobs:
upload_to_dockerhub:
name: Upload to Dockerhub
runs-on: ubuntu-latest
needs: [build_production_frontend, build_production_login_server, build_production_community_server, build_production_mariadb, build_production_nginx]
needs: [build_production_frontend, build_production_backend, build_production_login_server, build_production_community_server, build_production_mariadb, build_production_nginx]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
@ -212,13 +249,20 @@ jobs:
##########################################################################
# DOWNLOAD DOCKER IMAGES #################################################
##########################################################################
- name: Download Docker Image (Neo4J)
- name: Download Docker Image (Frontend)
uses: actions/download-artifact@v2
with:
name: docker-frontend-production
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/frontend.tar
- name: Download Docker Image (Backend)
uses: actions/download-artifact@v2
with:
name: docker-backend-production
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/backend.tar
- name: Download Docker Image (Login Server)
uses: actions/download-artifact@v2
with:
@ -254,6 +298,8 @@ jobs:
run: echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
- name: Push frontend
run: docker push --all-tags gradido/frontend
- name: Push backend
run: docker push --all-tags gradido/backend
- name: Push login_server
run: docker push --all-tags gradido/login_server
- name: Push community_server

View File

@ -30,6 +30,32 @@ jobs:
name: docker-frontend-test
path: /tmp/frontend.tar
##############################################################################
# JOB: DOCKER BUILD TEST BACKEND #############################################
##############################################################################
build_test_backend:
name: Docker Build Test - Backend
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# FRONTEND ###############################################################
##########################################################################
- name: Backend | Build `test` image
run: |
docker build --target test -t "gradido/backend:test" backend/
docker save "gradido/backend:test" > /tmp/backend.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: docker-backend-test
path: /tmp/backend.tar
##############################################################################
# JOB: DOCKER BUILD TEST LOGIN SERVER ########################################
##############################################################################
@ -159,6 +185,35 @@ jobs:
- name: frontend | Lint
run: docker run --rm gradido/frontend:test yarn run lint
##############################################################################
# JOB: LINT BACKEND #########################################################
##############################################################################
lint_backend:
name: Lint - Backend
runs-on: ubuntu-latest
needs: [build_test_backend]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# DOWNLOAD DOCKER IMAGE ##################################################
##########################################################################
- name: Download Docker Image (Backend)
uses: actions/download-artifact@v2
with:
name: docker-backend-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/backend.tar
##########################################################################
# LINT FRONTEND ###########################################################
##########################################################################
- name: backend | Lint
run: docker run --rm gradido/backend:test yarn run lint
##############################################################################
# JOB: UNIT TEST FRONTEND ###################################################
##############################################################################
@ -262,7 +317,7 @@ jobs:
report_name: Coverage Backend Login
type: lcov
result_path: ./coverage/coverage.info
min_coverage: 25
min_coverage: 34
token: ${{ github.token }}
##############################################################################

3
.gitmodules vendored
View File

@ -31,6 +31,3 @@
[submodule "login_server/src/proto"]
path = login_server/src/proto
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

5
backend/.env.dist Normal file
View File

@ -0,0 +1,5 @@
PORT=4000
GRAPHIQL=false
// LOGIN_API_URL=http://login-server:1201/
// COMMUNITY_API_URL=http://nginx/api/
// GDT_API_URL=https://gdt.gradido.net

3
backend/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
**/*.min.js
build

26
backend/.eslintrc.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
root: true,
env: {
node: true,
// jest: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint' /*, 'jest' */],
extends: [
'standard',
'eslint:recommended',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended',
],
// add your custom rules here
rules: {
'no-console': ['error'],
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
},
}

6
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules/
/.env
/build/
# emacs
*~

8
backend/.prettierrc.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
semi: false,
printWidth: 100,
singleQuote: true,
trailingComma: "all",
tabWidth: 2,
bracketSpacing: true,
};

95
backend/Dockerfile Normal file
View File

@ -0,0 +1,95 @@
##################################################################################
# BASE ###########################################################################
##################################################################################
FROM node:12.19.0-alpine3.10 as base
# ENVs (available in production aswell, can be overwritten by commandline or env file)
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
ENV DOCKER_WORKDIR="/app"
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
## We cannot do $(npm run version).${BUILD_NUMBER} here so we default to 0.0.0.0
ENV BUILD_VERSION="0.0.0.0"
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
ENV BUILD_COMMIT="0000000"
## SET NODE_ENV
ENV NODE_ENV="production"
## App relevant Envs
ENV PORT="4000"
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.name="gradido:backend"
LABEL org.label-schema.description="Gradido GraphQL Backend"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
LABEL org.label-schema.url="https://gradido.net"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/backend"
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
LABEL org.label-schema.vendor="Gradido Community"
LABEL org.label-schema.version="${BUILD_VERSION}"
LABEL org.label-schema.schema-version="1.0"
LABEL maintainer="support@gradido.net"
# Install Additional Software
## install: git
#RUN apk --no-cache add git
# Settings
## Expose Container Port
EXPOSE ${PORT}
## Workdir
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
##################################################################################
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
##################################################################################
FROM base as development
# We don't need to copy or build anything since we gonna bind to the
# local filesystem which will need a rebuild anyway
# Run command
# (for development we need to execute npm install since the
# node_modules are on another volume and need updating)
CMD /bin/sh -c "yarn install && yarn run dev"
##################################################################################
# BUILD (Does contain all files and is therefore bloated) ########################
##################################################################################
FROM base as build
# Copy everything
COPY . .
# npm install
RUN yarn install --production=false --frozen-lockfile --non-interactive
# npm build
RUN yarn run build
##################################################################################
# TEST ###########################################################################
##################################################################################
FROM build as test
# Run command
CMD /bin/sh -c "yarn run dev"
##################################################################################
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
##################################################################################
FROM base as production
# Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/build ./build
# We also copy the node_modules express and serve-static for the run script
# COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy static files
# COPY --from=build ${DOCKER_WORKDIR}/public ./public
# Copy package.json for script definitions (lock file should not be needed)
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
# Copy run scripts run/
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
# Run command
CMD /bin/sh -c "yarn run start"

6
backend/ormconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"type": "sqlite",
"database": "./db.sqlite3",
"entities": ["./src/graphql/models/*.ts"],
"synchronize": true
}

45
backend/package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "gradido-backend",
"version": "0.0.1",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",
"author": "Ulf Gebhardt",
"license": "MIT",
"private": false,
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"start": "node build/index.js",
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
"lint": "eslint . --ext .js,.ts"
},
"dependencies": {
"apollo-server-express": "^2.25.2",
"axios": "^0.21.1",
"class-validator": "^0.13.1",
"express": "^4.17.1",
"graphql": "^15.5.1",
"jsonwebtoken": "^8.5.1",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.1.1",
"typeorm": "^0.2.34"
},
"devDependencies": {
"@types/express": "^4.17.12",
"@types/jsonwebtoken": "^8.5.2",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"nodemon": "^2.0.7",
"prettier": "^2.3.1",
"ts-node": "^10.0.0",
"typescript": "^4.3.4"
}
}

View File

@ -0,0 +1,37 @@
import axios from 'axios'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiPost = async (url: string, payload: unknown): Promise<any> => {
return axios
.post(url, payload)
.then((result) => {
if (result.status !== 200) {
throw new Error('HTTP Status Error ' + result.status)
}
if (result.data.state !== 'success') {
throw new Error(result.data.msg)
}
return { success: true, data: result.data }
})
.catch((error) => {
return { success: false, data: error.message }
})
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiGet = async (url: string): Promise<any> => {
return axios
.get(url)
.then((result) => {
if (result.status !== 200) {
throw new Error('HTTP Status Error ' + result.status)
}
if (!['success', 'warning'].includes(result.data.state)) {
throw new Error(result.data.msg)
}
return { success: true, data: result.data }
})
.catch((error) => {
return { success: false, data: error.message }
})
}

View File

@ -0,0 +1,21 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
import dotenv from 'dotenv'
dotenv.config()
const server = {
PORT: process.env.PORT || 4000,
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
LOGIN_API_URL: process.env.LOGIN_API_URL || 'http://login-server:1201/',
COMMUNITY_API_URL: process.env.COMMUNITY_API_URL || 'http://ngninx/api/',
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
}
// This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET
const CONFIG = { ...server }
export default CONFIG

View File

@ -0,0 +1,16 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class GdtTransactionInput {
@Field(() => String)
email: string
@Field(() => Number)
firstPage?: number
@Field(() => Number)
items?: number
@Field(() => String)
order?: string
}

View File

@ -0,0 +1,76 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class UnsecureLoginArgs {
@Field(() => String)
email: string
@Field(() => String)
password: string
}
@ArgsType()
export class CreateUserArgs {
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
password: string
}
@ArgsType()
export class ChangePasswordArgs {
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => String)
password: string
}
@ArgsType()
export class UpdateUserInfosArgs {
@Field(() => Number)
sessionId!: number
@Field(() => String)
email!: string
@Field({ nullable: true })
firstName?: string
@Field({ nullable: true })
lastName?: string
@Field({ nullable: true })
description?: string
@Field({ nullable: true })
username?: string
@Field({ nullable: true })
language?: string
@Field({ nullable: true })
password?: string
@Field({ nullable: true })
passwordNew?: string
}
@ArgsType()
export class CheckUsernameArgs {
@Field(() => String)
username: string
@Field(() => Number, { nullable: true })
groupId?: number
}

View File

@ -0,0 +1,31 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export class TransactionListInput {
@Field(() => Number)
sessionId: number
@Field(() => Int)
firstPage: number
@Field(() => Int)
items: number
@Field(() => String)
order: string
}
@ArgsType()
export class TransactionSendArgs {
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => Number)
amount: number
@Field(() => String)
memo: string
}

View File

@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class Balance {
constructor(json: any) {
this.balance = Number(json.balance)
this.decay = Number(json.decay)
this.decayDate = json.decay_date
}
@Field(() => Number)
balance: number
@Field(() => Number)
decay: number
@Field(() => String)
decayDate: string
}

View File

@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class CheckUsernameResponse {
constructor(json: any) {
this.state = json.state
this.msg = json.msg
this.groupId = json.group_id
}
@Field(() => String)
state: string
@Field(() => String)
msg?: string
@Field(() => Number)
groupId?: number
}

View File

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql'
@ObjectType()
export class Decay {
constructor(json: any) {
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
}
@Field(() => Number)
balance: number
@Field(() => Int, { nullable: true })
decayStart?: number
@Field(() => Int, { nullable: true })
decayEnd?: number
@Field(() => String, { nullable: true })
decayDuration?: string
}

View File

@ -0,0 +1,57 @@
import { ObjectType, Field } from 'type-graphql'
export enum GdtEntryType {
FORM = 1,
CVS = 2,
ELOPAGE = 3,
ELOPAGE_PUBLISHER = 4,
DIGISTORE = 5,
CVS2 = 6,
GLOBAL_MODIFICATOR = 7,
}
@ObjectType()
export class GdtEntry {
constructor(json: any) {
this.amount = json.amount
this.date = json.date
this.email = json.email
this.comment = json.comment
this.couponCode = json.coupon_code
this.gdtEntryType = json.gdt_entry_type_id
this.factor = json.factor
this.amount2 = json.amount2
this.factor2 = json.factor2
this.gdt = json.gdt
}
@Field(() => Number)
amount: number
@Field(() => String)
date: string
@Field(() => String)
email: string
@Field(() => String)
comment: string
@Field(() => String)
couponCode: string
@Field(() => Number)
gdtEntryType: GdtEntryType
@Field(() => Number)
factor: number
@Field(() => Number)
amount2: number
@Field(() => Number)
factor2: number
@Field(() => Number)
gdt: number
}

View File

@ -0,0 +1,42 @@
import { GdtEntry } from './GdtEntry'
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class GdtSumPerEmail {
constructor(email: string, summe: number) {
this.email = email
this.summe = summe
}
@Field(() => String)
email: string
@Field(() => Number)
summe: number
}
@ObjectType()
export class GdtEntryList {
constructor(json: any) {
this.state = json.state
this.count = json.count
this.gdtEntries = json.gdtEntries ? json.gdtEntries.map((json: any) => new GdtEntry(json)) : []
this.gdtSum = json.gdtSum
this.timeUsed = json.timeUsed
}
@Field(() => String)
state: string
@Field(() => Number)
count: number
@Field(() => [GdtEntry])
gdtEntries: GdtEntry[]
@Field(() => Number)
gdtSum: number
@Field(() => Number)
timeUsed: number
}

View File

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

View File

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

View File

@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class SendPasswordResetEmailResponse {
constructor(json: any) {
this.state = json.state
this.msg = json.msg
}
@Field(() => String)
state: string
@Field(() => String)
msg?: string
}

View File

@ -0,0 +1,92 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { Decay } from './Decay'
// we need a better solution for the decay block:
// the first transaction on the first page shows the decay since the last transaction
// the format is actually a Decay and not a Transaction.
// Therefore we have a lot of nullable fields, which should be always present
@ObjectType()
export class Transaction {
constructor(json: any) {
// console.log('Transaction constructor', json)
this.type = json.type
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.memo = json.memo
this.transactionId = json.transaction_id
this.name = json.name
this.email = json.email
this.date = json.date
this.decay = json.decay ? new Decay(json.decay) : undefined
}
@Field(() => String)
type: string
@Field(() => Number)
balance: number
@Field({ nullable: true })
decayStart?: number
@Field({ nullable: true })
decayEnd?: number
@Field({ nullable: true })
decayDuration?: string
@Field(() => String)
memo: string
@Field(() => Number)
transactionId: number
@Field({ nullable: true })
name?: string
@Field({ nullable: true })
email?: string
@Field({ nullable: true })
date?: string
@Field({ nullable: true })
decay?: Decay
}
@ObjectType()
export class TransactionList {
constructor(json: any) {
this.gdtSum = Number(json.gdtSum)
this.count = json.count
this.balance = Number(json.balance)
this.decay = Number(json.decay)
this.decayDate = json.decay_date
this.transactions = json.transactions.map((el: any) => {
return new Transaction(el)
})
}
@Field(() => Number)
gdtSum: number
@Field(() => Number)
count: number
@Field(() => Number)
balance: number
@Field(() => Number)
decay: number
@Field(() => String)
decayDate: string
@Field(() => [Transaction])
transactions: Transaction[]
}

View File

@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class UpdateUserInfosResponse {
constructor(json: any) {
this.validValues = json.valid_values
}
@Field(() => Number)
validValues: number
}

View File

@ -0,0 +1,67 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class User {
/*
@Field(() => ID)
@PrimaryGeneratedColumn()
id: number
*/
constructor(json: any) {
this.email = json.email
this.firstName = json.first_name
this.lastName = json.last_name
this.username = json.username
this.description = json.description
this.language = json.language
}
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
username: string
@Field(() => String)
description: string
/*
@Field(() => String)
pubkey: string
// not sure about the type here. Maybe better to have a string
@Field(() => number)
created: number
@Field(() => Boolean)
emailChecked: boolean
@Field(() => Boolean)
passphraseShown: boolean
*/
@Field(() => String)
language: string
/*
@Field(() => Boolean)
disabled: boolean
*/
/* I suggest to have a group as type here
@Field(() => ID)
groupId: number
// what is puvlisherId?
@Field(() => ID)
publisherId: number
*/
}

View File

@ -0,0 +1,14 @@
import { Resolver, Query, /* Mutation, */ Arg } from 'type-graphql'
import CONFIG from '../../config'
import { Balance } from '../models/Balance'
import { apiGet } from '../../apis/loginAPI'
@Resolver()
export class BalanceResolver {
@Query(() => Balance)
async balance(@Arg('sessionId') sessionId: number): Promise<Balance> {
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
if (!result.success) throw new Error(result.data)
return new Balance(result.data)
}
}

View File

@ -0,0 +1,26 @@
// import jwt from 'jsonwebtoken'
import { Resolver, Query, /* Mutation, */ Args } from 'type-graphql'
import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionInput } from '../inputs/GdtInputs'
import { apiGet } from '../../apis/loginAPI'
@Resolver()
export class GdtResolver {
@Query(() => GdtEntryList)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async listGDTEntries(
@Args() { email, firstPage = 1, items = 5, order = 'DESC' }: GdtTransactionInput,
): Promise<GdtEntryList> {
email = email.trim().toLowerCase()
const result = await apiGet(
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${email}/${firstPage}/${items}/${order}`,
)
if (!result.success) {
throw new Error(result.data)
}
return new GdtEntryList(result.data)
}
}

View File

@ -0,0 +1,39 @@
import { Resolver, Query, /* Mutation, */ Args } from 'type-graphql'
import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
import { apiGet, apiPost } from '../../apis/loginAPI'
@Resolver()
export class TransactionResolver {
@Query(() => TransactionList)
async transactionList(
@Args() { sessionId, firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
): Promise<TransactionList> {
const result = await apiGet(
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
)
if (!result.success) throw new Error(result.data)
return new TransactionList(result.data)
}
@Query(() => String)
async sendCoins(
@Args() { sessionId, email, amount, memo }: TransactionSendArgs,
): Promise<string> {
const payload = {
session_id: sessionId,
target_email: email,
amount: amount * 10000,
memo,
auto_sign: true,
transaction_type: 'transfer',
blockchain_type: 'mysql',
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createTransaction', payload)
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
}

View File

@ -0,0 +1,161 @@
// import jwt from 'jsonwebtoken'
import { Resolver, Query, Args, Arg } from 'type-graphql'
import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { LoginResponse } from '../models/LoginResponse'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
import {
ChangePasswordArgs,
CheckUsernameArgs,
CreateUserArgs,
UnsecureLoginArgs,
UpdateUserInfosArgs,
} from '../inputs/LoginUserInput'
import { apiPost, apiGet } from '../../apis/loginAPI'
@Resolver()
export class UserResolver {
@Query(() => LoginResponse)
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<LoginResponse> {
email = email.trim().toLowerCase()
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
// if there is no user, throw an authentication error
if (!result.success) {
throw new Error(result.data)
}
// temporary solution until we have JWT implemented
return new LoginResponse(result.data)
// create and return the json web token
// 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)
async loginViaEmailVerificationCode(
@Arg('optin') optin: string,
): Promise<LoginViaVerificationCode> {
// I cannot use number as type here.
// The value received is not the same as sent by the query
const result = await apiGet(
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
)
if (!result.success) {
throw new Error(result.data)
}
return new LoginViaVerificationCode(result.data)
}
@Query(() => String)
async logout(@Arg('sessionId') sessionId: number): Promise<string> {
const payload = { session_id: sessionId }
const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@Query(() => String)
async create(@Args() { email, firstName, lastName, password }: CreateUserArgs): Promise<string> {
const payload = {
email,
first_name: firstName,
last_name: lastName,
password,
emailType: 2,
login_after_register: true,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createUser', payload)
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@Query(() => SendPasswordResetEmailResponse)
async sendResetPasswordEmail(
@Arg('email') email: string,
): Promise<SendPasswordResetEmailResponse> {
const payload = {
email,
email_text: 7,
email_verification_code_type: 'resetPassword',
}
const response = await apiPost(CONFIG.LOGIN_API_URL + 'sendEmail', payload)
if (!response.success) throw new Error(response.data)
return new SendPasswordResetEmailResponse(response.data)
}
@Query(() => String)
async resetPassword(
@Args()
{ sessionId, email, password }: ChangePasswordArgs,
): Promise<string> {
const payload = {
session_id: sessionId,
email,
password,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
if (!result.success) throw new Error(result.data)
return 'sucess'
}
@Query(() => UpdateUserInfosResponse)
async updateUserInfos(
@Args()
{
sessionId,
email,
firstName,
lastName,
description,
username,
language,
password,
passwordNew,
}: UpdateUserInfosArgs,
): Promise<UpdateUserInfosResponse> {
const payload = {
session_id: sessionId,
email,
update: {
'User.first_name': firstName || undefined,
'User.last_name': lastName || undefined,
'User.description': description || undefined,
'User.username': username || undefined,
'User.language': language || undefined,
'User.password': passwordNew || undefined,
'User.password_old': password || undefined,
},
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
if (!result.success) throw new Error(result.data)
return new UpdateUserInfosResponse(result.data)
}
@Query(() => CheckUsernameResponse)
async checkUsername(
@Args() { username, groupId = 1 }: CheckUsernameArgs,
): Promise<CheckUsernameResponse> {
const response = await apiGet(
CONFIG.LOGIN_API_URL + `checkUsername?username=${username}&group_id=${groupId}`,
)
if (!response.success) throw new Error(response.data)
return new CheckUsernameResponse(response.data)
}
}

48
backend/src/index.ts Normal file
View File

@ -0,0 +1,48 @@
import 'reflect-metadata'
import express from 'express'
import { buildSchema } from 'type-graphql'
import { ApolloServer } from 'apollo-server-express'
// import { createConnection } from 'typeorm'
import CONFIG from './config'
// TODO move to extern
// import { BookResolver } from './graphql/resolvers/BookResolver'
import { UserResolver } from './graphql/resolvers/UserResolver'
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
import { GdtResolver } from './graphql/resolvers/GdtResolver'
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
// TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
async function main() {
// const connection = await createConnection()
const schema = await buildSchema({
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
})
// Graphiql interface
let playground = false
if (CONFIG.GRAPHIQL) {
playground = true
}
// Express Server
const server = express()
// Apollo Server
const apollo = new ApolloServer({ schema, playground })
apollo.applyMiddleware({ app: server })
// Start Server
server.listen(CONFIG.PORT, () => {
// eslint-disable-next-line no-console
console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
if (CONFIG.GRAPHIQL) {
// eslint-disable-next-line no-console
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}/graphql`)
}
})
}
main()

72
backend/tsconfig.json Normal file
View File

@ -0,0 +1,72 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./build", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

3706
backend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -274,6 +274,8 @@ class AppRequestsController extends AppController
public function getBalance($session_id = 0)
{
$this->viewBuilder()->setLayout('ajax');
$this->response = $this->response->withType('application/json');
$login_result = $this->requestLogin($session_id, false);
if($login_result !== true) {
$this->set('body', $login_result);
@ -304,12 +306,16 @@ class AppRequestsController extends AppController
}
$body['decay_date'] = $now;
$this->addAdminError("AppRequests", "getBalance", $body, $user['id']);
$this->set('body', $body);
}
public function listTransactions($page = 1, $count = 25, $orderDirection = 'ASC', $session_id = 0)
{
$this->viewBuilder()->setLayout('ajax');
$this->response = $this->response->withType('application/json');
$startTime = microtime(true);
$login_result = $this->requestLogin($session_id, false);

View File

@ -6,6 +6,10 @@
* and open the template in the editor.
*/
$body['balance'] = $this->element('centToFloat', ['cent' => $body['balance'], 'precision' => 4]);
$body['decay'] = $this->element('centToFloat', ['cent' => $body['decay'], 'precision' => 4]);
if(isset($body['balance'])) {
$body['balance'] = $this->element('centToFloat', ['cent' => $body['balance'], 'precision' => 4]);
}
if(isset($body['decay'])) {
$body['decay'] = $this->element('centToFloat', ['cent' => $body['decay'], 'precision' => 4]);
}
?><?= json_encode($body) ?>

View File

@ -21,6 +21,25 @@ services:
# bind the local folder to the docker to allow live reload
- ./frontend:/app
########################################################
# BACKEND ##############################################
########################################################
backend:
image: gradido/backend:development
build:
target: development
networks:
- external-net
- internal-net
environment:
- NODE_ENV="development"
volumes:
# This makes sure the docker container has its own node modules.
# Therefore it is possible to have a different node version on the host machine
- backend_node_modules:/app/node_modules
# bind the local folder to the docker to allow live reload
- ./backend:/app
#########################################################
## LOGIN SERVER #########################################
#########################################################
@ -100,4 +119,5 @@ services:
volumes:
frontend_node_modules:
login_build_ubuntu_3.1:
backend_node_modules:
login_build_ubuntu_3.1:

View File

@ -47,7 +47,31 @@ services:
ports:
- 3306:3306
volumes:
- db_vol:/var/lib/mysql
- db_vol:/var/lib/mysql
########################################################
# BACKEND ##############################################
########################################################
backend:
image: gradido/backend:latest
build:
context: ./backend
target: production
networks:
- internal-net
ports:
- 4000:4000
environment:
# Envs used in Dockerfile
# - DOCKER_WORKDIR="/app"
# - PORT=4000
- BUILD_DATE
- BUILD_VERSION
- BUILD_COMMIT
- NODE_ENV="production"
# Application only envs
#env_file:
# - ./frontend/.env
#########################################################
## LOGIN SERVER #########################################

View File

@ -6,22 +6,19 @@
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
>
<b-form-group :label="label" :label-for="labelFor">
<b-input-group>
<b-form-input
v-model="currentValue"
v-bind="ariaInput"
:id="labelFor"
:name="name"
:placeholder="placeholder"
type="email"
:state="validated ? valid : false"
trim
class="email-form-input"
></b-form-input>
<b-form-invalid-feedback v-bind="ariaMsg">
{{ errors[0] }}
</b-form-invalid-feedback>
</b-input-group>
<b-form-input
v-model="currentValue"
v-bind="ariaInput"
:id="labelFor"
:name="name"
:placeholder="placeholder"
type="email"
:state="validated ? valid : false"
trim
></b-form-input>
<b-form-invalid-feedback v-bind="ariaMsg">
{{ errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</template>
@ -62,12 +59,3 @@ export default {
},
}
</script>
<style>
.email-form-input {
border-right-style: solid !important;
border-right-width: 1px !important;
padding-right: 12px !important;
border-top-right-radius: 6px !important;
border-bottom-right-radius: 6px !important;
}
</style>

View File

@ -19,7 +19,11 @@
:state="validated ? valid : false"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="toggleShowPassword">
<b-button
variant="outline-light"
@click="toggleShowPassword"
class="border-left-0 rounded-right"
>
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>

View File

@ -10,11 +10,11 @@
containsNumericCharacter: true,
atLeastEightCharactera: true,
}"
:label="$t('form.password_new')"
:label="register ? $t('form.password') : $t('form.password_new')"
:showAllErrors="true"
:immediate="true"
:name="$t('form.password_new')"
:placeholder="$t('form.password_new')"
:name="createId(register ? $t('form.password') : $t('form.password_new'))"
:placeholder="register ? $t('form.password') : $t('form.password_new')"
v-model="password"
></input-password>
</b-col>
@ -23,8 +23,9 @@
<b-col>
<input-password
:rules="{ samePassword: value.password }"
:label="$t('form.password_new_repeat')"
:placeholder="$t('form.password_new_repeat')"
:label="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')"
:name="createId(register ? $t('form.passwordRepeat') : $t('form.password_new_repeat'))"
:placeholder="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')"
v-model="passwordRepeat"
></input-password>
</b-col>
@ -44,6 +45,10 @@ export default {
type: Object,
required: true,
},
register: {
type: Boolean,
required: false,
},
},
data() {
return {
@ -51,6 +56,11 @@ export default {
passwordRepeat: '',
}
},
methods: {
createId(text) {
return text.replace(/ +/g, '-')
},
},
computed: {
passwordObject() {
return { password: this.password, passwordRepeat: this.passwordRepeat }

View File

@ -16,7 +16,11 @@
<ul class="nav align-items-center d-md-none">
<div class="media align-items-center">
<span class="avatar avatar-sm">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
<vue-qrcode
v-if="$store.state.email"
:value="$store.state.email"
type="image/png"
></vue-qrcode>
</span>
</div>
</ul>

View File

@ -59,6 +59,7 @@
"password_new_repeat":"neues Passwort wiederholen",
"change": "ändern",
"change-password": "Passwort ändern",
"change-name": "Name ändern",
"amount":"Betrag",
"memo":"Nachricht",
"message":"Nachricht",

View File

@ -59,6 +59,7 @@
"password_new_repeat":"Repeat new password",
"change": "change",
"change-password": "Change password",
"change-name": "Change name",
"amount":"Amount",
"memo":"Message",
"message":"Message",

View File

@ -61,15 +61,15 @@ describe('Register', () => {
})
it('has email input fields', () => {
expect(wrapper.find('#registerEmail').exists()).toBeTruthy()
expect(wrapper.find('#Email-input-field').exists()).toBeTruthy()
})
it('has password input fields', () => {
expect(wrapper.find('#registerPassword').exists()).toBeTruthy()
expect(wrapper.find('input[name="form.password"]').exists()).toBeTruthy()
})
it('has password repeat input fields', () => {
expect(wrapper.find('#registerPasswordRepeat').exists()).toBeTruthy()
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
})
it('has 1 checkbox input fields', () => {
@ -80,30 +80,31 @@ describe('Register', () => {
expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
})
it('shows a warning when no valid Email is entered', async () => {
wrapper.find('#registerEmail').setValue('no_valid@Email')
it('displays a message that Email is required', async () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
await expect(wrapper.find('#registerEmailLiveFeedback').text()).toEqual(
'validations.messages.email',
expect(wrapper.findAll('div.invalid-feedback').at(0).text()).toBe(
'validations.messages.required',
)
})
it('shows 4 warnings when no password is set', async () => {
const passwords = wrapper.findAll('input[type="password"]')
passwords.at(0).setValue('')
passwords.at(1).setValue('')
it('displays a message that password is required', async () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
await expect(wrapper.find('div.hints').text()).toContain(
'site.signup.lowercase',
'site.signup.uppercase',
'site.signup.minimum',
'site.signup.one_number',
expect(wrapper.findAll('div.invalid-feedback').at(1).text()).toBe(
'validations.messages.required',
)
})
// TODO test different invalid password combinations
it('displays a message that passwordConfirm is required', async () => {
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.findAll('div.invalid-feedback').at(2).text()).toBe(
'validations.messages.required',
)
})
})
// TODO test submit button
// To Do: Test lines 156-197,210-213
})
})

View File

@ -76,99 +76,14 @@
</b-form-group>
</validation-provider>
<validation-provider
name="Email"
:rules="{ required: true, email: true }"
v-slot="validationContext"
>
<b-form-group class="mb-3" label="Email" label-for="registerEmail">
<b-form-input
id="registerEmail"
name="Email"
v-model="form.email"
placeholder="Email"
:state="getValidationState(validationContext)"
aria-describedby="registerEmailLiveFeedback"
></b-form-input>
<b-form-invalid-feedback id="registerEmailLiveFeedback">
{{ validationContext.errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
<input-email v-model="form.email"></input-email>
<hr />
<input-password-confirmation
v-model="form.password"
:register="register"
></input-password-confirmation>
<validation-provider
:name="$t('form.password')"
:rules="{ required: true }"
v-slot="validationContext"
>
<b-form-group
class="mb-5"
:label="$t('form.password')"
label-for="registerPassword"
>
<b-input-group>
<b-form-input
id="registerPassword"
:name="$t('form.password')"
v-model="form.password"
:placeholder="$t('form.password')"
:type="passwordVisible ? 'text' : 'password'"
:state="getValidationState(validationContext)"
aria-describedby="registerPasswordLiveFeedback"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordVisibility">
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
<b-form-invalid-feedback id="registerPasswordLiveFeedback">
{{ validationContext.errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
<b-form-group
class="mb-5"
:label="$t('form.passwordRepeat')"
label-for="registerPasswordRepeat"
>
<b-input-group>
<b-form-input
id="registerPasswordRepeat"
:name="$t('form.passwordRepeat')"
v-model.lazy="form.passwordRepeat"
:placeholder="$t('form.passwordRepeat')"
:type="passwordVisibleRepeat ? 'text' : 'password'"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordRepeatVisibility">
<b-icon :icon="passwordVisibleRepeat ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<transition name="hint" appear>
<div v-if="passwordValidation.errors.length > 0 && !submitted" class="hints">
<ul>
<li v-for="error in passwordValidation.errors" :key="error">
<small>{{ error }}</small>
</li>
</ul>
</div>
<div class="matches" v-else-if="!samePasswords">
<p>
{{ $t('site.signup.dont_match') }}
<i class="ni ni-active-40" color="danger"></i>
</p>
</div>
</transition>
<b-row class="my-4">
<b-col cols="12">
<b-form-checkbox
@ -194,17 +109,7 @@
</span>
</b-alert>
<div
class="text-center"
v-if="
passwordsFilled &&
samePasswords &&
passwordValidation.valid &&
namesFilled &&
emailFilled &&
form.agree
"
>
<div class="text-center" v-if="namesFilled && emailFilled && form.agree">
<div class="text-center">
<b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button>
<b-button type="submit" variant="primary">{{ $t('signup') }}</b-button>
@ -224,8 +129,11 @@
</template>
<script>
import loginAPI from '../../apis/loginAPI'
import InputEmail from '../../components/Inputs/InputEmail.vue'
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
export default {
components: { InputPasswordConfirmation, InputEmail },
name: 'register',
data() {
return {
@ -234,15 +142,15 @@ export default {
lastname: '',
email: '',
agree: false,
password: '',
passwordRepeat: '',
password: {
password: '',
passwordRepeat: '',
},
},
passwordVisible: false,
passwordVisibleRepeat: false,
submitted: false,
showError: false,
messageError: '',
register: true,
}
},
methods: {
@ -254,36 +162,39 @@ export default {
firstname: '',
lastname: '',
email: '',
password: '',
passwordRepeat: '',
password: {
password: '',
passwordRepeat: '',
},
agree: false,
}
this.$nextTick(() => {
this.$refs.observer.reset()
})
},
togglePasswordVisibility() {
this.passwordVisible = !this.passwordVisible
},
togglePasswordRepeatVisibility() {
this.passwordVisibleRepeat = !this.passwordVisibleRepeat
},
async onSubmit() {
const result = await loginAPI.create(
this.form.email,
this.form.firstname,
this.form.lastname,
this.form.password,
this.form.password.password,
)
if (result.success) {
this.$store.dispatch('login', {
/* this.$store.dispatch('login', {
sessionId: result.result.data.session_id,
email: this.form.email,
user: {
email: this.form.email,
firstName: this.form.firstname,
lastName: this.form.lastname
}
})
*/
this.form.email = ''
this.form.firstname = ''
this.form.lastname = ''
this.password = ''
this.passwordVisibleRepeat = ''
this.form.password.password = ''
this.$router.push('/thx/register')
} else {
this.showError = true
@ -296,16 +207,10 @@ export default {
this.form.email = ''
this.form.firstname = ''
this.form.lastname = ''
this.form.password = ''
this.form.password.password = ''
},
},
computed: {
samePasswords() {
return this.form.password === this.form.passwordRepeat
},
passwordsFilled() {
return this.form.password !== '' && this.form.passwordRepeat !== ''
},
namesFilled() {
return (
this.form.firstname !== '' &&
@ -317,26 +222,6 @@ export default {
emailFilled() {
return this.form.email !== ''
},
rules() {
return [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
]
},
passwordValidation() {
const errors = []
for (const condition of this.rules) {
if (!condition.regex.test(this.form.password)) {
errors.push(condition.message)
}
}
if (errors.length === 0) {
return { valid: true, errors }
}
return { valid: false, errors }
},
},
}
</script>

View File

@ -26,7 +26,7 @@
<b-card-body class="p-4">
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
<input-password-confirmation v-model="form" />
<input-password-confirmation v-model="form" :register="register" />
<div class="text-center">
<b-button type="submit" variant="primary" class="mt-4">
{{ $t('reset') }}
@ -65,6 +65,7 @@ export default {
sessionId: null,
email: null,
pending: true,
register: false,
}
},
methods: {

View File

@ -3,26 +3,23 @@
<div class="w-100 text-center">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</div>
<b-row>
<b-col>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">
<div>
<span class="heading">
{{ $n(balance, 'decimal') }}
</span>
<span class="description">GDD</span>
</div>
<div>
<span class="heading">{{ transactionCount }}</span>
<span class="description">{{ $t('transactions') }}</span>
</div>
<div>
<span class="heading">--</span>
<span class="description">{{ $t('community') }}</span>
</div>
</div>
</b-col>
</b-row>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">
<div>
<span class="heading">
{{ $n(balance, 'decimal') }}
</span>
<span class="description">GDD</span>
</div>
<div>
<span class="heading">{{ transactionCount }}</span>
<span class="description">{{ $t('transactions') }}</span>
</div>
<div>
<span class="heading">--</span>
<span class="description">{{ $t('community') }}</span>
</div>
</div>
</b-card>
</template>
<script>

View File

@ -1,29 +1,22 @@
<template>
<div class="userdata_form">
<b-card
<div class="userdata_form" fluid="sm">
<div
id="userdata_form"
class="bg-transparent"
class="bg-transparent pt-3 pb-3"
style="background-color: #ebebeba3 !important"
>
<b-container>
<b-row class="text-right">
<b-col class="mb-3">
<b-icon
v-if="showUserData"
@click="showUserData = !showUserData"
class="pointer"
icon="pencil"
>
{{ $t('form.change') }}
</b-icon>
<a @click="showUserData ? (showUserData = !showUserData) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-name') }}</span>
<b-icon
v-else
@click="cancelEdit"
class="pointer"
icon="x-circle"
variant="danger"
></b-icon>
<b-icon v-if="showUserData" class="pointer" icon="pencil">
{{ $t('form.change') }}
</b-icon>
<b-icon v-else class="pointer" icon="x-circle" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</b-container>
@ -81,7 +74,7 @@
</b-row>
</b-form>
</b-container>
</b-card>
</div>
</div>
</template>
<script>

View File

@ -1,22 +1,22 @@
<template>
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
<div
id="change_pwd"
class="bg-transparent pt-3 pb-3"
style="background-color: #ebebeba3 !important"
>
<b-container>
<div v-if="!editPassword">
<div>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#change_pwd" @click="editPassword = !editPassword">
<span>{{ $t('form.change-password') }}</span>
<b-icon class="pointer ml-3" icon="pencil" />
<a @click="!editPassword ? (editPassword = !editPassword) : cancelEdit()">
<span class="pointer mr-3">{{ $t('form.change-password') }}</span>
<b-icon v-if="!editPassword" class="pointer ml-3" icon="pencil" />
<b-icon v-else icon="x-circle" class="pointer ml-3" variant="danger"></b-icon>
</a>
</b-col>
</b-row>
</div>
<div v-if="editPassword">
<b-row class="mb-4 text-right">
<b-col class="text-right">
<b-icon @click="cancelEdit()" class="pointer" icon="x-circle" variant="danger"></b-icon>
</b-col>
</b-row>
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-2">
@ -28,7 +28,7 @@
></input-password>
</b-col>
</b-row>
<input-password-confirmation v-model="form.newPassword" />
<input-password-confirmation v-model="form.newPassword" :register="register" />
<b-row class="text-right">
<b-col>
<div class="text-right">
@ -42,7 +42,7 @@
</validation-observer>
</div>
</b-container>
</b-card>
</div>
</template>
<script>
import loginAPI from '../../../apis/loginAPI'
@ -66,6 +66,7 @@ export default {
passwordRepeat: '',
},
},
register: false,
}
},
methods: {

View File

@ -1,10 +1,11 @@
<template>
<b-container fluid>
<div fluid="sm">
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
<form-user-data />
<p><form-user-data /></p>
<!--<form-username />-->
<form-user-passwort />
</b-container>
<hr />
<p><form-user-passwort /></p>
</div>
</template>
<script>
import UserCard from './UserProfile/UserCard.vue'

@ -1 +0,0 @@
Subproject commit 0b8d13a1d4cd9be16ed8a2230577aa9c296aa1ca