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
/node_modules/*
.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'),
(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

@ -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)
{
$this->checkForMigration($redirect);
$session = $this->getRequest()->getSession();
// check login
// 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');
$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

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,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 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
@ -28,6 +26,7 @@ use Cake\I18n\Time;
*/
class StateBalancesTable extends Table
{
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,20 @@ 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,
'memo' => ''
];
}
@ -273,4 +284,166 @@ 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;
}
}
}
}
//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);
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 +95,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 +108,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

@ -138,8 +138,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 +152,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

@ -12,6 +12,9 @@ $body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'pre
foreach($body['transactions'] as $i => $transaction) {
$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) ?>

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

@ -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

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

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,7 +13,7 @@
"back":"Zurück",
"send":"Senden",
"transactions":"Transaktionen",
"language":"Language",
"language":"Sprache",
"languages":{
"de": "Deutsch",
"en": "English"
@ -41,10 +41,12 @@
"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"
@ -121,7 +123,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,13 @@
"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.",
"attention": "Attention! Please check all your entries very carefully. You are solely responsible for your decisions. Sent Gradidos cannot be retrieved.",
"cancel":"Cancel",
"reset": "Reset",
"close":"Close",
@ -40,11 +40,13 @@
"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"
@ -123,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

@ -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', () => {
beforeEach(() => {
mocks.$i18n.locale = 'de'
@ -132,6 +138,12 @@ describe('ContentFooter', () => {
'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>
<div class="copyright text-center text-lg-center text-muted">
© {{ 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
</a>
|
@ -39,6 +43,9 @@
>
{{ $t('whitepaper') }}
</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-col>
</b-row>

View File

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

View File

@ -2,10 +2,11 @@
<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 show dismissible variant="default" class="text-center">
<span class="alert-text h3 text-light" 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">
@ -15,9 +16,9 @@
</b-col>
<div v-if="scan">
<!-- <b-row>
<b-row>
<qrcode-capture @detect="onDetect" capture="user" ></qrcode-capture>
</b-row> -->
</b-row>
<qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream>
@ -39,6 +40,7 @@
</b-alert>
</div>
</b-alert>
-->
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form
@ -47,11 +49,11 @@
@reset="onReset"
v-if="show"
>
<br />
<div>
<!-- <div>
<qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone>
</div>
<br />
-->
<div>
<validation-provider
name="Email"
@ -177,7 +179,7 @@
</b-list-group-item>
<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-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
@ -200,29 +202,46 @@
</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-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>
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
<hr />
<p class="text-center">
<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-row>
</div>
</template>
<script>
import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader'
// 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,
// QrcodeStream,
// QrcodeDropZone,
BIcon,
},
props: {
@ -231,7 +250,7 @@ export default {
},
data() {
return {
scan: false,
// scan: false,
show: true,
form: {
img: '',
@ -248,20 +267,21 @@ export default {
},
send: false,
row_check: false,
row_thx: false,
row_thx: true,
row_error: false,
}
},
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
},
// 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
@ -272,6 +292,7 @@ export default {
this.$emit('toggle-show-list', false)
this.row_check = true
this.row_thx = false
this.row_error = false
},
async sendTransaction() {
const result = await communityAPI.send(
@ -285,12 +306,13 @@ export default {
this.$emit('toggle-show-list', false)
this.row_check = false
this.row_thx = true
this.row_error = false
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
this.row_error = true
}
},
onReset(event) {
@ -302,6 +324,7 @@ export default {
this.$emit('toggle-show-list', true)
this.row_check = false
this.row_thx = false
this.row_error = false
this.$nextTick(() => {
this.show = true
})

View File

@ -44,24 +44,14 @@
v-model="model.password"
></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">
<b-row>
<b-col class="col-9 text-left">
<b-col class="col-9 text-left text-dark">
<strong>
Leider konnten wir keinen Account finden mit diesen Daten!
</strong>
</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>
</span>
</b-alert>
@ -112,6 +102,8 @@ export default {
},
methods: {
async onSubmit() {
// error info ausschalten
this.loginfail = false
const loader = this.$loading.show({
container: this.$refs.submitButton,
})
@ -128,10 +120,6 @@ export default {
this.loginfail = true
}
},
closeAlert() {
this.$loading.hide()
this.loginfail = false
},
},
}
</script>

View File

@ -109,7 +109,7 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
emailOptInModel->sendErrorsAsEmail();
return stateError("insert emailOptIn failed");
}
emailOptIn->setBaseUrl(mServerHost + "/" + ServerConfig::g_frontend_checkEmailPath);
em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType)));
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;
mClientIp = request.clientAddress().host();
if (request.secure()) {
mServerHost = "https://" + request.getHost();
}
else {
mServerHost = "http://" + request.getHost();
}
auto method = request.getMethod();
std::istream& request_stream = request.stream();
Poco::JSON::Object* json_result = nullptr;

View File

@ -26,6 +26,7 @@ public:
protected:
Poco::JSON::Object* mResultJson;
Poco::Net::IPAddress mClientIp;
std::string mServerHost;
Session* mSession;
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");
}
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)
{
session = sm->getNewSession();
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) {
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) {
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);
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);
email_verification_code_object->setBaseUrl(checkEmailUrl);
model::Email* email = nullptr;
if (emailType == model::EMAIL_CUSTOM_TEXT) {
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 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] != '/') {
link += '/';
}