Merge branch 'master' into community_ipv6_localhost

This commit is contained in:
einhorn_b 2021-05-20 16:35:26 +02:00
commit ada873f80e
164 changed files with 3983 additions and 1832 deletions

View File

@ -50,7 +50,7 @@ jobs:
##########################################################################
- name: login server | Build `test` image
run: |
docker build --target login_server_debug -t "gradido/login_server:test" -f ./login_server/Dockerfile.debug login_server/
docker build --target login_server -t "gradido/login_server:test" -f ./login_server/Dockerfile login_server/
docker save "gradido/login_server:test" > /tmp/login_server.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
@ -212,7 +212,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 15
min_coverage: 19
token: ${{ github.token }}
#test:

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
*.log
/node_modules/*
.vscode
messages.pot
.skeema
nbproject

9
.gitmodules vendored
View File

@ -27,5 +27,10 @@
[submodule "community_server/src/protobuf"]
path = community_server/src/protobuf
url = git@github.com:gradido/gradido_protocol.git
url = https://github.com/gradido/gradido_protocol.git
[submodule "login_server/dependencies/libsodium"]
path = login_server/dependencies/libsodium
url = https://github.com/jedisct1/libsodium.git
[submodule "login_server/src/proto"]
path = login_server/src/proto
url = https://github.com/gradido/gradido_protocol.git

View File

@ -4,8 +4,138 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.0.1](https://github.com/gradido/gradido/compare/1.0.0...1.0.1)
- add try catch blocks to prevent login-server from crashing [`22ff220`](https://github.com/gradido/gradido/commit/22ff22072956f8b843037c75c5b16b7ff5d6a2a3)
- fix [`14a4243`](https://github.com/gradido/gradido/commit/14a424347817b1fe6912a113bffd70e55d688112)
### [1.0.0](https://github.com/gradido/gradido/compare/0.9.4...1.0.0)
> 14 May 2021
- Login build alpine [`#423`](https://github.com/gradido/gradido/pull/423)
- Release fix [`#428`](https://github.com/gradido/gradido/pull/428)
- Send button only click [`#427`](https://github.com/gradido/gradido/pull/427)
- use new function for balance overview in old frontend, update balance… [`#422`](https://github.com/gradido/gradido/pull/422)
- thx.vue coloured background removed. design adapted to app [`#426`](https://github.com/gradido/gradido/pull/426)
- translation password rules in Register.vue and ResetPasswort.vue fixed [`#424`](https://github.com/gradido/gradido/pull/424)
- 06x style [`#419`](https://github.com/gradido/gradido/pull/419)
- feat: Dash is Shown When Balance is Loading [`#396`](https://github.com/gradido/gradido/pull/396)
- refactor: Split GddSend into Components [`#415`](https://github.com/gradido/gradido/pull/415)
- fix: Set Maximum Transactions Loaded to 1000 [`#395`](https://github.com/gradido/gradido/pull/395)
- BUG : preRELEASE : thx transaction false [`#394`](https://github.com/gradido/gradido/pull/394)
- feat: Validation of Input Fields of GddSend [`#386`](https://github.com/gradido/gradido/pull/386)
- add validation, check if user has tried to send themself gradidos [`#277`](https://github.com/gradido/gradido/pull/277)
- fix link generation [`#347`](https://github.com/gradido/gradido/pull/347)
- Community start decay [`#387`](https://github.com/gradido/gradido/pull/387)
- transaction error message, translations [`#388`](https://github.com/gradido/gradido/pull/388)
- docu: locales schöpfen-mysql befehl [`#392`](https://github.com/gradido/gradido/pull/392)
- Fix: QR Scanner Hide [`#380`](https://github.com/gradido/gradido/pull/380)
- change design- dark-blue of text-ligth [`#381`](https://github.com/gradido/gradido/pull/381)
- spelling & translation [`#385`](https://github.com/gradido/gradido/pull/385)
- 350 6 login [`#383`](https://github.com/gradido/gradido/pull/383)
- fix: breakout error [`#375`](https://github.com/gradido/gradido/pull/375)
- 233-Support-Button fix, and col error fix [`#343`](https://github.com/gradido/gradido/pull/343)
- fix: membersarea open in a new tap [`#370`](https://github.com/gradido/gradido/pull/370)
- 00x table footer componente [`#345`](https://github.com/gradido/gradido/pull/345)
- clickevent from icon to button [`#364`](https://github.com/gradido/gradido/pull/364)
- 404 link to login page fix [`#366`](https://github.com/gradido/gradido/pull/366)
- bug: session_id vs sessionId [`#365`](https://github.com/gradido/gradido/pull/365)
- eslint rule standard [`#344`](https://github.com/gradido/gradido/pull/344)
- don't show decays smaller than 0,01 GDD [`#329`](https://github.com/gradido/gradido/pull/329)
- 338 - mt-5 margin top 5 eingebunden [`#341`](https://github.com/gradido/gradido/pull/341)
- feat: Update Transactions When GddTable Is Rendered [`#339`](https://github.com/gradido/gradido/pull/339)
- my profile auskommentiert [`#337`](https://github.com/gradido/gradido/pull/337)
- give balance as float value in GDD (not longer GDD cent) [`#273`](https://github.com/gradido/gradido/pull/273)
- fix error with password reset email [`#276`](https://github.com/gradido/gradido/pull/276)
- transaction.show_all in en.json hinzugefügt [`#335`](https://github.com/gradido/gradido/pull/335)
- refactor: Add Paramerts to ListTransactions API Call [`#332`](https://github.com/gradido/gradido/pull/332)
- css abstände vom rand in mobilen style [`#272`](https://github.com/gradido/gradido/pull/272)
- error messages are displayed [`#304`](https://github.com/gradido/gradido/pull/304)
- cancel to reset, form rest event added [`#334`](https://github.com/gradido/gradido/pull/334)
- remove from error messages because its no longer exist [`#330`](https://github.com/gradido/gradido/pull/330)
- xx deleted [`#333`](https://github.com/gradido/gradido/pull/333)
- fix bug on Login group add host [`#274`](https://github.com/gradido/gradido/pull/274)
- transactionslist load and icon [`#324`](https://github.com/gradido/gradido/pull/324)
- THX changed to Thank you [`#305`](https://github.com/gradido/gradido/pull/305)
- fix_bug_template [`#284`](https://github.com/gradido/gradido/pull/284)
- feat: Restructure Overview [`#271`](https://github.com/gradido/gradido/pull/271)
- fix: Dynamic Balance in User Profile [`#278`](https://github.com/gradido/gradido/pull/278)
- refactor: Test and Clean Up Store [`#270`](https://github.com/gradido/gradido/pull/270)
- remove links to not work [`#267`](https://github.com/gradido/gradido/pull/267)
- change order of session_id [`#269`](https://github.com/gradido/gradido/pull/269)
- bug: Dynamic Balance [`#260`](https://github.com/gradido/gradido/pull/260)
- Fix problems with transactions in docker environment [`#261`](https://github.com/gradido/gradido/pull/261)
- update skeema call to allow also for unsafe db updates [`#250`](https://github.com/gradido/gradido/pull/250)
- turn registration on and off [`#266`](https://github.com/gradido/gradido/pull/266)
- 243 vue loader component - varante 2 [`#257`](https://github.com/gradido/gradido/pull/257)
- fix_issue_templates [`#262`](https://github.com/gradido/gradido/pull/262)
- fix_nginx_docker [`#253`](https://github.com/gradido/gradido/pull/253)
- 13_coverage [`#255`](https://github.com/gradido/gradido/pull/255)
- add language as fields which will be returned with user-object [`#244`](https://github.com/gradido/gradido/pull/244)
- add short description for target date in creation transaction [`#252`](https://github.com/gradido/gradido/pull/252)
- fix error "Integrity constraint violation: 1052 Column 'id' in where … [`#251`](https://github.com/gradido/gradido/pull/251)
- Clicks on button [`#246`](https://github.com/gradido/gradido/pull/246)
- Change css [`#231`](https://github.com/gradido/gradido/pull/231)
- feat: Reset Password [`#212`](https://github.com/gradido/gradido/pull/212)
- fix bug #221 [`#228`](https://github.com/gradido/gradido/pull/228)
- fix empty reply by empty group_id [`#227`](https://github.com/gradido/gradido/pull/227)
- Bugfix 22 04 21 [`#226`](https://github.com/gradido/gradido/pull/226)
- Rollback use prebuild dependencies [`#225`](https://github.com/gradido/gradido/pull/225)
- Docu [`#199`](https://github.com/gradido/gradido/pull/199)
- update login-server build [`#222`](https://github.com/gradido/gradido/pull/222)
- Login add language [`#220`](https://github.com/gradido/gradido/pull/220)
- User Object on Json Login [`#219`](https://github.com/gradido/gradido/pull/219)
- refactor: Remove loginAPI Call from Store [`#215`](https://github.com/gradido/gradido/pull/215)
- fix: Remove Rules for Password on Login [`#214`](https://github.com/gradido/gradido/pull/214)
- Community 15 04 2021 [`#198`](https://github.com/gradido/gradido/pull/198)
- refactor: Remove Right Menu [`#204`](https://github.com/gradido/gradido/pull/204)
- feat: Raise Frontend Test Coverage to 12% [`#206`](https://github.com/gradido/gradido/pull/206)
- refactor: Remove Cookies [`#202`](https://github.com/gradido/gradido/pull/202)
- fix_docker_compose [`#205`](https://github.com/gradido/gradido/pull/205)
- 62 pwd reset [`#203`](https://github.com/gradido/gradido/pull/203)
- workflows_refactor [`#200`](https://github.com/gradido/gradido/pull/200)
- feat: Link to Whitepaper in Content-Footer [`#196`](https://github.com/gradido/gradido/pull/196)
- update code which produce error which last fix (._.); [`#197`](https://github.com/gradido/gradido/pull/197)
- refactor: Redirects and Misuse of Children in Router [`#194`](https://github.com/gradido/gradido/pull/194)
- fix: Gradido-Akademie in Footer Links to gradido.net [`#170`](https://github.com/gradido/gradido/pull/170)
- fix register [`#160`](https://github.com/gradido/gradido/pull/160)
- Login 14.04.21 [`#175`](https://github.com/gradido/gradido/pull/175)
- coverage_no_report [`#176`](https://github.com/gradido/gradido/pull/176)
- Community 14.04.21 [`#174`](https://github.com/gradido/gradido/pull/174)
- feat: Add Coverage Check To GitHub Workflow [`#158`](https://github.com/gradido/gradido/pull/158)
- Stage2 [`#49`](https://github.com/gradido/gradido/pull/49)
- feat: Unit Test for KontoOverview [`#169`](https://github.com/gradido/gradido/pull/169)
- fix: Remove Hashtag From Route [`#167`](https://github.com/gradido/gradido/pull/167)
- documentation for reset password [`#166`](https://github.com/gradido/gradido/pull/166)
- 90 show release version in frontend [`#153`](https://github.com/gradido/gradido/pull/153)
- docu frontend menu page structur [`#159`](https://github.com/gradido/gradido/pull/159)
- userstorry_button.txt example [`#157`](https://github.com/gradido/gradido/pull/157)
- fix getBalance API call [`#147`](https://github.com/gradido/gradido/pull/147)
- Locale clear list merge master [`#119`](https://github.com/gradido/gradido/pull/119)
- fix-125-link-to-community [`#152`](https://github.com/gradido/gradido/pull/152)
- feat: Test Dashboard Layout [`#154`](https://github.com/gradido/gradido/pull/154)
- feat: Activate Coverage Report For Unit Tests In Frontend [`#156`](https://github.com/gradido/gradido/pull/156)
- Docu [`#120`](https://github.com/gradido/gradido/pull/120)
- Fix compose bug [`#138`](https://github.com/gradido/gradido/pull/138)
- feat: Test Login Form [`#113`](https://github.com/gradido/gradido/pull/113)
- at last, fix email-bug [`#134`](https://github.com/gradido/gradido/pull/134)
- Api password reset [`#82`](https://github.com/gradido/gradido/pull/82)
- Optimize login server build [`#101`](https://github.com/gradido/gradido/pull/101)
- markdown of login server api [`#111`](https://github.com/gradido/gradido/pull/111)
- Fix lineendings [`#112`](https://github.com/gradido/gradido/pull/112)
- Background color change [`#117`](https://github.com/gradido/gradido/pull/117)
- Delete unused files [`#116`](https://github.com/gradido/gradido/pull/116)
- store aufräumen teil 1 [`#115`](https://github.com/gradido/gradido/pull/115)
- add migrations table for automatic table data migration [`40a9a8c`](https://github.com/gradido/gradido/commit/40a9a8c2b587f5bef0fcc54136ed7bd13dd91b2b)
- update yarn.lock after running yarn install [`7f38c80`](https://github.com/gradido/gradido/commit/7f38c801213ad886e9d34a8d43b00ae423f5f2a0)
- use new function for balance overview in old frontend, update balance in session on every php-request [`97c570c`](https://github.com/gradido/gradido/commit/97c570c08cc51ed17a69eb8be8d987f95f3c2ce0)
#### [0.9.4](https://github.com/gradido/gradido/compare/0.9.3...0.9.4)
> 30 March 2021
- Vue with nginx [`#84`](https://github.com/gradido/gradido/pull/84)
- Build on run [`#103`](https://github.com/gradido/gradido/pull/103)
- update debug docker to use dependencies container pushed to docker hub [`1f002f4`](https://github.com/gradido/gradido/commit/1f002f4ed0b12d4b2bf63efceabe546d0c5b58ea)
- removed email tasks complete [`8a143be`](https://github.com/gradido/gradido/commit/8a143be8423d7bd894d4f512848895df8b9694b0)

View File

@ -0,0 +1,2 @@
INSERT INTO `migrations` (`id`, `db_version`) VALUES
(1, 2);

View File

@ -6,5 +6,5 @@ INSERT INTO `transaction_types` (`id`, `name`, `text`) VALUES
(5, 'group remove member', 'remove user from group, maybe he was moved elsewhere'),
(6, 'hedera topic create', 'create new topic on hedera'),
(7, 'hedera topic send message', 'send consensus message over hedera topic'),
(8, 'hedera account create', 'create new account on hedera for holding some founds with unencrypted keys');
(8, 'hedera account create', 'create new account on hedera for holding some founds with unencrypted keys'),
(9, 'decay start', 'signalize the starting point for decay calculation, allowed only once per chain');

View File

@ -0,0 +1,5 @@
CREATE TABLE `migrations` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`db_version` int DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -1,6 +1,6 @@
CREATE TABLE `transaction_types` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(90) COLLATE utf8mb4_unicode_ci NOT NULL,
`text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -21,7 +21,7 @@ use Cake\Routing\Router;
use Cake\ORM\TableRegistry;
use Cake\Core\Configure;
use Cake\I18n\Time;
use Cake\I18n\I18n;
use Cake\I18n\FrozenTime;
/**
* Application Controller
@ -156,8 +156,25 @@ class AppController extends Controller
}
}
protected function checkForMigration($html = true)
{
$migrationsTable = TableRegistry::getTableLocator()->get('Migrations');
$last_migration = $migrationsTable->find()->last();
$current_db_version = 1;
if($last_migration) {
$current_db_version = $last_migration->db_version;
}
$php_data_version = 2;
if($current_db_version < $php_data_version) {
$this->redirect(['controller' => 'Migrations', 'action' => 'migrate', 'html' => $html, 'db_version' => $current_db_version]);
}
}
protected function requestLogin($sessionId = 0, $redirect = true)
{
$this->checkForMigration($redirect);
$stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
$session = $this->getRequest()->getSession();
// check login
// disable encryption for cookies
@ -190,12 +207,15 @@ class AppController extends Controller
$transactionPendings = $session->read('Transactions.pending');
$transactionExecutings = $session->read('Transactions.executing');
$transaction_can_signed = $session->read('Transactions.can_signed');
if ($session->read('session_id') != $session_id ||
( $userStored && (!isset($userStored['id']) || !$userStored['email_checked'])) ||
intval($transactionPendings) > 0 ||
intval($transactionExecutings) > 0 ||
intval($transaction_can_signed > 0)) {
intval($transaction_can_signed > 0))
{
$http = new Client();
try {
@ -226,6 +246,7 @@ class AppController extends Controller
$session->write('Transactions.can_signed', $transaction_can_signed);
$session->write('session_id', $session_id);
$stateUserTable = TableRegistry::getTableLocator()->get('StateUsers');
if (isset($json['user']['public_hex']) && $json['user']['public_hex'] != '') {
$public_key_bin = hex2bin($json['user']['public_hex']);
@ -254,11 +275,6 @@ class AppController extends Controller
$this->Flash->error(__('error updating state user ' . json_encode($stateUser->errors())));
}
}
//var_dump($stateUser);
if (count($stateUser->state_balances) > 0) {
$session->write('StateUser.balance', $stateUser->state_balances[0]->decay);
}
$session->write('StateUser.id', $stateUser->id);
//echo $stateUser['id'];
} else {
@ -310,6 +326,11 @@ class AppController extends Controller
//continue;
}
}
$state_balance = $stateBalancesTable->find()->where(['state_user_id' => $session->read('StateUser.id')])->first();
if ($state_balance) {
$now = new FrozenTime;
$session->write('StateUser.balance', $stateBalancesTable->calculateDecay($state_balance->amount, $state_balance->record_date, $now));
}
} else {
// no login
//die("no login");

View File

@ -156,6 +156,9 @@ class AppRequestsController extends AppController
if($required_fields !== true) {
return $this->returnJson($required_fields);
}
if(!isset($params['memo']) || strlen($params['memo']) < 5 || strlen($params['memo']) > 150) {
return $this->returnJson(['state' => 'error', 'msg' => 'memo is not set or not in expected range [5;150]']);
}
$params['transaction_type'] = 'transfer';
$requestAnswear = $this->JsonRequestClient->sendRequest(json_encode($params), '/createTransaction');
@ -323,23 +326,33 @@ class AppRequestsController extends AppController
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, $user['id'] ? $user['id'] : 0);
}
$stateUserTransactions_total = $stateUserTransactionsTable
->find()
->select(['id'])
->where(['state_user_id' => $user['id']])
->contain([]);
$stateUserTransactionsQuery = $stateUserTransactionsTable
->find()
->where(['state_user_id' => $user['id']])
->order(['balance_date' => 'ASC'])
->order(['balance_date' => $orderDirection])
->contain([])
->limit($count)
->page($page)
;
$decay = true;
$transactions = [];
$transactions_from_db = $stateUserTransactionsQuery->toArray();
if($stateUserTransactionsQuery->count() > 0) {
$transactions = $transactionsTable->listTransactionsHumanReadable($stateUserTransactionsQuery->toArray(), $user, $decay);
if($orderDirection == 'DESC') {
$transactions_from_db = array_reverse($transactions_from_db);
}
$transactions = $transactionsTable->listTransactionsHumanReadable($transactions_from_db, $user, $decay);
if($orderDirection == 'DESC') {
$transactions = array_reverse($transactions);
}
}
$state_balance = $stateBalancesTable->find()->where(['state_user_id' => $user['id']])->first();
@ -348,7 +361,7 @@ class AppRequestsController extends AppController
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => count($transactions),
'count' => $stateUserTransactions_total->count(),
'gdtSum' => $gdtSum,
'timeUsed' => microtime(true) - $startTime
];
@ -360,7 +373,7 @@ class AppRequestsController extends AppController
$body['decay'] = 0.0;
} else {
$body['balance'] = $state_balance->amount;
$body['decay'] = $state_balance->partDecay($now);
$body['decay'] = $stateBalancesTable->calculateDecay($state_balance->amount, $state_balance->record_date, $now);
}
$this->set('body', $body);

View File

@ -4,9 +4,9 @@ namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
use Cake\Routing\Router;
use Cake\Http\Client;
use Cake\Core\Configure;
use Cake\Mailer\Email;
use Model\Transactions\TransactionTransfer;
use Model\Transactions\Transaction;
@ -332,33 +332,72 @@ class JsonRequestHandlerController extends AppController {
}
return $this->returnJson(['state' => 'success']);
}
private function sendEMailTransactionFailed($transaction, $reason_type)
{
$disable_email = Configure::read('disableEmail', false);
if($disable_email) {
return;
}
$transaction_body = $transaction->getTransactionBody();
$transaction_type_name = $transaction_body->getTransactionTypeName();
$senderUser = null;
if($transaction_type_name === 'transfer') {
$senderUser = $transaction_body->getSpecificTransaction()->getSenderUser();
} else if($transaction_type_name === 'creation') {
$senderUser = $transaction->getFirstSigningUser();
}
// send notification email
$noReplyEmail = Configure::read('noReplyEmail');
if($senderUser) {
try {
$email = new Email();
$emailViewBuilder = $email->viewBuilder();
$emailViewBuilder->setTemplate('notificationTransactionFailed')
->setVars(['user' => $senderUser, 'transaction' => $transaction, 'reason' => $reason_type]);
$receiverNames = $senderUser->getNames();
if($receiverNames == '' || $senderUser->email == '') {
$this->addError('TransactionCreation::sendNotificationEmail', 'to email is empty for user: ' . $senderUser->id);
return false;
}
$email->setFrom([$noReplyEmail => 'Gradido (nicht antworten)'])
->setTo([$senderUser->email => $senderUser->getNames()])
->setSubject(__('Gradido Transaktion fehlgeschlagen!'))
->send();
} catch(Exception $e) {
$this->addAdminError('JsonRequestController', 'sendEMailTransactionFailed', [$e->getMessage(), $reason_type], $senderUser->id);
}
}
}
private function putTransaction($transactionBase64) {
$transaction = new Transaction($transactionBase64);
//echo "after new transaction<br>";
if($transaction->hasErrors()) {
$this->sendEMailTransactionFailed($transaction, 'parse');
return $this->returnJson(['state' => 'error', 'msg' => 'error parsing transaction', 'details' => $transaction->getErrors()]);
}
//echo "after check on errors<br>";
if(!$transaction->validate()) {
//$transaction_details
$this->sendEMailTransactionFailed($transaction, 'validate');
return $this->returnJsonSaveError($transaction, ['state' => 'error', 'msg' => 'error validate transaction', 'details' => $transaction->getErrors()]);
}
//echo "after validate <br>";
if ($transaction->save()) {
// success
return $this->returnJson(['state' => 'success']);
} else {
$this->sendEMailTransactionFailed($transaction, 'save');
return $this->returnJsonSaveError($transaction, [
'state' => 'error',
'msg' => 'error saving transaction in db',
'details' => json_encode($transaction->getErrors())
]);
}
return $this->returnJson(['state' => 'success']);
}
private function moveTransaction($pubkeys, $memo, $session_id) {

View File

@ -0,0 +1,168 @@
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
/**
* Migrations Controller
*
* @property \App\Model\Table\MigrationsTable $Migrations
*
* @method \App\Model\Entity\Migration[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
*/
class MigrationsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Auth->allow('migrate');
}
/**
* Index method
*
* @return \Cake\Http\Response|null
*/
public function index()
{
$migrations = $this->paginate($this->Migrations);
$this->set(compact('migrations'));
}
protected function callFunctions(array $callables)
{
foreach($callables as $callable) {
$result = call_user_func($callable);
if(!$result['success']) {
return $result;
}
}
return ['success' => true];
}
public function migrate()
{
$html = $this->request->getQuery('html');
$current_db_version = $this->request->getQuery('db_version');
$startTime = microtime(true);
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
$transactionTypesTable = TableRegistry::getTableLocator()->get('TransactionTypes');
$stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
$blockchainTypesTable = TableRegistry::getTableLocator()->get('BlockchainTypes');
$new_db_version = 1;
$commands = [];
// migrate from version 1 to 2
if($current_db_version == 1) {
$stateUserTransactionsTable->truncate();
$commands = [
[$blockchainTypesTable, 'fillWithDefault'],
[$transactionTypesTable, 'fillWithDefault'],
[$stateBalancesTable, 'truncate'],
[$transactionsTable, 'fillStateUserTransactions'],
[$stateBalancesTable, 'updateAllBalances']
];
$new_db_version = 2;
}
$migration_result = $this->callFunctions($commands);
if($migration_result['success']) {
$migration_entity = $this->Migrations->newEntity();
$migration_entity->db_version = $new_db_version;
$this->Migrations->save($migration_entity);
}
if(!$html) {
return $this->returnJson($migration_result);
} else {
$this->set('db_version', $current_db_version);
$this->set('result', $migration_result);
$this->set('timeUsed', microtime(true) - $startTime);
}
}
/**
* View method
*
* @param string|null $id Migration id.
* @return \Cake\Http\Response|null
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$migration = $this->Migrations->get($id, [
'contain' => [],
]);
$this->set('migration', $migration);
}
/**
* Add method
*
* @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
*/
public function add()
{
$migration = $this->Migrations->newEntity();
if ($this->request->is('post')) {
$migration = $this->Migrations->patchEntity($migration, $this->request->getData());
if ($this->Migrations->save($migration)) {
$this->Flash->success(__('The migration has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The migration could not be saved. Please, try again.'));
}
$this->set(compact('migration'));
}
/**
* Edit method
*
* @param string|null $id Migration id.
* @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$migration = $this->Migrations->get($id, [
'contain' => [],
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$migration = $this->Migrations->patchEntity($migration, $this->request->getData());
if ($this->Migrations->save($migration)) {
$this->Flash->success(__('The migration has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The migration could not be saved. Please, try again.'));
}
$this->set(compact('migration'));
}
/**
* Delete method
*
* @param string|null $id Migration id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$migration = $this->Migrations->get($id);
if ($this->Migrations->delete($migration)) {
$this->Flash->success(__('The migration has been deleted.'));
} else {
$this->Flash->error(__('The migration could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}

View File

@ -2,7 +2,7 @@
namespace App\Controller;
use Cake\ORM\TableRegistry;
use Cake\I18n\Time;
use Cake\I18n\FrozenTime;
use Model\Navigation\NaviHierarchy;
use Model\Navigation\NaviHierarchyEntry;
@ -62,9 +62,10 @@ class StateBalancesController extends AppController
$user = $session->read('StateUser');
$update_balance_result = $this->StateBalances->updateBalances($user['id']);
if($update_balance_result !== true) {
if($update_balance_result['success'] !== true) {
$this->addAdminError('StateBalances', 'overview', $update_balance_result, $user['id']);
}
// sendRequestGDT
// listPerEmailApi
@ -84,200 +85,68 @@ class StateBalancesController extends AppController
//}
//
//
$stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$creationsTable = TableRegistry::getTableLocator()->get('TransactionCreations');
$creationTransactions = $creationsTable
->find('all')
->where(['state_user_id' => $user['id']])
->contain(['Transactions']);
$transferTable = TableRegistry::getTableLocator()->get('TransactionSendCoins');
$transferTransactions = $transferTable
->find('all')
->where(['OR' => ['state_user_id' => $user['id'], 'receiver_user_id' => $user['id']]])
->contain(['Transactions']);
$involvedUserIds = [];
foreach ($transferTransactions as $sendCoins) {
//var_dump($sendCoins);
if ($sendCoins->state_user_id != $user['id']) {
array_push($involvedUserIds, intval($sendCoins->state_user_id));
} elseif ($sendCoins->receiver_user_id != $user['id']) {
array_push($involvedUserIds, intval($sendCoins->receiver_user_id));
}
}
/*echo "state user from sendCoins: $sendCoins->state_user_id<br>";
echo "receiver user from sendCoins: $sendCoins->receiver_user_id<br>";
echo "user id from logged in user: ".$user['id']. '<br>';
*/
//var_dump($involvedUserIds);
// exchange key with values and drop duplicates
$involvedUser_temp = array_flip($involvedUserIds);
// exchange back
$involvedUserIds = array_flip($involvedUser_temp);
$userTable = TableRegistry::getTableLocator()->get('StateUsers');
$involvedUser = $userTable->find('all', [
'contain' => false,
'where' => ['id IN' => $involvedUserIds],
'fields' => ['id', 'first_name', 'last_name', 'email']
]);
//var_dump($involvedUser->toArray());
$involvedUserIndices = [];
foreach ($involvedUser as $involvedUser) {
$involvedUserIndices[$involvedUser->id] = $involvedUser;
}
// sender or receiver when user has sended money
// group name if creation
// type: gesendet / empfangen / geschöpft
// transaktion nr / id
// date
// balance
$transactions = [];
foreach ($creationTransactions as $creation) {
//var_dump($creation);
array_push($transactions, [
'name' => 'Gradido Akademie',
'type' => 'creation',
'transaction_id' => $creation->transaction_id,
'date' => $creation->target_date,
'balance' => $creation->amount,
'memo' => $creation->transaction->memo
]);
}
foreach ($transferTransactions as $sendCoins) {
$type = '';
$otherUser = null;
$other_user_public = '';
if ($sendCoins->state_user_id == $user['id']) {
$type = 'send';
if(isset($involvedUserIndices[$sendCoins->receiver_user_id])) {
$otherUser = $involvedUserIndices[$sendCoins->receiver_user_id];
}
$other_user_public = bin2hex(stream_get_contents($sendCoins->receiver_public_key));
} else if ($sendCoins->receiver_user_id == $user['id']) {
$type = 'receive';
if(isset($involvedUserIndices[$sendCoins->state_user_id])) {
$otherUser = $involvedUserIndices[$sendCoins->state_user_id];
}
if($sendCoins->sender_public_key) {
$other_user_public = bin2hex(stream_get_contents($sendCoins->sender_public_key));
}
}
if(null == $otherUser) {
$otherUser = $this->StateBalances->StateUsers->newEntity();
}
array_push($transactions, [
'name' => $otherUser->first_name . ' ' . $otherUser->last_name,
'email' => $otherUser->email,
'type' => $type,
'transaction_id' => $sendCoins->transaction_id,
'date' => $sendCoins->transaction->received,
'balance' => $sendCoins->amount,
'memo' => $sendCoins->transaction->memo,
'pubkey' => $other_user_public
]);
}
uasort($transactions, array($this, 'sortTransactions'));
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
// add decay transactions
$month_start_state_balance = null;
$current_state_balance = null;
$cursor = 0;
$transactions_reversed = array_reverse($transactions);
$decay_transactions = [];
$maxI = count($transactions_reversed);
$stateBalancesTable->updateBalances($user['id']);
foreach($transactions_reversed as $i => $transaction) {
if(!isset($transaction['transaction_id'])) {
//echo "missing transaction<br>";
continue;
}
$transaction_id = $transaction['transaction_id'];
//echo "transaction id: $transaction_id <br>";
$decay_transaction = NULL;
$state_balance = $this->StateBalances->newEntity();
if($i > 0 && isset($transactions_reversed[$i-1]['transaction_id'])) {
$prev_transaction = $transactions_reversed[$i-1];
$stateUserTransactions = $stateUserTransactionsTable
->find()
->where([
'transaction_id IN' => [$transaction_id, $prev_transaction['transaction_id']],
'state_user_id' => $user['id']
])
->order(['balance_date ASC'])
->toArray();
$prev = $stateUserTransactions[0];
if($prev->balance > 0) {
// var_dump($stateUserTransactions);
$current = $stateUserTransactions[1];
//echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "<br>";
$interval = $current->balance_date->diff($prev->balance_date);
$state_balance->amount = $prev->balance;
$state_balance->record_date = $prev->balance_date;
$diff_amount = $state_balance->partDecay($current->balance_date);
//echo $interval->format('%R%a days');
//echo "prev balance: " . $prev->balance . ", diff_amount: $diff_amount, summe: " . (-intval($prev->balance - $diff_amount)) . "<br>";
$decay_transaction = [
'type' => 'decay',
'balance' => -intval($prev->balance - $diff_amount),
'decay_duration' => $interval->format('%a days, %H hours, %I minutes, %S seconds'),
'memo' => ''
];
}
}
if($decay_transaction) {
$decay_transactions[] = $decay_transaction;
//array_splice($transactions_reversed, $i + $cursor, 0, [$decay_transaction]);
//$cursor++;
}
if($i == $maxI-1) {
$stateUserTransaction = $stateUserTransactionsTable
->find()
->where(['transaction_id' => $transaction_id, 'state_user_id' => $user['id']])
->order(['transaction_id ASC'])->first();
//var_dump($stateUserTransaction);
$state_balance->amount = $stateUserTransaction->balance;
$state_balance->record_date = $stateUserTransaction->balance_date;
$decay_transactions[] = [
//$transactions_reversed[] = [
'type' => 'decay',
'balance' => -intval($stateUserTransaction->balance - $state_balance->decay),
'decay_duration' => $stateUserTransaction->balance_date->timeAgoInWords(),
'memo' => ''
];
}
$gdtSum = 0;
$gdtEntries = $this->JsonRequestClient->sendRequestGDT(['email' => $user['email']], 'GdtEntries' . DS . 'sumPerEmailApi');
if('success' == $gdtEntries['state'] && 'success' == $gdtEntries['data']['state']) {
$gdtSum = intval($gdtEntries['data']['sum']);
} else {
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, $user['id'] ? $user['id'] : 0);
}
$final_transactions = [];
foreach($transactions_reversed as $i => $transaction) {
$final_transactions[] = $transaction;
$final_transactions[] = $decay_transactions[$i];
$stateUserTransactionsQuery = $stateUserTransactionsTable
->find()
->where(['state_user_id' => $user['id']])
->order(['balance_date' => 'ASC'])
->contain([])
;
$decay = true;
$transactions = [];
if($stateUserTransactionsQuery->count() > 0) {
$transactions = $transactionsTable->listTransactionsHumanReadable($stateUserTransactionsQuery->toArray(), $user, $decay);
}
$state_balance = $stateBalancesTable->find()->where(['state_user_id' => $user['id']])->first();
$body = [
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => count($transactions),
'gdtSum' => $gdtSum,
'timeUsed' => microtime(true) - $startTime
];
$now = new FrozenTime();
$body['decay_date'] = $now;
if(!$state_balance) {
$balance = 0.0;
} else {
$balance = $stateBalancesTable->calculateDecay($state_balance->amount, $state_balance->record_date, $now);
//$balance = $state_balance->partDecay($now);
}
// for debugging
$calculated_balance = 0;
foreach($final_transactions as $tr) {
if($tr['type'] == 'send') {
$calculated_balance -= intval($tr['balance']);
foreach($transactions as $transaction) {
if($transaction['type'] == 'decay' || $transaction['type'] == 'send') {
$calculated_balance -= $transaction['balance'];
} else {
$calculated_balance += intval($tr['balance']);
$calculated_balance += $transaction['balance'];
}
}
$this->set('calculated_balance', $calculated_balance);
$this->set('transactions', array_reverse($final_transactions));
$this->set('transactions', array_reverse($transactions));
$this->set('transactionExecutingCount', $session->read('Transactions.executing'));
$this->set('balance', $session->read('StateUser.balance'));
$this->set('balance', $balance);
$this->set('timeUsed', microtime(true) - $startTime);
$this->set('gdtSum', $gdtSum);
}
@ -301,151 +170,16 @@ class StateBalancesController extends AppController
if(!$state_balance) {
return $this->returnJson(['state' => 'success', 'balance' => 0]);
}
$now = new FrozenTime();
return $this->returnJson([
'state' => 'success',
'balance' => $state_balance->amount,
'decay' => $state_balance->decay
'decay' => $this->StateBalances->calculateDecay($state_balance->amount, $state_balance->record_date, $now),
'decay_date' => $now
]);
}
public function ajaxListTransactions($session_id, $page=1, $count=25)
{
if(!$session_id) {
return $this->returnJson(['state' => 'error', 'msg' => 'invalid session id']);
}
$startTime = microtime(true);
$login_result = $this->requestLogin($session_id, false);
if($login_result !== true) {
return $this->returnJson($login_result);
}
$session = $this->getRequest()->getSession();
$user = $session->read('StateUser');
$this->StateBalances->updateBalances($user['id']);
$gdtSum = 0;
$gdtEntries = $this->JsonRequestClient->sendRequestGDT(['email' => $user['email']], 'GdtEntries' . DS . 'sumPerEmailApi');
if('success' == $gdtEntries['state'] && 'success' == $gdtEntries['data']['state']) {
$gdtSum = intval($gdtEntries['data']['sum']);
} else {
if($user) {
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, $user['id']);
} else {
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, 0);
}
}
$creationsTable = TableRegistry::getTableLocator()->get('TransactionCreations');
$creationTransactions = $creationsTable
->find('all')
->where(['state_user_id' => $user['id']])
->contain(['Transactions']);
$transferTable = TableRegistry::getTableLocator()->get('TransactionSendCoins');
$transferTransactions = $transferTable
->find('all')
->where(['OR' => ['state_user_id' => $user['id'], 'receiver_user_id' => $user['id']]])
->contain(['Transactions']);
$involvedUserIds = [];
foreach ($transferTransactions as $sendCoins) {
//var_dump($sendCoins);
if ($sendCoins->state_user_id != $user['id']) {
array_push($involvedUserIds, intval($sendCoins->state_user_id));
} elseif ($sendCoins->receiver_user_id != $user['id']) {
array_push($involvedUserIds, intval($sendCoins->receiver_user_id));
}
}
/*echo "state user from sendCoins: $sendCoins->state_user_id<br>";
echo "receiver user from sendCoins: $sendCoins->receiver_user_id<br>";
echo "user id from logged in user: ".$user['id']. '<br>';
*/
//var_dump($involvedUserIds);
// exchange key with values and drop duplicates
$involvedUser_temp = array_flip($involvedUserIds);
// exchange back
$involvedUserIds = array_flip($involvedUser_temp);
$userTable = TableRegistry::getTableLocator()->get('StateUsers');
$involvedUser = $userTable->find('all', [
'contain' => false,
'where' => ['id IN' => $involvedUserIds],
'fields' => ['id', 'first_name', 'last_name', 'email']
]);
//var_dump($involvedUser->toArray());
$involvedUserIndices = [];
foreach ($involvedUser as $involvedUser) {
$involvedUserIndices[$involvedUser->id] = $involvedUser;
}
// sender or receiver when user has sended money
// group name if creation
// type: gesendet / empfangen / geschöpft
// transaktion nr / id
// date
// balance
$transactions = [];
foreach ($creationTransactions as $creation) {
//var_dump($creation);
array_push($transactions, [
'name' => 'Gradido Akademie',
'type' => 'creation',
'transaction_id' => $creation->transaction_id,
'date' => $creation->transaction->received,
'balance' => $creation->amount,
'memo' => $creation->transaction->memo
]);
}
foreach ($transferTransactions as $sendCoins) {
$type = '';
$otherUser = null;
if ($sendCoins->state_user_id == $user['id']) {
$type = 'send';
if(isset($involvedUserIndices[$sendCoins->receiver_user_id])) {
$otherUser = $involvedUserIndices[$sendCoins->receiver_user_id];
}
} else if ($sendCoins->receiver_user_id == $user['id']) {
$type = 'receive';
if(isset($involvedUserIndices[$sendCoins->state_user_id])) {
$otherUser = $involvedUserIndices[$sendCoins->state_user_id];
}
}
if(null == $otherUser) {
$otherUser = $this->StateBalances->StateUsers->newEntity();
}
array_push($transactions, [
'name' => $otherUser->first_name . ' ' . $otherUser->last_name,
'email' => $otherUser->email,
'type' => $type,
'transaction_id' => $sendCoins->transaction_id,
'date' => $sendCoins->transaction->received,
'balance' => $sendCoins->amount,
'memo' => $sendCoins->transaction->memo
]);
}
uasort($transactions, array($this, 'sortTransactions'));
if($sort == 'DESC') {
$transactions = array_reverse($transactions);
}
return $this->returnJson([
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => count($transactions),
'gdtSum' => $gdtSum,
'timeUsed' => microtime(true) - $startTime
]);
}
public function ajaxGdtOverview()
{

View File

@ -237,6 +237,11 @@ class TransactionSendCoinsController extends AppController
$this->set('timeUsed', microtime(true) - $startTime);
return;
}
if($answear_data['msg'] === 'memo is not set or not in expected range [5;150]') {
$this->Flash->error(__('Ein Verwendungszweck zwischen 5 und 150 Zeichen wird benötig!'));
$this->set('timeUsed', microtime(true) - $startTime);
return;
}
} else if($answear_data['state'] === 'not found' && $answear_data['msg'] === 'receiver not found') {
$this->Flash->error(__('Der Empfänger wurde nicht auf dem Login-Server gefunden, hat er sein Konto schon angelegt?'));
$this->set('timeUsed', microtime(true) - $startTime);

View File

@ -313,15 +313,20 @@ class TransactionsController extends AppController
if ($this->request->is('post')) {
$transaction = $this->Transactions->patchEntity($transaction, $this->request->getData());
if ($this->Transactions->save($transaction)) {
$this->Flash->success(__('The transaction has been saved.'));
return $this->redirect(['action' => 'index']);
$result = $this->Transactions->updateTxHash($transaction, 'start decay');
if($result === true) {
$this->Flash->success(__('The transaction has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('Error by saving: ' . json_encode($result)));
}
}
$this->Flash->error(__('The transaction could not be saved. Please, try again.'));
}
$stateGroups = $this->Transactions->StateGroups->find('list', ['limit' => 200]);
$transactionTypes = $this->Transactions->TransactionTypes->find('list', ['limit' => 200]);
$this->set(compact('transaction', 'stateGroups', 'transactionTypes'));
$blockchainTypes = $this->Transactions->BlockchainTypes->find('list');
$this->set(compact('transaction', 'stateGroups', 'transactionTypes', 'blockchainTypes'));
}
/**

View File

@ -0,0 +1,26 @@
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
/**
* Migration Entity
*
* @property int $id
* @property int|null $db_version
*/
class Migration extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* @var array
*/
protected $_accessible = [
'db_version' => true,
];
}

View File

@ -44,9 +44,7 @@ class StateBalance extends Entity
} else if(method_exists($dateOrTime, 'i18nFormat')) {
return $dateOrTime->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT);
} else {
var_dump($dateOrTime);
debug_print_backtrace(0, 6);
die("date or time unexpected object");
return 0;
}
}
@ -59,8 +57,12 @@ class StateBalance extends Entity
// SELECT TIMESTAMPDIFF(SECOND, modified, CURDATE()) AS age_in_seconds from state_balances
// decay_for_duration = decay_factor^seconds
// decay = gradido_cent * decay_for_duration
$decay_duration = intval(Time::now()->getTimestamp() - $this->convertToTimestamp($this->record_date));
$startDate = $this->convertToTimestamp($this->record_date);
if($startDate == 0) {
return $this->amount;
}
$decay_duration = intval(Time::now()->getTimestamp() - $startDate);
if($decay_duration === 0) {
return $this->amount;
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
class AppTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
}
public function truncate()
{
$truncateCommands = $this->getSchema()->truncateSql($this->getConnection());
foreach ($truncateCommands as $truncateCommand) {
$this->getConnection()->query($truncateCommand);
}
$this->getConnection()->query('ALTER TABLE ' . $this->getSchema()->name() . ' AUTO_INCREMENT=1');
return ['success' => true];
}
public function saveManyWithErrors($entities)
{
$save_results = $this->saveMany($entities);
// save all at once failed, no try one by one to get error message
if($save_results === false) {
foreach($entities as $entity) {
if(!$this->save($entity)) {
return ['success' => false, 'errors' => $entity->getErrors()];
}
}
} else {
return ['success' => true];
}
}
}

