Merge branch 'master' into semd-input-fields

This commit is contained in:
Moriz Wahl 2021-05-11 15:19:27 +02:00
commit e025bdc7dd
45 changed files with 1254 additions and 170 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.log *.log
/node_modules/* /node_modules/*
.vscode .vscode
.skeema
nbproject

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'), (5, 'group remove member', 'remove user from group, maybe he was moved elsewhere'),
(6, 'hedera topic create', 'create new topic on hedera'), (6, 'hedera topic create', 'create new topic on hedera'),
(7, 'hedera topic send message', 'send consensus message over hedera topic'), (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` ( CREATE TABLE `transaction_types` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `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, `text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -156,8 +156,24 @@ 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) protected function requestLogin($sessionId = 0, $redirect = true)
{ {
$this->checkForMigration($redirect);
$session = $this->getRequest()->getSession(); $session = $this->getRequest()->getSession();
// check login // check login
// disable encryption for cookies // disable encryption for cookies

View File

@ -0,0 +1,167 @@
<?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'],
[$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

@ -62,7 +62,7 @@ class StateBalancesController extends AppController
$user = $session->read('StateUser'); $user = $session->read('StateUser');
$update_balance_result = $this->StateBalances->updateBalances($user['id']); $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']); $this->addAdminError('StateBalances', 'overview', $update_balance_result, $user['id']);
} }
// sendRequestGDT // sendRequestGDT

View File

@ -313,15 +313,20 @@ class TransactionsController extends AppController
if ($this->request->is('post')) { if ($this->request->is('post')) {
$transaction = $this->Transactions->patchEntity($transaction, $this->request->getData()); $transaction = $this->Transactions->patchEntity($transaction, $this->request->getData());
if ($this->Transactions->save($transaction)) { if ($this->Transactions->save($transaction)) {
$this->Flash->success(__('The transaction has been saved.')); $result = $this->Transactions->updateTxHash($transaction, 'start decay');
if($result === true) {
return $this->redirect(['action' => 'index']); $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.')); $this->Flash->error(__('The transaction could not be saved. Please, try again.'));
} }
$stateGroups = $this->Transactions->StateGroups->find('list', ['limit' => 200]); $stateGroups = $this->Transactions->StateGroups->find('list', ['limit' => 200]);
$transactionTypes = $this->Transactions->TransactionTypes->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')) { } else if(method_exists($dateOrTime, 'i18nFormat')) {
return $dateOrTime->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT); return $dateOrTime->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT);
} else { } else {
var_dump($dateOrTime); return 0;
debug_print_backtrace(0, 6);
die("date or time unexpected object");
} }
} }
@ -59,8 +57,12 @@ class StateBalance extends Entity
// SELECT TIMESTAMPDIFF(SECOND, modified, CURDATE()) AS age_in_seconds from state_balances // SELECT TIMESTAMPDIFF(SECOND, modified, CURDATE()) AS age_in_seconds from state_balances
// decay_for_duration = decay_factor^seconds // decay_for_duration = decay_factor^seconds
// decay = gradido_cent * decay_for_duration // decay = gradido_cent * decay_for_duration
$startDate = $this->convertToTimestamp($this->record_date);
$decay_duration = intval(Time::now()->getTimestamp() - $this->convertToTimestamp($this->record_date)); if($startDate == 0) {
return $this->amount;
}
$decay_duration = intval(Time::now()->getTimestamp() - $startDate);
if($decay_duration === 0) { if($decay_duration === 0) {
return $this->amount; return $this->amount;
} }

View File

@ -0,0 +1,40 @@
<?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');
}
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[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\BlockchainType findOrCreate($search, callable $callback = null, $options = []) * @method \App\Model\Entity\BlockchainType findOrCreate($search, callable $callback = null, $options = [])
*/ */
class BlockchainTypesTable extends Table class BlockchainTypesTable extends AppTable
{ {
/** /**
* Initialize method * Initialize method
@ -65,4 +65,30 @@ class BlockchainTypesTable extends Table
return $validator; 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 <?php
namespace App\Model\Table; namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker; use Cake\ORM\RulesChecker;
use Cake\ORM\Table; use Cake\ORM\Table;
use Cake\Validation\Validator; use Cake\Validation\Validator;
use Cake\ORM\TableRegistry; use Cake\ORM\TableRegistry;
use Cake\I18n\Date; use Cake\I18n\FrozenTime;
use Cake\I18n\Time;
/** /**
* StateBalances Model * StateBalances Model
@ -28,6 +26,7 @@ use Cake\I18n\Time;
*/ */
class StateBalancesTable extends Table class StateBalancesTable extends Table
{ {
private static $startDecayDate = null;
/** /**
* Initialize method * Initialize method
* *
@ -49,6 +48,15 @@ class StateBalancesTable extends Table
'joinType' => 'INNER' '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. * Default validation rules.
@ -83,20 +91,60 @@ class StateBalancesTable extends Table
return $rules; return $rules;
} }
public function calculateDecay($startBalance, FrozenTime $startDate, FrozenTime $endDate, $withInterval = false)
public function sortTransactions($a, $b)
{ {
if ($a['date'] == $b['date']) { $decayStartDate = self::getDecayStartDateCached();
return 0; // 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) public function updateBalances($stateUserId)
{ {
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions'); $stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions'); $transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
$now = new FrozenTime;
// info: cakephp use lazy loading, query will be executed later only if needed // info: cakephp use lazy loading, query will be executed later only if needed
$state_balances = $this->find('all')->where(['state_user_id' => $stateUserId]); $state_balances = $this->find('all')->where(['state_user_id' => $stateUserId]);
$state_user_transactions = $stateUserTransactionsTable $state_user_transactions = $stateUserTransactionsTable
@ -107,7 +155,7 @@ class StateBalancesTable extends Table
; ;
if(!$state_user_transactions || !$state_user_transactions->count()) { if(!$state_user_transactions || !$state_user_transactions->count()) {
return true; return ['success' => true];
} }
// first: decide what todo // first: decide what todo
@ -128,18 +176,26 @@ class StateBalancesTable extends Table
if($state_user_transactions->count() == 0){ if($state_user_transactions->count() == 0){
$clear_state_balance = true; $clear_state_balance = true;
} else { } 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_state_user_transaction = $state_user_transactions->last();
$last_transaction = $this->newEntity(); $last_state_user_transaction_decayed = self::calculateDecay(
$last_transaction->amount = $last_state_user_transaction->balance; $last_state_user_transaction->balance,
$last_transaction->record_date = $last_state_user_transaction->balance_date; $last_state_user_transaction->balance_date,
$now);
// if entrys are nearly the same, we don't need doing anything // 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; $recalculate_state_user_transactions_balance = true;
$update_state_balance = true; $update_state_balance = true;
} }
} }
} }
if(!$recalculate_state_user_transactions_balance) { if(!$recalculate_state_user_transactions_balance) {
$last_state_user_transaction = $state_user_transactions->last(); $last_state_user_transaction = $state_user_transactions->last();
if($last_state_user_transaction && $last_state_user_transaction->balance <= 0) { 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]; $transaction = $transactions_indiced[$state_user_transaction->transaction_id];
if($transaction->transaction_type_id > 2) { if($transaction->transaction_type_id > 2) {
continue; continue;
} }
//echo "transaction id: ".$transaction->id . "<br>";
$amount_date = null;
$amount = 0; $amount = 0;
if($transaction->transaction_type_id == 1) { // creation if($transaction->transaction_type_id == 1) { // creation
$temp = $transaction->transaction_creation; $amount = intval($transaction->transaction_creation->amount);
/*$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 =
} else if($transaction->transaction_type_id == 2) { // transfer } else if($transaction->transaction_type_id == 2) { // transfer
$temp = $transaction->transaction_send_coin; $temp = $transaction->transaction_send_coin;
$amount = intval($temp->amount); $amount = intval($temp->amount);
// reverse if sender // reverse if sender
if($stateUserId == $temp->state_user_id) { if($stateUserId == $temp->state_user_id) {
$amount *= -1.0; $amount *= -1.0;
} }
$amount_date = $transaction->received;
} }
$amount_date = $transaction->received;
if($i == 0) { if($i == 0) {
$balance_cursor->amount = $amount; $balance_cursor->amount = $amount;
} else { } 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>"; //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 ['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[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\StateUserTransaction findOrCreate($search, callable $callback = null, $options = []) * @method \App\Model\Entity\StateUserTransaction findOrCreate($search, callable $callback = null, $options = [])
*/ */
class StateUserTransactionsTable extends Table class StateUserTransactionsTable extends AppTable
{ {
/** /**
* Initialize method * 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[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\TransactionType findOrCreate($search, callable $callback = null, $options = []) * @method \App\Model\Entity\TransactionType findOrCreate($search, callable $callback = null, $options = [])
*/ */
class TransactionTypesTable extends Table class TransactionTypesTable extends AppTable
{ {
/** /**
* Initialize method * Initialize method
@ -55,7 +55,7 @@ class TransactionTypesTable extends Table
$validator $validator
->scalar('name') ->scalar('name')
->maxLength('name', 24) ->maxLength('name', 45)
->requirePresence('name', 'create') ->requirePresence('name', 'create')
->notEmptyString('name'); ->notEmptyString('name');
@ -66,4 +66,55 @@ class TransactionTypesTable extends Table
return $validator; 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 <?php
namespace App\Model\Table; namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker; use Cake\ORM\RulesChecker;
use Cake\ORM\Table; use Cake\ORM\Table;
use Cake\Validation\Validator; use Cake\Validation\Validator;
use Cake\ORM\TableRegistry; use Cake\ORM\TableRegistry;
use Cake\I18n\Number; use Cake\I18n\FrozenTime;
/** /**
* Transactions Model * Transactions Model
* *
@ -172,18 +172,19 @@ class TransactionsTable extends Table
//var_dump($su_transaction); //var_dump($su_transaction);
//die("step"); //die("step");
// add decay transactions // add decay transactions
if($i > 0 && $decay == true) $prev = null;
{ if($i > 0 ) {
$prev = $stateUserTransactions[$i-1]; $prev = $stateUserTransactions[$i-1];
}
if($prev && $decay == true)
{
if($prev->balance > 0) { if($prev->balance > 0) {
// var_dump($stateUserTransactions); // var_dump($stateUserTransactions);
$current = $su_transaction; $current = $su_transaction;
//echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "<br>"; //echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "<br>";
$interval = $current->balance_date->diff($prev->balance_date); $calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
$state_balance->amount = $prev->balance; $balance = floatval($prev->balance - $calculated_decay['balance']);
$state_balance->record_date = $prev->balance_date;
$diff_amount = $state_balance->partDecay($current->balance_date);
$balance = floatval($prev->balance - $diff_amount);
// skip small decays (smaller than 0,00 GDD) // skip small decays (smaller than 0,00 GDD)
if(abs($balance) >= 100) { if(abs($balance) >= 100) {
@ -192,7 +193,7 @@ class TransactionsTable extends Table
$final_transactions[] = [ $final_transactions[] = [
'type' => 'decay', 'type' => 'decay',
'balance' => $balance, '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' => '' 'memo' => ''
]; ];
} }
@ -211,12 +212,16 @@ class TransactionsTable extends Table
echo "<br>";*/ echo "<br>";*/
if($su_transaction->transaction_type_id == 1) { // creation if($su_transaction->transaction_type_id == 1) { // creation
$creation = $transaction->transaction_creation; $creation = $transaction->transaction_creation;
$balance = $stateBalancesTable->calculateDecay($creation->amount, $creation->target_date, $transaction->received);
$final_transactions[] = [ $final_transactions[] = [
'name' => 'Gradido Akademie', 'name' => 'Gradido Akademie',
'type' => 'creation', 'type' => 'creation',
'transaction_id' => $transaction->id, 'transaction_id' => $transaction->id,
'date' => $creation->target_date, 'date' => $transaction->received,// $creation->target_date,
'balance' => $creation->amount, 'target_date' => $creation->target_date,
'creation_amount' => $creation->amount,
'balance' => $balance,
'memo' => $transaction->memo 'memo' => $transaction->memo
]; ];
} else if($su_transaction->transaction_type_id == 2) { // transfer or send coins } else if($su_transaction->transaction_type_id == 2) { // transfer or send coins
@ -256,14 +261,20 @@ class TransactionsTable extends Table
} }
if($i == $stateUserTransactionsCount-1 && $decay == true) { if($i == $stateUserTransactionsCount-1 && $decay == true) {
$state_balance->amount = $su_transaction->balance; $calculated_decay = $stateBalancesTable->calculateDecay(
$state_balance->record_date = $su_transaction->balance_date; $su_transaction->balance,
$balance = floatval($su_transaction->balance - $state_balance->decay); $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) { if($balance > 100) {
$final_transactions[] = [ $final_transactions[] = [
'type' => 'decay', 'type' => 'decay',
'balance' => $balance, 'balance' => $balance,
'decay_duration' => $su_transaction->balance_date->timeAgoInWords(), 'decay_duration' => $duration,
'memo' => '' 'memo' => ''
]; ];
} }
@ -273,4 +284,166 @@ class TransactionsTable extends Table
return $final_transactions; 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;
}
}
}
}
//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

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

View File

@ -138,8 +138,9 @@ class TransactionCreation extends TransactionBase {
return true; return true;
} }
public function save($transaction_id, $firstPublic) public function save($transaction_id, $firstPublic, $received)
{ {
$stateBalancesTable = self::getTable('stateBalances');
$transactionCreationEntity = $this->transactionCreationsTable->newEntity(); $transactionCreationEntity = $this->transactionCreationsTable->newEntity();
$transactionCreationEntity->transaction_id = $transaction_id; $transactionCreationEntity->transaction_id = $transaction_id;
@ -151,23 +152,27 @@ class TransactionCreation extends TransactionBase {
$this->addError('TransactionCreation::save', 'couldn\'t get state user id'); $this->addError('TransactionCreation::save', 'couldn\'t get state user id');
return false; return false;
} }
$transactionCreationEntity->state_user_id = $receiverUserId; $transactionCreationEntity->state_user_id = $receiverUserId;
$transactionCreationEntity->amount = $this->getAmount(); $transactionCreationEntity->amount = $this->getAmount();
$transactionCreationEntity->target_date = $this->protoTransactionCreation->getTargetDate()->getSeconds(); $transactionCreationEntity->target_date = $this->protoTransactionCreation->getTargetDate()->getSeconds();
$target_date = new FrozenTime($transactionCreationEntity->target_date); $target_date = new FrozenTime($transactionCreationEntity->target_date);
$decayed_balance = $stateBalancesTable->calculateDecay($this->getAmount(), $target_date, $received);
if(!$this->transactionCreationsTable->save($transactionCreationEntity)) { if(!$this->transactionCreationsTable->save($transactionCreationEntity)) {
$this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors())); $this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors()));
return false; return false;
} }
// update state balance // update state balance
$final_balance = $this->updateStateBalance($receiverUserId, $this->getAmount(), $target_date); $final_balance = $this->updateStateBalance($receiverUserId, $decayed_balance, $received);
if(false === $final_balance) { if(false === $final_balance) {
return false; return false;
} }
// decay is a virtual field which is calculated from amount and now() - record_date // 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; return false;
} }

