Merge branch 'master' into apollo_createTransactions_without_signation

This commit is contained in:
elweyn 2021-11-04 09:34:15 +01:00
commit 3aa6250eeb
87 changed files with 1129 additions and 883 deletions

View File

@ -147,7 +147,7 @@ jobs:
########################################################################## ##########################################################################
- name: mariadb | Build `test` image - name: mariadb | Build `test` image
run: | run: |
docker build --target mariadb_server_test -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./ docker build --target mariadb_server -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./
docker save "gradido/mariadb:test" > /tmp/mariadb.tar docker save "gradido/mariadb:test" > /tmp/mariadb.tar
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@ -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: 82 min_coverage: 83
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################
@ -409,10 +409,16 @@ jobs:
--health-timeout=3s --health-timeout=3s
--health-retries=4 --health-retries=4
steps: steps:
- name: Debug service # - name: Debug service
run: echo "$(docker ps)" # run: echo "$(docker ps)"
- name: Debug container choosing script #- name: Debug container choosing script
run: echo "$(docker container ls | grep mariadb | awk '{ print $1 }')" # run: echo "$(docker container ls | grep mariadb | awk '{ print $1 }')"
- name: get mariadb container id
run: echo "::set-output name=id::$(docker container ls | grep mariadb | awk '{ print $1 }')"
id: mariadb_container
- name: get automatic created network
run: echo "::set-output name=id::$(docker network ls | grep github_network | awk '{ print $1 }')"
id: network
########################################################################## ##########################################################################
# CHECKOUT CODE ########################################################## # CHECKOUT CODE ##########################################################
########################################################################## ##########################################################################
@ -420,6 +426,11 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
# Database migration
- name: Start database migration
run: |
docker build --target production_up -t "gradido/database:production_up" database/
docker run --network ${{ steps.network.outputs.id }} --name=database --env NODE_ENV=production --env DB_HOST=mariadb --env DB_DATABASE=gradido_community_test -d gradido/database:production_up
########################################################################## ##########################################################################
# Build Login-Server Test Docker image ################################### # Build Login-Server Test Docker image ###################################
########################################################################## ##########################################################################

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
/node_modules/* /node_modules/*
.vscode .vscode
messages.pot messages.pot
.skeema
nbproject nbproject
.metadata .metadata
/.env /.env

View File

@ -23,7 +23,7 @@ import schema from './graphql/schema'
// TODO implement // TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
const DB_VERSION = '0002-add_settings' const DB_VERSION = '0004-login_server_data'
async function main() { async function main() {
// open mysql connection // open mysql connection

View File

@ -15,7 +15,8 @@ class ServerUsersController extends AppController
public function initialize() public function initialize()
{ {
parent::initialize(); parent::initialize();
$this->Auth->allow(['add', 'edit']); // uncomment in devmode to add new community server admin user, but don't!!! commit it
//$this->Auth->allow(['add', 'edit']);
$this->Auth->deny('index'); $this->Auth->deny('index');
} }

View File

@ -17,7 +17,7 @@ phpServer.host = nginx
loginServer.path = http://localhost/account loginServer.path = http://localhost/account
loginServer.default_locale = de loginServer.default_locale = de
loginServer.db.host = mariadb loginServer.db.host = mariadb
loginServer.db.name = gradido_login loginServer.db.name = gradido_community
loginServer.db.user = root loginServer.db.user = root
loginServer.db.password = loginServer.db.password =
loginServer.db.port = 3306 loginServer.db.port = 3306

View File

@ -17,7 +17,7 @@ phpServer.host = nginx
loginServer.path = http://localhost/account loginServer.path = http://localhost/account
loginServer.default_locale = de loginServer.default_locale = de
loginServer.db.host = mariadb loginServer.db.host = mariadb
loginServer.db.name = gradido_login_test loginServer.db.name = gradido_community_test
loginServer.db.user = root loginServer.db.user = root
loginServer.db.password = loginServer.db.password =
loginServer.db.port = 3306 loginServer.db.port = 3306

View File

@ -1,6 +0,0 @@
INSERT INTO `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');

View File

@ -11,7 +11,7 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(` await queryFn(`
CREATE TABLE IF NOT EXISTS \`user_setting\` ( CREATE TABLE \`user_setting\` (
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
\`userId\` int(11) NOT NULL, \`userId\` int(11) NOT NULL,
\`key\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL,
@ -22,5 +22,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn // write downgrade logic as parameter of queryFn
await queryFn(`DROP TABLE IF EXISTS \`user_setting\`;`) await queryFn(`DROP TABLE \`user_setting\`;`)
} }

View File

@ -0,0 +1,153 @@
/* FIRST MIGRATION
*
* This migration is special since it takes into account that
* the database can be setup already but also may not be.
* Therefore you will find all `CREATE TABLE` statements with
* a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
* downgrade function all `DROP TABLE` with a `IF EXISTS`.
* This ensures compatibility for existing or non-existing
* databases.
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE \`login_app_access_tokens\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL,
\`access_code\` bigint unsigned NOT NULL,
\`created\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`updated\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`access_code\` (\`access_code\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_elopage_buys\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`elopage_user_id\` int DEFAULT NULL,
\`affiliate_program_id\` int NOT NULL,
\`publisher_id\` int NOT NULL,
\`order_id\` int NOT NULL,
\`product_id\` int NOT NULL,
\`product_price\` int NOT NULL,
\`payer_email\` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
\`publisher_email\` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
\`payed\` tinyint NOT NULL,
\`success_date\` datetime NOT NULL,
\`event\` varchar(255) NOT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_email_opt_in_types\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`name\` varchar(255) NOT NULL,
\`description\` varchar(255) NOT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_email_opt_in\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL,
\`verification_code\` bigint unsigned NOT NULL,
\`email_opt_in_type_id\` int NOT NULL,
\`created\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`resend_count\` int DEFAULT '0',
\`updated\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`verification_code\` (\`verification_code\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_groups\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`alias\` varchar(190) NOT NULL,
\`name\` varchar(255) NOT NULL,
\`url\` varchar(255) NOT NULL,
\`host\` varchar(255) DEFAULT "/",
\`home\` varchar(255) DEFAULT "/",
\`description\` text,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`alias\` (\`alias\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_pending_tasks\` (
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
\`user_id\` int UNSIGNED DEFAULT 0,
\`request\` varbinary(2048) NOT NULL,
\`created\` datetime NOT NULL,
\`finished\` datetime DEFAULT '2000-01-01 000000',
\`result_json\` text DEFAULT NULL,
\`param_json\` text DEFAULT NULL,
\`task_type_id\` int UNSIGNED NOT NULL,
\`child_pending_task_id\` int UNSIGNED DEFAULT 0,
\`parent_pending_task_id\` int UNSIGNED DEFAULT 0,
PRIMARY KEY (\`id\`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_roles\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`name\` varchar(255) NOT NULL,
\`description\` varchar(255) NOT NULL,
\`flags\` bigint NOT NULL DEFAULT '0',
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_user_backups\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL,
\`passphrase\` text NOT NULL,
\`mnemonic_type\` int DEFAULT '-1',
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_user_roles\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL,
\`role_id\` int NOT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
CREATE TABLE \`login_users\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`email\` varchar(191) NOT NULL,
\`first_name\` varchar(150) NOT NULL,
\`last_name\` varchar(255) DEFAULT '',
\`username\` varchar(255) DEFAULT '',
\`description\` text DEFAULT '',
\`password\` bigint unsigned DEFAULT '0',
\`pubkey\` binary(32) DEFAULT NULL,
\`privkey\` binary(80) DEFAULT NULL,
\`email_hash\` binary(32) DEFAULT NULL,
\`created\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`email_checked\` tinyint NOT NULL DEFAULT '0',
\`passphrase_shown\` tinyint NOT NULL DEFAULT '0',
\`language\` varchar(4) NOT NULL DEFAULT 'de',
\`disabled\` tinyint DEFAULT '0',
\`group_id\` int unsigned DEFAULT 0,
\`publisher_id\` int DEFAULT 0,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`email\` (\`email\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn
await queryFn(`DROP TABLE \`login_app_access_tokens\`;`)
await queryFn(`DROP TABLE \`login_elopage_buys\`;`)
await queryFn(`DROP TABLE \`login_email_opt_in_types\`;`)
await queryFn(`DROP TABLE \`login_email_opt_in\`;`)
await queryFn(`DROP TABLE \`login_groups\`;`)
await queryFn(`DROP TABLE \`login_pending_tasks\`;`)
await queryFn(`DROP TABLE \`login_roles\`;`)
await queryFn(`DROP TABLE \`login_user_backups\`;`)
await queryFn(`DROP TABLE \`login_user_roles\`;`)
await queryFn(`DROP TABLE \`login_users\`;`)
}

View File

@ -0,0 +1,74 @@
/* FIRST MIGRATION
*
* This migration is special since it takes into account that
* the database can be setup already but also may not be.
* Therefore you will find all `CREATE TABLE` statements with
* a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
* downgrade function all `DROP TABLE` with a `IF EXISTS`.
* This ensures compatibility for existing or non-existing
* databases.
*/
const LOGIN_SERVER_DB = 'gradido_login'
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
const loginDatabaseExists = await queryFn(`
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${LOGIN_SERVER_DB}'
`)
if (loginDatabaseExists.length === 0) {
// eslint-disable-next-line no-console
console.log(`Skipping Login Server Database migration - Database ${LOGIN_SERVER_DB} not found`)
return
}
await queryFn(`
INSERT INTO \`login_app_access_tokens\` SELECT * FROM ${LOGIN_SERVER_DB}.\`app_access_tokens\`;
`)
await queryFn(`
INSERT INTO \`login_elopage_buys\` SELECT * FROM ${LOGIN_SERVER_DB}.\`elopage_buys\`;
`)
await queryFn(`
INSERT INTO \`login_email_opt_in_types\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in_types\`;
`)
await queryFn(`
INSERT INTO \`login_email_opt_in\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in\`;
`)
await queryFn(`
INSERT INTO \`login_groups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`groups\`;
`)
await queryFn(`
INSERT INTO \`login_pending_tasks\` SELECT * FROM ${LOGIN_SERVER_DB}.\`pending_tasks\`;
`)
await queryFn(`
INSERT INTO \`login_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`roles\`;
`)
await queryFn(`
INSERT INTO \`login_user_backups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_backups\`;
`)
await queryFn(`
INSERT INTO \`login_user_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_roles\`;
`)
await queryFn(`
INSERT INTO \`login_users\` SELECT * FROM ${LOGIN_SERVER_DB}.\`users\`;
`)
// TODO clarify if we need this on non docker environment?
await queryFn(`
INSERT IGNORE 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');
`)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn
await queryFn(`DELETE FROM \`login_app_access_tokens\`;`)
await queryFn(`DELETE FROM \`login_elopage_buys\`;`)
await queryFn(`DELETE FROM \`login_email_opt_in_types\`;`)
await queryFn(`DELETE FROM \`login_email_opt_in\`;`)
await queryFn(`DELETE FROM \`login_groups\`;`)
await queryFn(`DELETE FROM \`login_pending_tasks\`;`)
await queryFn(`DELETE FROM \`login_roles\`;`)
await queryFn(`DELETE FROM \`login_user_backups\`;`)
await queryFn(`DELETE FROM \`login_user_roles\`;`)
await queryFn(`DELETE FROM \`login_users\`;`)
}

View File

@ -36,8 +36,6 @@ cd $PROJECT_PATH
# git checkout -f master # git checkout -f master
git pull git pull
cd deployment/bare_metal cd deployment/bare_metal
echo 'update schemas' >> $UPDATE_HTML
./update_db_schemas.sh
echo 'starting with rebuilding login-server<br>' >> $UPDATE_HTML echo 'starting with rebuilding login-server<br>' >> $UPDATE_HTML
./build_and_start_login_server.sh ./build_and_start_login_server.sh
echo 'starting with rebuilding frontend<br>' >> $UPDATE_HTML echo 'starting with rebuilding frontend<br>' >> $UPDATE_HTML