View File

@ -18,7 +18,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\BlockchainType[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\BlockchainType findOrCreate($search, callable $callback = null, $options = [])
*/
class BlockchainTypesTable extends Table
class BlockchainTypesTable extends AppTable
{
/**
* Initialize method
@ -65,4 +65,30 @@ class BlockchainTypesTable extends Table
return $validator;
}
public function fillWithDefault()
{
$entry_contents = [
[
'id' => 1,
'name' => 'mysql',
'text' => 'use mysql db as blockchain, work only with single community-server',
'symbol' => NULL
],
[
'id' => 2,
'name' => 'hedera',
'text' => 'use hedera for transactions',
'symbol' => 'HBAR'
]
];
$entities = $this->newEntities($entry_contents);
$this->truncate();
$save_results = $this->saveManyWithErrors($entities);
if(!$save_results['success']) {
$save_results['msg'] = 'error by saving default transaction types';
}
return $save_results;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Migrations Model
*
* @method \App\Model\Entity\Migration get($primaryKey, $options = [])
* @method \App\Model\Entity\Migration newEntity($data = null, array $options = [])
* @method \App\Model\Entity\Migration[] newEntities(array $data, array $options = [])
* @method \App\Model\Entity\Migration|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \App\Model\Entity\Migration saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \App\Model\Entity\Migration patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* @method \App\Model\Entity\Migration[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\Migration findOrCreate($search, callable $callback = null, $options = [])
*/
class MigrationsTable extends Table
{
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('migrations');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
}
/**
* Default validation rules.
*
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->nonNegativeInteger('id')
->allowEmptyString('id', null, 'create');
$validator
->integer('db_version')
->allowEmptyString('db_version');
return $validator;
}
}

View File

@ -1,14 +1,12 @@
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use Cake\I18n\Date;
use Cake\I18n\Time;
use Cake\I18n\FrozenTime;
/**
* StateBalances Model
@ -26,8 +24,9 @@ use Cake\I18n\Time;
*
* @mixin \Cake\ORM\Behavior\TimestampBehavior
*/
class StateBalancesTable extends Table
class StateBalancesTable extends AppTable
{
private static $startDecayDate = null;
/**
* Initialize method
*
@ -49,6 +48,15 @@ class StateBalancesTable extends Table
'joinType' => 'INNER'
]);
}
public static function getDecayStartDateCached()
{
if(self::$startDecayDate == null) {
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
self::$startDecayDate = $transactionsTable->getDecayStartDate();
}
return self::$startDecayDate;
}
/**
* Default validation rules.
@ -83,20 +91,60 @@ class StateBalancesTable extends Table
return $rules;
}
public function sortTransactions($a, $b)
public function calculateDecay($startBalance, FrozenTime $startDate, FrozenTime $endDate, $withInterval = false)
{
if ($a['date'] == $b['date']) {
return 0;
$decayStartDate = self::getDecayStartDateCached();
// if no start decay block exist, we just return input
// if start date for decay is after enddate, we also just return input
if($decayStartDate === null || $decayStartDate >= $endDate) {
if($withInterval) {
return ['balance' => $startBalance, 'interval' => new \DateInterval('PT0S')];
} else {
return $startBalance;
}
}
return ($a['date'] > $b['date']) ? -1 : 1;
$state_balance = $this->newEntity();
$state_balance->amount = $startBalance;
$interval = null;
// if decay start date is before start date we calculate decay for full duration
if($decayStartDate < $startDate) {
$state_balance->record_date = $startDate;
$interval = $endDate->diff($startDate);
}
// if decay start in between start date and end date we caculcate decay from decay start time to end date
else {
$state_balance->record_date = $decayStartDate;
$interval = $endDate->diff($decayStartDate);
}
$decay = $state_balance->partDecay($endDate);
if($withInterval) {
return ['balance' => $decay, 'interval' => $interval];
} else {
return $decay;
}
}
public function updateAllBalances()
{
$stateUserTable = TableRegistry::getTableLocator()->get('StateUsers');
$state_users = $stateUserTable->find()->select(['id'])->contain([]);
foreach($state_users as $state_user) {
$result = $this->updateBalances($state_user->id);
if($result['success'] === false) {
$result['state_user_id'] = $state_user->id;
return $result;
}
}
return ['success' => true];
}
public function updateBalances($stateUserId)
{
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
$now = new FrozenTime;
// info: cakephp use lazy loading, query will be executed later only if needed
$state_balances = $this->find('all')->where(['state_user_id' => $stateUserId]);
$state_user_transactions = $stateUserTransactionsTable
@ -107,7 +155,7 @@ class StateBalancesTable extends Table
;
if(!$state_user_transactions || !$state_user_transactions->count()) {
return true;
return ['success' => true];
}
// first: decide what todo
@ -128,18 +176,26 @@ class StateBalancesTable extends Table
if($state_user_transactions->count() == 0){
$clear_state_balance = true;
} else {
$first_state_balance = $state_balances->first();
$first_state_balance_decayed = self::calculateDecay(
$first_state_balance->amount,
$first_state_balance->record_date,
$now);
$last_state_user_transaction = $state_user_transactions->last();
$last_transaction = $this->newEntity();
$last_transaction->amount = $last_state_user_transaction->balance;
$last_transaction->record_date = $last_state_user_transaction->balance_date;
$last_state_user_transaction_decayed = self::calculateDecay(
$last_state_user_transaction->balance,
$last_state_user_transaction->balance_date,
$now);
// if entrys are nearly the same, we don't need doing anything
if(abs($last_transaction->decay - $state_balances->first()->decay) > 100) {
if(floor($last_state_user_transaction_decayed/100) !== floor($first_state_balance_decayed/100)) {
$recalculate_state_user_transactions_balance = true;
$update_state_balance = true;
}
}
}
if(!$recalculate_state_user_transactions_balance) {
$last_state_user_transaction = $state_user_transactions->last();
if($last_state_user_transaction && $last_state_user_transaction->balance <= 0) {
@ -193,37 +249,28 @@ class StateBalancesTable extends Table
$transaction = $transactions_indiced[$state_user_transaction->transaction_id];
if($transaction->transaction_type_id > 2) {
continue;
}
//echo "transaction id: ".$transaction->id . "<br>";
$amount_date = null;
}
$amount = 0;
if($transaction->transaction_type_id == 1) { // creation
$temp = $transaction->transaction_creation;
/*$balance_temp = $this->newEntity();
$balance_temp->amount = $temp->amount;
$balance_temp->record_date = $temp->target_date;
*/
$amount = intval($temp->amount);//$balance_temp->partDecay($transaction->received);
$amount_date = $temp->target_date;
//$amount_date =
$amount = intval($transaction->transaction_creation->amount);
} else if($transaction->transaction_type_id == 2) { // transfer
$temp = $transaction->transaction_send_coin;
$amount = intval($temp->amount);
// reverse if sender
if($stateUserId == $temp->state_user_id) {
$amount *= -1.0;
}
$amount_date = $transaction->received;
}
$amount_date = $transaction->received;
if($i == 0) {
$balance_cursor->amount = $amount;
} else {
$balance_cursor->amount = $balance_cursor->partDecay($amount_date) + $amount;
//$balance_cursor->amount = $balance_cursor->partDecay($amount_date) + $amount;
$balance_cursor->amount =
$this->calculateDecay($balance_cursor->amount, $balance_cursor->record_date, $amount_date)
+ $amount;
}
//echo "new balance: " . $balance_cursor->amount . "<br>";
@ -261,7 +308,7 @@ class StateBalancesTable extends Table
return ['success' => false, 'error' => 'error saving state balance', 'details' => $state_balance->getErrors()];
}
}
return true;
return ['success' => true];
}

