mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into login_call_createUser
This commit is contained in:
commit
45e82c1aff
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@ -344,7 +344,7 @@ jobs:
|
|||||||
report_name: Coverage Frontend
|
report_name: Coverage Frontend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 83
|
min_coverage: 85
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -353,7 +353,7 @@ jobs:
|
|||||||
unit_test_backend:
|
unit_test_backend:
|
||||||
name: Unit tests - Backend
|
name: Unit tests - Backend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_backend]
|
needs: [build_test_backend,build_test_mariadb]
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -363,6 +363,13 @@ jobs:
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGES #################################################
|
# DOWNLOAD DOCKER IMAGES #################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
- name: Download Docker Image (Mariadb)
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: docker-mariadb-test
|
||||||
|
path: /tmp
|
||||||
|
- name: Load Docker Image
|
||||||
|
run: docker load < /tmp/mariadb.tar
|
||||||
- name: Download Docker Image (Backend)
|
- name: Download Docker Image (Backend)
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
@ -373,10 +380,11 @@ jobs:
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
# UNIT TESTS BACKEND #####################################################
|
# UNIT TESTS BACKEND #####################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: backend | Unit tests
|
- name: backend | docker-compose
|
||||||
run: |
|
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb database
|
||||||
docker run -v ~/coverage:/app/coverage --rm gradido/backend:test yarn run test
|
- name: backend Unit tests | test
|
||||||
cp -r ~/coverage ./coverage
|
run: cd database && yarn && cd ../backend && yarn && yarn test
|
||||||
|
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE CHECK BACKEND #################################################
|
# COVERAGE CHECK BACKEND #################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -385,8 +393,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
report_name: Coverage Backend
|
report_name: Coverage Backend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./backend/coverage/lcov.info
|
||||||
min_coverage: 1
|
min_coverage: 41
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"streetsidesoftware.code-spell-checker",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
66
README.md
66
README.md
@ -8,33 +8,73 @@ The Gradido model can create global prosperity and peace
|
|||||||
The Corona crisis has fundamentally changed our world within a very short time.
|
The Corona crisis has fundamentally changed our world within a very short time.
|
||||||
The dominant financial system threatens to fail around the globe, followed by mass insolvencies, record unemployment and abject poverty. Only with a sustainable new monetary system can humanity master these challenges of the 21st century. The Gradido Academy for Bionic Economy has developed such a system.
|
The dominant financial system threatens to fail around the globe, followed by mass insolvencies, record unemployment and abject poverty. Only with a sustainable new monetary system can humanity master these challenges of the 21st century. The Gradido Academy for Bionic Economy has developed such a system.
|
||||||
|
|
||||||
|
Find out more about the Project on its [Website](https://gradido.net/). It is offering vast resources about the idea. The remaining document will discuss the gradido software only.
|
||||||
## Software requirements
|
## Software requirements
|
||||||
|
|
||||||
Currently we only support `docker` as environment to run all services, since many different programming languages and frameworks are used.
|
Currently we only support `docker` install instructions to run all services, since many different programming languages and frameworks are used.
|
||||||
|
|
||||||
- [docker](https://www.docker.com/)
|
- [docker](https://www.docker.com/)
|
||||||
|
- [docker-compose]
|
||||||
|
|
||||||
|
### For Arch Linux
|
||||||
|
Install the required packages:
|
||||||
|
```bash
|
||||||
|
sudo pacman -S docker
|
||||||
|
sudo pacman -S docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
Add group `docker` and then your user to it in order to allow you to run docker without sudo
|
||||||
|
```bash
|
||||||
|
sudo groupadd docker # may already exist `groupadd: group 'docker' already exists`
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
groups # verify you have the group (requires relog)
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the docker service:
|
||||||
|
```bash
|
||||||
|
sudo systemctrl start docker
|
||||||
|
```
|
||||||
|
|
||||||
## How to run?
|
## How to run?
|
||||||
|
|
||||||
1. Clone the repo and pull all submodules
|
### 1. Clone Sources
|
||||||
|
Clone the repo and pull all submodules
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:gradido/gradido.git
|
git clone git@github.com:gradido/gradido.git
|
||||||
git submodule update --recursive --init
|
git submodule update --recursive --init
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run docker compose
|
### 2. Run docker-compose
|
||||||
1. Run docker compose for the debug build
|
Run docker-compose to bring up the development environment
|
||||||
|
```bash
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
### Additional Build options
|
||||||
|
If you want to build for production you can do this aswell:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.yml up
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
## Services defined in this package
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Or run docker compose in production build
|
- [frontend](./frontend) Wallet frontend
|
||||||
|
- [backend](./backend) GraphQL & Business logic backend
|
||||||
|
- [mariadb](./mariadb) Database backend
|
||||||
|
- [login_server](./login_server) User credential storage & business logic backend
|
||||||
|
- [community_server](./community_server/) Business logic backend
|
||||||
|
|
||||||
```bash
|
We are currently restructuring the service to reduce dependencies and unify business logic into one place. Furthermore the databases defined for each service will be unified into one.
|
||||||
docker-compose -f docker-compose.yml up
|
|
||||||
```
|
### Open the wallet
|
||||||
|
|
||||||
|
Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Problem | Issue | Solution | Description |
|
||||||
|
| ------- | ----- | -------- | ----------- |
|
||||||
|
| docker-compose raises database connection errors | [#1062](https://github.com/gradido/gradido/issues/1062) | End `ctrl+c` and restart the `docker-compose up` after a successful build | Several Database connection related errors occur in the docker-compose log. |
|
||||||
|
| Wallet page is empty | [#1063](https://github.com/gradido/gradido/issues/1063) | Accept Cookies and Local Storage in your Browser | The page stays empty when navigating to [http://localhost/vue](http://localhost/vue) |
|
||||||
|
|
||||||
## Useful Links
|
## Useful Links
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,14 @@ DB_PORT=3306
|
|||||||
DB_USER=root
|
DB_USER=root
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
|
|
||||||
|
#EMAIL=true
|
||||||
|
#EMAIL_USERNAME=
|
||||||
|
#EMAIL_SENDER=
|
||||||
|
#EMAIL_PASSWORD=
|
||||||
|
#EMAIL_SMTP_URL=
|
||||||
|
#EMAIL_SMTP_PORT=587
|
||||||
|
|
||||||
#KLICKTIPP_USER=
|
#KLICKTIPP_USER=
|
||||||
#KLICKTIPP_PASSWORD=
|
#KLICKTIPP_PASSWORD=
|
||||||
#KLICKTIPP_APIKEY_DE=
|
#KLICKTIPP_APIKEY_DE=
|
||||||
|
|||||||
@ -85,7 +85,7 @@ RUN cd ../database && yarn run build
|
|||||||
FROM build as test
|
FROM build as test
|
||||||
|
|
||||||
# Run command
|
# Run command
|
||||||
CMD /bin/sh -c "yarn run dev"
|
CMD /bin/sh -c "yarn run start"
|
||||||
|
|
||||||
##################################################################################
|
##################################################################################
|
||||||
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
||||||
|
|||||||
7143
backend/package-lock.json
generated
7143
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,11 +13,12 @@
|
|||||||
"start": "node build/index.js",
|
"start": "node build/index.js",
|
||||||
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
|
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
|
||||||
"lint": "eslint . --ext .js,.ts",
|
"lint": "eslint . --ext .js,.ts",
|
||||||
"test": "jest --coverage"
|
"test": "jest --runInBand --coverage "
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
"apollo-server-express": "^2.25.2",
|
"apollo-server-express": "^2.25.2",
|
||||||
|
"apollo-server-testing": "^2.25.2",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"class-validator": "^0.13.1",
|
"class-validator": "^0.13.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"mysql2": "^2.3.0",
|
"mysql2": "^2.3.0",
|
||||||
|
"nodemailer": "^6.6.5",
|
||||||
"random-bigint": "^0.0.1",
|
"random-bigint": "^0.0.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sodium-native": "^3.3.0",
|
"sodium-native": "^3.3.0",
|
||||||
@ -38,6 +40,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.12",
|
"@types/express": "^4.17.12",
|
||||||
"@types/jsonwebtoken": "^8.5.2",
|
"@types/jsonwebtoken": "^8.5.2",
|
||||||
|
"@types/libsodium-wrappers": "^0.7.9",
|
||||||
|
"@types/node": "^16.10.3",
|
||||||
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||||
"@typescript-eslint/parser": "^4.28.0",
|
"@typescript-eslint/parser": "^4.28.0",
|
||||||
"eslint": "^7.29.0",
|
"eslint": "^7.29.0",
|
||||||
|
|||||||
@ -44,9 +44,18 @@ const loginServer = {
|
|||||||
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const email = {
|
||||||
|
EMAIL: process.env.EMAIL === 'true' || false,
|
||||||
|
EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email',
|
||||||
|
EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net',
|
||||||
|
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx',
|
||||||
|
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
|
||||||
|
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||||
|
}
|
||||||
|
|
||||||
// This is needed by graphql-directive-auth
|
// This is needed by graphql-directive-auth
|
||||||
process.env.APP_SECRET = server.JWT_SECRET
|
process.env.APP_SECRET = server.JWT_SECRET
|
||||||
|
|
||||||
const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...loginServer }
|
const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...email, ...loginServer }
|
||||||
|
|
||||||
export default CONFIG
|
export default CONFIG
|
||||||
|
|||||||
123
backend/src/graphql/resolver/CommunityResolver.test.ts
Normal file
123
backend/src/graphql/resolver/CommunityResolver.test.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
import createServer from '../../server/createServer'
|
||||||
|
import CONFIG from '../../config'
|
||||||
|
|
||||||
|
jest.mock('../../config')
|
||||||
|
|
||||||
|
let query: any
|
||||||
|
|
||||||
|
// to do: We need a setup for the tests that closes the connection
|
||||||
|
let con: any
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const server = await createServer({})
|
||||||
|
con = server.con
|
||||||
|
query = createTestClient(server.apollo).query
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('CommunityResolver', () => {
|
||||||
|
const getCommunityInfoQuery = `
|
||||||
|
query {
|
||||||
|
getCommunityInfo {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
url
|
||||||
|
registerUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const communities = `
|
||||||
|
query {
|
||||||
|
communities {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
url
|
||||||
|
description
|
||||||
|
registerUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
describe('getCommunityInfo', () => {
|
||||||
|
it('returns the default values', async () => {
|
||||||
|
expect(query({ query: getCommunityInfoQuery })).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
getCommunityInfo: {
|
||||||
|
name: 'Gradido Entwicklung',
|
||||||
|
description: 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||||
|
url: 'http://localhost/vue/',
|
||||||
|
registerUrl: 'http://localhost/vue/register',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('communities', () => {
|
||||||
|
describe('PRODUCTION = false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
CONFIG.PRODUCTION = false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns three communities', async () => {
|
||||||
|
expect(query({ query: communities })).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
communities: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Gradido Entwicklung',
|
||||||
|
description: 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||||
|
url: 'http://localhost/vue/',
|
||||||
|
registerUrl: 'http://localhost/vue/register-community',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Gradido Staging',
|
||||||
|
description: 'Der Testserver der Gradido-Akademie.',
|
||||||
|
url: 'https://stage1.gradido.net/vue/',
|
||||||
|
registerUrl: 'https://stage1.gradido.net/vue/register-community',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Gradido-Akademie',
|
||||||
|
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||||
|
url: 'https://gradido.net',
|
||||||
|
registerUrl: 'https://gdd1.gradido.com/vue/register-community',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('PRODUCTION = true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
CONFIG.PRODUCTION = true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns one community', async () => {
|
||||||
|
expect(query({ query: communities })).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
communities: [
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Gradido-Akademie',
|
||||||
|
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||||
|
url: 'https://gradido.net',
|
||||||
|
registerUrl: 'https://gdd1.gradido.com/vue/register-community',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,8 +1,10 @@
|
|||||||
|
/* eslint-disable new-cap */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
||||||
import { getCustomRepository } from 'typeorm'
|
import { getCustomRepository, getConnection, QueryRunner } from 'typeorm'
|
||||||
|
import { createTransport } from 'nodemailer'
|
||||||
|
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
|
|
||||||
@ -22,12 +24,205 @@ import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
|||||||
import { User as dbUser } from '@entity/User'
|
import { User as dbUser } from '@entity/User'
|
||||||
import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction'
|
import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction'
|
||||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||||
|
import { TransactionSendCoin as dbTransactionSendCoin } from '@entity/TransactionSendCoin'
|
||||||
|
import { Balance as dbBalance } from '@entity/Balance'
|
||||||
|
|
||||||
import { apiPost } from '../../apis/HttpRequest'
|
import { apiPost } from '../../apis/HttpRequest'
|
||||||
import { roundFloorFrom4, roundCeilFrom4 } from '../../util/round'
|
import { roundFloorFrom4, roundCeilFrom4 } from '../../util/round'
|
||||||
import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
|
import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
|
||||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||||
import { TransactionType } from '../enum/TransactionType'
|
import { TransactionType } from '../enum/TransactionType'
|
||||||
|
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||||
|
import { from_hex as fromHex } from 'libsodium-wrappers'
|
||||||
|
|
||||||
|
/*
|
||||||
|
# Test
|
||||||
|
|
||||||
|
## Prepare
|
||||||
|
> sudo systemctl start docker
|
||||||
|
> docker-compose up mariadb
|
||||||
|
> DROP all databases
|
||||||
|
> docker-compose down
|
||||||
|
> docker compose up mariadb database
|
||||||
|
> verify there is exactly one database `gradido_community`
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
INSERT INTO `login_groups` (`id`, `alias`, `name`, `url`, `host`, `home`, `description`) VALUES
|
||||||
|
(1, 'docker', 'docker gradido group', 'localhost', 'nginx', '/', 'gradido test group for docker and stage2 with blockchain db');
|
||||||
|
|
||||||
|
>> Database is cool
|
||||||
|
|
||||||
|
### Start login server
|
||||||
|
> docker-compose up login-server community-server nginx
|
||||||
|
>> Login & community servers and nginx proxy are up and running
|
||||||
|
|
||||||
|
## Build database
|
||||||
|
> cd database
|
||||||
|
> yarn
|
||||||
|
> yarn build
|
||||||
|
> cd ..
|
||||||
|
>> Database has been built successful
|
||||||
|
|
||||||
|
### Start backend (no docker for debugging)
|
||||||
|
> cd backend
|
||||||
|
> yarn
|
||||||
|
> yarn dev
|
||||||
|
>> Backend is up and running
|
||||||
|
|
||||||
|
### Create users
|
||||||
|
> chromium http://localhost:4000/graphql
|
||||||
|
> mutation{createUser(email: "receiver@user.net", firstName: "Receiver", lastName: "user", password: "123!AAAb", language: "de")}
|
||||||
|
> mutation{createUser(email: "sender@user.net", firstName: "Sender", lastName: "user", password: "123!AAAb", language: "de")}
|
||||||
|
> mutation{createUser(email: "creator@user.net", firstName: "Creator", lastName: "user", password: "123!AAAb", language: "de")}
|
||||||
|
>> Verify you have 3 entries in `login_users`, `login_user_backups` and `state_users`
|
||||||
|
|
||||||
|
### make creator an admin
|
||||||
|
> INSERT INTO login_user_roles (id, user_id, role_id) VALUES (NULL, '3', '1');
|
||||||
|
> UPDATE login_users SET email_checked = 1 WHERE id = 3;
|
||||||
|
> uncomment line: 19 in community_server/src/Controller/ServerUsersController.php
|
||||||
|
> chromium http://localhost/server-users/add
|
||||||
|
> create user `creator` `123` `creator@different.net`
|
||||||
|
>> verify you have 1 entry in `server_users`
|
||||||
|
> login with user on http://localhost/server-users
|
||||||
|
> activate server user by changing the corresponding flag in the interface
|
||||||
|
> navigate to http://localhost/transaction-creations/create-multi
|
||||||
|
> create 1000GDD for user sender@user.net
|
||||||
|
> navigate to http://localhost
|
||||||
|
> login with `creator@user.net` `123!AAAb`
|
||||||
|
> confirm transaction (top right corner - click the thingy, click the green button `Transaktion abschließen`)
|
||||||
|
|
||||||
|
### the test:
|
||||||
|
> chromium http://localhost:4000/graphql
|
||||||
|
> query{login(email: "sender@user.net", password: "123!AAAb"){pubkey}}
|
||||||
|
>> copy token from network tab (inspect)
|
||||||
|
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
|
||||||
|
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
|
||||||
|
> Headers: {"Authorization": "Bearer ${token}"}
|
||||||
|
>> Verify via Database that stuff is as it should see `state_balance` & `transaction_send_coins`
|
||||||
|
|
||||||
|
### create decay block
|
||||||
|
> chromium http://localhost/transactions/add
|
||||||
|
> login with `creator` `123`
|
||||||
|
> select `decay start`
|
||||||
|
> press submit
|
||||||
|
> wait for at least 0.02 display of decay on user sender@user.net on old frontend, this should be aprox 10min
|
||||||
|
> chromium http://localhost:4000/graphql
|
||||||
|
> query{login(email: "sender@user.net", password: "123!AAAb"){pubkey}}
|
||||||
|
>> copy token from network tab (inspect)
|
||||||
|
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
|
||||||
|
>> verify in `transaction_send_coins` that a decay was taken into account
|
||||||
|
>> same in `state_balances`
|
||||||
|
>> now check the old frontend
|
||||||
|
>>> sender@user.net should have a decay of 0.02
|
||||||
|
>>> while receiver@user.net should have zero decay on anything (old frontend)
|
||||||
|
|
||||||
|
### Export data
|
||||||
|
> docker-compose up phpmyadmin
|
||||||
|
> chromium http://localhost:8074/
|
||||||
|
> select gradido_community
|
||||||
|
> export
|
||||||
|
> select custom
|
||||||
|
> untick structure
|
||||||
|
> ok
|
||||||
|
|
||||||
|
## Results
|
||||||
|
NOTE: We decided not to write the `transaction_signatures` since its unused. This is the main difference.
|
||||||
|
NOTE: We fixed a bug in the `state_user_transactions code` with the new implementation of apollo
|
||||||
|
|
||||||
|
|
||||||
|
Master:
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `state_user_transactions`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `state_user_transactions` (`id`, `state_user_id`, `transaction_id`, `transaction_type_id`, `balance`, `balance_date`) VALUES
|
||||||
|
(1, 2, 1, 1, 10000000, '2021-11-05 12:45:18'),
|
||||||
|
(2, 2, 2, 2, 9900000, '2021-11-05 12:48:35'),
|
||||||
|
(3, 1, 2, 2, 100000, '2021-11-05 12:48:35'),
|
||||||
|
(4, 2, 3, 2, 9800000, '2021-11-05 12:49:07'),
|
||||||
|
(5, 1, 3, 2, 200000, '2021-11-05 12:49:07'),
|
||||||
|
(6, 2, 5, 2, 9699845, '2021-11-05 13:03:50'),
|
||||||
|
(7, 1, 5, 2, 99996, '2021-11-05 13:03:50');
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `transactions`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `transactions` (`id`, `state_group_id`, `transaction_type_id`, `tx_hash`, `memo`, `received`, `blockchain_type_id`) VALUES
|
||||||
|
(1, NULL, 1, 0x9ccdcd01ccb6320c09c2d1da2f0bf735a95ece0e7c1df6bbff51918fbaec061700000000000000000000000000000000, '', '2021-11-05 12:45:18', 1),
|
||||||
|
(2, NULL, 2, 0x58d7706a67fa4ff4b8038168c6be39a2963d7e28e9d3872759ad09c519fe093700000000000000000000000000000000, 'Hier!', '2021-11-05 12:48:35', 1),
|
||||||
|
(3, NULL, 2, 0x427cd214f92ef35af671129d50edc5a478c53d1e464f285b7615d9794a69f69b00000000000000000000000000000000, 'Hier!', '2021-11-05 12:49:07', 1),
|
||||||
|
(4, NULL, 9, 0x32807368f0906a21b94c072599795bc9eeab88fb565df82e85cc62a4fdcde48500000000000000000000000000000000, '', '2021-11-05 12:51:51', 1),
|
||||||
|
(5, NULL, 2, 0x75eb729e0f60a1c8cead1342955853d2440d7a2ea57dfef6d4a18bff0d94491e00000000000000000000000000000000, 'Hier!', '2021-11-05 13:03:50', 1);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `transaction_signatures`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `transaction_signatures` (`id`, `transaction_id`, `signature`, `pubkey`) VALUES
|
||||||
|
(1, 1, 0x5888edcdcf77aaadad6d321882903bc831d7416f17213fd5020a764365b5fcb336e4c7917385a1278ea44ccdb31eac4a09e448053b5e3f8f1fe5da3baf53c008, 0xd5b20f8dee415038bfa2b6b0e1b40ff54850351109444863b04d6d28825b7b7d),
|
||||||
|
(2, 2, 0xf6fef428f8f22faf7090f7d740e6088d1d90c58ae92d757117d7d91d799e659f3a3a0c65a3fd97cbde798e761f9d23eff13e8810779a184c97c411f28e7c4608, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a),
|
||||||
|
(3, 3, 0x8ebe9730c6cf61f56ef401d6f2bd229f3c298ca3c2791ee9137e4827b7af6c6d6566fca616eb1fe7adc2e4d56b5c7350ae3990c9905580630fa75ecffca8e001, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a),
|
||||||
|
(4, 5, 0x50cf418f7e217391e89ab9c2879ae68d7c7c597d846b4fe1c082b5b16e5d0c85c328fbf48ad3490bcfe94f446700ae0a4b0190e76d26cc752abced58f480c80f, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a);
|
||||||
|
|
||||||
|
This Feature Branch:
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `state_user_transactions`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `state_user_transactions` (`id`, `state_user_id`, `transaction_id`, `transaction_type_id`, `balance`, `balance_date`) VALUES
|
||||||
|
(1, 2, 1, 1, 10000000, '2021-11-05 00:25:46'),
|
||||||
|
(12, 2, 7, 2, 9900000, '2021-11-05 00:55:37'),
|
||||||
|
(13, 1, 7, 2, 100000, '2021-11-05 00:55:37'),
|
||||||
|
(14, 2, 8, 2, 9800000, '2021-11-05 01:00:04'),
|
||||||
|
(15, 1, 8, 2, 200000, '2021-11-05 01:00:04'),
|
||||||
|
(16, 2, 10, 2, 9699772, '2021-11-05 01:17:41'),
|
||||||
|
(17, 1, 10, 2, 299995, '2021-11-05 01:17:41');
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `transactions`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `transactions` (`id`, `state_group_id`, `transaction_type_id`, `tx_hash`, `memo`, `received`, `blockchain_type_id`) VALUES
|
||||||
|
(1, NULL, 1, 0xdd030d475479877587d927ed9024784ba62266cf1f3d87862fc98ad68f7b26e400000000000000000000000000000000, '', '2021-11-05 00:25:46', 1),
|
||||||
|
(7, NULL, 2, NULL, 'Hier!', '2021-11-05 00:55:37', 1),
|
||||||
|
(8, NULL, 2, NULL, 'Hier!', '2021-11-05 01:00:04', 1),
|
||||||
|
(9, NULL, 9, 0xb1cbedbf126aa35f5edbf06e181c415361d05228ab4da9d19a4595285a673dfa00000000000000000000000000000000, '', '2021-11-05 01:05:34', 1),
|
||||||
|
(10, NULL, 2, NULL, 'Hier!', '2021-11-05 01:17:41', 1);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `transaction_signatures`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `transaction_signatures` (`id`, `transaction_id`, `signature`, `pubkey`) VALUES
|
||||||
|
(1, 1, 0x60d632479707e5d01cdc32c3326b5a5bae11173a0c06b719ee7b552f9fd644de1a0cd4afc207253329081d39dac1a63421f51571d836995c649fc39afac7480a, 0x48c45cb4fea925e83850f68f2fa8f27a1a4ed1bcba68cdb59fcd86adef3f52ee);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const sendEMail = async (emailDef: any): Promise<boolean> => {
|
||||||
|
if (!CONFIG.EMAIL) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Emails are disabled via config')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const transporter = createTransport({
|
||||||
|
host: CONFIG.EMAIL_SMTP_URL,
|
||||||
|
port: Number(CONFIG.EMAIL_SMTP_PORT),
|
||||||
|
secure: false, // true for 465, false for other ports
|
||||||
|
requireTLS: true,
|
||||||
|
auth: {
|
||||||
|
user: CONFIG.EMAIL_USERNAME,
|
||||||
|
pass: CONFIG.EMAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const info = await transporter.sendMail(emailDef)
|
||||||
|
if (!info.messageId) {
|
||||||
|
throw new Error('error sending notification email, but transaction succeed')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
async function calculateAndAddDecayTransactions(
|
async function calculateAndAddDecayTransactions(
|
||||||
@ -210,6 +405,87 @@ async function listTransactions(
|
|||||||
return transactionList
|
return transactionList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper helper function
|
||||||
|
async function updateStateBalance(
|
||||||
|
user: dbUser,
|
||||||
|
centAmount: number,
|
||||||
|
received: Date,
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
): Promise<dbBalance> {
|
||||||
|
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||||
|
let balance = await balanceRepository.findByUser(user.id)
|
||||||
|
if (!balance) {
|
||||||
|
balance = new dbBalance()
|
||||||
|
balance.userId = user.id
|
||||||
|
balance.amount = centAmount
|
||||||
|
balance.modified = received
|
||||||
|
} else {
|
||||||
|
const decaiedBalance = await calculateDecay(balance.amount, balance.recordDate, received).catch(
|
||||||
|
() => {
|
||||||
|
throw new Error('error by calculating decay')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
balance.amount = Number(decaiedBalance) + centAmount
|
||||||
|
balance.modified = new Date()
|
||||||
|
}
|
||||||
|
if (balance.amount <= 0) {
|
||||||
|
throw new Error('error new balance <= 0')
|
||||||
|
}
|
||||||
|
balance.recordDate = received
|
||||||
|
return queryRunner.manager.save(balance).catch((error) => {
|
||||||
|
throw new Error('error saving balance:' + error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper helper function
|
||||||
|
async function addUserTransaction(
|
||||||
|
user: dbUser,
|
||||||
|
transaction: dbTransaction,
|
||||||
|
centAmount: number,
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
): Promise<dbUserTransaction> {
|
||||||
|
let newBalance = centAmount
|
||||||
|
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
||||||
|
const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id)
|
||||||
|
if (lastUserTransaction) {
|
||||||
|
newBalance += Number(
|
||||||
|
await calculateDecay(
|
||||||
|
Number(lastUserTransaction.balance),
|
||||||
|
lastUserTransaction.balanceDate,
|
||||||
|
transaction.received,
|
||||||
|
).catch(() => {
|
||||||
|
throw new Error('error by calculating decay')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBalance <= 0) {
|
||||||
|
throw new Error('error new balance <= 0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUserTransaction = new dbUserTransaction()
|
||||||
|
newUserTransaction.userId = user.id
|
||||||
|
newUserTransaction.transactionId = transaction.id
|
||||||
|
newUserTransaction.transactionTypeId = transaction.transactionTypeId
|
||||||
|
newUserTransaction.balance = newBalance
|
||||||
|
newUserTransaction.balanceDate = transaction.received
|
||||||
|
|
||||||
|
return queryRunner.manager.save(newUserTransaction).catch((error) => {
|
||||||
|
throw new Error('Error saving user transaction: ' + error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPublicKey(email: string, sessionId: number): Promise<string | undefined> {
|
||||||
|
const result = await apiPost(CONFIG.LOGIN_API_URL + 'getUserInfos', {
|
||||||
|
session_id: sessionId,
|
||||||
|
email,
|
||||||
|
ask: ['user.pubkeyhex'],
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
return result.data.userData.pubkeyhex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class TransactionResolver {
|
export class TransactionResolver {
|
||||||
@Authorized()
|
@Authorized()
|
||||||
@ -252,19 +528,147 @@ export class TransactionResolver {
|
|||||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||||
@Ctx() context: any,
|
@Ctx() context: any,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const payload = {
|
// TODO this is subject to replay attacks
|
||||||
session_id: context.sessionId,
|
// validate sender user (logged in)
|
||||||
target_email: email,
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
amount: amount * 10000,
|
const senderUser = await userRepository.findByPubkeyHex(context.pubKey)
|
||||||
memo,
|
if (senderUser.pubkey.length !== 32) {
|
||||||
auto_sign: true,
|
throw new Error('invalid sender public key')
|
||||||
transaction_type: 'transfer',
|
|
||||||
blockchain_type: 'mysql',
|
|
||||||
}
|
}
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createTransaction', payload)
|
if (!hasUserAmount(senderUser, amount)) {
|
||||||
if (!result.success) {
|
throw new Error("user hasn't enough GDD")
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate recipient user
|
||||||
|
// TODO: the detour over the public key is unnecessary
|
||||||
|
const recipiantPublicKey = await getPublicKey(email, context.sessionId)
|
||||||
|
if (!recipiantPublicKey) {
|
||||||
|
throw new Error('recipiant not known')
|
||||||
|
}
|
||||||
|
if (!isHexPublicKey(recipiantPublicKey)) {
|
||||||
|
throw new Error('invalid recipiant public key')
|
||||||
|
}
|
||||||
|
const recipiantUser = await userRepository.findByPubkeyHex(recipiantPublicKey)
|
||||||
|
if (!recipiantUser) {
|
||||||
|
throw new Error('Cannot find recipiant user by local send coins transaction')
|
||||||
|
} else if (recipiantUser.disabled) {
|
||||||
|
throw new Error('recipiant user account is disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate amount
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new Error('invalid amount')
|
||||||
|
}
|
||||||
|
|
||||||
|
const centAmount = Math.trunc(amount * 10000)
|
||||||
|
|
||||||
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
await queryRunner.connect()
|
||||||
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
try {
|
||||||
|
// transaction
|
||||||
|
let transaction = new dbTransaction()
|
||||||
|
transaction.transactionTypeId = TransactionTypeId.SEND
|
||||||
|
transaction.memo = memo
|
||||||
|
|
||||||
|
// TODO: NO! this is problematic in its construction
|
||||||
|
const insertResult = await queryRunner.manager.insert(dbTransaction, transaction)
|
||||||
|
transaction = await queryRunner.manager
|
||||||
|
.findOneOrFail(dbTransaction, insertResult.generatedMaps[0].id)
|
||||||
|
.catch((error) => {
|
||||||
|
throw new Error('error loading saved transaction: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Insert Transaction: sender - amount
|
||||||
|
const senderUserTransactionBalance = await addUserTransaction(
|
||||||
|
senderUser,
|
||||||
|
transaction,
|
||||||
|
-centAmount,
|
||||||
|
queryRunner,
|
||||||
|
)
|
||||||
|
// Insert Transaction: recipient + amount
|
||||||
|
const recipiantUserTransactionBalance = await addUserTransaction(
|
||||||
|
recipiantUser,
|
||||||
|
transaction,
|
||||||
|
centAmount,
|
||||||
|
queryRunner,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update Balance: sender - amount
|
||||||
|
const senderStateBalance = await updateStateBalance(
|
||||||
|
senderUser,
|
||||||
|
-centAmount,
|
||||||
|
transaction.received,
|
||||||
|
queryRunner,
|
||||||
|
)
|
||||||
|
// Update Balance: recipiant + amount
|
||||||
|
const recipiantStateBalance = await updateStateBalance(
|
||||||
|
recipiantUser,
|
||||||
|
centAmount,
|
||||||
|
transaction.received,
|
||||||
|
queryRunner,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (senderStateBalance.amount !== senderUserTransactionBalance.balance) {
|
||||||
|
throw new Error('db data corrupted, sender')
|
||||||
|
}
|
||||||
|
if (recipiantStateBalance.amount !== recipiantUserTransactionBalance.balance) {
|
||||||
|
throw new Error('db data corrupted, recipiant')
|
||||||
|
}
|
||||||
|
|
||||||
|
// transactionSendCoin
|
||||||
|
const transactionSendCoin = new dbTransactionSendCoin()
|
||||||
|
transactionSendCoin.transactionId = transaction.id
|
||||||
|
transactionSendCoin.userId = senderUser.id
|
||||||
|
transactionSendCoin.senderPublic = senderUser.pubkey
|
||||||
|
transactionSendCoin.recipiantUserId = recipiantUser.id
|
||||||
|
transactionSendCoin.recipiantPublic = Buffer.from(fromHex(recipiantPublicKey))
|
||||||
|
transactionSendCoin.amount = centAmount
|
||||||
|
transactionSendCoin.senderFinalBalance = senderStateBalance.amount
|
||||||
|
await queryRunner.manager.save(transactionSendCoin).catch((error) => {
|
||||||
|
throw new Error('error saving transaction send coin: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
await queryRunner.manager.save(transaction).catch((error) => {
|
||||||
|
throw new Error('error saving transaction with tx hash: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction()
|
||||||
|
} catch (e) {
|
||||||
|
await queryRunner.rollbackTransaction()
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
|
// TODO: This is broken code - we should never correct an autoincrement index in production
|
||||||
|
// according to dario it is required tho to properly work. The index of the table is used as
|
||||||
|
// index for the transaction which requires a chain without gaps
|
||||||
|
const count = await queryRunner.manager.count(dbTransaction)
|
||||||
|
// fix autoincrement value which seems not effected from rollback
|
||||||
|
await queryRunner
|
||||||
|
.query('ALTER TABLE `transactions` auto_increment = ?', [count])
|
||||||
|
.catch((error) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('problems with reset auto increment: %o', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// send notification email
|
||||||
|
// TODO: translate
|
||||||
|
await sendEMail({
|
||||||
|
from: 'Gradido (nicht antworten) <' + CONFIG.EMAIL_SENDER + '>',
|
||||||
|
to: recipiantUser.firstName + ' ' + recipiantUser.lastName + ' <' + recipiantUser.email + '>',
|
||||||
|
subject: 'Gradido Überweisung',
|
||||||
|
text: `Hallo ${recipiantUser.firstName} ${recipiantUser.lastName}
|
||||||
|
|
||||||
|
Du hast soeben ${amount} GDD von ${senderUser.firstName} ${senderUser.lastName} erhalten.
|
||||||
|
${senderUser.firstName} ${senderUser.lastName} schreibt:
|
||||||
|
|
||||||
|
${memo}
|
||||||
|
|
||||||
|
Bitte antworte nicht auf diese E-Mail!
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen Gradido Community Server`,
|
||||||
|
})
|
||||||
|
|
||||||
return 'success'
|
return 'success'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import isAuthorized from './directive/isAuthorized'
|
|||||||
|
|
||||||
const schema = async (): Promise<GraphQLSchema> => {
|
const schema = async (): Promise<GraphQLSchema> => {
|
||||||
return buildSchema({
|
return buildSchema({
|
||||||
resolvers: [path.join(__dirname, 'resolver', `*.{js,ts}`)],
|
resolvers: [path.join(__dirname, 'resolver', `!(*.test).{js,ts}`)],
|
||||||
authChecker: isAuthorized,
|
authChecker: isAuthorized,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,64 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import 'reflect-metadata'
|
import createServer from './server/createServer'
|
||||||
import 'module-alias/register'
|
|
||||||
import express from 'express'
|
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
|
||||||
|
|
||||||
// config
|
// config
|
||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
// database
|
|
||||||
import connection from './typeorm/connection'
|
|
||||||
import getDBVersion from './typeorm/getDBVersion'
|
|
||||||
|
|
||||||
// server
|
|
||||||
import cors from './server/cors'
|
|
||||||
import context from './server/context'
|
|
||||||
import plugins from './server/plugins'
|
|
||||||
|
|
||||||
// graphql
|
|
||||||
import schema from './graphql/schema'
|
|
||||||
|
|
||||||
// TODO implement
|
|
||||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
|
||||||
|
|
||||||
const DB_VERSION = '0004-login_server_data'
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// open mysql connection
|
const { app } = await createServer()
|
||||||
const con = await connection()
|
|
||||||
if (!con || !con.isConnected) {
|
|
||||||
throw new Error(`Couldn't open connection to database`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for correct database version
|
app.listen(CONFIG.PORT, () => {
|
||||||
const dbVersion = await getDBVersion()
|
|
||||||
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Wrong database version - the backend requires '${DB_VERSION}' but found '${
|
|
||||||
dbVersion || 'None'
|
|
||||||
}'`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Express Server
|
|
||||||
const server = express()
|
|
||||||
|
|
||||||
// cors
|
|
||||||
server.use(cors)
|
|
||||||
|
|
||||||
// Apollo Server
|
|
||||||
const apollo = new ApolloServer({
|
|
||||||
schema: await schema(),
|
|
||||||
playground: CONFIG.GRAPHIQL,
|
|
||||||
context,
|
|
||||||
plugins,
|
|
||||||
})
|
|
||||||
apollo.applyMiddleware({ app: server })
|
|
||||||
|
|
||||||
// Start Server
|
|
||||||
server.listen(CONFIG.PORT, () => {
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
|
console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
|
||||||
if (CONFIG.GRAPHIQL) {
|
if (CONFIG.GRAPHIQL) {
|
||||||
|
|||||||
64
backend/src/server/createServer.ts
Normal file
64
backend/src/server/createServer.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import 'reflect-metadata'
|
||||||
|
import 'module-alias/register'
|
||||||
|
|
||||||
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
|
import express from 'express'
|
||||||
|
|
||||||
|
// database
|
||||||
|
import connection from '../typeorm/connection'
|
||||||
|
import getDBVersion from '../typeorm/getDBVersion'
|
||||||
|
|
||||||
|
// server
|
||||||
|
import cors from './cors'
|
||||||
|
import serverContext from './context'
|
||||||
|
import plugins from './plugins'
|
||||||
|
|
||||||
|
// config
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
|
// graphql
|
||||||
|
import schema from '../graphql/schema'
|
||||||
|
|
||||||
|
// TODO implement
|
||||||
|
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||||
|
|
||||||
|
const DB_VERSION = '0004-login_server_data'
|
||||||
|
|
||||||
|
const createServer = async (context: any = serverContext): Promise<any> => {
|
||||||
|
// open mysql connection
|
||||||
|
const con = await connection()
|
||||||
|
if (!con || !con.isConnected) {
|
||||||
|
throw new Error(`Couldn't open connection to database`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for correct database version
|
||||||
|
const dbVersion = await getDBVersion()
|
||||||
|
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
|
||||||
|
throw new Error(
|
||||||
|
`Wrong database version - the backend requires '${DB_VERSION}' but found '${
|
||||||
|
dbVersion || 'None'
|
||||||
|
}'`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Express Server
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// cors
|
||||||
|
app.use(cors)
|
||||||
|
|
||||||
|
// Apollo Server
|
||||||
|
const apollo = new ApolloServer({
|
||||||
|
schema: await schema(),
|
||||||
|
playground: CONFIG.GRAPHIQL,
|
||||||
|
context,
|
||||||
|
plugins,
|
||||||
|
})
|
||||||
|
apollo.applyMiddleware({ app })
|
||||||
|
return { apollo, app, con }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createServer
|
||||||
@ -17,4 +17,11 @@ export class UserTransactionRepository extends Repository<UserTransaction> {
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.getManyAndCount()
|
.getManyAndCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findLastForUser(userId: number): Promise<UserTransaction | undefined> {
|
||||||
|
return this.createQueryBuilder('userTransaction')
|
||||||
|
.where('userTransaction.userId = :userId', { userId })
|
||||||
|
.orderBy('userTransaction.transactionId', 'DESC')
|
||||||
|
.getOne()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,24 +2,25 @@ import { decayFormula, calculateDecay } from './decay'
|
|||||||
|
|
||||||
describe('utils/decay', () => {
|
describe('utils/decay', () => {
|
||||||
describe('decayFormula', () => {
|
describe('decayFormula', () => {
|
||||||
it('has base 0.99999997802044727', async () => {
|
it('has base 0.99999997802044727', () => {
|
||||||
const amount = 1.0
|
const amount = 1.0
|
||||||
const seconds = 1
|
const seconds = 1
|
||||||
expect(await decayFormula(amount, seconds)).toBe(0.99999997802044727)
|
expect(decayFormula(amount, seconds)).toBe(0.99999997802044727)
|
||||||
})
|
})
|
||||||
// Not sure if the following skiped tests make sence!?
|
// Not sure if the following skiped tests make sence!?
|
||||||
it.skip('has negative decay?', async () => {
|
it('has negative decay?', async () => {
|
||||||
const amount = -1.0
|
const amount = -1.0
|
||||||
const seconds = 1
|
const seconds = 1
|
||||||
expect(await decayFormula(amount, seconds)).toBe(-0.99999997802044727)
|
expect(await decayFormula(amount, seconds)).toBe(-0.99999997802044727)
|
||||||
})
|
})
|
||||||
it.skip('has correct backward calculation', async () => {
|
it('has correct backward calculation', async () => {
|
||||||
const amount = 1.0
|
const amount = 1.0
|
||||||
const seconds = -1
|
const seconds = -1
|
||||||
expect(await decayFormula(amount, seconds)).toBe(1.0000000219795533)
|
expect(await decayFormula(amount, seconds)).toBe(1.0000000219795533)
|
||||||
})
|
})
|
||||||
it.skip('has correct forward calculation', async () => {
|
// not possible, nodejs hasn't enough accuracy
|
||||||
const amount = 1.000000219795533
|
it('has correct forward calculation', async () => {
|
||||||
|
const amount = 1.0 / 0.99999997802044727
|
||||||
const seconds = 1
|
const seconds = 1
|
||||||
expect(await decayFormula(amount, seconds)).toBe(1.0)
|
expect(await decayFormula(amount, seconds)).toBe(1.0)
|
||||||
})
|
})
|
||||||
@ -32,7 +33,7 @@ describe('utils/decay', () => {
|
|||||||
expect(await calculateDecay(1.0, oneSecondAgo, now)).toBe(0.99999997802044727)
|
expect(await calculateDecay(1.0, oneSecondAgo, now)).toBe(0.99999997802044727)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('returns input amount when from and to is the same', async () => {
|
it('returns input amount when from and to is the same', async () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
expect(await calculateDecay(100.0, now, now)).toBe(100.0)
|
expect(await calculateDecay(100.0, now, now)).toBe(100.0)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,6 +7,15 @@ function decayFormula(amount: number, seconds: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
|
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
|
||||||
|
if (amount === undefined || !from || !to) {
|
||||||
|
throw new Error('at least one parameter is undefined')
|
||||||
|
}
|
||||||
|
if (from === to) {
|
||||||
|
return amount
|
||||||
|
}
|
||||||
|
if (to < from) {
|
||||||
|
throw new Error('to < from, so the target date is in the past?')
|
||||||
|
}
|
||||||
// load decay start block
|
// load decay start block
|
||||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
const decayStartBlock = await transactionRepository.findDecayStartBlock()
|
||||||
|
|||||||
22
backend/src/util/round.test.ts
Normal file
22
backend/src/util/round.test.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { roundCeilFrom4, roundFloorFrom4, roundCeilFrom2, roundFloorFrom2 } from './round'
|
||||||
|
|
||||||
|
describe('utils/round', () => {
|
||||||
|
it('roundCeilFrom4', () => {
|
||||||
|
const amount = 11617
|
||||||
|
expect(roundCeilFrom4(amount)).toBe(1.17)
|
||||||
|
})
|
||||||
|
// Not sure if the following skiped tests make sence!?
|
||||||
|
it('roundFloorFrom4', () => {
|
||||||
|
const amount = 11617
|
||||||
|
expect(roundFloorFrom4(amount)).toBe(1.16)
|
||||||
|
})
|
||||||
|
it('roundCeilFrom2', () => {
|
||||||
|
const amount = 1216
|
||||||
|
expect(roundCeilFrom2(amount)).toBe(13)
|
||||||
|
})
|
||||||
|
// not possible, nodejs hasn't enough accuracy
|
||||||
|
it('roundFloorFrom2', () => {
|
||||||
|
const amount = 1216
|
||||||
|
expect(roundFloorFrom2(amount)).toBe(12)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
import { User as dbUser } from '@entity/User'
|
||||||
|
import { Balance as dbBalance } from '@entity/Balance'
|
||||||
|
import { getRepository } from 'typeorm'
|
||||||
|
import { calculateDecay } from './decay'
|
||||||
|
|
||||||
function isStringBoolean(value: string): boolean {
|
function isStringBoolean(value: string): boolean {
|
||||||
const lowerValue = value.toLowerCase()
|
const lowerValue = value.toLowerCase()
|
||||||
if (lowerValue === 'true' || lowerValue === 'false') {
|
if (lowerValue === 'true' || lowerValue === 'false') {
|
||||||
@ -6,4 +11,18 @@ function isStringBoolean(value: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isStringBoolean }
|
function isHexPublicKey(publicKey: string): boolean {
|
||||||
|
return /^[0-9A-Fa-f]{64}$/i.test(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasUserAmount(user: dbUser, amount: number): Promise<boolean> {
|
||||||
|
if (amount < 0) return false
|
||||||
|
const balanceRepository = getRepository(dbBalance)
|
||||||
|
const balance = await balanceRepository.findOne({ userId: user.id })
|
||||||
|
if (!balance) return false
|
||||||
|
|
||||||
|
const decay = await calculateDecay(balance.amount, balance.recordDate, new Date())
|
||||||
|
return decay > amount
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isHexPublicKey, hasUserAmount, isStringBoolean }
|
||||||
|
|||||||
2643
backend/yarn.lock
2643
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,9 @@ export class TransactionSendCoin extends BaseEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
@Column({ name: 'sender_final_balance' })
|
||||||
|
senderFinalBalance: number
|
||||||
|
|
||||||
@OneToOne(() => Transaction)
|
@OneToOne(() => Transaction)
|
||||||
@JoinColumn({ name: 'transaction_id' })
|
@JoinColumn({ name: 'transaction_id' })
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
|
|||||||
@ -72,6 +72,9 @@ services:
|
|||||||
login-server:
|
login-server:
|
||||||
build:
|
build:
|
||||||
dockerfile: Dockerfiles/ubuntu/Dockerfile.debug
|
dockerfile: Dockerfiles/ubuntu/Dockerfile.debug
|
||||||
|
networks:
|
||||||
|
- external-net
|
||||||
|
- internal-net
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
cap_add:
|
cap_add:
|
||||||
|
|||||||
@ -2,6 +2,26 @@ version: "3.4"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# BACKEND ##############################################
|
||||||
|
########################################################
|
||||||
|
backend:
|
||||||
|
image: gradido/backend:test
|
||||||
|
build:
|
||||||
|
target: test
|
||||||
|
networks:
|
||||||
|
- external-net
|
||||||
|
- internal-net
|
||||||
|
environment:
|
||||||
|
- NODE_ENV="test"
|
||||||
|
- DB_HOST=mariadb
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# DATABASE #############################################
|
||||||
|
########################################################
|
||||||
|
database:
|
||||||
|
restart: always # this is very dangerous, but worth a test for the delayed mariadb startup at first run
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## MARIADB ##############################################
|
## MARIADB ##############################################
|
||||||
#########################################################
|
#########################################################
|
||||||
@ -15,10 +35,11 @@ services:
|
|||||||
- MARIADB_USER=root
|
- MARIADB_USER=root
|
||||||
networks:
|
networks:
|
||||||
- internal-net
|
- internal-net
|
||||||
|
- external-net
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
volumes:
|
volumes:
|
||||||
- db_test_vol:/var/lib/mysql
|
- db_test_vol:/var/lib/mysql
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## LOGIN SERVER #########################################
|
## LOGIN SERVER #########################################
|
||||||
|
|||||||
BIN
docu/Gradido-Admin.epgz
Normal file
BIN
docu/Gradido-Admin.epgz
Normal file
Binary file not shown.
BIN
docu/graphics/gradido_admin.png
Normal file
BIN
docu/graphics/gradido_admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
docu/graphics/userdetails.png
Normal file
BIN
docu/graphics/userdetails.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@ -9,8 +9,9 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
// coverageReporters: ['lcov', 'text'],
|
// coverageReporters: ['lcov', 'text'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@/(.*)$': '<rootDir>/src/$1',
|
|
||||||
'\\.(css|less)$': 'identity-obj-proxy',
|
'\\.(css|less)$': 'identity-obj-proxy',
|
||||||
|
'\\.(scss)$': '<rootDir>/src/assets/mocks/styleMock.js',
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1',
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.vue$': 'vue-jest',
|
'^.+\\.vue$': 'vue-jest',
|
||||||
|
|||||||
1
frontend/src/assets/mocks/styleMock.js
Normal file
1
frontend/src/assets/mocks/styleMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = {}
|
||||||
25
frontend/src/plugins/dashboard-plugin.test.js
Normal file
25
frontend/src/plugins/dashboard-plugin.test.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import dashboardPlugin from './dashboard-plugin.js'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import GlobalComponents from './globalComponents'
|
||||||
|
import GlobalDirectives from './globalDirectives'
|
||||||
|
|
||||||
|
jest.mock('./globalComponents')
|
||||||
|
jest.mock('./globalDirectives')
|
||||||
|
|
||||||
|
jest.mock('vue')
|
||||||
|
|
||||||
|
const vueUseMock = jest.fn()
|
||||||
|
Vue.use = vueUseMock
|
||||||
|
|
||||||
|
describe('dashboard plugin', () => {
|
||||||
|
dashboardPlugin.install(Vue)
|
||||||
|
|
||||||
|
it('installs the global components', () => {
|
||||||
|
expect(vueUseMock).toBeCalledWith(GlobalComponents)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('installs the global directives', () => {
|
||||||
|
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -13,6 +13,7 @@
|
|||||||
#include "JsonCreateTransaction.h"
|
#include "JsonCreateTransaction.h"
|
||||||
#include "JsonCreateUser.h"
|
#include "JsonCreateUser.h"
|
||||||
#include "JsonGetLogin.h"
|
#include "JsonGetLogin.h"
|
||||||
|
#include "JsonSignTransaction.h"
|
||||||
#include "JsonUnknown.h"
|
#include "JsonUnknown.h"
|
||||||
#include "JsonGetRunningUserTasks.h"
|
#include "JsonGetRunningUserTasks.h"
|
||||||
#include "JsonGetUsers.h"
|
#include "JsonGetUsers.h"
|
||||||
@ -77,6 +78,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
|
|||||||
else if (url_first_part == "/checkSessionState") {
|
else if (url_first_part == "/checkSessionState") {
|
||||||
return new JsonCheckSessionState;
|
return new JsonCheckSessionState;
|
||||||
}
|
}
|
||||||
|
else if (url_first_part == "/signTransaction") {
|
||||||
|
return new JsonSignTransaction;
|
||||||
|
}
|
||||||
else if (url_first_part == "/checkUsername") {
|
else if (url_first_part == "/checkUsername") {
|
||||||
return new JsonCheckUsername;
|
return new JsonCheckUsername;
|
||||||
}
|
}
|
||||||
|
|||||||
48
login_server/src/cpp/JSONInterface/JsonSignTransaction.cpp
Normal file
48
login_server/src/cpp/JSONInterface/JsonSignTransaction.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "JsonSignTransaction.h"
|
||||||
|
#include "lib/DataTypeConverter.h"
|
||||||
|
|
||||||
|
Poco::JSON::Object* JsonSignTransaction::handle(Poco::Dynamic::Var params)
|
||||||
|
{
|
||||||
|
auto result = checkAndLoadSession(params);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string bodyBytes_base64;
|
||||||
|
auto mm = MemoryManager::getInstance();
|
||||||
|
|
||||||
|
// if is json object
|
||||||
|
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
|
||||||
|
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
|
||||||
|
/// Throws a RangeException if the value does not fit
|
||||||
|
/// into the result variable.
|
||||||
|
/// Throws a NotImplementedException if conversion is
|
||||||
|
/// not available for the given type.
|
||||||
|
/// Throws InvalidAccessException if Var is empty.
|
||||||
|
try {
|
||||||
|
paramJsonObject->get("bodyBytes").convert(bodyBytes_base64);
|
||||||
|
}
|
||||||
|
catch (Poco::Exception& ex) {
|
||||||
|
return stateError("json exception", ex.displayText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto user = mSession->getNewUser();
|
||||||
|
auto keyPair = user->getGradidoKeyPair();
|
||||||
|
if (!keyPair) {
|
||||||
|
return stateError("error reading keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bodyBytes = DataTypeConverter::base64ToBin(bodyBytes_base64);
|
||||||
|
auto sign = keyPair->sign(bodyBytes_base64);
|
||||||
|
mm->releaseMemory(bodyBytes);
|
||||||
|
|
||||||
|
if (!sign) {
|
||||||
|
return stateError("error signing transaction");
|
||||||
|
}
|
||||||
|
auto sign_base64 = DataTypeConverter::binToBase64(sign);
|
||||||
|
mm->releaseMemory(sign);
|
||||||
|
result = stateSuccess();
|
||||||
|
result->set("sign", sign_base64);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
15
login_server/src/cpp/JSONInterface/JsonSignTransaction.h
Normal file
15
login_server/src/cpp/JSONInterface/JsonSignTransaction.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef __JSON_INTERFACE_JSON_SIGN_TRANSACTION_
|
||||||
|
#define __JSON_INTERFACE_JSON_SIGN_TRANSACTION_
|
||||||
|
|
||||||
|
#include "JsonRequestHandler.h"
|
||||||
|
|
||||||
|
class JsonSignTransaction : public JsonRequestHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __JSON_INTERFACE_JSON_SIGN_TRANSACTION_
|
||||||
@ -69,7 +69,7 @@ namespace controller {
|
|||||||
|
|
||||||
using namespace Poco::Data::Keywords;
|
using namespace Poco::Data::Keywords;
|
||||||
Poco::Data::Statement select(session);
|
Poco::Data::Statement select(session);
|
||||||
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id FROM " << db->getTableName();
|
select << "SELECT id, first_name, last_name, email, username, description, pubkey, created, email_checked, disabled, group_id, publisher_id FROM " << db->getTableName();
|
||||||
select << " where email_checked = 0 ";
|
select << " where email_checked = 0 ";
|
||||||
select, into(resultFromDB);
|
select, into(resultFromDB);
|
||||||
if (searchString != "") {
|
if (searchString != "") {
|
||||||
|
|||||||
@ -74,7 +74,8 @@ enum PageState {
|
|||||||
{
|
{
|
||||||
//mSession->finalizeTransaction(false, true);
|
//mSession->finalizeTransaction(false, true);
|
||||||
//
|
//
|
||||||
if(!transaction.isNull() && transaction->getModel()->getUserId() == user_model->getID())
|
if(!transaction.isNull() &&
|
||||||
|
(transaction_body->isCreation() || transaction->getModel()->getUserId() == user_model->getID()))
|
||||||
{
|
{
|
||||||
if(pt->removeTask(transaction)) {
|
if(pt->removeTask(transaction)) {
|
||||||
transaction->deleteFromDB();
|
transaction->deleteFromDB();
|
||||||
@ -150,7 +151,7 @@ enum PageState {
|
|||||||
transaction_body = transaction->getTransactionBody();
|
transaction_body = transaction->getTransactionBody();
|
||||||
// user can only delete there own transactions
|
// user can only delete there own transactions
|
||||||
// TODO: Auto timeout for community transactions
|
// TODO: Auto timeout for community transactions
|
||||||
if(transaction->getModel()->getUserId() == user_model->getID()) {
|
if(transaction_body->isCreation() || transaction->getModel()->getUserId() == user_model->getID()) {
|
||||||
transaction_removeable = true;
|
transaction_removeable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,20 +339,19 @@ enum PageState {
|
|||||||
<%= gettext("Transaktion unterzeichnen") %>
|
<%= gettext("Transaktion unterzeichnen") %>
|
||||||
</button>
|
</button>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<button type="submit" class="form-button button-cancel" name="skip" value="skip">
|
||||||
|
<i class="material-icons-outlined">debug-step-over</i>
|
||||||
|
<%= gettext("Transaktion überspringen") %>
|
||||||
|
</button>
|
||||||
<% if(transaction_removeable) { %>
|
<% if(transaction_removeable) { %>
|
||||||
<button type="submit" class="form-button button-cancel" name="abort" value="abort">
|
<button type="submit" class="form-button button-cancel" name="abort" value="abort">
|
||||||
<i class="material-icons-outlined">delete</i>
|
<i class="material-icons-outlined">delete</i>
|
||||||
<%= gettext("Transaktion verwerfen") %>
|
<%= gettext("Transaktion verwerfen") %>
|
||||||
</button>
|
</button>
|
||||||
<% } else { %>
|
|
||||||
<button type="submit" class="form-button button-cancel" name="skip" value="skip">
|
|
||||||
<i class="material-icons-outlined">debug-step-over</i>
|
|
||||||
<%= gettext("Transaktion überspringen") %>
|
|
||||||
</button>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</form>
|
</form>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%@ include file="include/footer_chr.cpsp" %>
|
<%@ include file="include/footer_chr.cpsp" %>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user