View File

@ -1,7 +0,0 @@
#!/bin/bash
# For that to work skeema needed to be installed on system
# in login_server/skeema skeema configuration files need to be there also in the subfolders
# Update DB Schemas (only the schemas, not the data)
cd ../../login_server/skeema
skeema push --allow-unsafe

View File

@ -136,22 +136,7 @@ services:
- external-net - external-net
volumes: volumes:
- /sessions - /sessions
#########################################################
## skeema for updating dbs if changes happend ###########
#########################################################
skeema:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_dev_run
depends_on:
- mariadb
networks:
- internal-net
volumes:
- ./login_server/skeema/gradido_login:/skeema/gradido_login
volumes: volumes:
frontend_node_modules: frontend_node_modules:
backend_node_modules: backend_node_modules:

View File

@ -9,7 +9,7 @@ services:
build: build:
context: . context: .
dockerfile: ./mariadb/Dockerfile dockerfile: ./mariadb/Dockerfile
target: mariadb_server_test target: mariadb_server
environment: environment:
- MARIADB_ALLOW_EMPTY_PASSWORD=1 - MARIADB_ALLOW_EMPTY_PASSWORD=1
- MARIADB_USER=root - MARIADB_USER=root
@ -75,19 +75,6 @@ services:
- external-net - external-net
volumes: volumes:
- /sessions - /sessions
#########################################################
## skeema for updating dbs if changes happend ###########
#########################################################
skeema:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_run
depends_on:
- mariadb
networks:
- internal-net
networks: networks:
external-net: external-net:

View File

@ -35,9 +35,8 @@ services:
######################################################### #########################################################
mariadb: mariadb:
build: build:
context: . context: ./mariadb
dockerfile: ./mariadb/Dockerfile target: mariadb_server
target: mariadb_server_test
environment: environment:
- MARIADB_ALLOW_EMPTY_PASSWORD=1 - MARIADB_ALLOW_EMPTY_PASSWORD=1
- MARIADB_USER=root - MARIADB_USER=root
@ -158,41 +157,6 @@ services:
- internal-net - internal-net
volumes: volumes:
- ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini - ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
#########################################################
## skeema for updating dbs if changes happend ###########
#########################################################
skeema:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_run
depends_on:
- mariadb
networks:
- internal-net
#########################################################
## GRADIDO NODE v1 ######################################
#########################################################
# gradido-node:
# build:
# context: .
# dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
# volumes:
# - ${GN_INSTANCE_FOLDER}:/opt/instance
# container_name: ${GN_CONTAINER_NAME}
#########################################################
## GRADIDO NODE test ###################################
#########################################################
# gradido-node-test:
# build:
# context: .
# dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
# container_name: gn-test
# working_dir: /opt/gn/build
# command: ["./unit_tests"]
networks: networks:
external-net: external-net:

View File

@ -15,7 +15,7 @@ This document describes the technical overview for the Gradido infrastructur. Be
![CommunityServerAPI](../image/CommunityServerAPI.png) ![CommunityServerAPI](../image/CommunityServerAPI.png)
### Database Skeema ### Database Skeema (outdated)
![CommunityDBSkeema](../image/CommunityDBSkeema.png) ![CommunityDBSkeema](../image/CommunityDBSkeema.png)

View File

@ -0,0 +1,27 @@
### User creation
A user needs to be created on the login_server we do this when we create a User in the client https://$community_domain/vue/register.
### Admin user
To set a User admin we need the following SQL query on the gradido_login database:
```
INSERT INTO user_roles (id, user_id, role_id) VALUES (NULL, '1', '1');
```
user_id has to be found in users
Now when we login in on https://$community_domain/account/ we can create coins but we will be restricted cause we can't sign the creations.
### Signation account
At first we need to enable the server user account creation with uncommenting line: 19 in
community_server/src/Controller/ServerUsersController.php
```php
$this->Auth->allow(['add', 'edit']);
```
This enable us to use this action without being logged in.
To add a signation account we need to go on the following url: http://$community_domain/server-users/add
### Coin creation process
The coin creation for work is done in the following url: http://$community_domain/transaction-creations/create-multi
Where we can create coins for a number of as many users as we want excepted for our self.
Furthermore we must sign the transactions we created. Normally after clicking on the left button (Transaktion abschließen) we should be automatically forwarded to http://$community_domain/account/checkTransactions where we can do this.
If not this page can also be reached by clicking on the shield-icon with the hook in it on the Dashboard, which is only shown if at least one transaction is waiting for signing.
For debug purposes you can check the `pending_tasks` table, which is used to store the transactions which are not signed yet or had errors.

Binary file not shown.

View File

@ -27,7 +27,6 @@
"babel-preset-vue": "^2.0.2", "babel-preset-vue": "^2.0.2",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"bootstrap-vue": "^2.5.0", "bootstrap-vue": "^2.5.0",
"chart.js": "^2.9.3",
"d3": "^5.7.0", "d3": "^5.7.0",
"datamaps": "^0.5.9", "datamaps": "^0.5.9",
"date-fns": "^1.30.1", "date-fns": "^1.30.1",
@ -64,7 +63,6 @@
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-apollo": "^3.0.7", "vue-apollo": "^3.0.7",
"vue-bootstrap-typeahead": "^0.2.6", "vue-bootstrap-typeahead": "^0.2.6",
"vue-chartjs": "^3.5.0",
"vue-cli-plugin-i18n": "^1.0.1", "vue-cli-plugin-i18n": "^1.0.1",
"vue-clickaway": "^2.2.2", "vue-clickaway": "^2.2.2",
"vue-clipboard2": "^0.3.0", "vue-clipboard2": "^0.3.0",

View File

@ -0,0 +1,45 @@
import { mount } from '@vue/test-utils'
import Status from './Status'
const localVue = global.localVue
describe('Status', () => {
let wrapper
const mocks = {
$n: jest.fn((n) => n),
}
const propsData = {
balance: 1234,
statusText: 'GDD',
}
const Wrapper = () => {
return mount(Status, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
describe('balance is pending', () => {
it('it displays an en-dash', () => {
expect(wrapper.find('div.gdd-status-div').text()).toEqual('— GDD')
})
})
describe('balance is loaded', () => {
beforeEach(() => {
wrapper.setProps({
pending: false,
})
})
it('it displays the ammount of GDD', () => {
expect(wrapper.find('div.gdd-status-div').text()).toEqual('1234 GDD')
})
})
})
})

View File

@ -0,0 +1,24 @@
<template>
<div class="gdd-status">
<div class="p-0 gdd-status-div">
{{ pending ? '—' : $n(balance, 'decimal') }} {{ statusText }}
</div>
</div>
</template>
<script>
export default {
name: 'Status',
props: {
balance: { type: Number, default: 0 },
pending: {
type: Boolean,
default: true,
},
statusText: {
type: String,
default: '',
},
},
}
</script>

View File

@ -1,8 +1,11 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Transaction from './Transaction' import Transaction from './Transaction'
import Vue from 'vue'
const localVue = global.localVue const localVue = global.localVue
const consoleErrorMock = jest.fn()
describe('Transaction', () => { describe('Transaction', () => {
let wrapper let wrapper
@ -27,5 +30,206 @@ describe('Transaction', () => {
it('renders the component', () => { it('renders the component', () => {
expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy() expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy()
}) })
it('has a collapse button', () => {
expect(wrapper.find('button[type="button"].btn-secondary').text()).toBe('i')
})
describe('no valid GDT entry type', () => {
beforeEach(async () => {
// disable throwing Errors on warnings to catch the warning
Vue.config.warnHandler = (w) => {}
// eslint-disable-next-line no-console
console.error = consoleErrorMock
await wrapper.setProps({ gdtEntryType: 'NOT_VALID' })
})
it('throws an error', () => {
expect(consoleErrorMock).toBeCalledWith(
expect.objectContaining({ message: 'no lines for this type: NOT_VALID' }),
)
})
})
describe('default entry type FORM', () => {
beforeEach(async () => {
await wrapper.setProps({
amount: 100,
date: '2021-05-02T17:20:11+00:00',
comment: 'This is a comment',
factor: 17,
gdt: 1700,
id: 42,
})
})
it('has the heart icon', () => {
expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
})
it('has the description gdt.contribution', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.contribution')
})
it('renders the amount of euros', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('100 €')
})
it('renders the amount of GDT', () => {
expect(wrapper.findAll('div.row').at(1).text()).toContain('1700 GDT')
})
it('renders the comment message', () => {
expect(wrapper.findAll('div.row').at(2).text()).toContain('This is a comment')
})
it('renders the date', () => {
expect(wrapper.findAll('div.row').at(3).text()).toContain('Sun May 02 2021')
})
it('does not show the collapse by default', () => {
expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
})
describe('without comment', () => {
it('does not render the message row', async () => {
await wrapper.setProps({ comment: undefined })
expect(wrapper.findAll('div.row').at(2).text()).toContain('form.date')
})
})
/* how to open the collapse ?????
describe('collapse is open', () => {
beforeEach(async () => {
//console.log(wrapper.html())
await wrapper.find('div#gdt-collapse-42').trigger('click')
await wrapper.vm.$nextTick()
await flushPromises()
await wrapper.vm.$nextTick()
await flushPromises()
//console.log(wrapper.find('[enteractiveclass="collapsing"]').html())
})
it('shows the collapse', () => {
//console.log(wrapper.html())
expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeTruthy()
})
})
*/
})
describe('GdtEntryType.CVS', () => {
it('behaves as default FORM', async () => {
await wrapper.setProps({ gdtEntryType: 'CVS' })
expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
})
})
describe('GdtEntryType.ELOPAGE', () => {
it('behaves as default FORM', async () => {
await wrapper.setProps({ gdtEntryType: 'ELOPAGE' })
expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
})
})
describe('GdtEntryType.DIGISTORE', () => {
it('behaves as default FORM', async () => {
await wrapper.setProps({ gdtEntryType: 'DIGISTORE' })
expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
})
})
describe('GdtEntryType.CVS2', () => {
it('behaves as default FORM', async () => {
await wrapper.setProps({ gdtEntryType: 'CVS2' })
expect(wrapper.find('svg.bi-heart').exists()).toBeTruthy()
})
})
describe('GdtEntryType.ELOPAGE_PUBLISHER', () => {
beforeEach(async () => {
await wrapper.setProps({
amount: 365.67,
date: '2020-04-10T13:28:00+00:00',
comment: 'This is a comment',
gdtEntryType: 'ELOPAGE_PUBLISHER',
factor: 22,
gdt: 967.65,
id: 42,
})
})
it('has the person-check icon', () => {
expect(wrapper.find('svg.bi-person-check').exists()).toBeTruthy()
})
it('has the description gdt.recruited-member', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.recruited-member')
})
it('renders the percentage', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('5%')
})
it('renders the amount of GDT', () => {
expect(wrapper.findAll('div.row').at(1).text()).toContain('365.67 GDT')
})
it('renders the comment message', () => {
expect(wrapper.findAll('div.row').at(2).text()).toContain('This is a comment')
})
it('renders the date', () => {
expect(wrapper.findAll('div.row').at(3).text()).toContain('Fri Apr 10 2020')
})
it('does not show the collapse by default', () => {
expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
})
describe('without comment', () => {
it('does not render the message row', async () => {
await wrapper.setProps({ comment: undefined })
expect(wrapper.findAll('div.row').at(2).text()).toContain('form.date')
})
})
})
describe('GdtEntryType.GLOBAL_MODIFICATOR', () => {
beforeEach(async () => {
await wrapper.setProps({
amount: 123.45,
date: '2020-03-12T13:28:00+00:00',
comment: 'This is a comment',
gdtEntryType: 'GLOBAL_MODIFICATOR',
factor: 19,
gdt: 61.23,
id: 42,
})
})
it('has the gift icon', () => {
expect(wrapper.find('svg.bi-gift').exists()).toBeTruthy()
})
it('has the description gdt.gdt-received', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('gdt.gdt-received')
})
it('renders the comment', () => {
expect(wrapper.findAll('div.row').at(0).text()).toContain('This is a comment')
})
it('renders the amount of GDT', () => {
expect(wrapper.findAll('div.row').at(1).text()).toContain('61.23 GDT')
})
it('renders the date', () => {
expect(wrapper.findAll('div.row').at(2).text()).toContain('Thu Mar 12 2020')
})
it('does not show the collapse by default', () => {
expect(wrapper.find('div#gdt-collapse-42').isVisible()).toBeFalsy()
})
})
}) })
}) })