View File

@ -22,7 +22,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\StateUserTransaction[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\StateUserTransaction findOrCreate($search, callable $callback = null, $options = [])
*/
class StateUserTransactionsTable extends Table
class StateUserTransactionsTable extends AppTable
{
/**
* Initialize method

View File

@ -20,7 +20,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\TransactionType[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\TransactionType findOrCreate($search, callable $callback = null, $options = [])
*/
class TransactionTypesTable extends Table
class TransactionTypesTable extends AppTable
{
/**
* Initialize method
@ -55,7 +55,7 @@ class TransactionTypesTable extends Table
$validator
->scalar('name')
->maxLength('name', 24)
->maxLength('name', 45)
->requirePresence('name', 'create')
->notEmptyString('name');
@ -66,4 +66,55 @@ class TransactionTypesTable extends Table
return $validator;
}
public function fillWithDefault()
{
$entry_contents = [
[
'id' => 1,
'name' => 'creation',
'text' => 'create new gradidos for member and also for group (in development)',
], [
'id' => 2,
'name' => 'transfer',
'text' => 'send gradidos from one member to another, also cross group transfer',
], [
'id' => 3,
'name' => 'group create',
'text' => 'create a new group, trigger creation of new hedera topic and new blockchain on node server'
], [
'id' => 4,
'name' => 'group add member',
'text' => 'add user to a group or move if he was already in a group'
], [
'id' => 5,
'name' => 'group remove member',
'text' => 'remove user from group, maybe he was moved elsewhere'
],[
'id' => 6,
'name' => 'hedera topic create',
'text' => 'create new topic on hedera'
],[
'id' => 7,
'name' => 'hedera topic send message',
'text' => 'send consensus message over hedera topic'
],[
'id' => 8,
'name' => 'hedera account create',
'text' => 'create new account on hedera for holding some founds with unencrypted keys'
],[
'id' => 9,
'name' => 'decay start',
'text' => 'signalize the starting point for decay calculation, allowed only once per chain'
]
];
$entities = $this->newEntities($entry_contents);
$this->truncate();
$save_results = $this->saveManyWithErrors($entities);
if(!$save_results['success']) {
$save_results['msg'] = 'error by saving default transaction types';
}
return $save_results;
}
}

View File

@ -1,12 +1,12 @@
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use Cake\I18n\Number;
use Cake\I18n\FrozenTime;
/**
* Transactions Model
*
@ -172,18 +172,19 @@ class TransactionsTable extends Table
//var_dump($su_transaction);
//die("step");
// add decay transactions
if($i > 0 && $decay == true)
{
$prev = null;
if($i > 0 ) {
$prev = $stateUserTransactions[$i-1];
}
if($prev && $decay == true)
{
if($prev->balance > 0) {
// var_dump($stateUserTransactions);
$current = $su_transaction;
//echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "<br>";
$interval = $current->balance_date->diff($prev->balance_date);
$state_balance->amount = $prev->balance;
$state_balance->record_date = $prev->balance_date;
$diff_amount = $state_balance->partDecay($current->balance_date);
$balance = floatval($prev->balance - $diff_amount);
$calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
$balance = floatval($prev->balance - $calculated_decay['balance']);
// skip small decays (smaller than 0,00 GDD)
if(abs($balance) >= 100) {
@ -192,7 +193,7 @@ class TransactionsTable extends Table
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $interval->format('%a days, %H hours, %I minutes, %S seconds'),
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
'memo' => ''
];
}
@ -211,12 +212,16 @@ class TransactionsTable extends Table
echo "<br>";*/
if($su_transaction->transaction_type_id == 1) { // creation
$creation = $transaction->transaction_creation;
$balance = $stateBalancesTable->calculateDecay($creation->amount, $creation->target_date, $transaction->received);
$final_transactions[] = [
'name' => 'Gradido Akademie',
'type' => 'creation',
'transaction_id' => $transaction->id,
'date' => $creation->target_date,
'balance' => $creation->amount,
'date' => $transaction->received,// $creation->target_date,
'target_date' => $creation->target_date,
'creation_amount' => $creation->amount,
'balance' => $balance,
'memo' => $transaction->memo
];
} else if($su_transaction->transaction_type_id == 2) { // transfer or send coins
@ -256,14 +261,21 @@ class TransactionsTable extends Table
}
if($i == $stateUserTransactionsCount-1 && $decay == true) {
$state_balance->amount = $su_transaction->balance;
$state_balance->record_date = $su_transaction->balance_date;
$balance = floatval($su_transaction->balance - $state_balance->decay);
$calculated_decay = $stateBalancesTable->calculateDecay(
$su_transaction->balance,
$su_transaction->balance_date, new FrozenTime(), true);
$decay_start_date = $stateBalancesTable->getDecayStartDateCached();
$duration = $su_transaction->balance_date->timeAgoInWords();
if($decay_start_date > $su_transaction->balance_date) {
$duration = $decay_start_date->timeAgoInWords();
}
$balance = floatval($su_transaction->balance - $calculated_decay['balance']);
if($balance > 100) {
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $su_transaction->balance_date->timeAgoInWords(),
'decay_duration' => $duration,
'last_decay' => true,
'memo' => ''
];
}
@ -273,4 +285,169 @@ class TransactionsTable extends Table
return $final_transactions;
}
public function updateTxHash($transaction, $signatureMapString)
{
$transaction_id = $transaction->id;
$previousTxHash = null;
if($transaction_id > 1) {
try {
$previousTransaction = $this
->find('all', ['contain' => false])
->select(['tx_hash'])
->where(['id' => $transaction_id - 1])
->first();
/*$previousTransaction = $transactionsTable->get($this->mTransactionID - 1, [
'contain' => false,
'fields' => ['tx_hash']
]);*/
} catch(Cake\Datasource\Exception\RecordNotFoundException $ex) {
return ['state' => 'error', 'msg' => 'previous transaction not found', 'details' => $ex->getMessage()];
}
if(!$previousTransaction) {
// shouldn't occur
return ['state' => 'error', 'msg' => 'previous transaction not found'];
}
$previousTxHash = $previousTransaction->tx_hash;
}
try {
//$transactionEntity->received = $transactionsTable->get($transactionEntity->id, ['contain' => false, 'fields' => ['received']])->received;
$transaction->received = $this
->find('all', ['contain' => false])
->where(['id' => $transaction->id])
->select(['received'])->first()->received;
} catch(Cake\Datasource\Exception\RecordNotFoundException $ex) {
return ['state' => 'error', 'msg' => 'current transaction not found in db', 'details' => $ex->getMessage()];
}
// calculate tx hash
// previous tx hash + id + received + sigMap as string
// Sodium use for the generichash function BLAKE2b today (11.11.2019), mabye change in the future
$state = \Sodium\crypto_generichash_init();
//echo "prev hash: $previousTxHash\n";
if($previousTxHash != null) {
\Sodium\crypto_generichash_update($state, stream_get_contents($previousTxHash));
}
//echo "id: " . $transactionEntity->id . "\n";
\Sodium\crypto_generichash_update($state, strval($transaction->id));
//echo "received: " . $transactionEntity->received;
\Sodium\crypto_generichash_update($state, $transaction->received->i18nFormat('yyyy-MM-dd HH:mm:ss'));
\Sodium\crypto_generichash_update($state, $signatureMapString);
$transaction->tx_hash = \Sodium\crypto_generichash_final($state);
if ($this->save($transaction)) {
return true;
}
return ['state' => 'error', 'msg' => 'error by saving transaction', 'details' => $transaction->getErrors()];
}
/*!
* @return: false if no decay start block found
* @return: DateTime Object with start date if one start block found
* @return: ['state':'error'] if more than one found
*/
public function getDecayStartDate()
{
$transaction = $this->find()->where(['transaction_type_id' => 9])->select(['received'])->order(['received' => 'ASC']);
if($transaction->count() == 0) {
return null;
}
return $transaction->first()->received;
}
public function fillStateUserTransactions()
{
$missing_transaction_ids = [];
$transaction_ids = $this
->find('all')
->select(['id', 'transaction_type_id'])
->order(['id'])
->where(['transaction_type_id <' => 6])
->all()
;
$state_user_transaction_ids = $this->StateUserTransactions
->find('all')
->select(['transaction_id'])
->group(['transaction_id'])
->order(['transaction_id'])
->toArray()
;
$i2 = 0;
$count = count($state_user_transaction_ids);
foreach($transaction_ids as $tr_id) {
//echo "$i1: ";
if($i2 >= $count) {
$missing_transaction_ids[] = $tr_id;
//echo "adding to missing: $tr_id, continue <br>";
continue;
}
$stu_id = $state_user_transaction_ids[$i2];
if($tr_id->id == $stu_id->transaction_id) {
$i2++;
//echo "after i2++: $i2<br>";
} else if($tr_id->id < $stu_id->transaction_id) {
$missing_transaction_ids[] = $tr_id;
//echo "adding to missing: $tr_id<br>";
}
}
$tablesForType = [
1 => $this->TransactionCreations,
2 => $this->TransactionSendCoins,
3 => $this->TransactionGroupCreates,
4 => $this->TransactionGroupAddaddress,
5 => $this->TransactionGroupAddaddress
];
$idsForType = [];
foreach($missing_transaction_ids as $i => $transaction) {
if(!isset($idsForType[$transaction->transaction_type_id])) {
$idsForType[$transaction->transaction_type_id] = [];
}
$idsForType[$transaction->transaction_type_id][] = $transaction->id;
}
$entities = [];
$state_user_ids = [];
foreach($idsForType as $type_id => $transaction_ids) {
$specific_transactions = $tablesForType[$type_id]->find('all')->where(['transaction_id IN' => $transaction_ids])->toArray();
$keys = $tablesForType[$type_id]->getSchema()->columns();
//var_dump($keys);
foreach($specific_transactions as $specific) {
foreach($keys as $key) {
if(preg_match('/_user_id/', $key)) {
$entity = $this->StateUserTransactions->newEntity();
$entity->transaction_id = $specific['transaction_id'];
$entity->transaction_type_id = $type_id;
$entity->state_user_id = $specific[$key];
if(!in_array($entity->state_user_id, $state_user_ids)) {
array_push($state_user_ids, $entity->state_user_id);
}
$entities[] = $entity;
}
}
}
}
if(count($state_user_ids) < 1) {
return ['success' => true];
}
//var_dump($entities);
$stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers');
$existingStateUsers = $stateUsersTable->find('all')->select(['id'])->where(['id IN' => $state_user_ids])->order(['id'])->all();
$existing_state_user_ids = [];
$finalEntities = [];
foreach($existingStateUsers as $stateUser) {
$existing_state_user_ids[] = $stateUser->id;
}
foreach($entities as $entity) {
if(in_array($entity->state_user_id, $existing_state_user_ids)) {
array_push($finalEntities, $entity);
}
}
$save_results = $this->StateUserTransactions->saveManyWithErrors($finalEntities);
if(!$save_results['success']) {
$save_results['msg'] = 'error by saving at least one state user transaction';
}
return $save_results;
}
}

View File

@ -98,6 +98,11 @@ class Transaction extends TransactionBase {
return $sigPairs[0]->getPubKey();
}
public function getFirstSigningUser()
{
return $this->getStateUserFromPublickey($this->getFirstPublic());
}
public function getId() {
return $this->mProtoTransaction->getId();
}

View File

@ -60,6 +60,16 @@ class TransactionBase {
return NULL;
}
protected function getStateUserFromPublickey($publicKey) {
$stateUsersTable = self::getTable('state_users');
$stateUser = $stateUsersTable->find('all')->where(['public_key' => $publicKey])->first();
if($stateUser) {
return $stateUser;
}
return NULL;
}
protected function updateStateBalance($stateUserId, $addAmountCent, $recordDate) {
@ -72,9 +82,11 @@ class TransactionBase {
//debug($stateBalanceQuery);
if($stateBalanceQuery->count() > 0) {
$stateBalanceEntry = $stateBalanceQuery->first();
$stateBalanceEntry->amount = $stateBalanceEntry->partDecay($recordDate) + $addAmountCent;
$stateBalanceEntry->amount += $addAmountCent;
$stateBalanceEntry->amount =
$stateBalancesTable->calculateDecay($stateBalanceEntry->amount, $stateBalanceEntry->record_date, $recordDate)
+ $addAmountCent;
} else {
$stateBalanceEntry = $stateBalancesTable->newEntity();
$stateBalanceEntry->state_user_id = $stateUserId;
@ -93,11 +105,12 @@ class TransactionBase {
protected function addStateUserTransaction($stateUserId, $transactionId, $transactionTypeId, $balance, $balance_date) {
$stateUserTransactionTable = self::getTable('state_user_transactions');
$stateUserTransactions = $stateUserTransactionTable
->find('all')
->where(['state_user_id' => $stateUserId])
->order(['transaction_id DESC']);
$new_balance = $balance;
if($stateUserTransactions->count() > 0) {
$stateBalanceTable = self::getTable('state_balances');
$state_user_transaction = $stateUserTransactions->first();
@ -105,16 +118,17 @@ class TransactionBase {
$this->addError('TransactionBase::addStateUserTransaction', 'state_user_transaction is zero, no first entry exist?');
return false;
}
$balance_entity = $stateBalanceTable->newEntity();
$balance_entity->amount = $state_user_transaction->balance;
$balance_entity->record_date = $state_user_transaction->balance_date;
$balance = $balance_entity->decay + $balance;
$new_balance += $stateBalanceTable->calculateDecay(
$state_user_transaction->balance,
$state_user_transaction->balance_date,
$balance_date
);
}
$entity = $stateUserTransactionTable->newEntity();
$entity->state_user_id = $stateUserId;
$entity->transaction_id = $transactionId;
$entity->transaction_type_id = $transactionTypeId;
$entity->balance = $balance;
$entity->balance = $new_balance;
$entity->balance_date = $balance_date;
if(!$stateUserTransactionTable->save($entity)) {

View File

@ -72,7 +72,12 @@ class TransactionCreation extends TransactionBase {
return $this->protoTransactionCreation->getReceiver()->getPubkey();
}
public function getReceiverUser() {
return $this->getStateUserFromPublickey($this->getReceiverPublic());
}
public function getTargetDate() {
return new FrozenDate($this->protoTransactionCreation->getTargetDate()->getSeconds());
}
public function validate($sigPairs) {
// check if receiver public is not in signature list
@ -138,8 +143,9 @@ class TransactionCreation extends TransactionBase {
return true;
}
public function save($transaction_id, $firstPublic)
public function save($transaction_id, $firstPublic, $received)
{
$stateBalancesTable = self::getTable('stateBalances');
$transactionCreationEntity = $this->transactionCreationsTable->newEntity();
$transactionCreationEntity->transaction_id = $transaction_id;
@ -151,23 +157,27 @@ class TransactionCreation extends TransactionBase {
$this->addError('TransactionCreation::save', 'couldn\'t get state user id');
return false;
}
$transactionCreationEntity->state_user_id = $receiverUserId;
$transactionCreationEntity->amount = $this->getAmount();
$transactionCreationEntity->target_date = $this->protoTransactionCreation->getTargetDate()->getSeconds();
$target_date = new FrozenTime($transactionCreationEntity->target_date);
$decayed_balance = $stateBalancesTable->calculateDecay($this->getAmount(), $target_date, $received);
if(!$this->transactionCreationsTable->save($transactionCreationEntity)) {
$this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors()));
return false;
}
// update state balance
$final_balance = $this->updateStateBalance($receiverUserId, $this->getAmount(), $target_date);
$final_balance = $this->updateStateBalance($receiverUserId, $decayed_balance, $received);
if(false === $final_balance) {
return false;
}
// decay is a virtual field which is calculated from amount and now() - record_date
if(!$this->addStateUserTransaction($receiverUserId, $transaction_id, 1, $this->getAmount(), $target_date)) {
if(!$this->addStateUserTransaction($receiverUserId, $transaction_id, 1, $decayed_balance, $received)) {
return false;
}

View File

@ -186,11 +186,9 @@ class TransactionTransfer extends TransactionBase {
$local_transfer = $this->protoTransactionTransfer->getLocal();
$sender = $local_transfer->getSender();
$senderAmount = $sender->getAmount();
$senderUserId = $this->getStateUserId($sender->getPubkey());
$receiverUserId = $this->getStateUserId($local_transfer->getReceiver());
$senderUser = $this->getStateUserFromPublickey($sender->getPubkey());
$receiverUser = $this->getStateUserFromPublickey($local_transfer->getReceiver());
$receiverUser = $this->getStateUser($receiverUserId);
$senderUser = $this->getStateUser($senderUserId);
$serverAdminEmail = Configure::read('ServerAdminEmail');
try {
@ -218,6 +216,25 @@ class TransactionTransfer extends TransactionBase {
return true;
}
public function getSenderUser()
{
$local_transfer = $this->protoTransactionTransfer->getLocal();
return $this->getStateUserFromPublickey($local_transfer->getSender()->getPubkey());
}
public function getReceiverUser()
{
$local_transfer = $this->protoTransactionTransfer->getLocal();
return $this->getStateUserFromPublickey($local_transfer->getReceiver());
}
public function getAmount()
{
$local_transfer = $this->protoTransactionTransfer->getLocal();
$sender = $local_transfer->getSender();
return $sender->getAmount();
}
static public function fromEntity($transactionTransferEntity)
{
$protoTransfer = new \Proto\Gradido\GradidoTransfer();

View File

@ -11,7 +11,14 @@ $body['decay'] = $this->element('centToFloat', ['cent' => $body['decay'], 'preci
$body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'precision' => 2]);
foreach($body['transactions'] as $i => $transaction) {
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4]);
$useCeil = false;
if(isset($transaction['last_decay']) && $transaction['last_decay']) {
$useCeil = true;
}
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4, 'useCeil' => $useCeil]);
if(isset($transaction['creation_amount'])) {
$body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
}
}
?><?= json_encode($body) ?>

View File

@ -9,7 +9,11 @@
$cut_places = $precision - 2;
$transformAmount = $cent;
if($cut_places > 0) {
$transformAmount = floor($cent / pow(10, $cut_places));
if(isset($useCeil) && $useCeil) {
$transformAmount = ceil($cent / pow(10, $cut_places));
} else {
$transformAmount = floor($cent / pow(10, $cut_places));
}
}
if($cut_places < 0) {
$cut_places = 0;

View File

@ -0,0 +1,50 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
$this->assign('title', __('Gradido Transaktion fehlgeschlagen'));
$transaction_body = $transaction->getTransactionBody();
$specific_transaction = $transaction_body->getSpecificTransaction();
$transaction_type_name = $transaction_body->getTransactionTypeName();
?><?= __('Hallo') ?> <?= $user->first_name ?> <?= $user->last_name ?>,
<?= __('Deine letzte Transaktion ist leider fehlgeschlagen.') ?>
<?php if($reason != 'parse') :
if($transaction_type_name === 'creation') : ?><?= __('Du wolltest {0} für {1} schöpfen.',
$this->element('printGradido', ['number' => $specific_transaction->getAmount(), 'raw' => true]),
$specific_transaction->getReceiverUser()->getEmailWithName()) ?>
<?= __('Das Zieldatum war: ') . $specific_transaction->getTargetDate()->format('d.m.Y') ?>
<?php elseif($transaction_type_name === 'transfer'): ?>
<?= __('Du wolltest {0} an {1} senden.',
$this->element('printGradido', ['number' => $specific_transaction->getAmount(), 'raw' => true]),
$specific_transaction->getReceiverUser()->getEmailWithName()) ?>
<?php endif; endif; ?>
<?= __('Das ist schief gelaufen: ') ?>
<?php switch($reason) {
case 'save': echo __('Fehler beim speichern in der Datenbank. Bitte versuche es später erneut'); break;
case 'parse': echo __('Fehler beim parsen der Transaktion. Bitte versuche es später erneut'); break;
case 'validate':
$errors = $transaction->getErrors();
foreach($errors as $error) {
//echo "\t".json_encode($error);
echo "\n\t".$error[array_keys($error)[0]]."\n";
}
}?>
<?= __('Bitte antworte nicht auf diese E-Mail!'); ?>
<?= __('Mit freundlichen Grüßen'); ?>
Gradido Community Server

View File

@ -0,0 +1,23 @@
<?php
/**
* @var \App\View\AppView $this
* @var \App\Model\Entity\Migration $migration
*/
?>
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading"><?= __('Actions') ?></li>
<li><?= $this->Html->link(__('List Migrations'), ['action' => 'index']) ?></li>
</ul>
</nav>
<div class="migrations form large-9 medium-8 columns content">
<?= $this->Form->create($migration) ?>
<fieldset>
<legend><?= __('Add Migration') ?></legend>
<?php
echo $this->Form->control('db_version');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>

View File

@ -0,0 +1,29 @@
<?php
/**
* @var \App\View\AppView $this
* @var \App\Model\Entity\Migration $migration
*/
?>
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading"><?= __('Actions') ?></li>
<li><?= $this->Form->postLink(
__('Delete'),
['action' => 'delete', $migration->id],
['confirm' => __('Are you sure you want to delete # {0}?', $migration->id)]
)
?></li>
<li><?= $this->Html->link(__('List Migrations'), ['action' => 'index']) ?></li>
</ul>
</nav>
<div class="migrations form large-9 medium-8 columns content">
<?= $this->Form->create($migration) ?>
<fieldset>
<legend><?= __('Edit Migration') ?></legend>
<?php
echo $this->Form->control('db_version');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>

View File

@ -0,0 +1,47 @@
<?php
/**
* @var \App\View\AppView $this
* @var \App\Model\Entity\Migration[]|\Cake\Collection\CollectionInterface $migrations
*/
?>
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading"><?= __('Actions') ?></li>
<li><?= $this->Html->link(__('New Migration'), ['action' => 'add']) ?></li>
</ul>
</nav>
<div class="migrations index large-9 medium-8 columns content">
<h3><?= __('Migrations') ?></h3>
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col"><?= $this->Paginator->sort('id') ?></th>
<th scope="col"><?= $this->Paginator->sort('db_version') ?></th>
<th scope="col" class="actions"><?= __('Actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($migrations as $migration): ?>
<tr>
<td><?= $this->Number->format($migration->id) ?></td>
<td><?= $this->Number->format($migration->db_version) ?></td>
<td class="actions">
<?= $this->Html->link(__('View'), ['action' => 'view', $migration->id]) ?>
<?= $this->Html->link(__('Edit'), ['action' => 'edit', $migration->id]) ?>
<?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $migration->id], ['confirm' => __('Are you sure you want to delete # {0}?', $migration->id)]) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="paginator">
<ul class="pagination">
<?= $this->Paginator->first('<< ' . __('first')) ?>
<?= $this->Paginator->prev('< ' . __('previous')) ?>
<?= $this->Paginator->numbers() ?>
<?= $this->Paginator->next(__('next') . ' >') ?>
<?= $this->Paginator->last(__('last') . ' >>') ?>
</ul>
<p><?= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?></p>
</div>
</div>

View File

@ -0,0 +1,18 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
?><h2>Migrate DB</h2>
<p>Migrate from Version <?= $db_version ?></p>
<?php if($result['success']) : ?>
<h3><success>Success</success></h3>
<?php else : ?>
<h3><error>Error</error></h3>
<p><?= json_encode($result) ?></p>
<?php endif; ?>
<p><?= $this->Html->link('Back to Dashboard', ['controller' => 'Dashboard', 'action' => 'index']) ?></p>

View File

@ -0,0 +1,28 @@
<?php
/**
* @var \App\View\AppView $this
* @var \App\Model\Entity\Migration $migration
*/
?>
<nav class="large-3 medium-4 columns" id="actions-sidebar">
<ul class="side-nav">
<li class="heading"><?= __('Actions') ?></li>
<li><?= $this->Html->link(__('Edit Migration'), ['action' => 'edit', $migration->id]) ?> </li>
<li><?= $this->Form->postLink(__('Delete Migration'), ['action' => 'delete', $migration->id], ['confirm' => __('Are you sure you want to delete # {0}?', $migration->id)]) ?> </li>
<li><?= $this->Html->link(__('List Migrations'), ['action' => 'index']) ?> </li>
<li><?= $this->Html->link(__('New Migration'), ['action' => 'add']) ?> </li>
</ul>
</nav>
<div class="migrations view large-9 medium-8 columns content">
<h3><?= h($migration->id) ?></h3>
<table class="vertical-table">
<tr>
<th scope="row"><?= __('Id') ?></th>
<td><?= $this->Number->format($migration->id) ?></td>
</tr>
<tr>
<th scope="row"><?= __('Db Version') ?></th>
<td><?= $this->Number->format($migration->db_version) ?></td>
</tr>
</table>
</div>

View File

@ -35,7 +35,8 @@
<?php
echo $this->Form->control('state_group_id', ['options' => $stateGroups]);
echo $this->Form->control('transaction_type_id', ['options' => $transactionTypes]);
echo $this->Form->control('received');
echo $this->Form->control('memo', ['type' => 'textarea']);
echo $this->Form->control('blockchain_type_id', ['options' => $blockchainTypes]);
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>

View File

@ -0,0 +1,44 @@
<?php
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
/**
* MigrationsFixture
*/
class MigrationsFixture extends TestFixture
{
/**
* Fields
*
* @var array
*/
// @codingStandardsIgnoreStart
public $fields = [
'id' => ['type' => 'integer', 'length' => 10, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],
'db_version' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => true, 'default' => '0', 'comment' => '', 'precision' => null, 'autoIncrement' => null],
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
],
'_options' => [
'engine' => 'InnoDB',
'collation' => 'utf8mb4_unicode_ci'
],
];
// @codingStandardsIgnoreEnd
/**
* Init method
*
* @return void
*/
public function init()
{
$this->records = [
[
'id' => 1,
'db_version' => 1,
],
];
parent::init();
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\MigrationsController;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\MigrationsController Test Case
*
* @uses \App\Controller\MigrationsController
*/
class MigrationsControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.Migrations',
];
/**
* Test index method
*
* @return void
*/
public function testIndex()
{
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test view method
*
* @return void
*/
public function testView()
{
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test add method
*
* @return void
*/
public function testAdd()
{
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test edit method
*
* @return void
*/
public function testEdit()
{
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test delete method
*
* @return void
*/
public function testDelete()
{
$this->markTestIncomplete('Not implemented yet.');
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\MigrationsTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
/**
* App\Model\Table\MigrationsTable Test Case
*/
class MigrationsTableTest extends TestCase
{
/**
* Test subject
*
* @var \App\Model\Table\MigrationsTable
*/
public $Migrations;
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.Migrations',
];
/**
* setUp method
*
* @return void
*/
public function setUp()
{
parent::setUp();
$config = TableRegistry::getTableLocator()->exists('Migrations') ? [] : ['className' => MigrationsTable::class];
$this->Migrations = TableRegistry::getTableLocator()->get('Migrations', $config);
}
/**
* tearDown method
*
* @return void
*/
public function tearDown()
{
unset($this->Migrations);
parent::tearDown();
}
/**
* Test initialize method
*
* @return void
*/
public function testInitialize()
{
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test validationDefault method
*
* @return void
*/
public function testValidationDefault()
{
$this->markTestIncomplete('Not implemented yet.');
}
}

View File

@ -26,8 +26,8 @@ services:
#########################################################
login-server:
build:
target: login_server_debug
dockerfile: Dockerfile.debug
target: login_server_alpine_debug
dockerfile: Dockerfile.alpine-debug
security_opt:
- seccomp:unconfined
cap_add:
@ -35,8 +35,10 @@ services:
volumes:
- ./logs:/var/log/grd_login
- ./login_server/src:/code/src
- ./login_server/dependencies:/code/dependencies
- ./login_server/scripts:/code/scripts
- ./configs/login_server:/etc/grd_login
- login_build_conan:/code/build_vol
- login_build_alpine:/code/build
#########################################################
@ -101,5 +103,4 @@ services:
volumes:
frontend_node_modules:
login_build_conan:
login_build_alpine:

View File

@ -24,12 +24,12 @@ Additional session can be provided as GET-Parameter
{
"state":"success",
"balance":1590.60,
"decay":15873851,
"decay":1587.38,
"decay_date":"2021-04-16T11:47:21+00:00"
}
```
- `balance` : balance describes gradido
- `balance` : balance describes gradido as float with max two decimal places
- `decay` : balance with decay on it at the time in decay_date, so it is the precise balance of user at time of calling this function
- `decay_date`: date and time for decay amount, should be the time and date of function call
@ -59,6 +59,12 @@ Assuming: session is valid
{
"state":"success",
"transactions": [
{
"type": "decay",
"balance": "14.74",
"decay_duration": "4 days, 2 hours ago",
"memo": ""
},
{
"name": "Max Mustermann",
"email": "Maxim Mustermann",
@ -68,7 +74,17 @@ Assuming: session is valid
"balance": 192.0,
"memo": "a piece of cake :)",
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7"
}
},
{
"name": "Gradido Akademie",
"type": "creation",
"transaction_id": 10,
"date": "2021-04-15T11:19:45+00:00",
"target_date": "2021-02-01T00:00:00+00:00",
"creation_amount": "1000.0",
"balance": "1000.0",
"memo": "AGE Februar 2021"
}
],
"transactionExecutingCount": 0,
"count": 1,
@ -95,8 +111,11 @@ Transaction:
- `receiver`: user has received gradidos from another user
- `transaction_id`: id of transaction in db, in stage2 also the hedera sequence number of transaction
- `date`: date of ordering transaction (booking date)
- `balance`: Gradido
- `balance`: Gradido as float, max 2 Nachkommastellen, by creation balance after subtract decay amount
- `memo`: Details about transaction
- `decay_duration`: only for decay, time duration for decay calculation in english text
- `creation_amount`: only for creation transaction, created account before decay
- `target_date`: only by creation transaction, target date for creation, start time for decay calculation (if < as global decay start time)
## Creation transaction
Makes a creation transaction to create new Gradido
@ -117,7 +136,7 @@ with
{
"session_id" : -127182,
"email": "max.musterman@gmail.de",
"amount": 10000000,
"amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE",
"auto_sign": true
@ -128,7 +147,7 @@ with
{
"session_id" : -127182,
"username": "Maxi_786",
"amount": 10000000,
"amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE",
"auto_sign": true
@ -139,7 +158,7 @@ with
{
"session_id" : -127182,
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7",
"amount": 10000000,
"amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE",
"auto_sign": true
@ -149,8 +168,7 @@ with
- `session_id`: optional, only used if cookie GRADIDO_LOGIN not exist and no sesion_id in php session
- `email` or `username` or `pubkey`: used to identify how gets the gradidos (email and username are only aliases for pubkey)
- `amount`: gdd amount to transfer in gradido cent (10000000 = 1000,00 GDD)
- `target_date`: target date for creation, can be max 3 months before current date, but not after current date, allowed formats do you find here: https://pocoproject.org/docs/Poco.DateTimeFormat.html
- `amount`: gdd amount to transfer in gradido as float
- `memo`: text for receiver, currently saved as clear text in blockchain
- `auto_sign`: if set to true, transaction will be directly signed on login-server and proceed if needed signs are there
if set to false, transaction must be signed after on `http://localhost/account/checkTransactions`
@ -186,7 +204,7 @@ with
{
"session_id" : -127182,
"email": "max.musterman@gmail.de",
"amount": 1000000,
"amount": 100.0,
"memo":"a gift",
"auto_sign": true
}
@ -196,7 +214,7 @@ with
{
"session_id" : -127182,
"username": "Maxi_786",
"amount": 1000000,
"amount": 100.0,
"memo":"a gift",
"auto_sign": true
}
@ -206,13 +224,13 @@ with
{
"session_id" : -127182,
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7",
"amount": 1000000,
"amount": 100.0,
"memo":"a gift",
"auto_sign": true
}
```
- `session_id`: optional, only used if cookie GRADIDO_LOGIN not exist and no sesion_id in php session
- `amount`: amount to transfer, 2000000 = 200,00 GDD
- `amount`: amount to transfer as float
- `email` or `username` or `pubkey`: used to identify how gets the gradidos (email and username are only aliases for pubkey)
- `memo`: text for receiver, currently saved as clear text in blockchain
- `auto_sign`: if set to true, transaction will be directly signed on login-server and proceed if needed signs are there
@ -245,3 +263,41 @@ Without auto-sign the transaction is pending on the login-server and waits for t
// TODO Is this in line with our usability goals?
// TODO Should this not be handled client side?
# Klicktipp
## Subscribe
Subscribe current logged in user to gradido newsletter
### Request
`GET http://localhost/api/klicktipp_subscribe/[session_id]`
Parts symbolized by [] are optional
- session_id: session will be searched in php session and GRADIDO_LOGIN cookie and if not found use this
### Response
Assuming: session is valid
```json
{
"state": "success",
"redirect_url": "<redirect url from klicktipp>"
}
````
## Unsubscribe
Unsubscribe current logged in user from gradido newsletter
### Request
`GET http://localhost/api/klicktipp_unsubscribe/[session_id]`
Parts symbolized by [] are optional
- session_id: session will be searched in php session and GRADIDO_LOGIN cookie and if not found use this
### Response
Assuming: session is valid
```json
{
"state": "success"
}
````

View File

@ -0,0 +1,30 @@
# Das Locale Schöpfen
### MariaDB Insert Groups
wenn local geschöpft werden möchte kommt ein fehler das der user keiner gruppe zugeordnet ist.
folgende schritte musst du machen um eine gruppe anzulegen
hier findest du den Mysql befehl: configs/login_server/setup_db_tables/setup_docker_group.sql
in der Datei findest du folgenden Befehl
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');
# Ablauf
1. logge dich bei phpmyadmin ein http://localhost:8074/ (mariadb / root)
2. gehe auf tabelle "gradido_login"
3. gib folgenden Befehl in die console ein
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');
> es wird eine Gruppe mit id 1 angelgt. alle angelegten user sollten dieser gruppe zugeordnet sein.
das schöpfen sollte nun local funktionieren. :)
#ACHTUNG ! nach dem login kann noch zu fehlern kommen in der URL "localhostnginx/..." zu "localhost/..." ändern

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "0.9.4",
"version": "1.0.1",
"private": true,
"scripts": {
"start": "node run/server.js",

View File

@ -1,20 +1,5 @@
<template>
<div id="app" class="font-sans text-gray-800">
<header>
<b-col class="text-center">
<b-dropdown
size="sm"
split
variant="secondary"
:text="$t('language') + ' - ' + $i18n.locale"
class="m-md-2"
>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item @click.prevent="setLocale('de')">Deutsch</b-dropdown-item>
<b-dropdown-item @click.prevent="setLocale('en')">English</b-dropdown-item>
</b-dropdown>
</b-col>
</header>
<div class="">
<particles-bg type="custom" :config="config" :bg="true" />
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
@ -25,7 +10,6 @@
<script>
import { ParticlesBg } from 'particles-bg-vue'
import icon from './icon.js'
import { localeChanged } from 'vee-validate'
import DashboardLayout from '@/views/Layout/DashboardLayout_gdd.vue'
import AuthLayoutGDD from '@/views/Layout/AuthLayout_gdd.vue'
@ -54,13 +38,6 @@ export default {
},
}
},
methods: {
setLocale(locale) {
this.$i18n.locale = locale
this.$store.commit('language', this.$i18n.locale)
localeChanged(locale)
},
},
}
</script>
<style>

View File

@ -35,7 +35,7 @@ const communityAPI = {
balance: async (sessionId) => {
return apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
},
transactions: async (sessionId, firstPage = 1, items = 25, order = 'DESC') => {
transactions: async (sessionId, firstPage = 1, items = 1000, order = 'DESC') => {
return apiGet(
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
)
@ -51,14 +51,11 @@ const communityAPI = {
}
return apiPost(CONFIG.COMMUNITY_API__URL + 'createCoins/', payload)
}, */
send: async (sessionId, email, amount, memo, targetDate) => {
send: async (sessionId, data) => {
const payload = {
session_id: sessionId,
email,
amount,
memo,
target_date: targetDate,
auto_sign: true,
...data,
}
return apiPost(CONFIG.COMMUNITY_API_URL + 'sendCoins/', payload)
},

View File

@ -1,5 +1,7 @@
import axios from 'axios'
import CONFIG from '../config'
// eslint-disable-next-line no-unused-vars
import regeneratorRuntime from 'regenerator-runtime'
// control email-text sended with email verification code
const EMAIL_TYPE = {
@ -86,6 +88,16 @@ const loginAPI = {
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
updateLanguage: async (sessionId, email, language) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.language': language,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
}
export default loginAPI

View File

@ -0,0 +1,95 @@
import { mount } from '@vue/test-utils'
import LanguageSwitch from './LanguageSwitch'
const localVue = global.localVue
describe('LanguageSwitch', () => {
let wrapper
const state = {
sessionId: 1234,
email: 'he@ho.he',
language: null,
}
const mocks = {
$store: {
state,
commit: jest.fn(),
},
$i18n: {
locale: 'en',
},
}
const Wrapper = () => {
return mount(LanguageSwitch, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
describe('with locales en and de', () => {
describe('empty store', () => {
it('shows English as default navigator langauge', () => {
expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en')
})
describe('navigator language is "de-DE"', () => {
const mockNavigator = jest.fn(() => {
return 'de'
})
it('shows Deutsch as language ', async () => {
wrapper.vm.getNavigatorLanguage = mockNavigator
wrapper.vm.setCurrentLanguage()
await wrapper.vm.$nextTick()
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Deutsch - de')
})
})
describe('navigator language is "es-ES" (not supported)', () => {
const mockNavigator = jest.fn(() => {
return 'es'
})
it('shows English as language ', async () => {
wrapper.vm.getNavigatorLanguage = mockNavigator
wrapper.vm.setCurrentLanguage()
await wrapper.vm.$nextTick()
expect(wrapper.find('button.dropdown-toggle').text()).toBe('English - en')
})
})
})
describe('language "de" in store', () => {
it('shows Deutsch as language', async () => {
wrapper.vm.$store.state.language = 'de'
wrapper.vm.setCurrentLanguage()
await wrapper.vm.$nextTick()
expect(wrapper.find('button.dropdown-toggle').text()).toBe('Deutsch - de')
})
})
describe('dropdown menu', () => {
it('has English and German as languages to choose', () => {
expect(wrapper.findAll('li')).toHaveLength(2)
})
it('has English as first language to choose', () => {
expect(wrapper.findAll('li').at(0).text()).toBe('English')
})
it('has German as second language to choose', () => {
expect(wrapper.findAll('li').at(1).text()).toBe('Deutsch')
})
})
})
})
})

View File

@ -0,0 +1,72 @@
<template>
<div class="language-switch">
<b-dropdown size="sm" :text="currentLanguage.name + ' - ' + currentLanguage.code">
<b-dropdown-item
v-for="lang in locales"
@click.prevent="saveLocale(lang.code)"
:key="lang.code"
>
{{ lang.name }}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
import { localeChanged } from 'vee-validate'
import locales from '../locales/'
import loginAPI from '../apis/loginAPI'
export default {
name: 'LanguageSwitch',
data() {
return {
locales: locales,
currentLanguage: {},
}
},
methods: {
setLocale(locale) {
this.$i18n.locale = locale
this.$store.commit('language', this.$i18n.locale)
this.currentLanguage = this.getLocaleObject(locale)
localeChanged(locale)
},
async saveLocale(locale) {
this.setLocale(locale)
if (this.$store.state.sessionId && this.$store.state.email) {
const result = await loginAPI.updateLanguage(
this.$store.state.sessionId,
this.$store.state.email,
locale,
)
if (result.success) {
// toast success message
} else {
// toast error message
}
}
},
getLocaleObject(code) {
return this.locales.find((l) => l.code === code)
},
getNavigatorLanguage() {
const lang = navigator.language
if (lang) return lang.split('-')[0]
return lang
},
setCurrentLanguage() {
let locale = this.$store.state.language || this.getNavigatorLanguage() || 'en'
let object = this.getLocaleObject(locale)
if (!object) {
locale = 'en'
object = this.getLocaleObject(locale)
}
this.setLocale(locale)
this.currentLanguage = object
},
},
created() {
this.setCurrentLanguage()
},
}
</script>

View File

@ -0,0 +1,124 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import SideBar from './SideBar'
const localVue = global.localVue
describe('SideBar', () => {
let wrapper
const stubs = {
RouterLink: RouterLinkStub,
}
const propsData = {
balance: 1234.56,
}
const mocks = {
$store: {
state: {
email: 'test@example.org',
},
commit: jest.fn(),
},
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
$n: jest.fn((n) => n),
}
const Wrapper = () => {
return mount(SideBar, { localVue, mocks, stubs, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('#sidenav-main').exists()).toBeTruthy()
})
describe('balance', () => {
it('shows em-dash as balance while loading', () => {
expect(wrapper.find('div.row.text-center').text()).toBe('— GDD')
})
it('shows the when loaded', async () => {
wrapper.setProps({
pending: false,
})
await wrapper.vm.$nextTick()
expect(wrapper.find('div.row.text-center').text()).toBe('1234.56 GDD')
})
})
describe('navbar button', () => {
it('has a navbar button', () => {
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
})
it('calls showSidebar when clicked', async () => {
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
wrapper.find('button.navbar-toggler').trigger('click')
await wrapper.vm.$nextTick()
expect(spy).toHaveBeenCalledWith(true)
})
})
describe('close siedbar', () => {
it('calls closeSidebar when clicked', async () => {
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
wrapper.find('#sidenav-collapse-main').find('button.navbar-toggler').trigger('click')
await wrapper.vm.$nextTick()
expect(spy).toHaveBeenCalledWith(false)
})
})
describe('static menu items', () => {
describe("member's area", () => {
it('has a link to the elopage', () => {
expect(wrapper.findAll('li').at(0).text()).toBe('members_area')
})
it('links to the elopage', () => {
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
'https://elopage.com/s/gradido/sign_in?locale=en',
)
})
describe('with locale="de"', () => {
beforeEach(() => {
mocks.$i18n.locale = 'de'
})
it('links to the German elopage when locale is set to de', () => {
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
'https://elopage.com/s/gradido/sign_in?locale=de',
)
})
})
})
describe('logout', () => {
it('has a logout button', () => {
expect(wrapper.findAll('li').at(1).text()).toBe('logout')
})
it('emits logout when logout is clicked', async () => {
wrapper.findAll('li').at(1).find('a').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted('logout')).toEqual([[]])
})
})
describe('language-switch', () => {
it('has a language-switch button', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
})
})
})
})

View File

@ -10,7 +10,7 @@
<img :src="logo" class="navbar-brand-img" alt="..." />
</div>
<b-row class="text-center">
<b-col>{{ $n(balance) }} GDD</b-col>
<b-col>{{ pending ? '—' : $n(balance) }} GDD</b-col>
</b-row>
<slot name="mobile-right">
<ul class="nav align-items-center d-md-none">
@ -32,9 +32,7 @@
<div class="navbar-collapse-header d-md-none">
<div class="row">
<div class="col-6 collapse-brand">
<router-link to="/overview">
<img :src="logo" />
</router-link>
<img :src="logo" />
</div>
<div class="col-6 collapse-close">
<navbar-toggle-button @click.native="closeSidebar"></navbar-toggle-button>
@ -44,26 +42,29 @@
<ul class="navbar-nav">
<slot name="links"></slot>
</ul>
<hr class="my-3" />
<ul class="navbar-nav mb-md-3">
<hr class="my-2" />
<ul class="navbar-nav ml-3">
<li class="nav-item">
<a
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
class="nav-link text-lg"
class="nav-link"
target="_blank"
>
{{ $t('members_area') }}
</a>
</li>
</ul>
<hr class="my-3" />
<ul class="navbar-nav mb-md-3">
<ul class="navbar-nav ml-3">
<li class="nav-item">
<a class="nav-link text-lg pointer" @click="logout">
<a class="nav-link pointer" @click="logout">
{{ $t('logout') }}
</a>
</li>
</ul>
<div class="mt-5 ml-4">
<language-switch />
</div>
</div>
</div>
</nav>
@ -71,12 +72,14 @@
<script>
import NavbarToggleButton from '@/components/NavbarToggleButton'
import VueQrcode from 'vue-qrcode'
import LanguageSwitch from '@/components/LanguageSwitch.vue'
export default {
name: 'sidebar',
components: {
NavbarToggleButton,
VueQrcode,
LanguageSwitch,
},
props: {
logo: {
@ -84,6 +87,7 @@ export default {
default: 'img/brand/green.png',
description: 'Gradido Sidebar app logo',
},
value: { type: Array },
autoClose: {
type: Boolean,
default: true,
@ -93,6 +97,10 @@ export default {
type: Number,
default: 0,
},
pending: {
type: Boolean,
default: true,
},
},
provide() {
return {

View File

@ -3,7 +3,7 @@
"welcome":"Willkommen!",
"community": "Gemeinschaft",
"logout":"Abmelden",
"login":"Login",
"login":"Anmeldung",
"signup": "Registrieren",
"reset": "Passwort zurücksetzen",
"imprint":"Impressum",
@ -13,13 +13,12 @@
"back":"Zurück",
"send":"Senden",
"transactions":"Transaktionen",
"language":"Language",
"language":"Sprache",
"languages":{
"de": "Deutsch",
"en": "English"
},
"form": {
"attention": "<strong>Achtung!</strong> Bitte überprüfe alle deine Eingaben sehr genau. Du bist alleine Verantwortlich für deine Entscheidungen. Versendete Gradidos können nicht wieder zurück geholt werden.",
"cancel":"Abbrechen",
"reset": "Zurücksetzen",
"close":"schließen",
@ -41,10 +40,16 @@
"time":"Zeit",
"send_now":"Jetzt versenden",
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
"max_gdd_info":"maximale anzahl GDD zum versenden erreicht!",
"max_gdd_info":"Maximale anzahl GDD zum versenden erreicht!",
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
"thx":"Danke,",
"send_success":"deine Transaktion wurde erfolgreich ausgeführt"
"thx":"Danke",
"sorry":"Entschuldigung",
"send_transaction_success":"Deine Transaktion wurde erfolgreich ausgeführt",
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
"validation": {
"double": "Das Feld {field} muss eine Dezimalzahl mit zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen"
}
},
"error": {
"error":"Fehler"
@ -79,7 +84,9 @@
},
"thx": {
"title": "Danke!",
"subtitle": "Wir haben dir eine eMail gesendet."
"email": "Wir haben dir eine eMail gesendet.",
"reset": "Dein Passwort wurde geändert.",
"register": "Du bist jetzt regisriert."
},
"overview":{
"account_overview":"Kontoübersicht",
@ -117,7 +124,7 @@
}
},
"reset-password": {
"title": "Passwort Zurücksetzen",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünfitg in der GRADIDO App anmelden kannst."
"title": "Passwort zurücksetzen",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
}
}

View File

@ -13,13 +13,12 @@
"back":"Back",
"send":"Send",
"transactions":"Transactions",
"language":"Sprache",
"language":"Language",
"languages":{
"de": "Deutsch",
"en": "English"
},
"form": {
"attention": "Achtung! Bitte überprüfe alle deine Eingaben sehr genau. Du bist alleine Verantwortlich für deine Entscheidungen. Versendete Gradidos können nicht wieder zurück geholt werden.",
"cancel":"Cancel",
"reset": "Reset",
"close":"Close",
@ -40,18 +39,23 @@
"at":"at",
"time":"Time",
"send_now":"Send now",
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
"max_gdd_info":"maximale anzahl GDD zum versenden erreicht!",
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
"thx":"Thank you,",
"send_success":"your transaction was successfully completed"
"scann_code":"<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
"max_gdd_info":"Maximum number of GDDs to be sent has been reached!",
"send_check":"Confirm your payment. Please check all data again!",
"thx":"Thank you",
"sorry":"Sorry",
"send_transaction_success":"Your transaction was successfully completed",
"send_transaction_error":"Unfortunately, the transaction could not be executed!",
"validation": {
"double": "The {field} field must be a decimal with two digits",
"is-not": "You cannot send Gradidos to yourself"
}
},
"error": {
"error":"Error"
},
"transaction":{
"show_all":"View all <strong>{count}</strong> transactions.",
"show_part": "The last <strong>{count}</strong> transactions.",
"nullTransactions":"You don't have any transactions on your account yet.",
"more": "more"
},
@ -80,7 +84,9 @@
},
"thx": {
"title": "Thank you!",
"subtitle": "We have sent you an email."
"email": "We have sent you an email.",
"reset": "Your password has been changed.",
"register": "You are registred now."
},
"overview":{
"account_overview":"Account overview",
@ -119,6 +125,6 @@
},
"reset-password": {
"title": "Reset Password",
"text": "Now you can save a new password to login to the GRADIDO App in the future."
"text": "Now you can save a new password to login to the Gradido-App in the future."
}
}

View File

@ -0,0 +1,16 @@
const locales = [
{
name: 'English',
code: 'en',
iso: 'en-US',
enabled: true,
},
{
name: 'Deutsch',
code: 'de',
iso: 'de-DE',
enabled: true,
},
]
export default locales

View File

@ -2,6 +2,9 @@ import Vue from 'vue'
import DashboardPlugin from './plugins/dashboard-plugin'
import App from './App.vue'
import i18n from './i18n.js'
import { configure, extend } from 'vee-validate'
// eslint-disable-next-line camelcase
import { required, email, min, between, double, is_not } from 'vee-validate/dist/rules'
// store
import { store } from './store/store'
@ -21,6 +24,45 @@ router.beforeEach((to, from, next) => {
}
})
configure({
defaultMessage: (field, values) => {
values._field_ = i18n.t(`fields.${field}`)
return i18n.t(`validations.messages.${values._rule_}`, values)
},
})
extend('email', {
...email,
message: (_, values) => i18n.t('validations.messages.email', values),
})
extend('required', {
...required,
message: (_, values) => i18n.t('validations.messages.required', values),
})
extend('min', {
...min,
message: (_, values) => i18n.t('validations.messages.min', values),
})
extend('double', {
...double,
message: (_, values) => i18n.t('form.validation.double', values),
})
extend('between', {
...between,
message: (_, values) => i18n.t('validations.messages.between', values),
})
// eslint-disable-next-line camelcase
extend('is_not', {
// eslint-disable-next-line camelcase
...is_not,
message: (_, values) => i18n.t('form.validation.is-not', values),
})
/* eslint-disable no-new */
new Vue({
el: '#app',

View File

@ -47,8 +47,16 @@ const routes = [
component: () => import('../views/Pages/Login.vue'),
},
{
path: '/thx',
path: '/thx/:comingFrom',
component: () => import('../views/Pages/thx.vue'),
beforeEnter: (to, from, next) => {
const validFrom = ['password', 'reset', 'register']
if (!validFrom.includes(from.path.split('/')[1])) {
next({ path: '/login' })
} else {
next()
}
},
},
{
path: '/password',

View File

@ -18,7 +18,8 @@ export const mutations = {
export const actions = {
login: ({ dispatch, commit }, data) => {
commit('sessionId', data.sessionId)
commit('email', data.email)
commit('email', data.user.email)
commit('language', data.user.language)
},
logout: ({ commit, state }) => {
commit('sessionId', null)
@ -36,7 +37,7 @@ export const store = new Vuex.Store({
state: {
sessionId: null,
email: '',
language: 'en',
language: null,
modals: false,
},
getters: {},

View File

@ -35,20 +35,37 @@ describe('Vuex store', () => {
const commit = jest.fn()
const state = {}
it('calls two commits', () => {
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
expect(commit).toHaveBeenCalledTimes(2)
it('calls three commits', () => {
login(
{ commit, state },
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
)
expect(commit).toHaveBeenCalledTimes(3)
})
it('commits sessionId', () => {
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
login(
{ commit, state },
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
)
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234)
})
it('commits email', () => {
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
login(
{ commit, state },
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
)
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is')
})
it('commits language', () => {
login(
{ commit, state },
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
)
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en')
})
})
describe('logout', () => {

View File

@ -1,25 +0,0 @@
import { configure, extend } from 'vee-validate'
import { required, email, min } from 'vee-validate/dist/rules'
import i18n from './i18n'
configure({
defaultMessage: (field, values) => {
values._field_ = i18n.t(`fields.${field}`)
return i18n.t(`validations.messages.${values._rule_}`, values)
},
})
extend('email', {
...email,
message: (_, values) => i18n.t('validations.messages.email', values),
})
extend('required', {
...required,
message: (_, values) => i18n.t('validations.messages.required', values),
})
extend('min', {
...min,
message: (_, values) => i18n.t('validations.messages.min', values),
})

View File

@ -1,16 +1,19 @@
<template>
<div class="wrapper">
<div class="main-content">
<div class="main-content mt-4">
<router-view></router-view>
<language-switch class="text-center mb-5 mt-5" />
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
</div>
</div>
</template>
<script>
import ContentFooter from './ContentFooter.vue'
import LanguageSwitch from '@/components/LanguageSwitch.vue'
export default {
components: {
ContentFooter,
LanguageSwitch,
},
}
</script>

View File

@ -44,6 +44,9 @@ describe('DashboardLayoutGdd', () => {
const store = new Vuex.Store({
state,
mutations: {
language: jest.fn(),
},
})
const Wrapper = () => {
@ -59,10 +62,6 @@ describe('DashboardLayoutGdd', () => {
expect(wrapper.find('nav#sidenav-main').exists()).toBeTruthy()
})
it('has a notifications component', () => {
expect(wrapper.find('div.notifications').exists()).toBeTruthy()
})
it('has a main content div', () => {
expect(wrapper.find('div.main-content').exists()).toBeTruthy()
})
@ -79,31 +78,35 @@ describe('DashboardLayoutGdd', () => {
})
it('has five items in the navbar', () => {
expect(navbar.findAll('ul > li')).toHaveLength(2)
expect(navbar.findAll('ul > a')).toHaveLength(2)
})
it('has first item "send" in navbar', () => {
expect(navbar.findAll('ul > li').at(0).text()).toEqual('send')
expect(navbar.findAll('ul > a').at(0).text()).toEqual('send')
})
it('has first item "send" linked to overview in navbar', () => {
navbar.findAll('ul > li').at(0).trigger('click')
navbar.findAll('ul > a').at(0).trigger('click')
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/overview')
})
it('has second item "transactions" in navbar', () => {
expect(navbar.findAll('ul > li').at(1).text()).toEqual('transactions')
expect(navbar.findAll('ul > a').at(1).text()).toEqual('transactions')
})
// to do: get this working!
it.skip('has second item "transactions" linked to transactions in navbar', async () => {
navbar.findAll('ul > li > a').at(1).trigger('click')
navbar.findAll('ul > a').at(1).trigger('click')
await flushPromises()
await jest.runAllTimers()
await flushPromises()
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/transactions')
})
// it('has tree items in the navbar', () => {
// expect(navbar.findAll('ul > li')).toHaveLength(3)
// })
//
// it('has third item "My profile" in navbar', () => {
// expect(navbar.findAll('ul > li').at(2).text()).toEqual('site.navbar.my-profil')
// })

View File

@ -1,30 +1,35 @@
<template>
<div class="wrapper">
<notifications></notifications>
<side-bar @logout="logout" :balance="balance">
<side-bar @logout="logout" :balance="balance" :pending="pending">
<template slot="links">
<b-nav-item href="#!" to="/overview">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('send') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/transactions">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('transactions') }}</b-nav-text>
</b-nav-item>
<sidebar-item
:link="{
name: $t('send'),
path: '/overview',
}"
></sidebar-item>
<sidebar-item
:link="{
name: $t('transactions'),
path: '/transactions',
}"
></sidebar-item>
<!--
<b-nav-item href="#!" to="/profile">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.my-profil') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/profileedit">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.settings') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/activity">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.activity') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/profile">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.my-profil') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/profileedit">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.settings') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/activity">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.activity') }}</b-nav-text>
</b-nav-item>
-->
</template>
</side-bar>
<div class="main-content">
<dashboard-navbar :type="$route.meta.navbarType"></dashboard-navbar>
<div @click="$sidebar.displaySidebar(false)">
<fade-transition :duration="200" origin="center top" mode="out-in">
<!-- your content here -->
@ -33,6 +38,7 @@
:gdt-balance="GdtBalance"
:transactions="transactions"
:transactionCount="transactionCount"
:pending="pending"
@update-balance="updateBalance"
@update-transactions="updateTransactions"
></router-view>
@ -83,6 +89,7 @@ export default {
transactions: [],
bookedBalance: 0,
transactionCount: 0,
pending: true,
}
},
methods: {
@ -95,10 +102,12 @@ export default {
async logout() {
await loginAPI.logout(this.$store.state.sessionId)
// do we have to check success?
this.$sidebar.displaySidebar(false)
this.$store.dispatch('logout')
this.$router.push('/login')
},
async updateTransactions() {
this.pending = true
const result = await communityAPI.transactions(this.$store.state.sessionId)
if (result.success) {
this.GdtBalance = Number(result.result.data.gdtSum)
@ -106,7 +115,9 @@ export default {
this.balance = Number(result.result.data.decay)
this.bookedBalance = Number(result.result.data.balance)
this.transactionCount = result.result.data.count
this.pending = false
} else {
this.pending = true
// what to do when loading balance fails?
}
},

View File

@ -10,18 +10,18 @@
<li class="nav-item d-sm-none"></li>
</b-navbar-nav>
<b-navbar-nav class="align-items-center ml-auto ml-md-0">
<a class="pr-1 nav-link" slot="title-container pointer">
<div class="pr-1" slot="title-container ">
<b-media no-body class="align-items-center">
<span class="pb-2 text-lg font-weight-bold">
{{ $store.state.email }}
</span>
<b-media-body class="ml-2 d-none d-lg-block">
<b-media-body class="ml-2 d-none d-lg-block d-md-block">
<span class="avatar">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</span>
</b-media-body>
</b-media>
</a>
</div>
</b-navbar-nav>
</base-nav>
</template>

View File

@ -19,10 +19,6 @@ describe('AccountOverview', () => {
wrapper = Wrapper()
})
it('has a header', () => {
expect(wrapper.find('base-header-stub').exists()).toBeTruthy()
})
it('has a status line', () => {
expect(wrapper.find('gdd-status-stub').exists()).toBeTruthy()
})
@ -34,30 +30,5 @@ describe('AccountOverview', () => {
it('has a transactions table', () => {
expect(wrapper.find('gdd-table-stub').exists()).toBeTruthy()
})
describe('updateBalance method', () => {
beforeEach(async () => {
wrapper.find('gdd-send-stub').vm.$emit('update-balance', {
ammount: 42,
})
await wrapper.vm.$nextTick()
})
it('emmits updateBalance with correct value', () => {
expect(wrapper.emitted('update-balance')).toEqual([[42]])
})
})
describe('toggleShowList method', () => {
beforeEach(async () => {
wrapper.setProps({ showTransactionList: false })
wrapper.find('gdd-send-stub').vm.$emit('toggle-show-list', true)
await wrapper.vm.$nextTick()
})
it('changes the value of property showTransactionList', () => {
expect(wrapper.vm.showTransactionList).toBeTruthy()
})
})
})
})

View File

@ -1,25 +1,42 @@
<template>
<div>
<base-header class="pb-4 pt-2 bg-transparent"></base-header>
<b-container fluid class="p-2 mt-5">
<gdd-status v-if="showTransactionList" :balance="balance" :gdt-balance="GdtBalance" />
<br />
<gdd-send
<b-container fluid class="p-lg-2 mt-lg-1">
<gdd-status
v-if="showContext"
:pending="pending"
:balance="balance"
:show-transaction-list="showTransactionList"
@update-balance="updateBalance"
@toggle-show-list="toggleShowList"
:gdt-balance="GdtBalance"
/>
<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"
:date="transactionData.target_date"
:loading="loading"
@send-transaction="sendTransaction"
@on-reset="onReset"
></transaction-confirmation>
</template>
<template #transaction-result>
<transaction-result :error="error" @on-reset="onReset"></transaction-result>
</template>
</gdd-send>
<hr />
<gdd-table
v-if="showTransactionList"
v-if="showContext"
:transactions="transactions"
:max="5"
:timestamp="timestamp"
:transactionCount="transactionCount"
@update-transactions="updateTransactions"
@update-transactions="$emit('update-transactions')"
/>
<gdd-table-footer :count="transactionCount" />
<gdd-table-footer v-if="showContext" :count="transactionCount" />
</b-container>
</div>
</template>
@ -28,6 +45,17 @@ import GddStatus from './AccountOverview/GddStatus.vue'
import GddSend from './AccountOverview/GddSend.vue'
import GddTable from './AccountOverview/GddTable.vue'
import GddTableFooter from './AccountOverview/GddTableFooter.vue'
import TransactionForm from './AccountOverview/GddSend/TransactionForm.vue'
import TransactionConfirmation from './AccountOverview/GddSend/TransactionConfirmation.vue'
import TransactionResult from './AccountOverview/GddSend/TransactionResult.vue'
import communityAPI from '../../apis/communityAPI.js'
const EMPTY_TRANSACTION_DATA = {
email: '',
amount: 0,
memo: '',
target_date: '',
}
export default {
name: 'Overview',
@ -36,11 +64,17 @@ export default {
GddSend,
GddTable,
GddTableFooter,
TransactionForm,
TransactionConfirmation,
TransactionResult,
},
data() {
return {
showTransactionList: true,
timestamp: Date.now(),
transactionData: EMPTY_TRANSACTION_DATA,
error: false,
currentTransactionStep: 0,
loading: false,
}
},
props: {
@ -50,16 +84,37 @@ export default {
default: () => [],
},
transactionCount: { type: Number, default: 0 },
pending: {
type: Boolean,
default: true,
},
},
computed: {
showContext() {
return this.currentTransactionStep === 0
},
},
methods: {
toggleShowList(bool) {
this.showTransactionList = bool
setTransaction(data) {
data.target_date = new Date(Date.now()).toISOString()
this.transactionData = { ...data }
this.currentTransactionStep = 1
},
updateBalance(data) {
this.$emit('update-balance', data.ammount)
async sendTransaction() {
this.loading = true
const result = await communityAPI.send(this.$store.state.sessionId, this.transactionData)
if (result.success) {
this.error = false
this.$emit('update-balance', this.transactionData.amount)
} else {
this.error = true
}
this.currentTransactionStep = 2
this.loading = false
},
updateTransactions() {
this.$emit('update-transactions')
onReset() {
this.transactionData = EMPTY_TRANSACTION_DATA
this.currentTransactionStep = 0
},
},
}

View File

@ -1,33 +1,26 @@
import { mount } from '@vue/test-utils'
import GddSend from './GddSend'
import Vuex from 'vuex'
const localVue = global.localVue
describe('GddSend', () => {
let wrapper
const state = {
user: {
balance: 1234,
balance_gdt: 9876,
},
}
const store = new Vuex.Store({
state,
})
const mocks = {
// $n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$moment: jest.fn((m) => ({
format: () => m,
})),
$store: {
state: {
sessionId: 1234,
},
},
$i18n: {
locale: jest.fn(() => 'en'),
},
$n: jest.fn((n) => String(n)),
}
const Wrapper = () => {
return mount(GddSend, { localVue, store, mocks })
return mount(GddSend, { localVue, mocks })
}
describe('mount', () => {
@ -38,98 +31,5 @@ describe('GddSend', () => {
it('renders the component', () => {
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
})
describe('warning messages', () => {
it('has a warning message', () => {
expect(wrapper.find('div.alert-warning').find('span').text()).toBe('form.attention')
})
it('has a dismiss button', () => {
expect(wrapper.find('div.alert-warning').find('button').exists()).toBeTruthy()
})
it('dismisses the warning when button is clicked', async () => {
wrapper.find('div.alert-warning').find('button').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.find('div.alert-warning').exists()).toBeFalsy()
})
})
describe('transaction form', () => {
describe('email field', () => {
it('has an input field of type email', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('type')).toBe('email')
})
it('has an envelope icon', () => {
expect(wrapper.find('#input-group-1').find('svg').attributes('aria-label')).toBe(
'envelope',
)
})
it('has a label form.receiver', () => {
expect(wrapper.findAll('div.text-left').at(0).text()).toBe('form.receiver')
})
it('has a placeholder "E-Mail"', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('placeholder')).toBe(
'E-Mail',
)
})
})
describe('ammount field', () => {
it('has an input field of type number', () => {
expect(wrapper.find('#input-group-2').find('input').attributes('type')).toBe('number')
})
it('has an GDD text icon', () => {
expect(wrapper.find('#input-group-2').find('div.h3').text()).toBe('GDD')
})
it('has a label form.amount', () => {
expect(wrapper.findAll('div.text-left').at(1).text()).toBe('form.amount')
})
it('has a placeholder "0.01"', () => {
expect(wrapper.find('#input-group-2').find('input').attributes('placeholder')).toBe(
'0.01',
)
})
})
describe('message text box', () => {
it('has an textarea field', () => {
expect(wrapper.find('#input-group-3').find('textarea').exists()).toBeTruthy()
})
it('has an chat-right-text icon', () => {
expect(wrapper.find('#input-group-3').find('svg').attributes('aria-label')).toBe(
'chat right text',
)
})
it('has a label form.memo', () => {
expect(wrapper.findAll('div.text-left').at(2).text()).toBe('form.memo')
})
})
describe('cancel button', () => {
it('has a cancel button', () => {
expect(wrapper.find('button[type="reset"]').exists()).toBeTruthy()
})
it('has the text "form.cancel"', () => {
expect(wrapper.find('button[type="reset"]').text()).toBe('form.reset')
})
it.skip('clears the email field on click', async () => {
wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
wrapper.find('button[type="reset"]').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.form.email).toBeNull()
})
})
})
})
})

View File

@ -1,297 +1,18 @@
<template>
<div class="gdd-send">
<b-row v-show="showTransactionList">
<b-col xl="12" md="12">
<b-alert show dismissible variant="warning" class="text-center">
<span class="alert-text" v-html="$t('form.attention')"></span>
</b-alert>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<b-alert show variant="secondary">
<span class="alert-text" v-html="$t('form.scann_code')"></span>
<b-col v-show="!scan" lg="12" class="text-right">
<a @click="toggle" class="nav-link pointer">
<img src="img/icons/gradido/qr-scan-pure.png" height="50" />
</a>
</b-col>
<div v-if="scan">
<!-- <b-row>
<qrcode-capture @detect="onDetect" capture="user" ></qrcode-capture>
</b-row> -->
<qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream>
<b-container>
<b-row>
<b-col lg="8">
<b-alert show variant="secondary">
<span class="alert-text" v-html="$t('form.scann_code')"></span>
</b-alert>
</b-col>
</b-row>
</b-container>
</div>
<div @click="toggle">
<b-alert v-show="scan" show variant="primary" class="pointer text-center">
<span class="alert-text">
<strong>{{ $t('form.cancel') }}</strong>
</span>
</b-alert>
</div>
</b-alert>
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form
role="form"
@submit.prevent="handleSubmit(onSubmit)"
@reset="onReset"
v-if="show"
>
<br />
<div>
<qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone>
</div>
<br />
<div>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.receiver') }}</b-col>
<b-input-group
id="input-group-1"
label="Empfänger:"
label-for="input-1"
description="We'll never share your email with anyone else."
size="lg"
class="mb-3"
>
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="envelope" class="display-3"></b-icon>
</b-input-group-prepend>
<b-form-input
id="input-1"
v-model="form.email"
type="email"
placeholder="E-Mail"
:rules="{ required: true, email: true }"
required
style="font-size: xx-large; padding-left: 20px"
></b-form-input>
</b-input-group>
</div>
<br />
<div>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.amount') }}</b-col>
<b-col v-if="balance == form.amount" class="text-right">
<b-badge variant="primary">{{ $t('form.max_gdd_info') }}</b-badge>
</b-col>
<b-input-group
id="input-group-2"
label="Betrag:"
label-for="input-2"
size="lg"
class="mb-3"
>
<b-input-group-prepend class="p-2 d-none d-md-block">
<div class="h3 pt-3 pr-3">GDD</div>
</b-input-group-prepend>
<b-form-input
id="input-2"
v-model="form.amount"
type="number"
placeholder="0.01"
step="0.01"
min="0.01"
:max="balance"
style="font-size: xx-large; padding-left: 20px"
></b-form-input>
</b-input-group>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
<b-input-group id="input-group-3">
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="chat-right-text" class="display-3"></b-icon>
</b-input-group-prepend>
<b-form-textarea
rows="3"
v-model="form.memo"
class="pl-3"
style="font-size: x-large"
></b-form-textarea>
</b-input-group>
</div>
<br />
<b-row>
<b-col>
<b-button type="reset" variant="secondary" @click="onReset">
{{ $t('form.reset') }}
</b-button>
</b-col>
<b-col class="text-right">
<b-button type="submit" variant="success">
{{ $t('form.send_now') }}
</b-button>
</b-col>
</b-row>
<br />
</b-form>
</validation-observer>
</b-card>
</b-col>
</b-row>
<b-row v-show="row_check">
<b-col>
<div class="display-4 p-4">{{ $t('form.send_check') }}</div>
<b-list-group>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ ajaxCreateData.email }}
<b-badge variant="primary" pill>{{ $t('form.receiver') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ ajaxCreateData.amount }} GDD
<b-badge variant="primary" pill>{{ $t('form.amount') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ ajaxCreateData.memo ? ajaxCreateData.memo : '-' }}
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ $moment(ajaxCreateData.target_date).format('DD.MM.YYYY - HH:mm:ss') }}
<b-badge variant="primary" pill>{{ $t('form.date') }}</b-badge>
</b-list-group-item>
</b-list-group>
<hr />
<b-row>
<b-col>
<b-button @click="onReset">{{ $t('form.cancel') }}</b-button>
</b-col>
<b-col class="text-right">
<b-button variant="success" @click="sendTransaction">
{{ $t('form.send_now') }}
</b-button>
</b-col>
</b-row>
</b-col>
</b-row>
<b-row v-show="row_thx">
<b-col>
<div class="display-1 p-4">
{{ $t('form.thx') }}
<hr />
{{ $t('form.send_success') }}
</div>
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
<hr />
</b-col>
</b-row>
<slot :name="transactionSteps[currentTransactionStep]" />
</div>
</template>
<script>
import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader'
import { BIcon } from 'bootstrap-vue'
import communityAPI from '../../../apis/communityAPI.js'
export default {
name: 'GddSent',
components: {
QrcodeStream,
QrcodeDropZone,
BIcon,
},
props: {
balance: { type: Number, default: 0 },
showTransactionList: { type: Boolean, default: true },
},
name: 'GddSend',
data() {
return {
scan: false,
show: true,
form: {
img: '',
email: '',
amount: '',
memo: '',
},
ajaxCreateData: {
email: '',
amount: 0,
target_date: '',
memo: '',
auto_sign: true,
},
send: false,
row_check: false,
row_thx: false,
transactionSteps: ['transaction-form', 'transaction-confirmation', 'transaction-result'],
}
},
computed: {},
methods: {
toggle() {
this.scan = !this.scan
},
async onDecode(decodedString) {
const arr = JSON.parse(decodedString)
this.form.email = arr[0].email
this.form.amount = arr[0].amount
this.scan = false
},
async onSubmit() {
// event.preventDefault()
this.ajaxCreateData.email = this.form.email
this.ajaxCreateData.amount = this.form.amount
const now = new Date(Date.now()).toISOString()
this.ajaxCreateData.target_date = now
this.ajaxCreateData.memo = this.form.memo
this.$emit('toggle-show-list', false)
this.row_check = true
this.row_thx = false
},
async sendTransaction() {
const result = await communityAPI.send(
this.$store.state.sessionId,
this.ajaxCreateData.email,
this.ajaxCreateData.amount,
this.ajaxCreateData.memo,
this.ajaxCreateData.target_date,
)
if (result.success) {
this.$emit('toggle-show-list', false)
this.row_check = false
this.row_thx = true
this.$emit('update-balance', { ammount: this.ajaxCreateData.amount })
} else {
alert('error')
this.$emit('toggle-show-list', true)
this.row_check = false
this.row_thx = false
}
},
onReset(event) {
event.preventDefault()
this.form.email = ''
this.form.amount = ''
this.form.memo = ''
this.show = false
this.$emit('toggle-show-list', true)
this.row_check = false
this.row_thx = false
this.$nextTick(() => {
this.show = true
})
},
props: {
currentTransactionStep: { type: Number, default: 0 },
},
}
</script>
<style>
.pointer {
cursor: pointer;
}
video {
max-height: 665px;
max-width: 665px;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<b-alert show variant="secondary">
<span class="alert-text" v-html="$t('form.scann_code')"></span>
<b-col v-show="!scan" lg="12" class="text-right">
<a @click="toggle" class="nav-link pointer">
<img src="img/icons/gradido/qr-scan-pure.png" height="50" />
</a>
</b-col>
<div v-if="scan">
<b-row>
<qrcode-capture @detect="onDetect" capture="user"></qrcode-capture>
</b-row>
<qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream>
<b-container>
<b-row>
<b-col lg="8">
<b-alert show variant="secondary">
<span class="alert-text" v-html="$t('form.scann_code')"></span>
</b-alert>
</b-col>
</b-row>
</b-container>
</div>
<div @click="toggle">
<b-alert v-show="scan" show variant="primary" class="pointer text-center">
<span class="alert-text">
<strong>{{ $t('form.cancel') }}</strong>
</span>
</b-alert>
</div>
</b-alert>
</template>
<script>
import { QrcodeStream } from 'vue-qrcode-reader'
export default {
name: 'QrCode',
components: {
QrcodeStream,
},
data() {
return {
scan: false,
}
},
methods: {
toggle() {
this.scan = !this.scan
},
async onDecode(decodedString) {
const arr = JSON.parse(decodedString)
this.$emit('set-transaction', { email: arr[0].email, amount: arr[0].amount })
this.scan = false
},
},
}
</script>
<style>
.pointer {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<b-row>
<b-col>
<div class="display-4 p-4">{{ $t('form.send_check') }}</div>
<b-list-group>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ email }}
<b-badge variant="primary" pill>{{ $t('form.receiver') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ amount }} GDD
<b-badge variant="primary" pill>{{ $t('form.amount') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ memo ? memo : '-' }}
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ date }}
{{ $moment(date).format('DD.MM.YYYY - HH:mm:ss') }}
<b-badge variant="primary" pill>{{ $t('form.date') }}</b-badge>
</b-list-group-item>
</b-list-group>
<b-row>
<b-col>
<b-button @click="$emit('on-reset')">{{ $t('form.cancel') }}</b-button>
</b-col>
<b-col class="text-right">
<b-button variant="success" :disabled="loading" @click="$emit('send-transaction')">
{{ $t('form.send_now') }}
</b-button>
</b-col>
</b-row>
</b-col>
</b-row>
</template>
<script>
export default {
name: 'TransactionConfirmation',
props: {
email: { type: String, default: '' },
amount: { type: String, default: '' },
memo: { type: String, default: '' },
date: { type: String, default: '' },
loading: { type: Boolean, default: false },
},
}
</script>

View File

@ -0,0 +1,115 @@
import { mount } from '@vue/test-utils'
import TransactionForm from './TransactionForm'
const localVue = global.localVue
describe('GddSend', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$moment: jest.fn((m) => ({
format: () => m,
})),
$i18n: {
locale: jest.fn(() => 'en'),
},
$n: jest.fn((n) => String(n)),
$store: {
state: {
email: 'user@example.org',
},
},
}
const Wrapper = () => {
return mount(TransactionForm, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.transaction-form').exists()).toBeTruthy()
})
describe('transaction form', () => {
describe('email field', () => {
it('has an input field of type email', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('type')).toBe('email')
})
it('has an envelope icon', () => {
expect(wrapper.find('#input-group-1').find('svg').attributes('aria-label')).toBe(
'envelope',
)
})
it('has a label form.receiver', () => {
expect(wrapper.findAll('div.text-left').at(0).text()).toBe('form.receiver')
})
it('has a placeholder "E-Mail"', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('placeholder')).toBe(
'E-Mail',
)
})
})
describe('ammount field', () => {
it('has an input field of type number', () => {
expect(wrapper.find('#input-group-2').find('input').attributes('type')).toBe('number')
})
it('has an GDD text icon', () => {
expect(wrapper.find('#input-group-2').find('div.h3').text()).toBe('GDD')
})
it('has a label form.amount', () => {
expect(wrapper.findAll('div.text-left').at(1).text()).toBe('form.amount')
})
it('has a placeholder "0.01"', () => {
expect(wrapper.find('#input-group-2').find('input').attributes('placeholder')).toBe(
'0.01',
)
})
})
describe('message text box', () => {
it('has an textarea field', () => {
expect(wrapper.find('#input-group-3').find('textarea').exists()).toBeTruthy()
})
it('has an chat-right-text icon', () => {
expect(wrapper.find('#input-group-3').find('svg').attributes('aria-label')).toBe(
'chat right text',
)
})
it('has a label form.memo', () => {
expect(wrapper.findAll('div.text-left').at(2).text()).toBe('form.memo')
})
})
describe('cancel button', () => {
it('has a cancel button', () => {
expect(wrapper.find('button[type="reset"]').exists()).toBeTruthy()
})
it('has the text "form.cancel"', () => {
expect(wrapper.find('button[type="reset"]').text()).toBe('form.reset')
})
it.skip('clears the email field on click', async () => {
wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
wrapper.find('button[type="reset"]').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.form.email).toBeNull()
})
})
})
})
})

View File

@ -0,0 +1,187 @@
<template>
<b-row class="transaction-form">
<b-col xl="12" md="12">
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<!-- -<QrCode @set-transaction="setTransaction"></QrCode> -->
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)" @reset="onReset">
<!-- <div>
<qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone>
</div>
<br />
-->
<div>
<validation-provider
name="Email"
:rules="{
required: true,
email: true,
is_not: $store.state.email,
}"
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.receiver') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" :key="error" class="errors">{{ error }}</span>
</b-col>
</b-row>
<b-input-group
id="input-group-1"
label="Empfänger:"
label-for="input-1"
description="We'll never share your email with anyone else."
size="lg"
class="mb-3"
>
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="envelope" class="display-3"></b-icon>
</b-input-group-prepend>
<b-form-input
id="input-1"
v-model="form.email"
type="email"
placeholder="E-Mail"
style="font-size: xx-large; padding-left: 20px"
></b-form-input>
</b-input-group>
</validation-provider>
</div>
<br />
<div>
<validation-provider
:name="$t('form.amount')"
:rules="{
required: true,
double: [2, $i18n.locale === 'de' ? ',' : '.'],
between: [0.01, balance],
}"
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.amount') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
</b-col>
</b-row>
<b-input-group
id="input-group-2"
label="Betrag:"
label-for="input-2"
size="lg"
class="mb-3"
>
<b-input-group-prepend class="p-2 d-none d-md-block">
<div class="h3 pt-3 pr-3">GDD</div>
</b-input-group-prepend>
<b-form-input
id="input-2"
v-model="form.amount"
type="number"
:lang="$i18n.locale"
:placeholder="$n(0.01)"
step="0.01"
style="font-size: xx-large; padding-left: 20px"
></b-form-input>
</b-input-group>
</validation-provider>
<validation-provider
:rules="{
required: true,
min: 5,
max: 150,
}"
:name="$t('form.memo')"
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
</b-col>
</b-row>
<b-input-group id="input-group-3">
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="chat-right-text" class="display-3"></b-icon>
</b-input-group-prepend>
<b-form-textarea
rows="3"
v-model="form.memo"
class="pl-3"
style="font-size: x-large"
></b-form-textarea>
</b-input-group>
</validation-provider>
</div>
<br />
<b-row>
<b-col>
<b-button type="reset" variant="secondary" @click="onReset">
{{ $t('form.reset') }}
</b-button>
</b-col>
<b-col class="text-right">
<b-button type="submit" variant="success">
{{ $t('form.send_now') }}
</b-button>
</b-col>
</b-row>
<br />
</b-form>
</validation-observer>
</b-card>
</b-col>
</b-row>
</template>
<script>
// import QrCode from './QrCode'
// import { QrcodeDropZone } from 'vue-qrcode-reader'
import { BIcon } from 'bootstrap-vue'
export default {
name: 'TransactionForm',
components: {
BIcon,
// QrCode,
// QrcodeDropZone,
},
props: {
balance: { type: Number, default: 0 },
},
data() {
return {
form: {
email: '',
amount: '',
memo: '',
},
}
},
methods: {
onSubmit() {
this.$emit('set-transaction', {
email: this.form.email,
amount: this.form.amount,
memo: this.form.memo,
})
},
onReset(event) {
event.preventDefault()
this.form.email = ''
this.form.amount = ''
this.form.memo = ''
},
setTransaction(data) {
this.form.email = data.email
this.form.amount = data.amount
},
},
}
</script>
<style>
span.errors {
color: red;
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<b-row v-if="!error">
<b-col>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<div class="display-2 p-4">
{{ $t('form.thx') }}
<hr />
{{ $t('form.send_transaction_success') }}
</div>
<p class="text-center">
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
</p>
</b-card>
</b-col>
</b-row>
<b-row v-else>
<b-col>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<div class="display-2 p-4">
{{ $t('form.sorry') }}
<hr />
{{ $t('form.send_transaction_error') }}
</div>
<p class="text-center">
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
</p>
</b-card>
</b-col>
</b-row>
</template>
<script>
export default {
name: 'TransactionResult',
props: {
error: { type: Boolean, default: true },
},
}
</script>

View File

@ -24,12 +24,30 @@ describe('GddStatus', () => {
wrapper = Wrapper()
})
it('it displays the ammount of GDD', () => {
expect(wrapper.findAll('div.card-body').at(0).text()).toEqual('1234 GDD')
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')
})
})
it('it displays the ammount of GDT', () => {
expect(wrapper.findAll('div.card-body').at(1).text()).toEqual('9876 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

@ -2,24 +2,14 @@
<div>
<b-row>
<b-col>
<stats-card
type="gradient-red"
sub-title="balance_gdd"
class="mb-4 h1"
style="background-color: #ebebeba3 !important"
>
{{ $n(balance) }} GDD
</stats-card>
<b-card style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(balance) }} GDD
</b-card>
</b-col>
<b-col>
<stats-card
type="gradient-orange"
sub-title="balance_gdt"
class="mb-4 h1"
style="background-color: #ebebeba3 !important"
>
{{ $n(GdtBalance) }} GDT
</stats-card>
<b-card class="lg-h2 text-right" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(GdtBalance) }} GDT
</b-card>
</b-col>
</b-row>
</div>
@ -31,6 +21,10 @@ export default {
props: {
balance: { type: Number, default: 0 },
GdtBalance: { type: Number, default: 0 },
pending: {
type: Boolean,
default: true,
},
},
}
</script>

View File

@ -88,11 +88,9 @@
</b-card>
</b-collapse>
</b-list-group-item>
<b-list-group-item>
<b-alert v-if="transactions.length === 0" show variant="secondary">
<span class="alert-text">{{ $t('transaction.nullTransactions') }}</span>
</b-alert>
</b-list-group-item>
<div v-if="transactions.length === 0" class="mt-lg-4 text-center">
<span>{{ $t('transaction.nullTransactions') }}</span>
</div>
</b-list-group>
</div>
</template>
@ -102,7 +100,7 @@ export default {
name: 'GddTable',
props: {
transactions: { default: [] },
max: { type: Number, default: 25 },
max: { type: Number, default: 1000 },
timestamp: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
},

View File

@ -62,7 +62,7 @@ export default {
async onSubmit() {
const result = await loginAPI.sendEmail(this.form.email)
if (result.success) {
this.$router.push({ path: '/thx', params: { id: 'resetmail' } })
this.$router.push('/thx/password')
} else {
alert(result.result)
}

View File

@ -1,5 +1,4 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import Vuex from 'vuex'
import flushPromises from 'flush-promises'
import Login from './Login'
@ -16,20 +15,12 @@ describe('Login', () => {
$t: jest.fn((t) => t),
}
const state = {
loginfail: false,
}
const store = new Vuex.Store({
state,
})
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(Login, { localVue, mocks, store, stubs })
return mount(Login, { localVue, mocks, stubs })
}
describe('mount', () => {

View File

@ -34,15 +34,22 @@
v-model="model.email"
></base-input>
<base-input
alternative
class="mb-3"
name="Password"
prepend-icon="ni ni-lock-circle-open"
type="password"
:placeholder="$t('form.password')"
v-model="model.password"
></base-input>
<b-input-group>
<b-form-input
class="mb-0"
v-model="model.password"
name="Password"
:type="passwordVisible ? 'text' : 'password'"
prepend-icon="ni ni-lock-circle-open"
:placeholder="$t('form.password')"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordVisibility">
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
<b-alert v-show="loginfail" show dismissible variant="warning">
<span class="alert-text bv-example-row">
@ -98,9 +105,13 @@ export default {
},
loginfail: false,
allowRegister: CONFIG.ALLOW_REGISTER,
passwordVisible: false,
}
},
methods: {
togglePasswordVisibility() {
this.passwordVisible = !this.passwordVisible
},
async onSubmit() {
// error info ausschalten
this.loginfail = false
@ -111,7 +122,7 @@ export default {
if (result.success) {
this.$store.dispatch('login', {
sessionId: result.result.data.session_id,
email: this.model.email,
user: result.result.data.user,
})
this.$router.push('/overview')
loader.hide()

View File

@ -1,5 +1,4 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import Vuex from 'vuex'
import flushPromises from 'flush-promises'
import Register from './Register'
@ -16,20 +15,12 @@ describe('Register', () => {
$t: jest.fn((t) => t),
}
const state = {
// loginfail: false,
}
const store = new Vuex.Store({
state,
})
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(Register, { localVue, mocks, store, stubs })
return mount(Register, { localVue, mocks, stubs })
}
describe('mount', () => {

View File

@ -97,7 +97,6 @@
</p>
</div>
</transition>
<b-row class="my-4">
<b-col cols="12">
<base-input
@ -164,12 +163,7 @@ export default {
email: '',
agree: false,
},
rules: [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
],
password: '',
checkPassword: '',
passwordVisible: false,
@ -198,7 +192,7 @@ export default {
this.model.firstname = ''
this.model.lastname = ''
this.password = ''
this.$router.push('/thx')
this.$router.push('/thx/register')
} else {
this.showError = true
this.messageError = result.result.message
@ -231,6 +225,14 @@ export default {
emailFilled() {
return this.model.email !== ''
},
rules() {
return [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
]
},
passwordValidation() {
const errors = []
for (const condition of this.rules) {

View File

@ -90,12 +90,6 @@ export default {
name: 'reset',
data() {
return {
rules: [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
],
password: '',
checkPassword: '',
passwordVisible: false,
@ -113,7 +107,13 @@ export default {
const result = await loginAPI.changePassword(this.sessionId, this.email, this.password)
if (result.success) {
this.password = ''
this.$router.push('/thx')
/*
this.$store.dispatch('login', {
sessionId: result.result.data.session_id,
email: result.result.data.user.email,
})
*/
this.$router.push('/thx/reset')
} else {
alert(result.result.message)
}
@ -137,6 +137,14 @@ export default {
passwordsFilled() {
return this.password !== '' && this.checkPassword !== ''
},
rules() {
return [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
]
},
passwordValidation() {
const errors = []
for (const condition of this.rules) {

View File

@ -1,7 +1,7 @@
<template>
<div>
<div
class="header pb-8 pt-5 pt-lg-8 d-flex align-items-center profile-header"
class="header pb-8 pt-lg-4 d-flex align-items-center profile-header"
style="max-height: 200px"
></div>
<b-container fluid class="mt--6">

View File

@ -1,28 +1,52 @@
<template>
<div>
<!-- Header -->
<div class="header bg-gradient-info py-7 py-lg-8 pt-lg-9">
<div class="header py-7 py-lg-8 pt-lg-9">
<b-container>
<div class="header-body text-center mb-7">
<p class="h1">{{ $t('site.thx.title') }}</p>
<p class="h4">{{ $t('site.thx.subtitle') }}</p>
<p class="h4">{{ $t(displaySetup.subtitle) }}</p>
<hr />
<b-button to="/login">{{ $t('login') }}</b-button>
<b-button :to="displaySetup.linkTo">{{ $t(displaySetup.button) }}</b-button>
</div>
</b-container>
<div class="separator separator-bottom separator-skew zindex-100">
<svg
x="0"
y="0"
viewBox="0 0 2560 100"
preserveAspectRatio="none"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<polygon class="fill-default" points="2560 0 2560 100 0 100"></polygon>
</svg>
</div>
</div>
<!-- Page content -->
</div>
</template>
<script>
const textFields = {
password: {
subtitle: 'site.thx.email',
button: 'login',
linkTo: '/login',
},
reset: {
subtitle: 'site.thx.reset',
button: 'login',
linkTo: '/login',
},
register: {
subtitle: 'site.thx.register',
button: 'site.login.signin',
linkTo: '/overview',
},
}
export default {
name: 'Thx',
data() {
return {
displaySetup: {},
}
},
methods: {
setDisplaySetup(from) {
this.displaySetup = textFields[this.$route.params.comingFrom]
},
},
created() {
this.setDisplaySetup()
},
}
</script>

View File

@ -2,10 +2,17 @@ cmake_minimum_required(VERSION 3.18.2)
project(Gradido_LoginServer C CXX)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin" )
SET ( CMAKE_CXX_FLAGS "-std=c++17" )
SET(CMAKE_CXX_FLAGS "-std=c++17" )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
IF(WIN32)
set(CMAKE_CXX_FLAGS "/MP /EHsc")
ENDIF()
set(INSTALL_BINDIR "bin")
set(INSTALL_PLUGINDIR "bin")
include_directories(
"dependencies"
"dependencies/tinf/src/"
@ -14,13 +21,119 @@ include_directories(
"dependencies/spirit-po/include"
"dependencies/grpc/include"
"dependencies/grpc/third_party/protobuf/src"
"src/cpp/proto"
"dependencies/grpc/third_party/googletest/googletest/include"
"build"
"build/proto"
)
#if(WIN32)
#include_directories("dependencies/mariadb-connector-c/include", "dependencies/mariadb-connector-c/build/include")
set(MYSQL_INCLUDE_DIR "dependencies/mariadb-connector-c/include")
#endif(WIN32)
IF(UNIX)
include_directories(
"dependencies/poco/Crypto/include"
"dependencies/poco/Data/include"
"dependencies/poco/Util/include"
"dependencies/poco/Foundation/include"
"dependencies/poco/JSON/include"
"dependencies/poco/Net/include"
"dependencies/poco/NetSSL_OpenSSL/include"
)
ENDIF()
############################## config and add grpc ###################################
set(GRPC_PATH "${DEP_PATH}/grpc/build")
set(GRPC_ABSL_PATH "${GRPC_PATH}/third_party/abseil-cpp/absl/types")
set(GRPC_CARES_PATH "${GRPC_PATH}/third_party/cares/cares/lib")
set(GRPC_BORING_SSL_PATH "${GRPC_PATH}/third_party/boringssl-with-bazel")
set(GRPC_RE2_PATH "${GRPC_PATH}/third_party/re2")
set(GRPC_PROTOBUF_PATH "${GRPC_PATH}/third_party/protobuf")
if(WIN32)
set(GRPC_PATH "${GRPC_PATH}/Debug")
set(GRPC_ABSL_PATH "${GRPC_ABSL_PATH}/Debug")
set(GRPC_CARES_PATH "${GRPC_CARES_PATH}/Debug")
set(GRPC_BORING_SSL_PATH "${GRPC_CGRPC_BORING_SSL_PATHARES_PATH}/Debug")
set(GRPC_RE2_PATH "${GRPC_RE2_PATH}/Debug")
set(GRPC_PROTOBUF_DEBUG_PATH "${GRPC_PROTOBUF_PATH}/Debug")
endif()
set(BUILD_TESTING OFF)
#set(gRPC_SSL_PROVIDER "package")
add_subdirectory("dependencies/grpc/")
#set(gRPC_SSL_PROVIDER "package")
message(STATUS "Using gRPC via add_subdirectory.")
set(GRPC_LIBS libprotobuf grpc++_reflection grpc++)
############################## parse protobuf files ###################################
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/proto GRADIDO_PROTO_MODEL_PATH)
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/hedera/hedera-protobuf/src/main/proto HEDERA_PROTO_MODEL_PATH)
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/proto PROTOBINDING_PATH)
file(MAKE_DIRECTORY ${PROTOBINDING_PATH})
file(MAKE_DIRECTORY ${PROTOBINDING_PATH}/gradido)
file(MAKE_DIRECTORY ${PROTOBINDING_PATH}/hedera)
FILE(GLOB DATAMODEL_GRADIDO_PROTOS "${GRADIDO_PROTO_MODEL_PATH}/gradido/*.proto")
FILE(GLOB DATAMODEL_HEDERA_PROTOS "${HEDERA_PROTO_MODEL_PATH}/*.proto")
IF(WIN32)
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin PATHS "build/dependencies/grpc/bin/Debug")
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin PATHS "build/dependencies/grpc/bin/Release")
find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS "build/dependencies/grpc/third_party/protobuf/bin/Debug" )
find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS "build/dependencies/grpc/third_party/protobuf/bin/Release" )
ELSE()
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin PATHS "build/dependencies/grpc/bin")
find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS "build/dependencies/grpc/third_party/protobuf/bin" )
ENDIF()
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dependencies/grpc/third_party/protobuf/src GOOGLE_PROTOBUF_INCLUDES)
MESSAGE("protoc: ${PROTOBUF_PROTOC_EXECUTABLE} in build/dependencies/grpc/bin/${CMAKE_BUILD_TYPE}")
FOREACH(proto ${DATAMODEL_GRADIDO_PROTOS})
FILE(TO_NATIVE_PATH ${proto} proto_native)
get_filename_component(proto_parsed ${proto} NAME_WLE)
FILE(TO_NATIVE_PATH ${PROTOBINDING_PATH}/gradido/${proto_parsed}.pb.h proto_parsed_native)
IF(${proto_native} IS_NEWER_THAN ${proto_parsed_native})
EXECUTE_PROCESS(
COMMAND
${PROTOBUF_PROTOC_EXECUTABLE}
--proto_path=${GRADIDO_PROTO_MODEL_PATH}
--cpp_out=${PROTOBINDING_PATH}
${proto_native}
RESULT_VARIABLE rv
)
# Optional, but that can show the user if something have gone wrong with the proto generation
IF(${rv})
MESSAGE("Generation of data model returned ${rv} for proto ${proto_native}")
ENDIF()
ENDIF()
ENDFOREACH(proto)
FOREACH(proto ${DATAMODEL_HEDERA_PROTOS})
FILE(TO_NATIVE_PATH ${proto} proto_native)
get_filename_component(proto_parsed ${proto} NAME_WLE)
FILE(TO_NATIVE_PATH ${PROTOBINDING_PATH}/hedera/${proto_parsed}.pb.h proto_parsed_native)
IF(${proto_native} IS_NEWER_THAN ${proto_parsed_native})
EXECUTE_PROCESS(
COMMAND
${PROTOBUF_PROTOC_EXECUTABLE}
--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
--proto_path=${HEDERA_PROTO_MODEL_PATH}
--proto_path=${GOOGLE_PROTOBUF_INCLUDES}
--cpp_out=${PROTOBINDING_PATH}/hedera
--grpc_out ${PROTOBINDING_PATH}/hedera
${proto_native}
RESULT_VARIABLE rv
)
# Optional, but that can show the user if something have gone wrong with the proto generation
IF(${rv})
MESSAGE("Generation of data model returned ${rv} for proto ${proto_native}")
ENDIF()
ENDIF()
ENDFOREACH(proto)
############################## bind source files ###################################
FILE(GLOB CONTROLLER "src/cpp/controller/*.cpp" "src/cpp/controller/*.h")
FILE(GLOB TINF "dependencies/tinf/src/*.c" "dependencies/tinf/src/*.h")
@ -37,8 +150,8 @@ FILE(GLOB MODEL_GRADIDO "src/cpp/model/gradido/*.h" "src/cpp/model/gradido/*.cpp
FILE(GLOB CRYPTO "src/cpp/Crypto/*.h" "src/cpp/Crypto/*.cpp")
FILE(GLOB MAIN "src/cpp/*.cpp" "src/cpp/*.c" "src/cpp/*.h")
FILE(GLOB MYSQL "src/cpp/MySQL/*.cpp" "src/cpp/MySQL/*.h" "src/cpp/MySQL/Poco/*.h")
FILE(GLOB PROTO_GRADIDO "src/cpp/proto/gradido/*.cc" "src/cpp/proto/gradido/*.h")
FILE(GLOB PROTO_HEDERA "src/cpp/proto/hedera/*.cc" "src/cpp/proto/hedera/*.h")
FILE(GLOB PROTO_GRADIDO "build/proto/gradido/*.cc" "build/proto/gradido/*.h")
FILE(GLOB PROTO_HEDERA "build/proto/hedera/*.cc" "build/proto/hedera/*.h")
# used only for test project
FILE(GLOB TEST "src/cpp/test/*.cpp" "src/cpp/test/*.h")
@ -84,12 +197,15 @@ if(MSVC)
source_group("Test" FILES ${TEST})
endif()
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
IF(WIN32)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
#add_compile_definitions(POCO_NETSSL_WIN)
ENDIF()
add_executable(Gradido_LoginServer ${LOCAL_SRCS})
############################## config and add mariadb ###################################
set(CLIENT_PLUGIN_DIALOG OFF)
set(CLIENT_PLUGIN_MYSQL_CLEAR_PASSWORD OFF)
set(CLIENT_PLUGIN_REMOTE_IO OFF)
@ -111,99 +227,34 @@ add_subdirectory("dependencies/mariadb-connector-c")
set(DEP_PATH "dependencies")
set(MARIADB_CONNECTOR_PATH "${DEP_PATH}/mariadb-connector-c/build/libmariadb")
set(GRPC_PATH "${DEP_PATH}/grpc/build")
set(GRPC_ABSL_PATH "${GRPC_PATH}/third_party/abseil-cpp/absl/types")
set(GRPC_CARES_PATH "${GRPC_PATH}/third_party/cares/cares/lib")
set(GRPC_BORING_SSL_PATH "${GRPC_PATH}/third_party/boringssl-with-bazel")
set(GRPC_RE2_PATH "${GRPC_PATH}/third_party/re2")
set(GRPC_PROTOBUF_PATH "${GRPC_PATH}/third_party/protobuf")
#if(WIN32)
#find_library(MYSQL_LIBRARIES mariadbclient PATHS "${MARIADB_CONNECTOR_PATH}/Release" REQUIRED)
#find_library(COMPILED_MARIADB_CLIENT_DEBUG mariadbclient PATHS "${MARIADB_CONNECTOR_PATH}/Debug" REQUIRED)
#endif()
############################## config and add poco ###################################
#SET(SOME_EXPAT_OPTION OFF CACHE BOOL "Use some expat option")
IF(UNIX)
SET(ENABLE_MONGODB OFF CACHE BOOL "" FORCE)
SET(ENABLE_DATA_SQLITE OFF CACHE BOOL "" FORCE)
SET(ENABLE_REDIS OFF CACHE BOOL "" FORCE)
SET(ENABLE_PAGECOMPILER_FILE2PAGE OFF CACHE BOOL "" FORCE)
add_subdirectory("dependencies/poco")
set(POCO_LIBS PocoFoundation PocoUtil PocoNet PocoNetSSL PocoData)
ENDIF()
############################## build login server ###################################
target_link_libraries(Gradido_LoginServer ${GRPC_LIBS})
if(WIN32)
find_library(MYSQL_LIBRARIES mariadbclient PATHS "${MARIADB_CONNECTOR_PATH}/Release" REQUIRED)
find_library(COMPILED_MARIADB_CLIENT_DEBUG mariadbclient PATHS "${MARIADB_CONNECTOR_PATH}/Debug" REQUIRED)
find_library(CONAN_OPENSSL_SSL ssleay32 PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
find_library(CONAN_OPENSSL_CRYPTO libeay32 PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
set(GRPC_PATH "${GRPC_PATH}/Debug")
set(GRPC_ABSL_PATH "${GRPC_ABSL_PATH}/Debug")
set(GRPC_CARES_PATH "${GRPC_CARES_PATH}/Debug")
set(GRPC_BORING_SSL_PATH "${GRPC_CGRPC_BORING_SSL_PATHARES_PATH}/Debug")
set(GRPC_RE2_PATH "${GRPC_RE2_PATH}/Debug")
set(GRPC_PROTOBUF_DEBUG_PATH "${GRPC_PROTOBUF_PATH}/Debug")
list(REMOVE_ITEM CONAN_LIBS "libeay32.lib")
list(REMOVE_ITEM CONAN_LIBS "ssleay32.lib")
else()
#find_package(MariaDBClient PATHS "dependencies/cmake-modules")
#find_library(MYSQL_LIBRARIES libmariadb.so PATHS ${MARIADB_CONNECTOR_PATH} REQUIRED)
find_library(CONAN_OPENSSL_SSL ssl PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
find_library(CONAN_OPENSSL_CRYPTO crypto PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
set(GRPC_PROTOBUF_DEBUG_PATH "${GRPC_PROTOBUF_PATH}")
list(REMOVE_ITEM CONAN_LIBS "ssl")
list(REMOVE_ITEM CONAN_LIBS "crypto")
list(REMOVE_ITEM CONAN_LIBS "dl")
# find_library(MYSQL_LIBRARIES libmariadb.so PATHS ${MARIADB_CONNECTOR_PATH} REQUIRED)
find_library(CONAN_OPENSSL_SSL ssl PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
find_library(CONAN_OPENSSL_CRYPTO crypto PATHS ${CONAN_LIB_DIRS_OPENSSL} REQUIRED NO_DEFAULT_PATH )
set(GRPC_PROTOBUF_DEBUG_PATH "${GRPC_PROTOBUF_PATH}")
list(REMOVE_ITEM CONAN_LIBS "ssl")
list(REMOVE_ITEM CONAN_LIBS "crypto")
list(REMOVE_ITEM CONAN_LIBS "dl")
endif()
# load same ssl version like used from poco
#find_package(OpenSSL PATHS "../" NO_DEFAULT_PATH)
set(CONAN_OPENSSL_CUSTOM_LIBS
${CONAN_OPENSSL_SSL}
${CONAN_OPENSSL_CRYPTO}
)
include_directories(${CONAN_INCLUDE_DIRS_OPENSSL})
# build grpc
if(WIN32)
set(CMAKE_CXX_FLAGS "/MP /EHsc")
#set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
#set(CMAKE_CXX_FLAGS_RELEASE "-O3")
else()
set(INSTALL_BINDIR "bin")
set(INSTALL_PLUGINDIR "bin")
target_link_libraries(Gradido_LoginServer ${CONAN_OPENSSL_CUSTOM_LIBS})
endif()
set(BUILD_TESTING OFF)
set(gRPC_SSL_PROVIDER "package")
add_subdirectory("dependencies/grpc/")
set(gRPC_SSL_PROVIDER "package")
message(STATUS "Using gRPC via add_subdirectory.")
set(GRPC_LIBS libprotobuf grpc++_reflection grpc++)
target_link_libraries(Gradido_LoginServer ${CONAN_LIBS})
if(WIN32)
TARGET_LINK_LIBRARIES(Gradido_LoginServer optimized ${MYSQL_LIBRARIES} Shlwapi)
TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${GRPC_LIBS} ${PROTOBUF_DEBUG_LIBS})
target_link_libraries(Gradido_LoginServer mariadbclient ${CONAN_LIBS})
#TARGET_LINK_LIBRARIES(Gradido_LoginServer optimized ${MYSQL_LIBRARIES} Shlwapi)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
else() # unix
target_link_libraries(Gradido_LoginServer libmariadb ${GRPC_LIBS} ${CMAKE_DL_LIBS} ${PROTOBUF_LIBS})
target_link_libraries(Gradido_LoginServer ${POCO_LIBS} libmariadb sodium)
endif()
# install
@ -228,15 +279,15 @@ enable_testing()
add_executable(Gradido_LoginServer_Test ${LOCAL_SRCS} ${LOCAL_TEST_SRC})
target_compile_definitions(Gradido_LoginServer_Test PUBLIC "_TEST_BUILD")
target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} )
target_link_libraries(Gradido_LoginServer_Test ${GRPC_LIBS} )
if(WIN32)
TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi)
TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${GRPC_LIBS} ${PROTOBUF_DEBUG_LIBS})
target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} )
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${GRPC_LIBS} ${PROTOBUF_DEBUG_LIBS})
else()
target_link_libraries(Gradido_LoginServer_Test libmariadb ${GRPC_LIBS} ${CMAKE_DL_LIBS} ${PROTOBUF_LIBS})
target_link_libraries(Gradido_LoginServer_Test ${POCO_LIBS} libmariadb sodium)
endif()
add_test(NAME main COMMAND Gradido_LoginServer_Test)

View File

@ -0,0 +1,189 @@
cmake_minimum_required(VERSION 3.18.2)
project(Gradido_LoginServer C CXX)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin" )
SET ( CMAKE_CXX_FLAGS "-std=c++17" )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32)
set(CMAKE_CXX_FLAGS "/MP /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
else()
set(INSTALL_BINDIR "bin")
set(INSTALL_PLUGINDIR "bin")
endif()
include_directories(
"dependencies"
"dependencies/tinf/src"
"dependencies/spirit-po/include"
"/usr/local/include/mariadb"
"build"
"build/proto"
)
############################## config and add grpc ###################################
find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
FIND_PACKAGE(gRPC CONFIG REQUIRED)
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
set(PROTOBUF_LIBS protobuf::libprotobuf protobuf::libprotobuf-lite protobuf::libprotoc)
############################## parse proto files ###################################
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/proto GRADIDO_PROTO_MODEL_PATH)
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/hedera/hedera-protobuf/src/main/proto HEDERA_PROTO_MODEL_PATH)
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/proto PROTOBINDING_PATH)
file(MAKE_DIRECTORY ${PROTOBINDING_PATH})
file(MAKE_DIRECTORY ${PROTOBINDING_PATH}/gradido)
file(MAKE_DIRECTORY ${PROTOBINDING_PATH}/hedera)
FILE(GLOB DATAMODEL_GRADIDO_PROTOS "${GRADIDO_PROTO_MODEL_PATH}/gradido/*.proto")
FILE(GLOB DATAMODEL_HEDERA_PROTOS "${HEDERA_PROTO_MODEL_PATH}/*.proto")
FOREACH(proto ${DATAMODEL_GRADIDO_PROTOS})
FILE(TO_NATIVE_PATH ${proto} proto_native)
get_filename_component(proto_parsed ${proto} NAME_WLE)
FILE(TO_NATIVE_PATH ${PROTOBINDING_PATH}/gradido/${proto_parsed}.pb.h proto_parsed_native)
IF(${proto_native} IS_NEWER_THAN ${proto_parsed_native})
EXECUTE_PROCESS(
COMMAND
${PROTOBUF_PROTOC_EXECUTABLE}
--proto_path=${GRADIDO_PROTO_MODEL_PATH}
--cpp_out=${PROTOBINDING_PATH}
${proto_native}
RESULT_VARIABLE rv
)
# Optional, but that can show the user if something have gone wrong with the proto generation
IF(${rv})
MESSAGE("Generation of data model returned ${rv} for proto ${proto_native}")
ENDIF()
ENDIF()
ENDFOREACH(proto)
FOREACH(proto ${DATAMODEL_HEDERA_PROTOS})
FILE(TO_NATIVE_PATH ${proto} proto_native)
get_filename_component(proto_parsed ${proto} NAME_WLE)
FILE(TO_NATIVE_PATH ${PROTOBINDING_PATH}/hedera/${proto_parsed}.pb.h proto_parsed_native)
IF(${proto_native} IS_NEWER_THAN ${proto_parsed_native})
EXECUTE_PROCESS(
COMMAND
${PROTOBUF_PROTOC_EXECUTABLE}
--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
--proto_path=${HEDERA_PROTO_MODEL_PATH}
--proto_path=${GOOGLE_PROTOBUF_INCLUDES}
--cpp_out=${PROTOBINDING_PATH}/hedera
--grpc_out ${PROTOBINDING_PATH}/hedera
${proto_native}
RESULT_VARIABLE rv
)
# Optional, but that can show the user if something have gone wrong with the proto generation
IF(${rv})
MESSAGE("Generation of data model returned ${rv} for proto ${proto_native}")
ENDIF()
ENDIF()
ENDFOREACH(proto)
############################## include src files ###################################
#set(MYSQL_INCLUDE_DIR "dependencies/mariadb-connector-c/include")
FILE(GLOB CONTROLLER "src/cpp/controller/*.cpp" "src/cpp/controller/*.h")
FILE(GLOB TINF "dependencies/tinf/src/*.c" "dependencies/tinf/src/*.h")
FILE(GLOB HTTPInterface "src/cpp/HTTPInterface/*.h" "src/cpp/HTTPInterface/*.cpp")
FILE(GLOB JSONInterface "src/cpp/JSONInterface/*.h" "src/cpp/JSONInterface/*.cpp")
FILE(GLOB TASKS "src/cpp/tasks/*.cpp" "src/cpp/tasks/*.h")
FILE(GLOB SINGLETON_MANAGER "src/cpp/SingletonManager/*.h" "src/cpp/SingletonManager/*.cpp")
FILE(GLOB LIB_SRC "src/cpp/lib/*.h" "src/cpp/lib/*.cpp")
FILE(GLOB MODEL "src/cpp/model/*.h" "src/cpp/model/*.cpp")
FILE(GLOB MODEL_TABLE "src/cpp/model/table/*.h" "src/cpp/model/table/*.cpp")
FILE(GLOB MODEL_EMAIL "src/cpp/model/email/*.h" "src/cpp/model/email/*.cpp")
FILE(GLOB MODEL_HEDERA "src/cpp/model/hedera/*.h" "src/cpp/model/hedera/*.cpp")
FILE(GLOB MODEL_GRADIDO "src/cpp/model/gradido/*.h" "src/cpp/model/gradido/*.cpp")
FILE(GLOB CRYPTO "src/cpp/Crypto/*.h" "src/cpp/Crypto/*.cpp")
FILE(GLOB MAIN "src/cpp/*.cpp" "src/cpp/*.c" "src/cpp/*.h")
FILE(GLOB MYSQL "src/cpp/MySQL/*.cpp" "src/cpp/MySQL/*.h" "src/cpp/MySQL/Poco/*.h")
FILE(GLOB PROTO_GRADIDO "${PROTOBINDING_PATH}/gradido/*.cc" "${PROTOBINDING_PATH}/gradido/*.h")
FILE(GLOB PROTO_HEDERA "${PROTOBINDING_PATH}/hedera/*.cc" "${PROTOBINDING_PATH}/hedera/*.h")
# used only for test project
FILE(GLOB TEST "src/cpp/test/*.cpp" "src/cpp/test/*.h")
FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h")
FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h")
FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h")
FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h")
SET(LOCAL_SRCS
${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface}
${JSONInterface} ${CRYPTO}
${MODEL} ${MODEL_TABLE} ${MODEL_EMAIL} ${MODEL_HEDERA} ${MODEL_GRADIDO}
${SINGLETON_MANAGER} ${LIB_SRC} ${MYSQL} ${TASKS}
${PROTO_GRADIDO} ${PROTO_HEDERA}
)
SET(LOCAL_TEST_SRC
${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER}
)
aux_source_directory("src/cpp" LOCAL_SRCS)
if(MSVC)
# src
source_group("controller" FILES ${CONTROLLER})
source_group("proto\\gradido" FILES ${PROTO_GRADIDO})
source_group("proto\\hedera" FILES ${PROTO_HEDERA})
source_group("tinf" FILES ${TINF})
source_group("Crypto" FILES ${CRYPTO})
source_group("tasks" FILES ${TASKS})
source_group("model\\table" FILES ${MODEL_TABLE})
source_group("model\\email" FILES ${MODEL_EMAIL})
source_group("model\\hedera" FILES ${MODEL_HEDERA})
source_group("model\\gradido" FILES ${MODEL_GRADIDO})
source_group("model" FILES ${MODEL})
source_group("mysql" FILES ${MYSQL})
source_group("SingletonManager" FILES ${SINGLETON_MANAGER})
source_group("lib" FILES ${LIB_SRC})
source_group("HTTP-Interface" FILES ${HTTPInterface})
source_group("Json-Interface" FILES ${JSONInterface})
source_group("Test\\crypto" FILES ${TEST_CRYPTO})
source_group("Test\\model\\table" FILES ${TEST_MODEL_TABLE})
source_group("Test\\model" FILES ${TEST_MODEL})
source_group("Test\\controller" FILES ${TEST_CONTROLLER})
source_group("Test" FILES ${TEST})
endif()
add_executable(Gradido_LoginServer ${LOCAL_SRCS})
############################## find mariadb ###################################
find_library(MYSQL_LIBRARIES mariadb PATHS "/usr/local/lib/mariadb" REQUIRED)
############################## config and add poco ###################################
set(BUILD_LIB_PATH "(/usr/local/lib")
IF(CMAKE_BUILD_TYPE MATCHES Debug)
find_library(POCO_FOUNDATION_LIB PocoFoundationd PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_UTIL_LIB PocoUtild PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_NET_SSL_LIB PocoNetSSLd PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_DATA_LIB PocoDatad PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_JSON_LIB PocoJSONd PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_NET_LIB PocoNetd PATHS ${BUILD_LIB_PATH} REQUIRED)
message("use Poco Debug libs")
ELSE()
find_library(POCO_FOUNDATION_LIB PocoFoundation PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_UTIL_LIB PocoUtil PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_NET_SSL_LIB PocoNetSSL PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_DATA_LIB PocoData PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_JSON_LIB PocoJSON PATHS ${BUILD_LIB_PATH} REQUIRED)
find_library(POCO_NET_LIB PocoNet PATHS ${BUILD_LIB_PATH} REQUIRED)
message("use Poco Release libs")
ENDIF()
set(POCO_LIBS ${POCO_FOUNDATION_LIB} ${POCO_UTIL_LIB} ${POCO_NET_SSL_LIB} ${POCO_DATA_LIB} ${POCO_JSON_LIB} ${POCO_NET_LIB})
############################## build login server ###################################
target_link_libraries(Gradido_LoginServer gRPC::grpc++ ${PROTOBUF_LIBS} ${MYSQL_LIBRARIES} ${POCO_LIBS} sodium pthread)

View File

@ -1,50 +1,45 @@
#########################################################################################################
# Build release
#########################################################################################################
From conanio/gcc9 as release
FROM gradido/login_dependencies:alpine-release-1 as release
ENV DOCKER_WORKDIR="/code"
USER root
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
COPY . .
RUN cd dependencies/mariadb-connector-c && \
mkdir build && \
cd build && \
cmake -DWITH_SSL=OFF ..
RUN chmod +x compile_pot.sh && ./compile_pot.sh
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
COPY ./src ./src
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
RUN cd scripts && \
chmod +x compile_pot.sh && \
./compile_pot.sh
RUN mkdir build && \
cd build && \
conan install .. --build=missing && \
cmake .. && \
make -j$(nproc) protoc grpc_cpp_plugin
RUN chmod +x unix_parse_proto.sh && ./unix_parse_proto.sh
cmake -DCMAKE_BUILD_TYPE=Release .. && \
make -j$(nproc) Gradido_LoginServer
RUN cd build && \
cmake .. && \
make -j$(nproc) Gradido_LoginServer
CMD ["./code"]
#########################################################################################################
# run release
#########################################################################################################
#From alpine:latest as login_server
FROM alpine:latest as login_server
FROM alpine:3.13.5 as login_server
USER root
WORKDIR "/usr/bin"
COPY --from=release /code/build/bin/Gradido_LoginServer /usr/bin/
COPY --from=release /code/build/lib/libmariadb.so.3 /usr/lib/
COPY --from=release /usr/local/lib/mariadb/libmariadb.so.3 /usr/local/lib/
COPY --from=release /usr/local/lib/libPoco* /usr/local/lib/
COPY --from=release /usr/lib/libsodium.so.23 /usr/lib/
COPY --from=release /usr/lib/libstdc++.so.6 /usr/lib/
COPY --from=release /usr/lib/libgcc_s.so.1 /usr/lib/
RUN chmod +x /usr/bin/Gradido_LoginServer
ENTRYPOINT ["/usr/bin/Gradido_LoginServer"]
CMD Gradido_LoginServer
#CMD Gradido_LoginServer

View File

@ -0,0 +1,14 @@
FROM gradido/login_dependencies:alpine-debug-1 as login_server_alpine_debug
ENV DOCKER_WORKDIR="/code"
EXPOSE 1200
EXPOSE 1201
WORKDIR ${DOCKER_WORKDIR}
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
COPY ./scripts ./scripts
CMD cd scripts; ./build_debug.sh; cd ..; ./build/bin/Gradido_LoginServer

View File

@ -1,3 +1,19 @@
#########################################################################################################
# cmake
#########################################################################################################
FROM gcc:9 as cmake-gcc-9
ENV DOCKER_WORKDIR="/code"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
USER root
RUN git clone https://github.com/Kitware/CMake.git --branch=v3.19.8 \
&& cd CMake \
&& ./bootstrap \
&& make -j$(nproc) \
&& make install
#########################################################################################################
# Build debug
@ -5,14 +21,15 @@
From gradido/login_dependencies:stage2 as debug
ENV DOCKER_WORKDIR="/code"
USER root
WORKDIR ${DOCKER_WORKDIR}
COPY . .
COPY . .
RUN chmod +x unix_parse_proto.sh
RUN chmod +x compile_pot.sh
RUN cd scripts \
&& chmod +x ./prepare_build.sh \
&& ./prepare_build.sh
RUN ./compile_pot.sh
RUN ./unix_parse_proto.sh
@ -30,13 +47,14 @@ ENV DOCKER_WORKDIR="/code"
# apt-get autoremove && \
# apt-get clean && \
# rm -rf /var/lib/apt/lists/*
VOLUME /var/log/grd_login
VOLUME /code/src
EXPOSE 1200
EXPOSE 1201
WORKDIR ${DOCKER_WORKDIR}
RUN chmod +x ./Dockerfiles/build_and_run.sh
RUN chmod +x ./scripts/build_and_run.sh
CMD ./Dockerfiles/build_and_run.sh; ./build_vol/bin/Gradido_LoginServer

View File

@ -1,56 +1,93 @@
#########################################################################################################
# debug build preparation
# gcc 9 with libssl
#########################################################################################################
From conanio/gcc9 as build_debug_preparation
FROM gcc:9 as gcc_9_libssl
USER root
RUN apt-get update && \
apt-get install -y --no-install-recommends libssl-dev libboost-dev && \
apt-get autoclean && \
apt-get autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
#########################################################################################################
# gcc 9 cmake
#########################################################################################################
FROM gcc_9_libssl as gcc_9_cmake
USER root
ENV DOCKER_WORKDIR="/code"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
RUN git clone https://github.com/Kitware/CMake.git --branch=v3.19.8 && \
cd CMake && \
./bootstrap --parallel=$(nproc) && \
make -j$(nproc) && \
make install && \
cd .. && \
rm -rf CMake
#########################################################################################################
# debug build preparation
#########################################################################################################
FROM gcc_9_libssl as build_debug_dependencies
USER root
ENV DOCKER_WORKDIR="/code"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
# copy CMake from last stage
COPY --from=gcc_9_cmake /usr/local/bin/cmake /usr/local/bin/cmake
COPY --from=gcc_9_cmake /usr/local/share/cmake-3.19/Modules /usr/local/share/cmake-3.19/Modules
COPY --from=gcc_9_cmake /usr/local/share/cmake-3.19/Templates /usr/local/share/cmake-3.19/Templates
COPY ./dependencies ./dependencies
COPY ./conanfile.txt ./conanfile.txt
RUN cd dependencies/mariadb-connector-c && \
mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_SSL=OFF ..
RUN mkdir build && \
cd build && \
conan install .. --build=missing -s build_type=Debug
#########################################################################################################
# debug build proto and grpc
#########################################################################################################
From build_debug_preparation as proto_grpc
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
COPY ./scripts ./scripts
COPY ./CMakeLists.txt .
RUN cd build && \
cmake -DCMAKE_BUILD_TYPE=Debug .. && \
make -j${CPU_COUNT} protoc grpc_cpp_plugin
COPY ./src ./src
RUN cd scripts && \
chmod +x ./prepare_build.sh && \
./prepare_build.sh && \
mkdir ../build && \
chmod +x ./build_debug.sh && \
./build_debug.sh
# remove unneccessary stuff
RUN rm -rf build/bin/Gradido_LoginServer
#########################################################################################################
# parse proto and gettext
# debug build
#########################################################################################################
From proto_grpc as proto_parse
FROM gcc_9_libssl as build_debug
USER root
ENV DOCKER_WORKDIR="/code"
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
RUN mkdir src && \
cd src && \
mkdir cpp
COPY ./src/proto ./src/proto
COPY ./unix_parse_proto.sh .
RUN chmod +x unix_parse_proto.sh && \
./unix_parse_proto.sh
# copy CMake from last stage
COPY --from=build_debug_dependencies /usr/local/bin/cmake /usr/local/bin/cmake
COPY --from=build_debug_dependencies /usr/local/share/cmake-3.19 /usr/local/share/cmake-3.19
COPY --from=build_debug_dependencies /code/build/bin /code/build/bin
COPY --from=build_debug_dependencies /code/build/lib /code/build/lib
# grpc
COPY --from=build_debug_dependencies /code/build/dependencies/grpc/lib /build/dependencies/grpc/lib
COPY --from=build_debug_dependencies /code/build/dependencies/grpc/third_party/protobuf/lib /build/dependencies/grpc/third_party/protobuf/lib
COPY --from=build_debug_dependencies /code/build/dependencies/grpc/third_party/re2/lib /build/dependencies/grpc/third_party/re2/lib
COPY --from=build_debug_dependencies /code/build/dependencies/grpc/third_party/zlib/lib /build/dependencies/grpc/third_party/zlib/lib
COPY --from=build_debug_dependencies /code/build/dependencies/grpc/third_party/cares/cares/lib /build/dependencies/grpc/third_party/cares/cares/lib
COPY --from=build_debug_dependencies /code/build/dependencies/mariadb-connector-c/libmariadb/lib /build/dependencies/mariadb-connector-c/libmariadb/lib

View File

@ -0,0 +1,79 @@
##### BUILD-ENV #####
FROM alpine:3.13.5 as alpine-build
RUN apk add --update --no-cache icu-dev
RUN apk add --no-cache git openssl-dev make gcc musl-dev g++ linux-headers libintl gettext-dev boost-dev libsodium-dev
##### CMAKE #####
FROM alpine-build as alpine-gxx-cmake
RUN git clone https://github.com/Kitware/CMake.git --branch=v3.19.8 && \
cd CMake && \
./bootstrap --parallel=$(nproc) && \
make -j$(nproc) && \
make install
######### BUILD grpc ##############
FROM alpine-gxx-cmake as alpine-gxx-grpc
ARG BUILD_TYPE=Debug
RUN git clone https://github.com/grpc/grpc.git --branch=v1.37.0 --recursive -j4 && \
cd grpc && \
mkdir build && cd build && \
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. && make -j$(nproc) && \
make install
# abseil don't install themself correctly
RUN cp -r grpc/third_party/abseil-cpp/absl /usr/local/include/
# protobuf libs missing after make install
RUN cp grpc/build/third_party/protobuf/*.a /usr/local/lib/
######### BUILD poco ##############
FROM alpine-gxx-cmake as alpine-gxx-poco
ARG BUILD_TYPE=Debug
RUN git clone https://github.com/pocoproject/poco.git --recursive && \
cd poco && \
git checkout poco-1.9.4-release && \
mkdir cmake-build && cd cmake-build && \
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. && make -j$(nproc) && \
make install
######### BUILD mariadb ###########
FROM alpine-gxx-cmake as alpine-gxx-mariadb-connector
ARG BUILD_TYPE=Debug
RUN git clone https://github.com/mariadb-corporation/mariadb-connector-c.git && \
cd mariadb-connector-c && \
git checkout 159540f && \
mkdir build && cd build && \
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. && make -j$(nproc) && \
make install
#########################################################################################################
# builded libs and binaries
#########################################################################################################
FROM alpine-build as alpine-libs
# copy CMake from cmake stage
COPY --from=alpine-gxx-cmake /usr/local/bin/cmake /usr/local/bin/cmake
COPY --from=alpine-gxx-cmake /usr/local/share/cmake-3.19/Modules /usr/local/share/cmake-3.19/Modules
COPY --from=alpine-gxx-cmake /usr/local/share/cmake-3.19/Templates /usr/local/share/cmake-3.19/Templates
# copy from grpc
COPY --from=alpine-gxx-grpc /usr/local /usr/local
# COPY from poco
COPY --from=alpine-gxx-poco /usr/local /usr/local
# COPY from mariadb
COPY --from=alpine-gxx-mariadb-connector /usr/local /usr/local

View File

@ -1,48 +0,0 @@
#!/bin/bash
cp build/conan* build_vol/
cd build_vol
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j${CPU_COUNT} protoc grpc_cpp_plugin
cd ..
if [ ! -f "./src/cpp/proto/gradido/TransactionBody.pb.h" ] ; then
chmod +x unix_parse_proto.sh
echo "parse proto files"
./unix_parse_proto.sh
fi
chmod +x compile_pot.sh
./compile_pot.sh
cd build_vol
cmake ..
make -j$(nproc) Gradido_LoginServer
#echo "building done"
chmod +x ./bin/Gradido_LoginServer
#./bin/Gradido_LoginServer
: '
cd build
conan install .. --build=missing -s build_type=Debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j${CPU_COUNT} protoc grpc_cpp_plugin
cd ..
if [ ! -d "./src/cpp/proto/hedera" ] ; then
#if [ ! -f "./src/cpp/proto/gradido/TransactionBody.pb.h"] ; then
chmod +x unix_parse_proto.sh
./unix_parse_proto.sh
fi
chmod +x compile_pot.sh
./compile_pot.sh
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc) Gradido_LoginServer
#echo "building done"
chmod +x ./bin/Gradido_LoginServer
#./bin/Gradido_LoginServer
'

View File

@ -1,3 +1,5 @@
sudo apt install libsodium-dev
# get dependencies
git submodule update --init --recursive

View File

@ -1,2 +0,0 @@
#!/bin/bash
xgettext -D src/cpp -p src/LOCALE -o messages.pot --from-code=UTF-8 --files-from=files_to_translate.txt

View File

@ -2,12 +2,7 @@
Poco/1.9.4@pocoproject/stable
libsodium/1.0.18@bincrafters/stable
boost/1.71.0@conan/stable
gtest/1.8.1@bincrafters/stable
[generators]
cmake
[options]
Poco:enable_data_sqlite=False
Poco:enable_mongodb=False
Poco:enable_redis=False

@ -0,0 +1 @@
Subproject commit 8aedf4733884a25434b5c17c79c7e7dee27e6eb0

@ -1 +1 @@
Subproject commit 3fc3e5f5b8462f7666952b43381383a79b8b5d92
Subproject commit b95393dcc3640807838e8323b4e600e54d2e8116

View File

@ -1,16 +0,0 @@
#!/bin/bash
if [ ! -d "./src/cpp/proto" ] ; then
mkdir ./src/cpp/proto
fi
if [ ! -d "./src/cpp/proto/gradido" ] ; then
mkdir ./src/cpp/proto/gradido
fi
./protoc --cpp_out=./src/cpp/proto --proto_path=./src/proto ./src/proto/gradido/*.proto
if [ ! -d "./src/cpp/proto/hedera" ] ; then
mkdir ./src/cpp/proto/hedera
fi
./protoc --plugin=protoc-gen-grpc=./grpc_cpp_plugin.exe --cpp_out=./src/cpp/proto/hedera --grpc_out=./src/cpp/proto/hedera --proto_path=./src/proto/hedera/hedera-protobuf/src/main/proto ./src/proto/hedera/hedera-protobuf/src/main/proto/*.proto

View File

@ -0,0 +1,12 @@
#!/bin/sh
cd ../scripts
chmod +x compile_pot.sh
./compile_pot.sh
cd ../build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc) Gradido_LoginServer
chmod +x ./bin/Gradido_LoginServer

View File

@ -0,0 +1,2 @@
#!/bin/sh
xgettext -D ../src/cpp -p ../src/LOCALE -o messages.pot --from-code=UTF-8 --files-from=files_to_translate.txt

View File

@ -6,4 +6,5 @@ model/Session.cpp
model/email/Email.cpp
model/email/EmailCustomReply.cpp
model/email/EmailNotificationCreation.cpp
model/email/EmailNotificationTransfer.cpp
model/email/EmailNotificationTransfer.cpp
SingletonManager/SessionManager.cpp

Some files were not shown because too many files have changed in this diff Show More