diff --git a/.gitignore b/.gitignore index 5c23ad8bd..44f403757 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.log /node_modules/* .vscode +.skeema +nbproject \ No newline at end of file diff --git a/community_server/db/setup_db_tables/insert_migrations.sql b/community_server/db/setup_db_tables/insert_migrations.sql new file mode 100644 index 000000000..e6f38117d --- /dev/null +++ b/community_server/db/setup_db_tables/insert_migrations.sql @@ -0,0 +1,2 @@ +INSERT INTO `migrations` (`id`, `db_version`) VALUES +(1, 2); diff --git a/community_server/db/setup_db_tables/insert_transaction_types.sql b/community_server/db/setup_db_tables/insert_transaction_types.sql index 02ef8374a..37919045d 100644 --- a/community_server/db/setup_db_tables/insert_transaction_types.sql +++ b/community_server/db/setup_db_tables/insert_transaction_types.sql @@ -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'); diff --git a/community_server/db/skeema/gradido_community/migrations.sql b/community_server/db/skeema/gradido_community/migrations.sql new file mode 100644 index 000000000..7665bcf29 --- /dev/null +++ b/community_server/db/skeema/gradido_community/migrations.sql @@ -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; diff --git a/community_server/db/skeema/gradido_community/transaction_types.sql b/community_server/db/skeema/gradido_community/transaction_types.sql index a3e6779d9..10aad25b0 100644 --- a/community_server/db/skeema/gradido_community/transaction_types.sql +++ b/community_server/db/skeema/gradido_community/transaction_types.sql @@ -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; diff --git a/community_server/src/Controller/AppController.php b/community_server/src/Controller/AppController.php index 1cdcf4418..9f577d77a 100644 --- a/community_server/src/Controller/AppController.php +++ b/community_server/src/Controller/AppController.php @@ -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 diff --git a/community_server/src/Controller/MigrationsController.php b/community_server/src/Controller/MigrationsController.php new file mode 100644 index 000000000..31fa41001 --- /dev/null +++ b/community_server/src/Controller/MigrationsController.php @@ -0,0 +1,167 @@ +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']); + } +} diff --git a/community_server/src/Controller/StateBalancesController.php b/community_server/src/Controller/StateBalancesController.php index 3994b989a..655dbc026 100644 --- a/community_server/src/Controller/StateBalancesController.php +++ b/community_server/src/Controller/StateBalancesController.php @@ -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 diff --git a/community_server/src/Controller/TransactionsController.php b/community_server/src/Controller/TransactionsController.php index 4f8e04c2b..90e78b7fb 100644 --- a/community_server/src/Controller/TransactionsController.php +++ b/community_server/src/Controller/TransactionsController.php @@ -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')); } /** diff --git a/community_server/src/Model/Entity/Migration.php b/community_server/src/Model/Entity/Migration.php new file mode 100644 index 000000000..700136cf8 --- /dev/null +++ b/community_server/src/Model/Entity/Migration.php @@ -0,0 +1,26 @@ + true, + ]; +} diff --git a/community_server/src/Model/Entity/StateBalance.php b/community_server/src/Model/Entity/StateBalance.php index 7d595653e..334db222f 100644 --- a/community_server/src/Model/Entity/StateBalance.php +++ b/community_server/src/Model/Entity/StateBalance.php @@ -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; } diff --git a/community_server/src/Model/Table/AppTable.php b/community_server/src/Model/Table/AppTable.php new file mode 100644 index 000000000..138a7f949 --- /dev/null +++ b/community_server/src/Model/Table/AppTable.php @@ -0,0 +1,40 @@ +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]; + } + } +} diff --git a/community_server/src/Model/Table/BlockchainTypesTable.php b/community_server/src/Model/Table/BlockchainTypesTable.php index 90213ac9f..3aa67a83d 100644 --- a/community_server/src/Model/Table/BlockchainTypesTable.php +++ b/community_server/src/Model/Table/BlockchainTypesTable.php @@ -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; + + } } diff --git a/community_server/src/Model/Table/MigrationsTable.php b/community_server/src/Model/Table/MigrationsTable.php new file mode 100644 index 000000000..b5cb42154 --- /dev/null +++ b/community_server/src/Model/Table/MigrationsTable.php @@ -0,0 +1,56 @@ +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; + } +} diff --git a/community_server/src/Model/Table/StateBalancesTable.php b/community_server/src/Model/Table/StateBalancesTable.php index 7564c30f2..7e5f96be9 100644 --- a/community_server/src/Model/Table/StateBalancesTable.php +++ b/community_server/src/Model/Table/StateBalancesTable.php @@ -1,14 +1,12 @@ '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 . "
"; - $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 . "
"; @@ -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]; } diff --git a/community_server/src/Model/Table/StateUserTransactionsTable.php b/community_server/src/Model/Table/StateUserTransactionsTable.php index 6cfe94a23..2c984d964 100644 --- a/community_server/src/Model/Table/StateUserTransactionsTable.php +++ b/community_server/src/Model/Table/StateUserTransactionsTable.php @@ -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 diff --git a/community_server/src/Model/Table/TransactionTypesTable.php b/community_server/src/Model/Table/TransactionTypesTable.php index 5cc842f5e..2ffd1e64c 100644 --- a/community_server/src/Model/Table/TransactionTypesTable.php +++ b/community_server/src/Model/Table/TransactionTypesTable.php @@ -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; + } } diff --git a/community_server/src/Model/Table/TransactionsTable.php b/community_server/src/Model/Table/TransactionsTable.php index 2ff39f469..742ce7882 100644 --- a/community_server/src/Model/Table/TransactionsTable.php +++ b/community_server/src/Model/Table/TransactionsTable.php @@ -1,12 +1,12 @@ 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 . "
"; - $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 "
";*/ 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
"; + continue; + } + $stu_id = $state_user_transaction_ids[$i2]; + if($tr_id->id == $stu_id->transaction_id) { + $i2++; + //echo "after i2++: $i2
"; + } else if($tr_id->id < $stu_id->transaction_id) { + $missing_transaction_ids[] = $tr_id; + //echo "adding to missing: $tr_id
"; + } + } + + + $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; + } } diff --git a/community_server/src/Model/Transactions/TransactionBase.php b/community_server/src/Model/Transactions/TransactionBase.php index a688daf02..1f80cc687 100644 --- a/community_server/src/Model/Transactions/TransactionBase.php +++ b/community_server/src/Model/Transactions/TransactionBase.php @@ -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)) { diff --git a/community_server/src/Model/Transactions/TransactionCreation.php b/community_server/src/Model/Transactions/TransactionCreation.php index 6aeddd955..63b886a3f 100644 --- a/community_server/src/Model/Transactions/TransactionCreation.php +++ b/community_server/src/Model/Transactions/TransactionCreation.php @@ -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; } diff --git a/community_server/src/Template/AppRequests/list_transactions.ctp b/community_server/src/Template/AppRequests/list_transactions.ctp index f829b5f16..28a76f2be 100644 --- a/community_server/src/Template/AppRequests/list_transactions.ctp +++ b/community_server/src/Template/AppRequests/list_transactions.ctp @@ -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]); + } } ?> \ No newline at end of file diff --git a/community_server/src/Template/Migrations/add.ctp b/community_server/src/Template/Migrations/add.ctp new file mode 100644 index 000000000..94a555733 --- /dev/null +++ b/community_server/src/Template/Migrations/add.ctp @@ -0,0 +1,23 @@ + + +
+ Form->create($migration) ?> +
+ + Form->control('db_version'); + ?> +
+ Form->button(__('Submit')) ?> + Form->end() ?> +
diff --git a/community_server/src/Template/Migrations/edit.ctp b/community_server/src/Template/Migrations/edit.ctp new file mode 100644 index 000000000..1e916afa1 --- /dev/null +++ b/community_server/src/Template/Migrations/edit.ctp @@ -0,0 +1,29 @@ + + +
+ Form->create($migration) ?> +
+ + Form->control('db_version'); + ?> +
+ Form->button(__('Submit')) ?> + Form->end() ?> +
diff --git a/community_server/src/Template/Migrations/index.ctp b/community_server/src/Template/Migrations/index.ctp new file mode 100644 index 000000000..9d755ecff --- /dev/null +++ b/community_server/src/Template/Migrations/index.ctp @@ -0,0 +1,47 @@ + + +
+