View File

@ -1,13 +1,10 @@
<template> <template>
<div> <div>
<div class="list-group"> <div class="list-group">
<div class="list-group-item gdt-transaction-list-item" v-b-toggle="'a' + date + ''"> <div class="list-group-item gdt-transaction-list-item" v-b-toggle="collapseId">
<!-- icon --> <!-- icon -->
<div class="text-right" style="position: absolute"> <div class="text-right" style="position: absolute">
<b-icon <b-icon :icon="getLinesByType.icon" :class="getLinesByType.iconclasses"></b-icon>
:icon="getLinesByType(gdtEntryType).icon"
:class="getLinesByType(gdtEntryType).iconclasses"
></b-icon>
</div> </div>
<!-- collaps Button --> <!-- collaps Button -->
@ -20,10 +17,10 @@
<!-- type --> <!-- type -->
<b-row> <b-row>
<b-col cols="6" class="text-right"> <b-col cols="6" class="text-right">
{{ getLinesByType(gdtEntryType).description }} {{ getLinesByType.description }}
</b-col> </b-col>
<b-col cols="6"> <b-col cols="6">
{{ getLinesByType(gdtEntryType).descriptiontext }} {{ getLinesByType.descriptiontext }}
</b-col> </b-col>
</b-row> </b-row>
@ -33,7 +30,7 @@
{{ $t('gdt.credit') }} {{ $t('gdt.credit') }}
</b-col> </b-col>
<b-col cols="6"> <b-col cols="6">
{{ getLinesByType(gdtEntryType).credittext }} {{ getLinesByType.credittext }}
</b-col> </b-col>
</b-row> </b-row>
@ -58,7 +55,7 @@
</b-row> </b-row>
<!-- collaps trancaction info--> <!-- collaps trancaction info-->
<b-collapse :id="'a' + date + ''" class="mt-2 pb-4"> <b-collapse :id="collapseId" class="mt-2 pb-4">
<transaction-collapse <transaction-collapse
:amount="amount" :amount="amount"
:gdtEntryType="gdtEntryType" :gdtEntryType="gdtEntryType"
@ -86,15 +83,17 @@ export default {
gdtEntryType: { type: String, default: GdtEntryType.FORM }, gdtEntryType: { type: String, default: GdtEntryType.FORM },
factor: { type: Number }, factor: { type: Number },
gdt: { type: Number }, gdt: { type: Number },
id: { type: Number },
}, },
computed: { computed: {
isGlobalModificator: function () { collapseId() {
return 'gdt-collapse-' + String(this.id)
},
isGlobalModificator() {
return this.gdtEntryType === GdtEntryType.GLOBAL_MODIFICATOR return this.gdtEntryType === GdtEntryType.GLOBAL_MODIFICATOR
}, },
}, getLinesByType() {
methods: { switch (this.gdtEntryType) {
getLinesByType(givenType) {
switch (givenType) {
case GdtEntryType.FORM: case GdtEntryType.FORM:
case GdtEntryType.CVS: case GdtEntryType.CVS:
case GdtEntryType.ELOPAGE: case GdtEntryType.ELOPAGE:
@ -127,7 +126,7 @@ export default {
} }
} }
default: default:
throw new Error('no lines for this type: ' + givenType) throw new Error('no lines for this type: ' + this.gdtEntryType)
} }
}, },
}, },

View File

@ -2,8 +2,12 @@ import { mount } from '@vue/test-utils'
import TransactionCollapse from './TransactionCollapse' import TransactionCollapse from './TransactionCollapse'
import { GdtEntryType } from '../graphql/enums' import { GdtEntryType } from '../graphql/enums'
import Vue from 'vue'
const localVue = global.localVue const localVue = global.localVue
const consoleErrorMock = jest.fn()
describe('TransactionCollapse', () => { describe('TransactionCollapse', () => {
let wrapper let wrapper
@ -16,6 +20,31 @@ describe('TransactionCollapse', () => {
return mount(TransactionCollapse, { localVue, mocks, propsData }) return mount(TransactionCollapse, { localVue, mocks, propsData })
} }
describe('no valid GDT entry type', () => {
beforeEach(async () => {
// disable throwing Errors on warnings to catch the warning
Vue.config.warnHandler = (w) => {}
// eslint-disable-next-line no-console
console.error = consoleErrorMock
const propsData = {
amount: 100,
gdt: 110,
factor: 22,
gdtEntryType: GdtEntryType.FORM,
}
wrapper = Wrapper(propsData)
await wrapper.setProps({ gdtEntryType: 'NOT_VALID' })
})
it('throws an error', () => {
expect(consoleErrorMock).toBeCalledWith(
expect.objectContaining({
message: 'no additional transaction info for this type: NOT_VALID',
}),
)
})
})
describe('mount with gdtEntryType: FORM', () => { describe('mount with gdtEntryType: FORM', () => {
beforeEach(() => { beforeEach(() => {
const propsData = { const propsData = {
@ -24,7 +53,6 @@ describe('TransactionCollapse', () => {
factor: 22, factor: 22,
gdtEntryType: GdtEntryType.FORM, gdtEntryType: GdtEntryType.FORM,
} }
wrapper = Wrapper(propsData) wrapper = Wrapper(propsData)
}) })
@ -41,23 +69,23 @@ describe('TransactionCollapse', () => {
}) })
it('renders the component collapse-headline', () => { it('renders the component collapse-headline', () => {
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.calculation') expect(wrapper.find('.collapse-headline').text()).toBe('gdt.calculation')
}) })
it('renders the component collapse-first', () => { it('renders the component collapse-first', () => {
expect(wrapper.find('#collapse-first').text()).toBe('gdt.factor') expect(wrapper.find('.collapse-first').text()).toBe('gdt.factor')
}) })
it('renders the component collapse-second', () => { it('renders the component collapse-second', () => {
expect(wrapper.find('#collapse-second').text()).toBe('gdt.formula') expect(wrapper.find('.collapse-second').text()).toBe('gdt.formula')
}) })
it('renders the component collapse-firstMath', () => { it('renders the component collapse-firstMath', () => {
expect(wrapper.find('#collapse-firstMath').text()).toBe('22 GDT pro €') expect(wrapper.find('.collapse-firstMath').text()).toBe('22 GDT pro €')
}) })
it('renders the component collapse-secondMath', () => { it('renders the component collapse-secondMath', () => {
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 € * 22 GDT / € = 110 GDT') expect(wrapper.find('.collapse-secondMath').text()).toBe('100 € * 22 GDT / € = 110 GDT')
}) })
}) })
@ -86,23 +114,23 @@ describe('TransactionCollapse', () => {
}) })
it('renders the component collapse-headline', () => { it('renders the component collapse-headline', () => {
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.conversion-gdt-euro') expect(wrapper.find('.collapse-headline').text()).toBe('gdt.conversion-gdt-euro')
}) })
it('renders the component collapse-first', () => { it('renders the component collapse-first', () => {
expect(wrapper.find('#collapse-first').text()).toBe('gdt.raise') expect(wrapper.find('.collapse-first').text()).toBe('gdt.raise')
}) })
it('renders the component collapse-second', () => { it('renders the component collapse-second', () => {
expect(wrapper.find('#collapse-second').text()).toBe('gdt.conversion') expect(wrapper.find('.collapse-second').text()).toBe('gdt.conversion')
}) })
it('renders the component collapse-firstMath', () => { it('renders the component collapse-firstMath', () => {
expect(wrapper.find('#collapse-firstMath').text()).toBe('2200 %') expect(wrapper.find('.collapse-firstMath').text()).toBe('2200 %')
}) })
it('renders the component collapse-secondMath', () => { it('renders the component collapse-secondMath', () => {
expect(wrapper.find('#collapse-secondMath').text()).toBe('100 GDT * 2200 % = 2200 GDT') expect(wrapper.find('.collapse-secondMath').text()).toBe('100 GDT * 2200 % = 2200 GDT')
}) })
}) })
@ -131,23 +159,23 @@ describe('TransactionCollapse', () => {
}) })
it('renders the component collapse-headline', () => { it('renders the component collapse-headline', () => {
expect(wrapper.find('#collapse-headline').text()).toBe('gdt.publisher') expect(wrapper.find('.collapse-headline').text()).toBe('gdt.publisher')
}) })
it('renders the component collapse-first', () => { it('renders the component collapse-first', () => {
expect(wrapper.find('#collapse-first').text()).toBe('') expect(wrapper.find('.collapse-first').text()).toBe('')
}) })
it('renders the component collapse-second', () => { it('renders the component collapse-second', () => {
expect(wrapper.find('#collapse-second').text()).toBe('') expect(wrapper.find('.collapse-second').text()).toBe('')
}) })
it('renders the component collapse-firstMath', () => { it('renders the component collapse-firstMath', () => {
expect(wrapper.find('#collapse-firstMath').text()).toBe('') expect(wrapper.find('.collapse-firstMath').text()).toBe('')
}) })
it('renders the component collapse-secondMath', () => { it('renders the component collapse-secondMath', () => {
expect(wrapper.find('#collapse-secondMath').text()).toBe('') expect(wrapper.find('.collapse-secondMath').text()).toBe('')
}) })
}) })
}) })

View File

@ -4,19 +4,19 @@
style="border: 0px; background-color: #f1f1f1" style="border: 0px; background-color: #f1f1f1"
> >
<b-row class="gdt-list-collapse-header-text text-center pb-3"> <b-row class="gdt-list-collapse-header-text text-center pb-3">
<b-col id="collapse-headline"> <b-col class="collapse-headline">
<b>{{ getLinesByType(gdtEntryType).headline }}</b> <b>{{ getLinesByType.headline }}</b>
</b-col> </b-col>
</b-row> </b-row>
<b-row class="gdt-list-collapse-box--all"> <b-row class="gdt-list-collapse-box--all">
<b-col cols="6" class="text-right collapse-col-left"> <b-col cols="6" class="text-right collapse-col-left">
<div id="collapse-first">{{ getLinesByType(gdtEntryType).first }}</div> <div class="collapse-first">{{ getLinesByType.first }}</div>
<div id="collapse-second">{{ getLinesByType(gdtEntryType).second }}</div> <div class="collapse-second">{{ getLinesByType.second }}</div>
</b-col> </b-col>
<b-col cols="6" class="collapse-col-right"> <b-col cols="6" class="collapse-col-right">
<div id="collapse-firstMath">{{ getLinesByType(gdtEntryType).firstMath }}</div> <div class="collapse-firstMath">{{ getLinesByType.firstMath }}</div>
<div id="collapse-secondMath"> <div class="collapse-secondMath">
{{ getLinesByType(gdtEntryType).secondMath }} {{ getLinesByType.secondMath }}
</div> </div>
</b-col> </b-col>
</b-row> </b-row>
@ -33,9 +33,9 @@ export default {
factor: { type: Number }, factor: { type: Number },
gdt: { type: Number }, gdt: { type: Number },
}, },
methods: { computed: {
getLinesByType(givenType) { getLinesByType() {
switch (givenType) { switch (this.gdtEntryType) {
case GdtEntryType.FORM: case GdtEntryType.FORM:
case GdtEntryType.CVS: case GdtEntryType.CVS:
case GdtEntryType.ELOPAGE: case GdtEntryType.ELOPAGE:
@ -80,7 +80,7 @@ export default {
} }
} }
default: default:
throw new Error('no additional transaction info for this type: ' + givenType) throw new Error('no additional transaction info for this type: ' + this.gdtEntryType)
} }
}, },
}, },

