mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #387 from gradido/community_start_decay
Community start decay
This commit is contained in:
commit
9f9e656482
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
*.log
|
||||
/node_modules/*
|
||||
.vscode
|
||||
.skeema
|
||||
nbproject
|
||||
@ -0,0 +1,2 @@
|
||||
INSERT INTO `migrations` (`id`, `db_version`) VALUES
|
||||
(1, 2);
|
||||
@ -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');
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
167
community_server/src/Controller/MigrationsController.php
Normal file
167
community_server/src/Controller/MigrationsController.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
26
community_server/src/Model/Entity/Migration.php
Normal file
26
community_server/src/Model/Entity/Migration.php
Normal 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,
|
||||
];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
40
community_server/src/Model/Table/AppTable.php
Normal file
40
community_server/src/Model/Table/AppTable.php
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
56
community_server/src/Model/Table/MigrationsTable.php
Normal file
56
community_server/src/Model/Table/MigrationsTable.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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];
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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) ?>
|
||||
23
community_server/src/Template/Migrations/add.ctp
Normal file
23
community_server/src/Template/Migrations/add.ctp
Normal 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>
|
||||
29
community_server/src/Template/Migrations/edit.ctp
Normal file
29
community_server/src/Template/Migrations/edit.ctp
Normal 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>
|
||||
47
community_server/src/Template/Migrations/index.ctp
Normal file
47
community_server/src/Template/Migrations/index.ctp
Normal 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>
|
||||
18
community_server/src/Template/Migrations/migrate.ctp
Normal file
18
community_server/src/Template/Migrations/migrate.ctp
Normal 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>
|
||||
|
||||
28
community_server/src/Template/Migrations/view.ctp
Normal file
28
community_server/src/Template/Migrations/view.ctp
Normal 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>
|
||||
@ -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')) ?>
|
||||
|
||||
44
community_server/tests/Fixture/MigrationsFixture.php
Normal file
44
community_server/tests/Fixture/MigrationsFixture.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -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.');
|
||||
}
|
||||
}
|
||||
@ -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.');
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
````
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user