View File

@ -12,6 +12,9 @@ $body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'pre
foreach($body['transactions'] as $i => $transaction) { foreach($body['transactions'] as $i => $transaction) {
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4]); $body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4]);
if(isset($transaction['creation_amount'])) {
$body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
}
} }
?><?= json_encode($body) ?> ?><?= json_encode($body) ?>

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 <?php
echo $this->Form->control('state_group_id', ['options' => $stateGroups]); echo $this->Form->control('state_group_id', ['options' => $stateGroups]);
echo $this->Form->control('transaction_type_id', ['options' => $transactionTypes]); 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> </fieldset>
<?= $this->Form->button(__('Submit')) ?> <?= $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

@ -24,12 +24,12 @@ Additional session can be provided as GET-Parameter
{ {
"state":"success", "state":"success",
"balance":1590.60, "balance":1590.60,
"decay":15873851, "decay":1587.38,
"decay_date":"2021-04-16T11:47:21+00:00" "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` : 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 - `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", "state":"success",
"transactions": [ "transactions": [
{
"type": "decay",
"balance": "14.74",
"decay_duration": "4 days, 2 hours ago",
"memo": ""
},
{ {
"name": "Max Mustermann", "name": "Max Mustermann",
"email": "Maxim Mustermann", "email": "Maxim Mustermann",
@ -68,7 +74,17 @@ Assuming: session is valid
"balance": 192.0, "balance": 192.0,
"memo": "a piece of cake :)", "memo": "a piece of cake :)",
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7" "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, "transactionExecutingCount": 0,
"count": 1, "count": 1,
@ -95,8 +111,11 @@ Transaction:
- `receiver`: user has received gradidos from another user - `receiver`: user has received gradidos from another user
- `transaction_id`: id of transaction in db, in stage2 also the hedera sequence number of transaction - `transaction_id`: id of transaction in db, in stage2 also the hedera sequence number of transaction
- `date`: date of ordering transaction (booking date) - `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 - `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 ## Creation transaction
Makes a creation transaction to create new Gradido Makes a creation transaction to create new Gradido
@ -117,7 +136,7 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"email": "max.musterman@gmail.de", "email": "max.musterman@gmail.de",
"amount": 10000000, "amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00", "target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE", "memo":"AGE",
"auto_sign": true "auto_sign": true
@ -128,7 +147,7 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"username": "Maxi_786", "username": "Maxi_786",
"amount": 10000000, "amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00", "target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE", "memo":"AGE",
"auto_sign": true "auto_sign": true
@ -139,7 +158,7 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7", "pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7",
"amount": 10000000, "amount": 1000.0,
"target_date":"2021-02-19T13:25:36+00:00", "target_date":"2021-02-19T13:25:36+00:00",
"memo":"AGE", "memo":"AGE",
"auto_sign": true "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 - `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) - `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) - `amount`: gdd amount to transfer in gradido as float
- `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
- `memo`: text for receiver, currently saved as clear text in blockchain - `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 - `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` if set to false, transaction must be signed after on `http://localhost/account/checkTransactions`
@ -186,7 +204,7 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"email": "max.musterman@gmail.de", "email": "max.musterman@gmail.de",
"amount": 1000000, "amount": 100.0,
"memo":"a gift", "memo":"a gift",
"auto_sign": true "auto_sign": true
} }
@ -196,7 +214,7 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"username": "Maxi_786", "username": "Maxi_786",
"amount": 1000000, "amount": 100.0,
"memo":"a gift", "memo":"a gift",
"auto_sign": true "auto_sign": true
} }
@ -206,13 +224,13 @@ with
{ {
"session_id" : -127182, "session_id" : -127182,
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7", "pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7",
"amount": 1000000, "amount": 100.0,
"memo":"a gift", "memo":"a gift",
"auto_sign": true "auto_sign": true
} }
``` ```
- `session_id`: optional, only used if cookie GRADIDO_LOGIN not exist and no sesion_id in php session - `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) - `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 - `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 - `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 Is this in line with our usability goals?
// TODO Should this not be handled client side? // 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

@ -50,6 +50,7 @@
<a <a
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`" :href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
class="nav-link text-lg" class="nav-link text-lg"
target="_blank"
> >
{{ $t('members_area') }} {{ $t('members_area') }}
</a> </a>

View File

@ -3,7 +3,7 @@
"welcome":"Willkommen!", "welcome":"Willkommen!",
"community": "Gemeinschaft", "community": "Gemeinschaft",
"logout":"Abmelden", "logout":"Abmelden",
"login":"Login", "login":"Anmeldung",
"signup": "Registrieren", "signup": "Registrieren",
"reset": "Passwort zurücksetzen", "reset": "Passwort zurücksetzen",
"imprint":"Impressum", "imprint":"Impressum",
@ -13,7 +13,7 @@
"back":"Zurück", "back":"Zurück",
"send":"Senden", "send":"Senden",
"transactions":"Transaktionen", "transactions":"Transaktionen",
"language":"Language", "language":"Sprache",
"languages":{ "languages":{
"de": "Deutsch", "de": "Deutsch",
"en": "English" "en": "English"
@ -41,10 +41,12 @@
"time":"Zeit", "time":"Zeit",
"send_now":"Jetzt versenden", "send_now":"Jetzt versenden",
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners", "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!", "send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
"thx":"Danke,", "thx":"Danke",
"send_success":"deine Transaktion wurde erfolgreich ausgeführt", "sorry":"Entschuldigung",
"send_transaction_success":"Deine Transaktion wurde erfolgreich ausgeführt",
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
"validation": { "validation": {
"double": "Das Feld {field} muss eine Dezimalzahl mit zwei Nachkommastellen sein", "double": "Das Feld {field} muss eine Dezimalzahl mit zwei Nachkommastellen sein",
"is-not": "Du kannst Dir selbst keine Gradidos überweisen" "is-not": "Du kannst Dir selbst keine Gradidos überweisen"
@ -121,7 +123,7 @@
} }
}, },
"reset-password": { "reset-password": {
"title": "Passwort Zurücksetzen", "title": "Passwort zurücksetzen",
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünfitg in der GRADIDO App anmelden kannst." "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,13 @@
"back":"Back", "back":"Back",
"send":"Send", "send":"Send",
"transactions":"Transactions", "transactions":"Transactions",
"language":"Sprache", "language":"Language",
"languages":{ "languages":{
"de": "Deutsch", "de": "Deutsch",
"en": "English" "en": "English"
}, },
"form": { "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.", "attention": "Attention! Please check all your entries very carefully. You are solely responsible for your decisions. Sent Gradidos cannot be retrieved.",
"cancel":"Cancel", "cancel":"Cancel",
"reset": "Reset", "reset": "Reset",
"close":"Close", "close":"Close",
@ -40,11 +40,13 @@
"at":"at", "at":"at",
"time":"Time", "time":"Time",
"send_now":"Send now", "send_now":"Send now",
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners", "scann_code":"<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
"max_gdd_info":"maximale anzahl GDD zum versenden erreicht!", "max_gdd_info":"Maximum number of GDDs to be sent has been reached!",
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!", "send_check":"Confirm your payment. Please check all data again!",
"thx":"Thank you,", "thx":"Thank you",
"send_success":"your transaction was successfully completed", "sorry":"Sorry",
"send_transaction_success":"Your transaction was successfully completed",
"send_transaction_error":"Unfortunately, the transaction could not be executed!",
"validation": { "validation": {
"double": "The {field} field must be a decimal with two digits", "double": "The {field} field must be a decimal with two digits",
"is-not": "You cannot send Gradidos to yourself" "is-not": "You cannot send Gradidos to yourself"
@ -123,6 +125,6 @@
}, },
"reset-password": { "reset-password": {
"title": "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

@ -98,6 +98,12 @@ describe('ContentFooter', () => {
) )
}) })
it('links to the support', () => {
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
'https://gradido.net/en/contact/',
)
})
describe('links are localized', () => { describe('links are localized', () => {
beforeEach(() => { beforeEach(() => {
mocks.$i18n.locale = 'de' mocks.$i18n.locale = 'de'
@ -132,6 +138,12 @@ describe('ContentFooter', () => {
'https://docs.google.com/document/d/1jZp-DiiMPI9ZPNXmjsvOQ1BtnfDFfx8BX7CDmA8KKjY/edit?usp=sharing', 'https://docs.google.com/document/d/1jZp-DiiMPI9ZPNXmjsvOQ1BtnfDFfx8BX7CDmA8KKjY/edit?usp=sharing',
) )
}) })
it('links to the German support-page when locale is de', () => {
expect(wrapper.findAll('a.nav-link').at(4).attributes('href')).toEqual(
'https://gradido.net/de/contact/',
)
})
}) })
}) })
}) })

View File

@ -4,7 +4,11 @@
<b-col> <b-col>
<div class="copyright text-center text-lg-center text-muted"> <div class="copyright text-center text-lg-center text-muted">
© {{ year }} © {{ year }}
<a :href="`https://gradido.net/${$i18n.locale}`" class="font-weight-bold ml-1"> <a
:href="`https://gradido.net/${$i18n.locale}`"
class="font-weight-bold ml-1"
target="_blank"
>
Gradido-Akademie Gradido-Akademie
</a> </a>
| |
@ -39,6 +43,9 @@
> >
{{ $t('whitepaper') }} {{ $t('whitepaper') }}
</b-nav-item> </b-nav-item>
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/contact/`" target="_blank">
{{ $t('site.navbar.support') }}
</b-nav-item>
</b-nav> </b-nav>
</b-col> </b-col>
</b-row> </b-row>

View File

@ -45,17 +45,7 @@ describe('GddSend', () => {
describe('warning messages', () => { describe('warning messages', () => {
it('has a warning message', () => { it('has a warning message', () => {
expect(wrapper.find('div.alert-warning').find('span').text()).toBe('form.attention') expect(wrapper.find('div.alert-default').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()
}) })
}) })

View File

@ -2,10 +2,11 @@
<div class="gdd-send"> <div class="gdd-send">
<b-row v-show="showTransactionList"> <b-row v-show="showTransactionList">
<b-col xl="12" md="12"> <b-col xl="12" md="12">
<b-alert show dismissible variant="warning" class="text-center"> <b-alert show dismissible variant="default" class="text-center">
<span class="alert-text" v-html="$t('form.attention')"></span> <span class="alert-text h3 text-light" v-html="$t('form.attention')"></span>
</b-alert> </b-alert>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important"> <b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<!--
<b-alert show variant="secondary"> <b-alert show variant="secondary">
<span class="alert-text" v-html="$t('form.scann_code')"></span> <span class="alert-text" v-html="$t('form.scann_code')"></span>
<b-col v-show="!scan" lg="12" class="text-right"> <b-col v-show="!scan" lg="12" class="text-right">
@ -15,9 +16,9 @@
</b-col> </b-col>
<div v-if="scan"> <div v-if="scan">
<!-- <b-row> <b-row>
<qrcode-capture @detect="onDetect" capture="user" ></qrcode-capture> <qrcode-capture @detect="onDetect" capture="user" ></qrcode-capture>
</b-row> --> </b-row>
<qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream> <qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream>
@ -39,6 +40,7 @@
</b-alert> </b-alert>
</div> </div>
</b-alert> </b-alert>
-->
<validation-observer v-slot="{ handleSubmit }" ref="formValidator"> <validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form <b-form
@ -47,11 +49,11 @@
@reset="onReset" @reset="onReset"
v-if="show" v-if="show"
> >
<br /> <!-- <div>
<div>
<qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone> <qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone>
</div> </div>
<br /> <br />
-->
<div> <div>
<validation-provider <validation-provider
name="Email" name="Email"
@ -177,7 +179,7 @@
</b-list-group-item> </b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center"> <b-list-group-item class="d-flex justify-content-between align-items-center">
{{ ajaxCreateData.memo }} {{ ajaxCreateData.memo ? ajaxCreateData.memo : '-' }}
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge> <b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
</b-list-group-item> </b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center"> <b-list-group-item class="d-flex justify-content-between align-items-center">
@ -200,29 +202,46 @@
</b-row> </b-row>
<b-row v-show="row_thx"> <b-row v-show="row_thx">
<b-col> <b-col>
<div class="display-1 p-4"> <b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
{{ $t('form.thx') }} <div class="display-2 p-4">
<hr /> {{ $t('form.thx') }}
{{ $t('form.send_success') }} <hr />
</div> {{ $t('form.send_transaction_success') }}
</div>
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button> <p class="text-center">
<hr /> <b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
</p>
</b-card>
</b-col>
</b-row>
<b-row v-show="row_error">
<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="onReset">{{ $t('form.close') }}</b-button>
</p>
</b-card>
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
</template> </template>
<script> <script>
import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader' // import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader'
import { BIcon } from 'bootstrap-vue' import { BIcon } from 'bootstrap-vue'
import communityAPI from '../../../apis/communityAPI.js' import communityAPI from '../../../apis/communityAPI.js'
export default { export default {
name: 'GddSent', name: 'GddSent',
components: { components: {
QrcodeStream, // QrcodeStream,
QrcodeDropZone, // QrcodeDropZone,
BIcon, BIcon,
}, },
props: { props: {
@ -231,7 +250,7 @@ export default {
}, },
data() { data() {
return { return {
scan: false, // scan: false,
show: true, show: true,
form: { form: {
img: '', img: '',
@ -248,20 +267,21 @@ export default {
}, },
send: false, send: false,
row_check: false, row_check: false,
row_thx: false, row_thx: true,
row_error: false,
} }
}, },
computed: {}, computed: {},
methods: { methods: {
toggle() { // toggle() {
this.scan = !this.scan // this.scan = !this.scan
}, // },
async onDecode(decodedString) { // async onDecode(decodedString) {
const arr = JSON.parse(decodedString) // const arr = JSON.parse(decodedString)
this.form.email = arr[0].email // this.form.email = arr[0].email
this.form.amount = arr[0].amount // this.form.amount = arr[0].amount
this.scan = false // this.scan = false
}, // },
async onSubmit() { async onSubmit() {
// event.preventDefault() // event.preventDefault()
this.ajaxCreateData.email = this.form.email this.ajaxCreateData.email = this.form.email
@ -272,6 +292,7 @@ export default {
this.$emit('toggle-show-list', false) this.$emit('toggle-show-list', false)
this.row_check = true this.row_check = true
this.row_thx = false this.row_thx = false
this.row_error = false
}, },
async sendTransaction() { async sendTransaction() {
const result = await communityAPI.send( const result = await communityAPI.send(
@ -285,12 +306,13 @@ export default {
this.$emit('toggle-show-list', false) this.$emit('toggle-show-list', false)
this.row_check = false this.row_check = false
this.row_thx = true this.row_thx = true
this.row_error = false
this.$emit('update-balance', { ammount: this.ajaxCreateData.amount }) this.$emit('update-balance', { ammount: this.ajaxCreateData.amount })
} else { } else {
alert('error')
this.$emit('toggle-show-list', true) this.$emit('toggle-show-list', true)
this.row_check = false this.row_check = false
this.row_thx = false this.row_thx = false
this.row_error = true
} }
}, },
onReset(event) { onReset(event) {
@ -302,6 +324,7 @@ export default {
this.$emit('toggle-show-list', true) this.$emit('toggle-show-list', true)
this.row_check = false this.row_check = false
this.row_thx = false this.row_thx = false
this.row_error = false
this.$nextTick(() => { this.$nextTick(() => {
this.show = true this.show = true
}) })

View File

@ -44,24 +44,14 @@
v-model="model.password" v-model="model.password"
></base-input> ></base-input>
<b-alert v-show="loginfail" show variant="warning"> <b-alert v-show="loginfail" show dismissible variant="warning">
<span class="alert-text bv-example-row"> <span class="alert-text bv-example-row">
<b-row> <b-row>
<b-col class="col-9 text-left"> <b-col class="col-9 text-left text-dark">
<strong> <strong>
Leider konnten wir keinen Account finden mit diesen Daten! Leider konnten wir keinen Account finden mit diesen Daten!
</strong> </strong>
</b-col> </b-col>
<b-col class="text-right">
<a @click="closeAlert">
<div>
<b-icon-exclamation-triangle-fill
class="h2 mb-0"
></b-icon-exclamation-triangle-fill>
<b-icon-x class="h1 pl-2"></b-icon-x>
</div>
</a>
</b-col>
</b-row> </b-row>
</span> </span>
</b-alert> </b-alert>
@ -112,6 +102,8 @@ export default {
}, },
methods: { methods: {
async onSubmit() { async onSubmit() {
// error info ausschalten
this.loginfail = false
const loader = this.$loading.show({ const loader = this.$loading.show({
container: this.$refs.submitButton, container: this.$refs.submitButton,
}) })
@ -128,10 +120,6 @@ export default {
this.loginfail = true this.loginfail = true
} }
}, },
closeAlert() {
this.$loading.hide()
this.loginfail = false
},
}, },
} }
</script> </script>

View File

@ -109,7 +109,7 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
emailOptInModel->sendErrorsAsEmail(); emailOptInModel->sendErrorsAsEmail();
return stateError("insert emailOptIn failed"); return stateError("insert emailOptIn failed");
} }
emailOptIn->setBaseUrl(mServerHost + "/" + ServerConfig::g_frontend_checkEmailPath);
em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType))); em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType)));
if (login_after_register && session) { if (login_after_register && session) {

View File

@ -43,7 +43,13 @@ void JsonRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Po
//std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream; //std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;
mClientIp = request.clientAddress().host(); mClientIp = request.clientAddress().host();
if (request.secure()) {
mServerHost = "https://" + request.getHost();
}
else {
mServerHost = "http://" + request.getHost();
}
auto method = request.getMethod(); auto method = request.getMethod();
std::istream& request_stream = request.stream(); std::istream& request_stream = request.stream();
Poco::JSON::Object* json_result = nullptr; Poco::JSON::Object* json_result = nullptr;

View File

@ -26,6 +26,7 @@ public:
protected: protected:
Poco::JSON::Object* mResultJson; Poco::JSON::Object* mResultJson;
Poco::Net::IPAddress mClientIp; Poco::Net::IPAddress mClientIp;
std::string mServerHost;
Session* mSession; Session* mSession;
Poco::JSON::Object* checkAndLoadSession(Poco::Dynamic::Var params, bool checkIp = false); Poco::JSON::Object* checkAndLoadSession(Poco::Dynamic::Var params, bool checkIp = false);

View File

@ -105,12 +105,12 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
return stateError("invalid email"); return stateError("invalid email");
} }
auto receiver_user_id = receiver_user->getModel()->getID(); auto receiver_user_id = receiver_user->getModel()->getID();
std::string checkEmailUrl = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath;
if (emailVerificationCodeType == model::table::EMAIL_OPT_IN_RESET_PASSWORD) if (emailVerificationCodeType == model::table::EMAIL_OPT_IN_RESET_PASSWORD)
{ {
session = sm->getNewSession(); session = sm->getNewSession();
if (emailType == model::EMAIL_USER_RESET_PASSWORD) { if (emailType == model::EMAIL_USER_RESET_PASSWORD) {
auto r = session->sendResetPasswordEmail(receiver_user, true, receiver_user->getGroupBaseUrl()); auto r = session->sendResetPasswordEmail(receiver_user, true, checkEmailUrl);
if (1 == r) { if (1 == r) {
return stateWarning("email already sended"); return stateWarning("email already sended");
} }
@ -120,6 +120,7 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
} }
else if (emailType == model::EMAIL_CUSTOM_TEXT) { else if (emailType == model::EMAIL_CUSTOM_TEXT) {
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, model::table::EMAIL_OPT_IN_RESET_PASSWORD); auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, model::table::EMAIL_OPT_IN_RESET_PASSWORD);
email_verification_code_object->setBaseUrl(checkEmailUrl);
auto email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject); auto email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);
em->addEmail(email); em->addEmail(email);
} }
@ -135,6 +136,7 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
} }
auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, emailVerificationCodeType); auto email_verification_code_object = controller::EmailVerificationCode::loadOrCreate(receiver_user_id, emailVerificationCodeType);
email_verification_code_object->setBaseUrl(checkEmailUrl);
model::Email* email = nullptr; model::Email* email = nullptr;
if (emailType == model::EMAIL_CUSTOM_TEXT) { if (emailType == model::EMAIL_CUSTOM_TEXT) {
email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject); email = new model::Email(email_verification_code_object, receiver_user, emailCustomText, emailCustomSubject);

View File

@ -122,10 +122,7 @@ namespace controller {
std::string EmailVerificationCode::getLink() std::string EmailVerificationCode::getLink()
{ {
std::string link = mBaseUrl; std::string link = mBaseUrl;
if (ServerConfig::g_frontend_checkEmailPath.size() > 1 && ServerConfig::g_frontend_checkEmailPath.data()[0] != '/') {
link += '/';
}
link += ServerConfig::g_frontend_checkEmailPath;
if (link.data()[link.size() - 1] != '/') { if (link.data()[link.size() - 1] != '/') {
link += '/'; link += '/';
} }