View File

@ -96,9 +96,10 @@
"conversion-gdt-euro": "Umrechnung Euro / Gradido Transform (GDT)", "conversion-gdt-euro": "Umrechnung Euro / Gradido Transform (GDT)",
"credit": "Gutschrift", "credit": "Gutschrift",
"factor": "Faktor", "factor": "Faktor",
"formula": "Berechungsformel", "formula": "Berechnungsformel",
"funding": "Zu den Förderbeiträgen",
"gdt-received": "Gradido Transform (GDT) erhalten", "gdt-received": "Gradido Transform (GDT) erhalten",
"no-transactions": "Du hast zur Zeit keine Transaktionen", "no-transactions": "Du hast noch keine Gradido Transform (GDT).",
"publisher": "Dein geworbenes Mitglied hat einen Beitrag bezahlt", "publisher": "Dein geworbenes Mitglied hat einen Beitrag bezahlt",
"raise": "Erhöhung", "raise": "Erhöhung",
"recruited-member": "Geworbenes Mitglied" "recruited-member": "Geworbenes Mitglied"
@ -109,6 +110,7 @@
"logout": "Abmelden", "logout": "Abmelden",
"members_area": "Mitgliederbereich", "members_area": "Mitgliederbereich",
"message": "hallo gradido !!", "message": "hallo gradido !!",
"overview": "Übersicht",
"privacy_policy": "Datenschutzerklärung", "privacy_policy": "Datenschutzerklärung",
"send": "Senden", "send": "Senden",
"settings": { "settings": {
@ -130,8 +132,8 @@
}, },
"newsletter": { "newsletter": {
"newsletter": "Newsletter", "newsletter": "Newsletter",
"newsletterFalse": "Du bist aus Newslettersystem ausgetragen.", "newsletterFalse": "Du erhältst keine Informationen per E-Mail.",
"newsletterTrue": "Du bist im Newslettersystem eingetragen." "newsletterTrue": "Du erhältst Informationen per E-Mail."
}, },
"password": { "password": {
"change-password": "Passwort ändern", "change-password": "Passwort ändern",

View File

@ -97,8 +97,9 @@
"credit": "Credit", "credit": "Credit",
"factor": "Factor", "factor": "Factor",
"formula": "Calculation formula", "formula": "Calculation formula",
"funding": "Regarding the funding contributions",
"gdt-received": "Gradido Transform (GDT) received", "gdt-received": "Gradido Transform (GDT) received",
"no-transactions": "You currently have no transactions", "no-transactions": "You do not have Gradido Transform (GDT) yet.",
"publisher": "A member you referred has paid a contribution", "publisher": "A member you referred has paid a contribution",
"raise": "Increase", "raise": "Increase",
"recruited-member": "Recruited Member" "recruited-member": "Recruited Member"
@ -109,6 +110,7 @@
"logout": "Logout", "logout": "Logout",
"members_area": "Member's area", "members_area": "Member's area",
"message": "hello gradido !!", "message": "hello gradido !!",
"overview": "Overview",
"privacy_policy": "Privacy policy", "privacy_policy": "Privacy policy",
"send": "Send", "send": "Send",
"settings": { "settings": {
@ -130,8 +132,8 @@
}, },
"newsletter": { "newsletter": {
"newsletter": "Newsletter", "newsletter": "Newsletter",
"newsletterFalse": "You are unsubscribed from newsletter system.", "newsletterFalse": "You will not receive any information by e-mail.",
"newsletterTrue": "You are subscribed to newsletter system." "newsletterTrue": "You will receive information by e-mail."
}, },
"password": { "password": {
"change-password": "Change password", "change-password": "Change password",

View File

@ -6,6 +6,7 @@ import { loadAllRules } from './validation-rules'
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost' import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import CONFIG from './config' import CONFIG from './config'
import addNavigationGuards from './routes/guards' import addNavigationGuards from './routes/guards'
import { store } from './store/store' import { store } from './store/store'

View File

@ -49,8 +49,8 @@ describe('router', () => {
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' }) expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
}) })
it('has twelve routes defined', () => { it('has fourteen routes defined', () => {
expect(routes).toHaveLength(13) expect(routes).toHaveLength(14)
}) })
describe('overview', () => { describe('overview', () => {
@ -64,6 +64,17 @@ describe('router', () => {
}) })
}) })
describe('send', () => {
it('requires authorization', () => {
expect(routes.find((r) => r.path === '/send').meta.requiresAuth).toBeTruthy()
})
it('loads the "Send" component', async () => {
const component = await routes.find((r) => r.path === '/send').component()
expect(component.default.name).toBe('SendOverview')
})
})
describe('profile', () => { describe('profile', () => {
it('requires authorization', () => { it('requires authorization', () => {
expect(routes.find((r) => r.path === '/profile').meta.requiresAuth).toBeTruthy() expect(routes.find((r) => r.path === '/profile').meta.requiresAuth).toBeTruthy()

View File

@ -14,6 +14,13 @@ const routes = [
requiresAuth: true, requiresAuth: true,
}, },
}, },
{
path: '/send',
component: () => import('../views/Pages/SendOverview.vue'),
meta: {
requiresAuth: true,
},
},
{ {
path: '/profile', path: '/profile',
component: () => import('../views/Pages/UserProfileOverview.vue'), component: () => import('../views/Pages/UserProfileOverview.vue'),

View File

@ -89,33 +89,40 @@ describe('DashboardLayoutGdd', () => {
navbar = wrapper.findAll('ul.navbar-nav').at(0) navbar = wrapper.findAll('ul.navbar-nav').at(0)
}) })
it('has three items in the navbar', () => { it('has four items in the navbar', () => {
expect(navbar.findAll('ul > a')).toHaveLength(3) expect(navbar.findAll('ul > a')).toHaveLength(4)
}) })
it('has first item "send" in navbar', () => { it('has first item "overview" in navbar', () => {
expect(navbar.findAll('ul > a').at(0).text()).toEqual('send') expect(navbar.findAll('ul > a').at(0).text()).toEqual('overview')
}) })
it('has first item "send" linked to overview in navbar', () => { it('has first item "overview" linked to overview in navbar', () => {
navbar.findAll('ul > a').at(0).trigger('click') expect(navbar.findAll('ul > a > a').at(0).attributes('href')).toBe('/overview')
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/overview')
}) })
it('has second item "transactions" in navbar', () => { it('has second item "send" in navbar', () => {
expect(navbar.findAll('ul > a').at(1).text()).toEqual('transactions') expect(navbar.findAll('ul > a').at(1).text()).toEqual('send')
}) })
it('has second item "transactions" linked to transactions in navbar', async () => { it('has second item "send" linked to /send in navbar', () => {
expect(wrapper.findAll('a').at(3).attributes('href')).toBe('/transactions') expect(wrapper.findAll('ul > a > a').at(1).attributes('href')).toBe('/send')
}) })
it('has three items in the navbar', () => { it('has third item "transactions" in navbar', () => {
expect(navbar.findAll('ul > a')).toHaveLength(3) expect(navbar.findAll('ul > a').at(2).text()).toEqual('transactions')
}) })
it('has third item "My profile" linked to profile in navbar', async () => { it('has third item "transactions" linked to transactions in navbar', async () => {
expect(wrapper.findAll('a').at(5).attributes('href')).toBe('/profile') expect(wrapper.findAll('ul > a > a').at(2).attributes('href')).toBe('/transactions')
})
it('has fourth item "My profile" in navbar', () => {
expect(navbar.findAll('ul > a').at(3).text()).toEqual('site.navbar.my-profil')
})
it('has fourth item "My profile" linked to profile in navbar', async () => {
expect(wrapper.findAll('ul > a > a').at(3).attributes('href')).toBe('/profile')
}) })
it('has a link to the members area', () => { it('has a link to the members area', () => {

View File

@ -2,11 +2,16 @@
<div> <div>
<side-bar @logout="logout" :balance="balance" :pending="pending"> <side-bar @logout="logout" :balance="balance" :pending="pending">
<template slot="links"> <template slot="links">
<p></p> <sidebar-item
:link="{
name: $t('overview'),
path: '/overview',
}"
></sidebar-item>
<sidebar-item <sidebar-item
:link="{ :link="{
name: $t('send'), name: $t('send'),
path: '/overview', path: '/send',
}" }"
></sidebar-item> ></sidebar-item>
<sidebar-item <sidebar-item

View File

@ -1,9 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import AccountOverview from './AccountOverview' import AccountOverview from './AccountOverview'
const sendMock = jest.fn()
sendMock.mockResolvedValue('success')
const localVue = global.localVue const localVue = global.localVue
window.scrollTo = jest.fn() window.scrollTo = jest.fn()
@ -11,26 +8,16 @@ window.scrollTo = jest.fn()
describe('AccountOverview', () => { describe('AccountOverview', () => {
let wrapper let wrapper
const propsData = {
balance: 123.45,
transactionCount: 1,
}
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$n: jest.fn((n) => String(n)), $n: jest.fn(),
$store: {
state: {
email: 'sender@example.org',
},
},
$apollo: {
mutate: sendMock,
},
} }
const Wrapper = () => { const Wrapper = () => {
return mount(AccountOverview, { localVue, mocks, propsData }) return mount(AccountOverview, {
localVue,
mocks,
})
} }
describe('mount', () => { describe('mount', () => {
@ -38,97 +25,21 @@ describe('AccountOverview', () => {
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('has a status line', () => { it('has a status gdd-status-gdd', () => {
expect(wrapper.find('div.gdd-status').exists()).toBeTruthy() expect(wrapper.find('div.gdd-status-gdd').exists()).toBeTruthy()
}) })
it('has a status gdd-status-gdt', () => {
it('has a send field', () => { expect(wrapper.find('div.gdd-status-gdt').exists()).toBeTruthy()
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
}) })
it('has a transactions table', () => { it('has a transactions table', () => {
expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy() expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
}) })
describe('transaction form', () => { describe('timestamp updates', () => {
it('steps forward in the dialog', async () => { it('emits update transactions', async () => {
await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', { expect(wrapper.emitted('update-transactions')).toHaveLength(1)
email: 'user@example.org', await wrapper.setData({ timestamp: Date.now() })
amount: 23.45, expect(wrapper.emitted('update-transactions')).toHaveLength(2)
memo: 'Make the best of it!',
})
expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy()
})
})
describe('confirm transaction', () => {
beforeEach(() => {
wrapper.setData({
currentTransactionStep: 1,
transactionData: {
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
},
})
})
it('resets the transaction process when on-reset is emitted', async () => {
await wrapper.findComponent({ name: 'TransactionConfirmation' }).vm.$emit('on-reset')
expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBeTruthy()
expect(wrapper.vm.transactionData).toEqual({
email: '',
amount: 0,
memo: '',
})
})
describe('transaction is confirmed and server response is success', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper
.findComponent({ name: 'TransactionConfirmation' })
.vm.$emit('send-transaction')
})
it('calls the API when send-transaction is emitted', async () => {
expect(sendMock).toBeCalledWith(
expect.objectContaining({
variables: {
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
},
}),
)
})
it('emits update-balance', () => {
expect(wrapper.emitted('update-balance')).toBeTruthy()
expect(wrapper.emitted('update-balance')).toEqual([[23.45]])
})
it('shows the succes page', () => {
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_success')
})
})
describe('transaction is confirmed and server response is error', () => {
beforeEach(async () => {
jest.clearAllMocks()
sendMock.mockRejectedValue({ message: 'receiver not found' })
await wrapper
.findComponent({ name: 'TransactionConfirmation' })
.vm.$emit('send-transaction')
})
it('shows the error page', () => {
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error')
})
it('shows recipient not found', () => {
expect(wrapper.text()).toContain('transaction.receiverNotFound')
})
}) })
}) })
}) })

View File

@ -1,83 +1,59 @@
<template> <template>
<div> <div>
<b-container fluid> <b-container fluid>
<gdd-status <b-row>
v-if="showContext" <b-col class="col-6">
:pending="pending" <b-row>
:balance="balance" <b-col class="col-11 bg-gray text-white p-3">
:gdt-balance="GdtBalance" <status
/> class="gdd-status-gdd"
:pending="pending"
:balance="balance"
status-text="GDD"
/>
</b-col>
</b-row>
</b-col>
<b-col class="col-6 text-right">
<b-row>
<b-col class="bg-white text-gray p-3">
<status
class="gdd-status-gdt"
:pending="pending"
:balance="GdtBalance"
status-text="GDT"
/>
</b-col>
</b-row>
</b-col>
</b-row>
<br /> <br />
<gdd-send :currentTransactionStep="currentTransactionStep">
<template #transaction-form>
<transaction-form :balance="balance" @set-transaction="setTransaction"></transaction-form>
</template>
<template #transaction-confirmation>
<transaction-confirmation
:email="transactionData.email"
:amount="transactionData.amount"
:memo="transactionData.memo"
:loading="loading"
@send-transaction="sendTransaction"
@on-reset="onReset"
></transaction-confirmation>
</template>
<template #transaction-result>
<transaction-result
:error="error"
:errorResult="errorResult"
@on-reset="onReset"
></transaction-result>
</template>
</gdd-send>
<hr />
<gdd-transaction-list <gdd-transaction-list
v-if="showContext"
:transactions="transactions" :transactions="transactions"
:pageSize="5" :pageSize="5"
:timestamp="timestamp" :timestamp="timestamp"
:transaction-count="transactionCount" :transaction-count="transactionCount"
@update-transactions="updateTransactions" @update-transactions="updateTransactions"
/> />
<gdd-transaction-list-footer v-if="showContext" :count="transactionCount" /> <gdd-transaction-list-footer :count="transactionCount" />
</b-container> </b-container>
</div> </div>
</template> </template>
<script> <script>
import GddStatus from './AccountOverview/GddStatus.vue' import Status from '../../components/Status.vue'
import GddSend from './AccountOverview/GddSend.vue'
import GddTransactionList from './AccountOverview/GddTransactionList.vue' import GddTransactionList from './AccountOverview/GddTransactionList.vue'
import GddTransactionListFooter from './AccountOverview/GddTransactionListFooter.vue' import GddTransactionListFooter from './AccountOverview/GddTransactionListFooter.vue'
import TransactionForm from './AccountOverview/GddSend/TransactionForm.vue'
import TransactionConfirmation from './AccountOverview/GddSend/TransactionConfirmation.vue'
import TransactionResult from './AccountOverview/GddSend/TransactionResult.vue'
import { sendCoins } from '../../graphql/mutations.js'
const EMPTY_TRANSACTION_DATA = {
email: '',
amount: 0,
memo: '',
}
export default { export default {
name: 'Overview', name: 'Overview',
components: { components: {
GddStatus, Status,
GddSend,
GddTransactionList, GddTransactionList,
GddTransactionListFooter, GddTransactionListFooter,
TransactionForm,
TransactionConfirmation,
TransactionResult,
}, },
data() { data() {
return { return {
timestamp: Date.now(), timestamp: Date.now(),
transactionData: { ...EMPTY_TRANSACTION_DATA },
error: false,
errorResult: '',
currentTransactionStep: 0,
loading: false,
} }
}, },
props: { props: {
@ -92,38 +68,7 @@ export default {
default: true, default: true,
}, },
}, },
computed: {
showContext() {
return this.currentTransactionStep === 0
},
},
methods: { methods: {
setTransaction(data) {
this.transactionData = { ...data }
this.currentTransactionStep = 1
},
async sendTransaction() {
this.loading = true
this.$apollo
.mutate({
mutation: sendCoins,
variables: this.transactionData,
})
.then(() => {
this.error = false
this.$emit('update-balance', this.transactionData.amount)
})
.catch((err) => {
this.errorResult = err.message
this.error = true
})
this.currentTransactionStep = 2
this.loading = false
},
onReset() {
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
this.currentTransactionStep = 0
},
updateTransactions(pagination) { updateTransactions(pagination) {
this.$emit('update-transactions', pagination) this.$emit('update-transactions', pagination)
}, },

View File

@ -1,53 +0,0 @@
import { mount } from '@vue/test-utils'
import GddStatus from './GddStatus'
const localVue = global.localVue
describe('GddStatus', () => {
let wrapper
const mocks = {
$n: jest.fn((n) => n),
}
const propsData = {
balance: 1234,
GdtBalance: 9876,
}
const Wrapper = () => {
return mount(GddStatus, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
describe('balance is loading', () => {
it('it displays em-dash as the ammount of GDD', () => {
expect(wrapper.findAll('div.card-body').at(0).text()).toEqual('— GDD')
})
it('it displays em-dash as the ammount of GDT', () => {
expect(wrapper.findAll('div.card-body').at(1).text()).toEqual('— GDT')
})
})
describe('balance is loaded', () => {
beforeEach(() => {
wrapper.setProps({
pending: false,
})
})
it('it displays the ammount of GDD', () => {
expect(wrapper.findAll('div.card-body').at(0).text()).toEqual('1234 GDD')
})
it('it displays the ammount of GDT', () => {
expect(wrapper.findAll('div.card-body').at(1).text()).toEqual('9876 GDT')
})
})
})
})

View File

@ -1,30 +0,0 @@
<template>
<div class="gdd-status">
<b-row>
<b-col class="p-0">
<b-card class="p-0" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(balance, 'decimal') }} GDD
</b-card>
</b-col>
<b-col class="pr-0">
<b-card class="p-0 text-right" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(GdtBalance, 'decimal') }} GDT
</b-card>
</b-col>
</b-row>
</div>
</template>
<script>
export default {
name: 'GddStatus',
props: {
balance: { type: Number, default: 0 },
GdtBalance: { type: Number, default: 0 },
pending: {
type: Boolean,
default: true,
},
},
}
</script>

View File

@ -124,7 +124,7 @@
:per-page="pageSize" :per-page="pageSize"
:total-rows="transactionCount" :total-rows="transactionCount"
></pagination-buttons> ></pagination-buttons>
<div v-if="transactions.length === 0" class="mt-4 text-center"> <div v-if="transactionCount === 0" class="mt-4 text-center">
<span>{{ $t('transaction.nullTransactions') }}</span> <span>{{ $t('transaction.nullTransactions') }}</span>
</div> </div>
</div> </div>
@ -198,4 +198,8 @@ export default {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
} }
.gdd-transaction-list-item {
outline: none !important;
}
</style> </style>

View File

@ -7,41 +7,8 @@ const localVue = global.localVue
const apolloMock = jest.fn().mockResolvedValue({ const apolloMock = jest.fn().mockResolvedValue({
data: { data: {
listGDTEntries: { listGDTEntries: {
count: 4, count: 0,
gdtEntries: [ gdtEntries: [],
{
amount: 100,
gdt: 1700,
factor: 17,
comment: '',
date: '2021-05-02T17:20:11+00:00',
gdtEntryType: GdtEntryType.FORM,
},
{
amount: 1810,
gdt: 362,
factor: 0.2,
comment: 'Dezember 20',
date: '2020-12-31T12:00:00+00:00',
gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR,
},
{
amount: 100,
gdt: 1700,
factor: 17,
comment: '',
date: '2020-05-07T17:00:00+00:00',
gdtEntryType: GdtEntryType.FORM,
},
{
amount: 100,
gdt: 110,
factor: 22,
comment: '',
date: '2020-04-10T13:28:00+00:00',
gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER,
},
],
}, },
}, },
}) })
@ -51,10 +18,18 @@ const windowScrollToMock = jest.fn()
window.scrollTo = windowScrollToMock window.scrollTo = windowScrollToMock
describe('GdtTransactionList', () => { const state = {
language: 'en',
}
describe('GdtTransactionList ', () => {
let wrapper let wrapper
const mocks = { const mocks = {
$store: {
state,
commit: jest.fn(),
},
$i18n: { $i18n: {
locale: 'en', locale: 'en',
}, },
@ -73,15 +48,80 @@ describe('GdtTransactionList', () => {
return mount(GdtTransactionList, { localVue, mocks }) return mount(GdtTransactionList, { localVue, mocks })
} }
describe('mount', () => { describe('mount - When no transactions are loaded', () => {
beforeEach(() => { beforeEach(() => {
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('renders the funding button ', () => {
expect(wrapper.find('.gdt-funding').exists()).toBe(true)
})
it('links to https://gradido.net/en/memberships/ when clicking', async () => {
expect(wrapper.find('.gdt-funding').attributes('href')).toBe(
'https://gradido.net/' + state.language + '/memberships/',
)
})
})
describe('mount - When transactions are loaded', () => {
beforeEach(() => {
apolloMock.mockResolvedValue({
data: {
listGDTEntries: {
count: 4,
gdtEntries: [
{
id: 1,
amount: 100,
gdt: 1700,
factor: 17,
comment: '',
date: '2021-05-02T17:20:11+00:00',
gdtEntryType: GdtEntryType.FORM,
},
{
id: 2,
amount: 1810,
gdt: 362,
factor: 0.2,
comment: 'Dezember 20',
date: '2020-12-31T12:00:00+00:00',
gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR,
},
{
id: 3,
amount: 100,
gdt: 1700,
factor: 17,
comment: '',
date: '2020-05-07T17:00:00+00:00',
gdtEntryType: GdtEntryType.FORM,
},
{
id: 4,
amount: 100,
gdt: 110,
factor: 22,
comment: '',
date: '2020-04-10T13:28:00+00:00',
gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER,
},
],
},
},
})
wrapper = Wrapper()
})
it('renders the component', () => { it('renders the component', () => {
expect(wrapper.find('div.gdt-transaction-list').exists()).toBeTruthy() expect(wrapper.find('div.gdt-transaction-list').exists()).toBeTruthy()
}) })
it('does not render the funding button ', () => {
expect(wrapper.find('.gdt-funding').exists()).toBe(false)
})
describe('server returns valid data', () => { describe('server returns valid data', () => {
it('calls the API', async () => { it('calls the API', async () => {
await wrapper.vm.$nextTick() await wrapper.vm.$nextTick()

View File

@ -1,21 +1,17 @@
<template> <template>
<div class="gdt-transaction-list"> <div class="gdt-transaction-list">
<div class="list-group" style="background-color: #fff"> <div class="list-group">
<div v-if="transactionGdtCount === 0"> <div v-if="transactionGdtCount === 0" class="text-center">
{{ $t('gdt.no-transactions') }} {{ $t('gdt.no-transactions') }}
<hr />
<b-button class="gdt-funding" :href="link" target="_blank">
{{ $t('gdt.funding') }}
</b-button>
</div> </div>
<div <div
v-else v-else
v-for="{ v-for="{ id, amount, date, comment, gdtEntryType, factor, gdt } in transactionsGdt"
transactionId, :key="id"
amount,
date,
comment,
gdtEntryType,
factor,
gdt,
} in transactionsGdt"
:key="transactionId"
> >
<transaction <transaction
:amount="amount" :amount="amount"
@ -24,6 +20,7 @@
:gdtEntryType="gdtEntryType" :gdtEntryType="gdtEntryType"
:factor="factor" :factor="factor"
:gdt="gdt" :gdt="gdt"
:id="id"
></transaction> ></transaction>
</div> </div>
</div> </div>
@ -52,6 +49,7 @@ export default {
transactionGdtCount: { type: Number, default: 0 }, transactionGdtCount: { type: Number, default: 0 },
currentPage: 1, currentPage: 1,
pageSize: 25, pageSize: 25,
link: 'https://gradido.net/' + this.$store.state.language + '/memberships/',
} }
}, },
methods: { methods: {
@ -97,4 +95,8 @@ export default {
.nav-tabs .nav-item.show .nav-link { .nav-tabs .nav-item.show .nav-link {
background-color: #f8f9fe38; background-color: #f8f9fe38;
} }
.gdt-transaction-list-item {
outline: none !important;
}
</style> </style>

View File

@ -5,10 +5,10 @@
<div class="header-body text-center mb-7"> <div class="header-body text-center mb-7">
<b-row class="justify-content-center"> <b-row class="justify-content-center">
<b-col xl="5" lg="6" md="8" class="px-2"> <b-col xl="5" lg="6" md="8" class="px-2">
<h1>{{ $t('checkEmail.title') }}</h1> <h1>{{ $t('site.checkEmail.title') }}</h1>
<div class="pb-4" v-if="!pending"> <div class="pb-4" v-if="!pending">
<span v-if="!authenticated"> <span v-if="!authenticated">
{{ $t('checkEmail.errorText') }} {{ $t('site.checkEmail.errorText') }}
</span> </span>
</div> </div>
</b-col> </b-col>

View File

@ -205,12 +205,6 @@ export default {
}, },
}) })
.then(() => { .then(() => {
this.form.email = ''
this.form.firstname = ''
this.form.lastname = ''
this.form.password.password = ''
this.form.password.passwordRepeat = ''
this.language = ''
this.$router.push('/thx/register') this.$router.push('/thx/register')
}) })
.catch((error) => { .catch((error) => {
@ -226,7 +220,6 @@ export default {
this.form.lastname = '' this.form.lastname = ''
this.form.password.password = '' this.form.password.password = ''
this.form.password.passwordRepeat = '' this.form.password.passwordRepeat = ''
this.language = ''
}, },
}, },
computed: { computed: {

View File

@ -0,0 +1,135 @@
import { mount } from '@vue/test-utils'
import SendOverview from './SendOverview'
const sendMock = jest.fn()
sendMock.mockResolvedValue('success')
const localVue = global.localVue
// window.scrollTo = jest.fn()
describe('SendOverview', () => {
let wrapper
const propsData = {
balance: 123.45,
transactionCount: 1,
}
const mocks = {
$t: jest.fn((t) => t),
$n: jest.fn((n) => String(n)),
$store: {
state: {
email: 'sender@example.org',
},
},
$apollo: {
mutate: sendMock,
},
}
const Wrapper = () => {
return mount(SendOverview, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('has a status GDD line gdd-status-gdd', () => {
expect(wrapper.find('div.gdd-status-gdd').exists()).toBeTruthy()
})
it('has a send field', () => {
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
})
// it('has a transactions table', () => {
// expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
// })
describe('transaction form', () => {
it('steps forward in the dialog', async () => {
await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', {
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
})
expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy()
})
})
describe('confirm transaction', () => {
beforeEach(() => {
wrapper.setData({
currentTransactionStep: 1,
transactionData: {
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
},
})
})
it('resets the transaction process when on-reset is emitted', async () => {
await wrapper.findComponent({ name: 'TransactionConfirmation' }).vm.$emit('on-reset')
expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBeTruthy()
expect(wrapper.vm.transactionData).toEqual({
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
})
})
describe('transaction is confirmed and server response is success', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper
.findComponent({ name: 'TransactionConfirmation' })
.vm.$emit('send-transaction')
})
it('calls the API when send-transaction is emitted', async () => {
expect(sendMock).toBeCalledWith(
expect.objectContaining({
variables: {
email: 'user@example.org',
amount: 23.45,
memo: 'Make the best of it!',
},
}),
)
})
it('emits update-balance', () => {
expect(wrapper.emitted('update-balance')).toBeTruthy()
expect(wrapper.emitted('update-balance')).toEqual([[23.45]])
})
it('shows the succes page', () => {
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_success')
})
})
describe('transaction is confirmed and server response is error', () => {
beforeEach(async () => {
jest.clearAllMocks()
sendMock.mockRejectedValue({ message: 'receiver not found' })
await wrapper
.findComponent({ name: 'TransactionConfirmation' })
.vm.$emit('send-transaction')
})
it('shows the error page', () => {
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error')
})
it('shows recipient not found', () => {
expect(wrapper.text()).toContain('transaction.receiverNotFound')
})
})
})
})
})

View File

@ -0,0 +1,122 @@
<template>
<div>
<b-container fluid>
<b-row>
<b-col class="bg-gray text-white text-center p-3">
<status
class="gdd-status-gdd"
v-if="showContext"
:pending="pending"
:balance="balance"
status-text="GDD"
/>
</b-col>
</b-row>
<br />
<gdd-send :currentTransactionStep="currentTransactionStep">
<template #transaction-form>
<transaction-form :balance="balance" @set-transaction="setTransaction"></transaction-form>
</template>
<template #transaction-confirmation>
<transaction-confirmation
:email="transactionData.email"
:amount="transactionData.amount"
:memo="transactionData.memo"
:loading="loading"
@send-transaction="sendTransaction"
@on-reset="onReset"
></transaction-confirmation>
</template>
<template #transaction-result>
<transaction-result
:error="error"
:errorResult="errorResult"
@on-reset="onReset"
></transaction-result>
</template>
</gdd-send>
<hr />
</b-container>
</div>
</template>
<script>
import Status from '../../components/Status.vue'
import GddSend from './SendOverview/GddSend.vue'
import TransactionForm from './SendOverview/GddSend/TransactionForm.vue'
import TransactionConfirmation from './SendOverview/GddSend/TransactionConfirmation.vue'
import TransactionResult from './SendOverview/GddSend/TransactionResult.vue'
import { sendCoins } from '../../graphql/mutations.js'
const EMPTY_TRANSACTION_DATA = {
email: '',
amount: 0,
memo: '',
}
export default {
name: 'SendOverview',
components: {
Status,
GddSend,
TransactionForm,
TransactionConfirmation,
TransactionResult,
},
data() {
return {
transactionData: { ...EMPTY_TRANSACTION_DATA },
error: false,
errorResult: '',
currentTransactionStep: 0,
loading: false,
}
},
props: {
balance: { type: Number, default: 0 },
GdtBalance: { type: Number, default: 0 },
transactions: {
default: () => [],
},
pending: {
type: Boolean,
default: true,
},
},
computed: {
showContext() {
return this.currentTransactionStep === 0
},
},
methods: {
setTransaction(data) {
this.transactionData = { ...data }
this.currentTransactionStep = 1
},
async sendTransaction() {
this.loading = true
this.$apollo
.mutate({
mutation: sendCoins,
variables: this.transactionData,
})
.then(() => {
this.error = false
this.$emit('update-balance', this.transactionData.amount)
})
.catch((err) => {
this.errorResult = err.message
this.error = true
})
this.currentTransactionStep = 2
this.loading = false
},
onReset() {
this.currentTransactionStep = 0
},
},
}
</script>

View File

@ -62,4 +62,8 @@ export default {
background-color: aquamarine; background-color: aquamarine;
font-size: larger; font-size: larger;
} }
.nav-tabs > li > a {
outline: none !important;
}
</style> </style>

View File

@ -1735,13 +1735,6 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/chart.js@^2.7.55":
version "2.9.30"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.30.tgz#34b99897f4f5ef0f74c8fe4ced70ac52b4d752dd"
integrity sha512-EgjxUUZFvf6ls3kW2CwyrnSJhgyKxgwrlp/W5G9wqyPEO9iFatO63zAA7L24YqgMxiDjQ+tG7ODU+2yWH91lPg==
dependencies:
moment "^2.10.2"
"@types/d3@3.5.38": "@types/d3@3.5.38":
version "3.5.38" version "3.5.38"
resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.38.tgz#76f8f2e9159ae562965b2fa0e6fbee1aa643a1bc" resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.38.tgz#76f8f2e9159ae562965b2fa0e6fbee1aa643a1bc"
@ -3823,29 +3816,6 @@ chardet@^0.4.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
chart.js@^2.9.3:
version "2.9.4"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
dependencies:
color-name "^1.0.0"
chartjs-color@^2.1.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
dependencies:
chartjs-color-string "^0.6.0"
color-convert "^1.9.3"
check-types@^8.0.3: check-types@^8.0.3:
version "8.0.3" version "8.0.3"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
@ -4073,7 +4043,7 @@ collection-visit@^1.0.0:
map-visit "^1.0.0" map-visit "^1.0.0"
object-visit "^1.0.0" object-visit "^1.0.0"
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3: color-convert@^1.9.0, color-convert@^1.9.1:
version "1.9.3" version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@ -9620,7 +9590,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.10.2, moment@^2.19.2: moment@^2.19.2:
version "2.29.1" version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
@ -13469,13 +13439,6 @@ vue-bootstrap-typeahead@^0.2.6:
resize-observer-polyfill "^1.5.0" resize-observer-polyfill "^1.5.0"
vue "^2.5.17" vue "^2.5.17"
vue-chartjs@^3.5.0:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
integrity sha512-foocQbJ7FtveICxb4EV5QuVpo6d8CmZFmAopBppDIGKY+esJV8IJgwmEW0RexQhxqXaL/E1xNURsgFFYyKzS/g==
dependencies:
"@types/chart.js" "^2.7.55"
vue-cli-plugin-i18n@^1.0.1: vue-cli-plugin-i18n@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/vue-cli-plugin-i18n/-/vue-cli-plugin-i18n-1.0.1.tgz#5a3077de5d62c9b4068e486db1fc97fce9fa0072" resolved "https://registry.yarnpkg.com/vue-cli-plugin-i18n/-/vue-cli-plugin-i18n-1.0.1.tgz#5a3077de5d62c9b4068e486db1fc97fce9fa0072"

View File

@ -5,6 +5,5 @@ src/cpsp/*.h
src/cpsp/*.cpp src/cpsp/*.cpp
src/cpp/proto/ src/cpp/proto/
build*/ build*/
/skeema/gradido_login/insert/crypto_key.sql
src/LOCALE/messages.pot src/LOCALE/messages.pot

View File

@ -56,8 +56,7 @@ To update messages.pot run
This will be also called by ./scripts/build_debug.sh This will be also called by ./scripts/build_debug.sh
## database ## database
Login-Server needs a db to run, it is tested with mariadb Login-Server needs a db to run, it is tested with mariadb.
table definitions are found in folder ./skeema/gradido_login
Currently at least one group must be present in table groups. Currently at least one group must be present in table groups.
For example: For example:
```sql ```sql

View File

@ -1,9 +0,0 @@
CREATE TABLE `app_access_tokens` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`access_code` bigint unsigned NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `access_code` (`access_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,15 +0,0 @@
CREATE TABLE `elopage_buys` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`elopage_user_id` int DEFAULT NULL,
`affiliate_program_id` int NOT NULL,
`publisher_id` int NOT NULL,
`order_id` int NOT NULL,
`product_id` int NOT NULL,
`product_price` int NOT NULL,
`payer_email` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`publisher_email` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`payed` tinyint NOT NULL,
`success_date` datetime NOT NULL,
`event` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,11 +0,0 @@
CREATE TABLE `email_opt_in` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`verification_code` bigint unsigned NOT NULL,
`email_opt_in_type_id` int NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`resend_count` int DEFAULT '0',
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `verification_code` (`verification_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,6 +0,0 @@
CREATE TABLE `email_opt_in_types` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,11 +0,0 @@
CREATE TABLE `groups` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`alias` varchar(190) NOT NULL,
`name` varchar(255) NOT NULL,
`url` varchar(255) NOT NULL,
`host` varchar(255) DEFAULT "/",
`home` varchar(255) DEFAULT "/",
`description` text,
PRIMARY KEY (`id`),
UNIQUE KEY `alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,13 +0,0 @@
CREATE TABLE `pending_tasks` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int UNSIGNED DEFAULT 0,
`request` varbinary(2048) NOT NULL,
`created` datetime NOT NULL,
`finished` datetime DEFAULT '2000-01-01 000000',
`result_json` text DEFAULT NULL,
`param_json` text DEFAULT NULL,
`task_type_id` int UNSIGNED NOT NULL,
`child_pending_task_id` int UNSIGNED DEFAULT 0,
`parent_pending_task_id` int UNSIGNED DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,7 +0,0 @@
CREATE TABLE `roles` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` varchar(255) NOT NULL,
`flags` bigint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,7 +0,0 @@
CREATE TABLE `user_backups` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`passphrase` text NOT NULL,
`mnemonic_type` int DEFAULT '-1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,6 +0,0 @@
CREATE TABLE `user_roles` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,21 +0,0 @@
CREATE TABLE `users` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(191) NOT NULL,
`first_name` varchar(150) NOT NULL,
`last_name` varchar(255) DEFAULT '',
`username` varchar(255) DEFAULT '',
`description` text DEFAULT '',
`password` bigint unsigned DEFAULT '0',
`pubkey` binary(32) DEFAULT NULL,
`privkey` binary(80) DEFAULT NULL,
`email_hash` binary(32) DEFAULT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`email_checked` tinyint NOT NULL DEFAULT '0',
`passphrase_shown` tinyint NOT NULL DEFAULT '0',
`language` varchar(4) NOT NULL DEFAULT 'de',
`disabled` tinyint DEFAULT '0',
`group_id` int unsigned DEFAULT 0,
`publisher_id` int DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -58,7 +58,7 @@ bool EmailManager::init(const Poco::Util::LayeredConfiguration& cfg)
void EmailManager::addEmail(model::Email* email) { void EmailManager::addEmail(model::Email* email) {
if (mDisableEmail) { if (mDisableEmail) {
std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S"); std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
std::string log_message = dateTimeString + " Email should be sended to: "; std::string log_message = dateTimeString + " Email should have been sent to: ";
auto email_user = email->getUser(); auto email_user = email->getUser();
Poco::AutoPtr<model::table::User> email_model; Poco::AutoPtr<model::table::User> email_model;
if (email_user) { if (email_user) {

View File

@ -148,7 +148,7 @@ Session* SessionManager::getNewSession(int* handle)
mWorkingMutex.tryLock(500); mWorkingMutex.tryLock(500);
} }
catch (Poco::TimeoutException &ex) { catch (Poco::TimeoutException &ex) {
printf("[%s] exception timout mutex: %s\n", functionName, ex.displayText().data()); printf("[%s] exception timeout mutex: %s\n", functionName, ex.displayText().data());
return nullptr; return nullptr;
} }
//mWorkingMutex.lock(); //mWorkingMutex.lock();

View File

@ -439,6 +439,7 @@ namespace controller {
return 0; return 0;
auto cm = ConnectionManager::getInstance(); auto cm = ConnectionManager::getInstance();
auto em = ErrorManager::getInstance(); auto em = ErrorManager::getInstance();
auto db = new model::table::User();
static const char* function_name = "User::checkIfVerificationEmailsShouldBeResend"; static const char* function_name = "User::checkIfVerificationEmailsShouldBeResend";
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
@ -446,8 +447,9 @@ namespace controller {
std::vector<Poco::Tuple<int,Poco::DateTime>> results; std::vector<Poco::Tuple<int,Poco::DateTime>> results;
int email_checked = 0; int email_checked = 0;
int resend_count = 1; int resend_count = 1;
select << "select u.id, v.created from users as u " std::string table_name_email_opt_in = "login_email_opt_in";
<< "LEFT JOIN email_opt_in as v ON(u.id = v.user_id) " select << "select u.id, v.created from " << db->getTableName() << " as u "
<< "LEFT JOIN " << table_name_email_opt_in << " as v ON(u.id = v.user_id) "
<< "where u.email_checked = ? " << "where u.email_checked = ? "
<< "AND v.resend_count <= ? " << "AND v.resend_count <= ? "
<< "ORDER BY u.id, v.created " , << "ORDER BY u.id, v.created " ,
@ -519,14 +521,15 @@ namespace controller {
{ {
auto cm = ConnectionManager::getInstance(); auto cm = ConnectionManager::getInstance();
auto em = ErrorManager::getInstance(); auto em = ErrorManager::getInstance();
auto db = new model::table::User();
static const char* function_name = "User::addMissingEmailHashes"; static const char* function_name = "User::addMissingEmailHashes";
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement select(session); Poco::Data::Statement select(session);
std::vector<Poco::Tuple<int, std::string>> results; std::vector<Poco::Tuple<int, std::string>> results;
select << "select id, email from users " select << "select id, email from " << db->getTableName()
<< "where email_hash IS NULL " << " where email_hash IS NULL "
, Poco::Data::Keywords::into(results) , Poco::Data::Keywords::into(results)
; ;
int result_count = 0; int result_count = 0;
@ -556,7 +559,7 @@ namespace controller {
// update db // update db
// reuse connection, I hope it's working // reuse connection, I hope it's working
Poco::Data::Statement update(session); Poco::Data::Statement update(session);
update << "UPDATE users set email_hash = ? where id = ?" update << "UPDATE " << db->getTableName() << " set email_hash = ? where id = ?"
, Poco::Data::Keywords::use(updates); , Poco::Data::Keywords::use(updates);
int updated_count = 0; int updated_count = 0;
try { try {

View File

@ -19,7 +19,7 @@ namespace model {
// generic db operations // generic db operations
const char* getTableName() const { return "app_access_tokens"; } const char* getTableName() const { return "login_app_access_tokens"; }
std::string toString(); std::string toString();
inline Poco::UInt64 getCode() const { return mAccessCode; } inline Poco::UInt64 getCode() const { return mAccessCode; }

View File

@ -34,7 +34,7 @@ namespace model {
ElopageBuy(); ElopageBuy();
// generic db operations // generic db operations
const char* getTableName() const { return "elopage_buys"; } const char* getTableName() const { return "login_elopage_buys"; }
std::string toString(); std::string toString();

View File

@ -29,7 +29,7 @@ namespace model {
// generic db operations // generic db operations
const char* getTableName() const { return "email_opt_in"; } const char* getTableName() const { return "login_email_opt_in"; }
std::string toString(); std::string toString();
inline Poco::UInt64 getCode() const { return mEmailVerificationCode; } inline Poco::UInt64 getCode() const { return mEmailVerificationCode; }

View File

@ -17,7 +17,7 @@ namespace model {
Group(GroupTuple userTuple); Group(GroupTuple userTuple);
// generic db operations // generic db operations
const char* getTableName() const { return "groups"; } const char* getTableName() const { return "login_groups"; }
std::string toString(); std::string toString();
inline const std::string& getAlias() const { return mAlias; } inline const std::string& getAlias() const { return mAlias; }

View File

@ -30,7 +30,7 @@ namespace model {
// generic db operations // generic db operations
const char* getTableName() const { return "pending_tasks"; } const char* getTableName() const { return "login_pending_tasks"; }
std::string toString(); std::string toString();
//! \brief update table row with current request //! \brief update table row with current request

View File

@ -1,60 +0,0 @@
#ifndef GRADIDO_LOGIN_SERVER_MODEL_TABLE_ROLES_INCLUDE
#define GRADIDO_LOGIN_SERVER_MODEL_TABLE_ROLES_INCLUDE
#include "ModelBase.h"
#include "Poco/Types.h"
#include "Poco/Tuple.h"
namespace model {
namespace table {
enum RoleType {
ROLE_ADMIN = 1
};
class Roles : public ModelBase
{
};
/*
typedef Poco::Tuple<int, int, Poco::UInt64, int> EmailOptInTuple;
class EmailOptIn : public ModelBase
{
public:
EmailOptIn(const Poco::UInt64& code, int user_id, EmailOptInType type);
EmailOptIn(const Poco::UInt64& code, EmailOptInType type);
EmailOptIn(const EmailOptInTuple& tuple);
EmailOptIn();
~EmailOptIn();
// generic db operations
const char* getTableName() { return "email_opt_in"; }
std::string toString();
inline Poco::UInt64 getCode() const { return mEmailVerificationCode; }
inline int getUserId() const { return mUserId; }
inline EmailOptInType getType() const { return static_cast<EmailOptInType>(mType); }
inline void setCode(Poco::UInt64 code) { mEmailVerificationCode = code; }
inline void setUserId(int user_Id) { mUserId = user_Id; }
static const char* typeToString(EmailOptInType type);
protected:
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadIdFromDB(Poco::Data::Session session);
Poco::Data::Statement _loadMultipleFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
Poco::Data::Statement _insertIntoDB(Poco::Data::Session session);
int mUserId;
// data type must be a multiple of 4
Poco::UInt64 mEmailVerificationCode;
int mType;
};
*/
}
}
#endif //GRADIDO_LOGIN_SERVER_MODEL_TABLE_ROLES_INCLUDE

View File

@ -83,11 +83,11 @@ namespace model {
if (mPasswordHashed) { if (mPasswordHashed) {
insert << "INSERT INTO users (email, first_name, last_name, username, description, password, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?,?);", insert << "INSERT INTO " << getTableName() << " (email, first_name, last_name, username, description, password, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), bind(mPasswordHashed), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId); use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), bind(mPasswordHashed), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId);
} }
else { else {
insert << "INSERT INTO users (email, first_name, last_name, username, description, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?);", insert << "INSERT INTO " << getTableName() << " (email, first_name, last_name, username, description, email_hash, language, group_id, publisher_id) VALUES(?,?,?,?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId); use(mEmail), use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mEmailHash), use(mLanguageKey), use(mGroupId), use(mPublisherId);
} }
@ -103,10 +103,13 @@ namespace model {
} }
Poco::Data::Statement select(session); Poco::Data::Statement select(session);
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, username, description, password, pubkey, privkey, email_hash, created, email_checked, language, disabled, group_id, publisher_id, user_roles.role_id " std::string table_name_user_roles = "login_user_roles";
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, username, description, password, pubkey, privkey, email_hash, created, email_checked, language, disabled, group_id, publisher_id, " << table_name_user_roles << ".role_id "
<< " FROM " << getTableName() << " FROM " << getTableName()
<< " LEFT JOIN user_roles ON " << getTableName() << ".id = user_roles.user_id " << " LEFT JOIN " << table_name_user_roles
<< " WHERE " << _fieldName << " = ?" , << " ON " << getTableName() << ".id = " << table_name_user_roles << ".user_id "
<< " WHERE " << _fieldName << " = ?; " ,
into(mID), into(mEmail), into(mFirstName), into(mLastName), into(mUsername), into(mDescription), into(mPasswordHashed), into(mID), into(mEmail), into(mFirstName), into(mLastName), into(mUsername), into(mDescription), into(mPasswordHashed),
into(mPublicKey), into(mPrivateKey), into(mEmailHash), into(mCreated), into(mEmailChecked), into(mPublicKey), into(mPrivateKey), into(mEmailHash), into(mCreated), into(mEmailChecked),
into(mLanguageKey), into(mDisabled), into(mGroupId), into(mPublisherId), into(mRole); into(mLanguageKey), into(mDisabled), into(mGroupId), into(mPublisherId), into(mRole);
@ -194,7 +197,7 @@ namespace model {
Poco::Data::Statement update(session); Poco::Data::Statement update(session);
update << "UPDATE users SET password = ?, privkey = ? where id = ?;", update << "UPDATE " << getTableName() << " SET password = ?, privkey = ? where id = ?;",
bind(mPasswordHashed), use(mPrivateKey), use(mID); bind(mPasswordHashed), use(mPrivateKey), use(mID);
@ -221,7 +224,7 @@ namespace model {
Poco::Data::Statement update(session); Poco::Data::Statement update(session);
update << "UPDATE users SET pubkey = ?, privkey = ? where id = ?;", update << "UPDATE " << getTableName() << " SET pubkey = ?, privkey = ? where id = ?;",
use(mPublicKey), use(mPrivateKey), use(mID); use(mPublicKey), use(mPrivateKey), use(mID);
@ -246,7 +249,7 @@ namespace model {
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement update(session); Poco::Data::Statement update(session);
update << "UPDATE users SET first_name = ?, last_name = ?, username = ?, description = ?, disabled = ?, language = ?, publisher_id = ? where id = ?;", update << "UPDATE " << getTableName() << " SET first_name = ?, last_name = ?, username = ?, description = ?, disabled = ?, language = ?, publisher_id = ? where id = ?;",
use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mDisabled), use(mLanguageKey), use(mPublisherId), use(mID); use(mFirstName), use(mLastName), use(mUsername), use(mDescription), use(mDisabled), use(mLanguageKey), use(mPublisherId), use(mID);
try { try {

View File

@ -54,7 +54,7 @@ namespace model {
// generic db operations // generic db operations
const char* getTableName() const { return "users"; } const char* getTableName() const { return "login_users"; }
std::string toString(); std::string toString();
std::string toHTMLString(); std::string toHTMLString();

View File

@ -17,7 +17,7 @@ namespace model {
UserBackup(); UserBackup();
// generic db operations // generic db operations
const char* getTableName() const { return "user_backups"; } const char* getTableName() const { return "login_user_backups"; }
std::string toString(); std::string toString();
inline int getUserId() const { return mUserId; } inline int getUserId() const { return mUserId; }

View File

@ -4,7 +4,6 @@
#include "ModelBase.h" #include "ModelBase.h"
#include "Poco/Types.h" #include "Poco/Types.h"
#include "Poco/Tuple.h" #include "Poco/Tuple.h"
//#include "Roles.h"
namespace model { namespace model {
namespace table { namespace table {
@ -25,7 +24,7 @@ namespace model {
UserRole(); UserRole();
// generic db operations // generic db operations
const char* getTableName() const { return "user_roles"; } const char* getTableName() const { return "login_user_roles"; }
std::string toString(); std::string toString();
inline int getUserId() const { return mUserId; } inline int getUserId() const { return mUserId; }

View File

@ -176,9 +176,9 @@ int load(int argc, char* argv[]) {
// clean up and fill db // clean up and fill db
std::string tables[] = { std::string tables[] = {
"groups", "login_groups",
"users", "login_users",
"user_roles" "login_user_roles"
}; };
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (runMysql("TRUNCATE " + tables[i])) { if (runMysql("TRUNCATE " + tables[i])) {
@ -191,7 +191,7 @@ int load(int argc, char* argv[]) {
std::stringstream ss; std::stringstream ss;
// password = TestP4ssword&H // password = TestP4ssword&H
ss << "INSERT INTO `users` (`id`, `email`, `first_name`, `last_name`, `username`, `password`, `pubkey`, `privkey`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`) VALUES " ss << "INSERT INTO `login_users` (`id`, `email`, `first_name`, `last_name`, `username`, `password`, `pubkey`, `privkey`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`) VALUES "
<< "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 18242007140018938940, 0x69f2fefd6fa6947a370b9f8d3147f6617cf67416517ce25cb2d63901c666933c, 0x567f3e623a1899d1f8d69190c5799433c134ce0137c0c38cc0347874586d6234a19f2a0b484e6cc1863502e580ae6c17db1131f29a35eba45a46be29c7ee592940a3bd3ad519075fdeed6e368f0eb818, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), "; << "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 18242007140018938940, 0x69f2fefd6fa6947a370b9f8d3147f6617cf67416517ce25cb2d63901c666933c, 0x567f3e623a1899d1f8d69190c5799433c134ce0137c0c38cc0347874586d6234a19f2a0b484e6cc1863502e580ae6c17db1131f29a35eba45a46be29c7ee592940a3bd3ad519075fdeed6e368f0eb818, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), ";
// if this isn't the same, some tests will fail, so we update the test data here. // if this isn't the same, some tests will fail, so we update the test data here.
@ -211,7 +211,7 @@ int load(int argc, char* argv[]) {
} }
ss.str(std::string()); ss.str(std::string());
ss << "INSERT INTO `user_roles` (`id`, `user_id`, `role_id`) VALUES" ss << "INSERT INTO `login_user_roles` (`id`, `user_id`, `role_id`) VALUES"
<< "(1, 3, 1);"; << "(1, 3, 1);";
if (runMysql(ss.str())) { if (runMysql(ss.str())) {
@ -219,7 +219,7 @@ int load(int argc, char* argv[]) {
} }
ss.str(std::string()); ss.str(std::string());
ss << "INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `description`) VALUES" ss << "INSERT INTO `login_groups` (`id`, `alias`, `name`, `url`, `description`) VALUES"
<< "(1, 'gdd1', 'Gradido1', 'gdd1.gradido.com', 'Der erste offizielle Gradido Server (zum Testen)'), " << "(1, 'gdd1', 'Gradido1', 'gdd1.gradido.com', 'Der erste offizielle Gradido Server (zum Testen)'), "
<< "(2, 'gdd_test', 'Gradido Test', 'gdd1.gradido.com', 'Testgroup (zum Testen)'); "; << "(2, 'gdd_test', 'Gradido Test', 'gdd1.gradido.com', 'Testgroup (zum Testen)'); ";
if (runMysql(ss.str())) { if (runMysql(ss.str())) {

View File

@ -1,5 +0,0 @@
[production]
flavor=mariadb:10.5
host=127.0.0.1
port=3306
user=root

View File

@ -1,3 +0,0 @@
default-character-set=utf8mb4
default-collation=utf8mb4_unicode_ci
schema=gradido_login

View File

@ -3,29 +3,7 @@
######################################################################################################### #########################################################################################################
FROM mariadb/server:10.5 as mariadb_server FROM mariadb/server:10.5 as mariadb_server
ENV DOCKER_WORKDIR="/docker-entrypoint-initdb.d" # ENV DOCKER_WORKDIR="/docker-entrypoint-initdb.d"
RUN mkdir -p ${DOCKER_WORKDIR} # RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR} # WORKDIR ${DOCKER_WORKDIR}
# create databases
COPY ./mariadb/setup_dbs.sql a1_setup_dbs.sql
# login server db
COPY ./login_server/skeema/ .
RUN cd ./gradido_login/ && for f in *.sql; do cp -- "$f" "../b1_$f"; sed -i '1i use gradido_login;' "../b1_$f"; done
COPY ./configs/login_server/setup_db_tables ./gradido_login/insert
RUN cd ./gradido_login/insert && for f in *.sql; do cp -- "$f" "../../c1_$f"; sed -i '1i use gradido_login;' "../../c1_$f"; done
#########################################################################################################
# mariadb server with test dbs
#########################################################################################################
FROM mariadb_server as mariadb_server_test
# create test databases
COPY ./mariadb/setup_test_dbs.sql a2_setup_dbs.sql
# login server test db
COPY ./login_server/skeema/ .
RUN cd ./gradido_login/ && for f in *.sql; do cp -- "$f" "../b2_$f"; sed -i '1i use gradido_login_test;' "../b2_$f"; done
COPY ./configs/login_server/setup_db_tables ./gradido_login/insert
RUN cd ./gradido_login/insert && for f in *.sql; do cp -- "$f" "../../c2_$f"; sed -i '1i use gradido_login_test;' "../../c2_$f"; done

View File

@ -1,7 +0,0 @@
create database gradido_login
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
create database IF NOT EXISTS _skeema_tmp
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
FLUSH PRIVILEGES;

View File

@ -1,55 +0,0 @@
#!/bin/bash
COLOR_GREEN="\033[0;32m"
COLOR_YELLOW="\e[33m"
COLOR_NONE="\033[0m"
LOGIN_DB_USER=gradido_login_live
LOGIN_DB_NAME=gradido_login_live
LOGIN_DB_PASSWD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
COMMUNITY_DB_USER=gradido_community_live
COMMUNITY_DB_NAME=gradido_community_live
COMMUNITY_DB_PASSWD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
# create table
mysql <<EOFMYSQL
create database $LOGIN_DB_NAME
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
create database $COMMUNITY_DB_NAME
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
create database IF NOT EXISTS _skeema_tmp
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE USER '$LOGIN_DB_USER'@'localhost' IDENTIFIED BY '$LOGIN_DB_PASSWD';
GRANT ALL PRIVILEGES ON $LOGIN_DB_NAME.* TO '$LOGIN_DB_USER'@'localhost';
GRANT ALL PRIVILEGES ON _skeema_tmp.* TO '$LOGIN_DB_USER'@'localhost';
CREATE USER '$COMMUNITY_DB_USER'@'localhost' IDENTIFIED BY '$COMMUNITY_DB_PASSWD';
GRANT ALL PRIVILEGES ON $COMMUNITY_DB_NAME.* TO '$COMMUNITY_DB_USER'@'localhost';
GRANT ALL PRIVILEGES ON _skeema_tmp.* TO '$COMMUNITY_DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOFMYSQL
# populate db of login-server
cd ../login_server/skeema
sudo cat << EOF > .skeema
[production]
flavor=mariadb:10.3.25
host=127.0.0.1
port=3306
user=$LOGIN_DB_USER
EOF
cd gradido_login
sudo cat << EOF > .skeema
default-character-set=utf8mb4
default-collation=utf8mb4_unicode_ci
schema=$LOGIN_DB_NAME
EOF
source $HOME/.gvm/scripts/gvm
gvm use go1.14.4
skeema push -p$LOGIN_DB_PASSWD
echo -e "${COLOR_YELLOW}Login-Server db password: $LOGIN_DB_PASSWD${COLOR_NONE}"

View File

@ -1,10 +0,0 @@
create database gradido_login_test
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
create database gradido_community_test
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
create database IF NOT EXISTS _skeema_tmp
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
FLUSH PRIVILEGES;

View File

@ -1,5 +0,0 @@
[production]
flavor=mariadb:10.5
host=mariadb
port=3306
user=root

View File

@ -1,39 +0,0 @@
#########################################################################################################
# Build skeema
#########################################################################################################
FROM golang:1.17.1 as skeema_build
RUN go get -d -v github.com/skeema/skeema
WORKDIR /go/src/github.com/skeema/skeema
RUN go install github.com/skeema/skeema@v1.5.3
#########################################################################################################
# Run skeema for dev (dynamic)
#########################################################################################################
FROM skeema_build as skeema_dev_run
ENV DOCKER_WORKDIR="/skeema"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
COPY ./skeema/.skeema .
COPY ./mariadb/.skeema.login .
CMD cp .skeema.login ./gradido_login/.skeema && skeema push --allow-unsafe && rm ./gradido_login/.skeema
#########################################################################################################
# Run skeema
#########################################################################################################
FROM skeema_build as skeema_run
ENV DOCKER_WORKDIR="/skeema"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
COPY ./skeema/.skeema .
COPY ./login_server/skeema/ .
COPY ./mariadb/.skeema.login ./gradido_login/.skeema
CMD skeema push --allow-unsafe