+ + + + + + + + + + + + + + + + + +
Paginator->sort('id') ?>Paginator->sort('db_version') ?>
Number->format($migration->id) ?>Number->format($migration->db_version) ?> + Html->link(__('View'), ['action' => 'view', $migration->id]) ?> + Html->link(__('Edit'), ['action' => 'edit', $migration->id]) ?> + Form->postLink(__('Delete'), ['action' => 'delete', $migration->id], ['confirm' => __('Are you sure you want to delete # {0}?', $migration->id)]) ?> +
+
+ +

Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?>

+
+
diff --git a/community_server/src/Template/Migrations/migrate.ctp b/community_server/src/Template/Migrations/migrate.ctp new file mode 100644 index 000000000..d345c9a90 --- /dev/null +++ b/community_server/src/Template/Migrations/migrate.ctp @@ -0,0 +1,18 @@ +

Migrate DB

+

Migrate from Version

+ +

Success

+ +

Error

+

+ +

Html->link('Back to Dashboard', ['controller' => 'Dashboard', 'action' => 'index']) ?>

+ diff --git a/community_server/src/Template/Migrations/view.ctp b/community_server/src/Template/Migrations/view.ctp new file mode 100644 index 000000000..dc4c5ded3 --- /dev/null +++ b/community_server/src/Template/Migrations/view.ctp @@ -0,0 +1,28 @@ + + +
+

