Merge branch 'master' into 522-Userprofile-change-password-mobile-friendly-2

This commit is contained in:
Moriz Wahl 2021-08-04 16:10:52 +02:00
commit 5d7a69a147
37 changed files with 4856 additions and 12 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 ###################################################
##############################################################################

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

4
backend/.env.dist Normal file
View File

@ -0,0 +1,4 @@
PORT=4000
GRAPHIQL=false
// LOGIN_API_URL=http://login-server:1201/
// COMMUNITY_API_URL=http://nginx/api/

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,20 @@
// 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/',
}
// 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,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,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,37 @@
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,
email,
amount,
memo,
auto_sign: true,
}
const result = await apiPost(CONFIG.COMMUNITY_API_URL + 'sendCoins', 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)
}
}

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

@ -0,0 +1,47 @@
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 { 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],
})
// 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

@ -271,6 +271,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);
@ -301,12 +303,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 #########################################
#########################################################
@ -99,4 +118,5 @@ services:
volumes:
frontend_node_modules:
backend_node_modules:
login_build_ubuntu_3.1:

View File

@ -49,6 +49,30 @@ services:
volumes:
- 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

@ -23,7 +23,7 @@
></sidebar-item>
</template>
</side-bar>
<div class="main-content">
<div class="main-content" style="max-width: 1000px">
<div class="d-none d-md-block">
<b-navbar>
<b-navbar-nav class="ml-auto">

View File

@ -104,6 +104,7 @@
rows="3"
v-model="form.memo"
class="pl-3"
style="font-size: large"
></b-form-textarea>
</b-input-group>
<b-col v-if="errors">
@ -197,4 +198,9 @@ export default {
span.errors {
color: red;
}
#input-1:focus,
#input-2:focus,
#input-3:focus {
font-weight: bold;
}
</style>

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