id) ?>

+ + + + + + + + + +
Number->format($migration->id) ?>
Number->format($migration->db_version) ?>
+
diff --git a/community_server/src/Template/Transactions/add.ctp b/community_server/src/Template/Transactions/add.ctp index 59782d944..012d82027 100644 --- a/community_server/src/Template/Transactions/add.ctp +++ b/community_server/src/Template/Transactions/add.ctp @@ -35,7 +35,8 @@ 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]); ?> Form->button(__('Submit')) ?> diff --git a/community_server/tests/Fixture/MigrationsFixture.php b/community_server/tests/Fixture/MigrationsFixture.php new file mode 100644 index 000000000..1fab6a133 --- /dev/null +++ b/community_server/tests/Fixture/MigrationsFixture.php @@ -0,0 +1,44 @@ + ['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(); + } +} diff --git a/community_server/tests/TestCase/Controller/MigrationsControllerTest.php b/community_server/tests/TestCase/Controller/MigrationsControllerTest.php new file mode 100644 index 000000000..4bb794d4d --- /dev/null +++ b/community_server/tests/TestCase/Controller/MigrationsControllerTest.php @@ -0,0 +1,75 @@ +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.'); + } +} diff --git a/community_server/tests/TestCase/Model/Table/MigrationsTableTest.php b/community_server/tests/TestCase/Model/Table/MigrationsTableTest.php new file mode 100644 index 000000000..f88893585 --- /dev/null +++ b/community_server/tests/TestCase/Model/Table/MigrationsTableTest.php @@ -0,0 +1,72 @@ +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.'); + } +} diff --git a/docu/community-server.api.md b/docu/community-server.api.md index 9354bf232..11556513b 100644 --- a/docu/community-server.api.md +++ b/docu/community-server.api.md @@ -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": "" +} +```` + +## 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" +} +```` + diff --git a/docu/other/mariadb_groups_insert.md b/docu/other/mariadb_groups_insert.md new file mode 100644 index 000000000..3bb61ab0b --- /dev/null +++ b/docu/other/mariadb_groups_insert.md @@ -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 \ No newline at end of file diff --git a/frontend/src/components/SidebarPlugin/SideBar.vue b/frontend/src/components/SidebarPlugin/SideBar.vue index 6542bd23e..9e0885d3f 100755 --- a/frontend/src/components/SidebarPlugin/SideBar.vue +++ b/frontend/src/components/SidebarPlugin/SideBar.vue @@ -50,6 +50,7 @@ {{ $t('members_area') }} diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 9bda785f5..be4574e6d 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -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":"QR Code Scanner - 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." } } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 7705eb18e..fc2e15cc7 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -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":"QR Code Scanner - 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":"QR Code Scanner - 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." } } diff --git a/frontend/src/views/Layout/ContentFooter.spec.js b/frontend/src/views/Layout/ContentFooter.spec.js index a5e321466..8d0c6dfbd 100644 --- a/frontend/src/views/Layout/ContentFooter.spec.js +++ b/frontend/src/views/Layout/ContentFooter.spec.js @@ -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/', + ) + }) }) }) }) diff --git a/frontend/src/views/Layout/ContentFooter.vue b/frontend/src/views/Layout/ContentFooter.vue index b261cafb3..09a2418fd 100755 --- a/frontend/src/views/Layout/ContentFooter.vue +++ b/frontend/src/views/Layout/ContentFooter.vue @@ -4,7 +4,11 @@