mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into login_build_alpine
This commit is contained in:
commit
9d0c1da9ad
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,4 +6,4 @@ title: 🐛 [Bug]
|
||||
---
|
||||
|
||||
## 🐛 Bugreport
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.--
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.-->
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
||||
/node_modules/*
|
||||
.vscode
|
||||
messages.pot
|
||||
.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
|
||||
|
||||
@ -22,6 +22,7 @@ class AppRequestsController extends AppController
|
||||
{
|
||||
parent::initialize();
|
||||
$this->loadComponent('JsonRequestClient');
|
||||
$this->loadComponent('GradidoNumber');
|
||||
//$this->loadComponent('JsonRpcRequestClient');
|
||||
//$this->Auth->allow(['add', 'edit']);
|
||||
$this->Auth->allow(['index', 'sendCoins', 'createCoins', 'getBalance', 'listTransactions']);
|
||||
@ -115,9 +116,10 @@ class AppRequestsController extends AppController
|
||||
return $required_fields;
|
||||
}
|
||||
|
||||
if(intval($param['amount']) <= 0) {
|
||||
if(floatval($param['amount']) <= 0.0) {
|
||||
return ['state' => 'error', 'msg' => 'amount is invalid', 'details' => $param['amount']];
|
||||
}
|
||||
$param['amount'] = $this->GradidoNumber->parseInputNumberToCentNumber($param['amount']);
|
||||
|
||||
if(isset($data->memo)) {
|
||||
$param['memo'] = $data->memo;
|
||||
@ -268,7 +270,7 @@ class AppRequestsController extends AppController
|
||||
|
||||
public function getBalance($session_id = 0)
|
||||
{
|
||||
|
||||
$this->viewBuilder()->setLayout('ajax');
|
||||
$login_result = $this->requestLogin($session_id, false);
|
||||
if($login_result !== true) {
|
||||
return $this->returnJson($login_result);
|
||||
@ -284,16 +286,18 @@ class AppRequestsController extends AppController
|
||||
return $this->returnJson(['state' => 'success', 'balance' => 0]);
|
||||
}
|
||||
$now = new FrozenTime();
|
||||
return $this->returnJson([
|
||||
$body = [
|
||||
'state' => 'success',
|
||||
'balance' => $state_balance->amount,
|
||||
'decay' => $state_balance->partDecay($now),
|
||||
'decay_date' => $now
|
||||
]);
|
||||
];
|
||||
$this->set('body', $body);
|
||||
}
|
||||
|
||||
public function listTransactions($page = 1, $count = 25, $orderDirection = 'ASC', $session_id = 0)
|
||||
{
|
||||
$this->viewBuilder()->setLayout('ajax');
|
||||
$startTime = microtime(true);
|
||||
$login_result = $this->requestLogin($session_id, false);
|
||||
if($login_result !== true) {
|
||||
@ -337,14 +341,29 @@ class AppRequestsController extends AppController
|
||||
$transactions = array_reverse($transactions);
|
||||
}
|
||||
}
|
||||
return $this->returnJson([
|
||||
'state' => 'success',
|
||||
'transactions' => $transactions,
|
||||
'transactionExecutingCount' => $session->read('Transactions.executing'),
|
||||
'count' => count($transactions),
|
||||
'gdtSum' => $gdtSum,
|
||||
'timeUsed' => microtime(true) - $startTime
|
||||
]);
|
||||
|
||||
$state_balance = $stateBalancesTable->find()->where(['state_user_id' => $user['id']])->first();
|
||||
|
||||
$body = [
|
||||
'state' => 'success',
|
||||
'transactions' => $transactions,
|
||||
'transactionExecutingCount' => $session->read('Transactions.executing'),
|
||||
'count' => count($transactions),
|
||||
'gdtSum' => $gdtSum,
|
||||
'timeUsed' => microtime(true) - $startTime
|
||||
];
|
||||
$now = new FrozenTime();
|
||||
$body['decay_date'] = $now;
|
||||
|
||||
if(!$state_balance) {
|
||||
$body['balance'] = 0.0;
|
||||
$body['decay'] = 0.0;
|
||||
} else {
|
||||
$body['balance'] = $state_balance->amount;
|
||||
$body['decay'] = $state_balance->partDecay($now);
|
||||
}
|
||||
|
||||
$this->set('body', $body);
|
||||
}
|
||||
|
||||
private function acquireAccessToken($session_id)
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
@ -3,6 +3,7 @@ namespace App\Model\Entity;
|
||||
|
||||
use Cake\ORM\Entity;
|
||||
use Cake\I18n\Time;
|
||||
use Cake\I18n\Number;
|
||||
|
||||
/**
|
||||
* StateBalance Entity
|
||||
@ -34,7 +35,7 @@ class StateBalance extends Entity
|
||||
'state_user' => true
|
||||
];
|
||||
|
||||
protected $_virtual = ['decay'];
|
||||
protected $_virtual = ['decay','amount_float'];
|
||||
|
||||
private function convertToTimestamp($dateOrTime)
|
||||
{
|
||||
@ -43,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,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;
|
||||
}
|
||||
@ -67,6 +70,7 @@ class StateBalance extends Entity
|
||||
return intval($this->amount * pow(0.99999997802044727, $decay_duration));
|
||||
|
||||
}
|
||||
|
||||
public function partDecay($target_date)
|
||||
{
|
||||
$decay_duration = intval($this->convertToTimestamp($target_date) - $this->convertToTimestamp($this->record_date));
|
||||
|
||||
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
|
||||
|
||||
@ -122,9 +122,9 @@ class StateUsersTable extends Table
|
||||
// exchange back
|
||||
$involvedUserIds = array_flip($involvedUser_temp);
|
||||
$involvedUser = $this->find('all', [
|
||||
'contain' => false,
|
||||
'contain' => [],
|
||||
'where' => ['id IN' => $involvedUserIds],
|
||||
'fields' => ['id', 'first_name', 'last_name', 'email']
|
||||
'fields' => ['id', 'first_name', 'last_name', 'email'],
|
||||
]);
|
||||
//var_dump($involvedUser->toArray());
|
||||
$involvedUserIndices = [];
|
||||
|
||||
@ -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\FrozenTime;
|
||||
/**
|
||||
* Transactions Model
|
||||
*
|
||||
@ -172,26 +172,31 @@ 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);
|
||||
|
||||
//echo $interval->format('%R%a days');
|
||||
//echo "prev balance: " . $prev->balance . ", diff_amount: $diff_amount, summe: " . (-intval($prev->balance - $diff_amount)) . "<br>";
|
||||
$final_transactions[] = [
|
||||
'type' => 'decay',
|
||||
'balance' => -intval($prev->balance - $diff_amount),
|
||||
'decay_duration' => $interval->format('%a days, %H hours, %I minutes, %S seconds'),
|
||||
'memo' => ''
|
||||
];
|
||||
$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) {
|
||||
//echo $interval->format('%R%a days');
|
||||
//echo "prev balance: " . $prev->balance . ", diff_amount: $diff_amount, summe: " . (-intval($prev->balance - $diff_amount)) . "<br>";
|
||||
$final_transactions[] = [
|
||||
'type' => 'decay',
|
||||
'balance' => $balance,
|
||||
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
|
||||
'memo' => ''
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,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
|
||||
@ -252,18 +261,189 @@ class TransactionsTable extends Table
|
||||
}
|
||||
|
||||
if($i == $stateUserTransactionsCount-1 && $decay == true) {
|
||||
$state_balance->amount = $su_transaction->balance;
|
||||
$state_balance->record_date = $su_transaction->balance_date;
|
||||
$final_transactions[] = [
|
||||
'type' => 'decay',
|
||||
'balance' => -intval($su_transaction->balance - $state_balance->decay),
|
||||
'decay_duration' => $su_transaction->balance_date->timeAgoInWords(),
|
||||
'memo' => ''
|
||||
];
|
||||
$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' => $duration,
|
||||
'memo' => ''
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -99,12 +99,12 @@ class TransactionTransfer extends TransactionBase {
|
||||
->where(['public_key' => $senderPublic])
|
||||
->contain(['StateBalances' => ['fields' => ['amount', 'state_user_id']]])->first();
|
||||
if(!$user) {
|
||||
$this->addError($functionName, 'couldn\'t find sender ' . $i .' in db' );
|
||||
$this->addError($functionName, 'couldn\'t find sender in db' );
|
||||
return false;
|
||||
}
|
||||
//var_dump($user);
|
||||
if(intval($user->state_balances[0]->amount) < intval($amount)) {
|
||||
$this->addError($functionName, 'sender ' . $i . ' hasn\t enough GDD');
|
||||
$this->addError($functionName, 'sender hasn\t enough GDD');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ class TransactionTransfer extends TransactionBase {
|
||||
// check if receiver exist
|
||||
$receiver_user = $stateUsersTable->find('all')->select(['id'])->where(['public_key' => $receiver_public_key])->first();
|
||||
if(!$receiver_user) {
|
||||
$this->addError($functionName, 'couldn\'t find receiver ' . $i .' in db' );
|
||||
$this->addError($functionName, 'couldn\'t find receiver in db' );
|
||||
return false;
|
||||
}
|
||||
if($amount < 0) {
|
||||
|
||||
11
community_server/src/Template/AppRequests/get_balance.ctp
Normal file
11
community_server/src/Template/AppRequests/get_balance.ctp
Normal file
@ -0,0 +1,11 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
$body['balance'] = $this->element('centToFloat', ['cent' => $body['balance'], 'precision' => 4]);
|
||||
$body['decay'] = $this->element('centToFloat', ['cent' => $body['decay'], 'precision' => 4]);
|
||||
?><?= json_encode($body) ?>
|
||||
@ -0,0 +1,20 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
$body['balance'] = $this->element('centToFloat', ['cent' => $body['balance'], 'precision' => 4]);
|
||||
$body['decay'] = $this->element('centToFloat', ['cent' => $body['decay'], 'precision' => 4]);
|
||||
$body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'precision' => 2]);
|
||||
|
||||
foreach($body['transactions'] as $i => $transaction) {
|
||||
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4]);
|
||||
if(isset($transaction['creation_amount'])) {
|
||||
$body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
|
||||
}
|
||||
}
|
||||
|
||||
?><?= json_encode($body) ?>
|
||||
18
community_server/src/Template/Element/centToFloat.ctp
Normal file
18
community_server/src/Template/Element/centToFloat.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.
|
||||
*/
|
||||
|
||||
$cut_places = $precision - 2;
|
||||
$transformAmount = $cent;
|
||||
if($cut_places > 0) {
|
||||
$transformAmount = floor($cent / pow(10, $cut_places));
|
||||
}
|
||||
if($cut_places < 0) {
|
||||
$cut_places = 0;
|
||||
}
|
||||
echo $transformAmount / pow(10, $precision - $cut_places);
|
||||
|
||||
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.');
|
||||
}
|
||||
}
|
||||
@ -23,13 +23,13 @@ Additional session can be provided as GET-Parameter
|
||||
```json
|
||||
{
|
||||
"state":"success",
|
||||
"balance":15906078,
|
||||
"decay":15873851,
|
||||
"balance":1590.60,
|
||||
"decay":1587.38,
|
||||
"decay_date":"2021-04-16T11:47:21+00:00"
|
||||
}
|
||||
```
|
||||
|
||||
- `balance` : balance describes gradido cents which are 4 digits behind the separator. A balance value of 174500 equals therefor 17,45 GDD
|
||||
- `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,16 +59,32 @@ 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",
|
||||
"type": "send",
|
||||
"transaction_id": 2,
|
||||
"date": "2021-02-19T13:25:36+00:00",
|
||||
"balance": 1920000,
|
||||
"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 Cent, 4 Nachkommastellen (2 Reserve), 1920000 = 192,00 GDD
|
||||
- `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
|
||||
@ -246,3 +264,40 @@ Without auto-sign the transaction is pending on the login-server and waits for t
|
||||
// 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"
|
||||
}
|
||||
````
|
||||
|
||||
|
||||
30
docu/other/mariadb_groups_insert.md
Normal file
30
docu/other/mariadb_groups_insert.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Das Locale Schöpfen
|
||||
|
||||
### MariaDB Insert Groups
|
||||
|
||||
wenn local geschöpft werden möchte kommt ein fehler das der user keiner gruppe zugeordnet ist.
|
||||
|
||||
folgende schritte musst du machen um eine gruppe anzulegen
|
||||
|
||||
hier findest du den Mysql befehl: configs/login_server/setup_db_tables/setup_docker_group.sql
|
||||
in der Datei findest du folgenden Befehl
|
||||
|
||||
INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `host`, `home`, `description`) VALUES
|
||||
(1, 'docker', 'docker gradido group', 'localhost', 'nginx', '/', 'gradido test group for docker and stage2 with blockchain db');
|
||||
|
||||
# Ablauf
|
||||
|
||||
1. logge dich bei phpmyadmin ein http://localhost:8074/ (mariadb / root)
|
||||
2. gehe auf tabelle "gradido_login"
|
||||
3. gib folgenden Befehl in die console ein
|
||||
|
||||
INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `host`, `home`, `description`) VALUES
|
||||
(1, 'docker', 'docker gradido group', 'localhost', 'nginx', '/', 'gradido test group for docker and stage2 with blockchain db');
|
||||
|
||||
> es wird eine Gruppe mit id 1 angelgt. alle angelegten user sollten dieser gruppe zugeordnet sein.
|
||||
|
||||
das schöpfen sollte nun local funktionieren. :)
|
||||
|
||||
|
||||
|
||||
#ACHTUNG ! nach dem login kann noch zu fehlern kommen in der URL "localhostnginx/..." zu "localhost/..." ändern
|
||||
@ -3,28 +3,24 @@ module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
jest: true
|
||||
jest: true,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
parser: 'babel-eslint',
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'plugin:prettier/recommended'
|
||||
],
|
||||
extends: ['standard', 'plugin:vue/essential', 'plugin:prettier/recommended'],
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'vue',
|
||||
'prettier',
|
||||
'jest'
|
||||
],
|
||||
plugins: ['vue', 'prettier', 'jest'],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'no-console': ['error'],
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'vue/component-name-in-template-casing': ['error', 'kebab-case'],
|
||||
'prettier/prettier': ['error', {
|
||||
htmlWhitespaceSensitivity: 'ignore'
|
||||
}],
|
||||
}
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ Wenn alles okay:
|
||||
"type": "creation|send|receive",
|
||||
"transaction_id": <transaction_id>, // db id not id from blockchain
|
||||
"date": "<date string>",
|
||||
"balance": <GDD balance in GDD cent /10000>,
|
||||
"balance": <GDD balance in GDD cent>,
|
||||
"memo": "<Verwendungszweck>",
|
||||
"pubkey": "<other_user.public_key in hex>"
|
||||
|
||||
@ -319,7 +319,7 @@ Wenn alles okay:
|
||||
"type": "creation|send|receive",
|
||||
"transaction_id": <transaction_id>, // db id not id from blockchain
|
||||
"date": "<date string>",
|
||||
"balance": <GDD balance in GDD cent /10000>,
|
||||
"balance": <GDD balance in GDD cent>,
|
||||
"memo": "<Verwendungszweck>",
|
||||
"pubkey": "<other_user.public_key in hex>"
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.{js,vue}', '!**/node_modules/**', '!**/?(*.)+(spec|test).js?(x)'],
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
//'jsx',
|
||||
// 'jsx',
|
||||
'json',
|
||||
'vue',
|
||||
],
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
"dropzone": "^5.5.1",
|
||||
"element-ui": "2.4.11",
|
||||
"es6-promise": "^4.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-loader": "^4.0.2",
|
||||
@ -40,7 +40,6 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-vue": "^7.8.0",
|
||||
"express": "^4.17.1",
|
||||
"flatpickr": "^4.5.7",
|
||||
|
||||
@ -7,6 +7,7 @@ const port = process.env.PORT || 3000
|
||||
|
||||
// Express Server
|
||||
const app = express()
|
||||
// eslint-disable-next-line node/no-path-concat
|
||||
app.use(serveStatic(__dirname + '/../dist'))
|
||||
app.listen(port)
|
||||
|
||||
|
||||
@ -36,11 +36,6 @@ export default {
|
||||
DashboardLayout,
|
||||
AuthLayoutGDD,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
language: 'en',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
|
||||
@ -32,15 +32,17 @@ const apiPost = async (url, payload) => {
|
||||
}
|
||||
|
||||
const communityAPI = {
|
||||
balance: async (session_id) => {
|
||||
return apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + session_id)
|
||||
balance: async (sessionId) => {
|
||||
return apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
|
||||
},
|
||||
transactions: async (session_id) => {
|
||||
return apiGet(CONFIG.COMMUNITY_API_URL + 'listTransactions/1/25/ASC/' + session_id)
|
||||
transactions: async (sessionId, firstPage = 1, items = 1000, order = 'DESC') => {
|
||||
return apiGet(
|
||||
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
|
||||
)
|
||||
},
|
||||
/*create: async (session_id, email, amount, memo, target_date = new Date() ) => {
|
||||
/* create: async (sessionId, email, amount, memo, target_date = new Date() ) => {
|
||||
const payload = {
|
||||
session_id,
|
||||
sessionId,
|
||||
email,
|
||||
amount,
|
||||
target_date,
|
||||
@ -48,14 +50,14 @@ const communityAPI = {
|
||||
auto_sign: true,
|
||||
}
|
||||
return apiPost(CONFIG.COMMUNITY_API__URL + 'createCoins/', payload)
|
||||
},*/
|
||||
send: async (session_id, email, amount, memo, target_date) => {
|
||||
}, */
|
||||
send: async (sessionId, email, amount, memo, targetDate) => {
|
||||
const payload = {
|
||||
session_id,
|
||||
session_id: sessionId,
|
||||
email,
|
||||
amount,
|
||||
memo,
|
||||
target_date,
|
||||
target_date: targetDate,
|
||||
auto_sign: true,
|
||||
}
|
||||
return apiPost(CONFIG.COMMUNITY_API_URL + 'sendCoins/', payload)
|
||||
|
||||
@ -29,7 +29,7 @@ const apiPost = async (url, payload) => {
|
||||
throw new Error('HTTP Status Error ' + result.status)
|
||||
}
|
||||
if (result.data.state === 'warning') {
|
||||
return { success: true, result: error }
|
||||
return { success: true, result: result.error }
|
||||
}
|
||||
if (result.data.state !== 'success') {
|
||||
throw new Error(result.data.msg)
|
||||
@ -48,15 +48,15 @@ const loginAPI = {
|
||||
}
|
||||
return apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', payload)
|
||||
},
|
||||
logout: async (session_id) => {
|
||||
const payload = { session_id }
|
||||
logout: async (sessionId) => {
|
||||
const payload = { session_id: sessionId }
|
||||
return apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
|
||||
},
|
||||
create: async (email, first_name, last_name, password) => {
|
||||
create: async (email, firstName, lastName, password) => {
|
||||
const payload = {
|
||||
email,
|
||||
first_name,
|
||||
last_name,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
password,
|
||||
emailType: EMAIL_TYPE.DEFAULT,
|
||||
login_after_register: true,
|
||||
@ -76,9 +76,9 @@ const loginAPI = {
|
||||
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
|
||||
)
|
||||
},
|
||||
changePassword: async (session_id, email, password) => {
|
||||
changePassword: async (sessionId, email, password) => {
|
||||
const payload = {
|
||||
session_id,
|
||||
session_id: sessionId,
|
||||
email,
|
||||
update: {
|
||||
'User.password': password,
|
||||
|
||||
@ -42,7 +42,7 @@ export default {
|
||||
FadeTransition,
|
||||
},
|
||||
created() {
|
||||
//console.log('base-alert gesetzt in =>', this.$route.path)
|
||||
// console.log('base-alert gesetzt in =>', this.$route.path)
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
|
||||
@ -62,7 +62,7 @@ export default {
|
||||
})
|
||||
const slider = this.$el.noUiSlider
|
||||
slider.on('slide', () => {
|
||||
let value = slider.get()
|
||||
const value = slider.get()
|
||||
if (value !== this.value) {
|
||||
this.$emit('input', value)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { parseOptions } from '@/components/Charts/optionHelpers'
|
||||
import Chart from 'chart.js'
|
||||
|
||||
export const Charts = {
|
||||
mode: 'light', //(themeMode) ? themeMode : 'light';
|
||||
mode: 'light', // (themeMode) ? themeMode : 'light';
|
||||
fonts: {
|
||||
base: 'Open Sans',
|
||||
},
|
||||
@ -34,9 +34,9 @@ export const Charts = {
|
||||
}
|
||||
|
||||
function chartOptions() {
|
||||
let { colors, mode, fonts } = Charts
|
||||
const { colors, mode, fonts } = Charts
|
||||
// Options
|
||||
let options = {
|
||||
const options = {
|
||||
defaults: {
|
||||
global: {
|
||||
responsive: true,
|
||||
@ -59,21 +59,21 @@ function chartOptions() {
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
backgroundColor: colors.theme['primary'],
|
||||
backgroundColor: colors.theme.primary,
|
||||
},
|
||||
line: {
|
||||
tension: 0.4,
|
||||
borderWidth: 4,
|
||||
borderColor: colors.theme['primary'],
|
||||
borderColor: colors.theme.primary,
|
||||
backgroundColor: colors.transparent,
|
||||
borderCapStyle: 'rounded',
|
||||
},
|
||||
rectangle: {
|
||||
backgroundColor: colors.theme['warning'],
|
||||
backgroundColor: colors.theme.warning,
|
||||
},
|
||||
arc: {
|
||||
backgroundColor: colors.theme['primary'],
|
||||
borderColor: mode == 'dark' ? colors.gray[800] : colors.white,
|
||||
backgroundColor: colors.theme.primary,
|
||||
borderColor: mode === 'dark' ? colors.gray[800] : colors.white,
|
||||
borderWidth: 4,
|
||||
},
|
||||
},
|
||||
@ -94,11 +94,11 @@ function chartOptions() {
|
||||
},
|
||||
cutoutPercentage: 83,
|
||||
legendCallback: function (chart) {
|
||||
let data = chart.data
|
||||
const data = chart.data
|
||||
let content = ''
|
||||
|
||||
data.labels.forEach(function (label, index) {
|
||||
let bgColor = data.datasets[0].backgroundColor[index]
|
||||
const bgColor = data.datasets[0].backgroundColor[index]
|
||||
|
||||
content += '<span class="chart-legend-item">'
|
||||
content +=
|
||||
@ -172,7 +172,7 @@ export const basicOptions = {
|
||||
},
|
||||
responsive: true,
|
||||
}
|
||||
export let blueChartOptions = {
|
||||
export const blueChartOptions = {
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
@ -185,7 +185,7 @@ export let blueChartOptions = {
|
||||
},
|
||||
}
|
||||
|
||||
export let lineChartOptionsBlue = {
|
||||
export const lineChartOptionsBlue = {
|
||||
...basicOptions,
|
||||
tooltips: {
|
||||
backgroundColor: '#f5f5f5',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Parse global options
|
||||
export function parseOptions(parent, options) {
|
||||
for (let item in options) {
|
||||
for (const item in options) {
|
||||
if (typeof options[item] !== 'object') {
|
||||
parent[item] = options[item]
|
||||
} else {
|
||||
|
||||
@ -4,13 +4,13 @@
|
||||
//
|
||||
import Chart from 'chart.js'
|
||||
Chart.elements.Rectangle.prototype.draw = function () {
|
||||
let ctx = this._chart.ctx
|
||||
let vm = this._view
|
||||
let left, right, top, bottom, signX, signY, borderSkipped, radius
|
||||
const ctx = this._chart.ctx
|
||||
const vm = this._view
|
||||
let left, right, top, bottom, signX, signY, borderSkipped
|
||||
let borderWidth = vm.borderWidth
|
||||
// Set Radius Here
|
||||
// If radius is large enough to cause drawing errors a max radius is imposed
|
||||
let cornerRadius = 6
|
||||
const cornerRadius = 6
|
||||
|
||||
if (!vm.horizontal) {
|
||||
// bar
|
||||
@ -36,14 +36,14 @@ Chart.elements.Rectangle.prototype.draw = function () {
|
||||
// adjust the sizes to fit if we're setting a stroke on the line
|
||||
if (borderWidth) {
|
||||
// borderWidth shold be less than bar width and bar height.
|
||||
let barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom))
|
||||
const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom))
|
||||
borderWidth = borderWidth > barSize ? barSize : borderWidth
|
||||
let halfStroke = borderWidth / 2
|
||||
const halfStroke = borderWidth / 2
|
||||
// Adjust borderWidth when bar top position is near vm.base(zero).
|
||||
let borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0)
|
||||
let borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0)
|
||||
let borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0)
|
||||
let borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0)
|
||||
const borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0)
|
||||
const borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0)
|
||||
const borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0)
|
||||
const borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0)
|
||||
// not become a vertical line?
|
||||
if (borderLeft !== borderRight) {
|
||||
top = borderTop
|
||||
@ -64,7 +64,7 @@ Chart.elements.Rectangle.prototype.draw = function () {
|
||||
// Corner points, from bottom-left to bottom-right clockwise
|
||||
// | 1 2 |
|
||||
// | 0 3 |
|
||||
let corners = [
|
||||
const corners = [
|
||||
[left, bottom],
|
||||
[left, top],
|
||||
[right, top],
|
||||
@ -72,7 +72,7 @@ Chart.elements.Rectangle.prototype.draw = function () {
|
||||
]
|
||||
|
||||
// Find first (starting) corner with fallback to 'bottom'
|
||||
let borders = ['bottom', 'left', 'top', 'right']
|
||||
const borders = ['bottom', 'left', 'top', 'right']
|
||||
let startCorner = borders.indexOf(borderSkipped, 0)
|
||||
if (startCorner === -1) {
|
||||
startCorner = 0
|
||||
@ -89,16 +89,14 @@ Chart.elements.Rectangle.prototype.draw = function () {
|
||||
for (let i = 1; i < 4; i++) {
|
||||
corner = cornerAt(i)
|
||||
let nextCornerId = i + 1
|
||||
if (nextCornerId == 4) {
|
||||
if (nextCornerId === 4) {
|
||||
nextCornerId = 0
|
||||
}
|
||||
|
||||
let nextCorner = cornerAt(nextCornerId)
|
||||
|
||||
let width = corners[2][0] - corners[1][0]
|
||||
let height = corners[0][1] - corners[1][1]
|
||||
let x = corners[1][0]
|
||||
let y = corners[1][1]
|
||||
const width = corners[2][0] - corners[1][0]
|
||||
const height = corners[0][1] - corners[1][1]
|
||||
const x = corners[1][0]
|
||||
const y = corners[1][1]
|
||||
|
||||
let radius = cornerRadius
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ const localVue = global.localVue
|
||||
|
||||
describe('CloseButton', () => {
|
||||
let wrapper
|
||||
let propsData = {
|
||||
const propsData = {
|
||||
target: 'Target',
|
||||
expanded: false,
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
activate() {
|
||||
let wasActive = this.active
|
||||
const wasActive = this.active
|
||||
if (!this.multipleActive) {
|
||||
this.deactivateAll()
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateValue(evt) {
|
||||
let value = evt.target.value
|
||||
const value = evt.target.value
|
||||
this.$emit('input', value)
|
||||
},
|
||||
onFocus(evt) {
|
||||
|
||||
@ -60,7 +60,7 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
validator(value) {
|
||||
let acceptedValues = ['', 'notice', 'mini']
|
||||
const acceptedValues = ['', 'notice', 'mini']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Modal type (notice|mini|"") ',
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
type: String,
|
||||
description: 'Modal size',
|
||||
validator(value) {
|
||||
let acceptedValues = ['', 'sm', 'lg']
|
||||
const acceptedValues = ['', 'sm', 'lg']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
},
|
||||
|
||||
@ -39,7 +39,7 @@ export default {
|
||||
submittedNames: [],
|
||||
}
|
||||
},
|
||||
/*Modal*/
|
||||
/* Modal */
|
||||
checkFormValidity() {
|
||||
const valid = this.$refs.form.checkValidity()
|
||||
this.nameState = valid
|
||||
|
||||
@ -90,8 +90,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
let color = `bg-${this.type}`
|
||||
let classes = [
|
||||
const color = `bg-${this.type}`
|
||||
const classes = [
|
||||
{ 'navbar-transparent': this.transparent },
|
||||
{ [`navbar-expand-${this.expand}`]: this.expand },
|
||||
]
|
||||
|
||||
@ -59,7 +59,7 @@ export default {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: (value) => {
|
||||
let acceptedValues = ['top', 'bottom']
|
||||
const acceptedValues = ['top', 'bottom']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Vertical alignment of notification (top|bottom)',
|
||||
@ -68,7 +68,7 @@ export default {
|
||||
type: String,
|
||||
default: 'right',
|
||||
validator: (value) => {
|
||||
let acceptedValues = ['left', 'center', 'right']
|
||||
const acceptedValues = ['left', 'center', 'right']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Horizontal alignment of notification (left|center|right)',
|
||||
@ -77,7 +77,7 @@ export default {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: (value) => {
|
||||
let acceptedValues = ['default', 'info', 'primary', 'danger', 'warning', 'success']
|
||||
const acceptedValues = ['default', 'info', 'primary', 'danger', 'warning', 'success']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description:
|
||||
@ -129,8 +129,8 @@ export default {
|
||||
return `alert-${this.type}`
|
||||
},
|
||||
customPosition() {
|
||||
let initialMargin = 20
|
||||
let alertHeight = this.elmHeight + 10
|
||||
const initialMargin = 20
|
||||
const alertHeight = this.elmHeight + 10
|
||||
let sameAlertsCount = this.$notifications.state.filter((alert) => {
|
||||
return (
|
||||
alert.horizontalAlign === this.horizontalAlign &&
|
||||
@ -141,8 +141,8 @@ export default {
|
||||
if (this.$notifications.settings.overlap) {
|
||||
sameAlertsCount = 1
|
||||
}
|
||||
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin
|
||||
let styles = {}
|
||||
const pixels = (sameAlertsCount - 1) * alertHeight + initialMargin
|
||||
const styles = {}
|
||||
if (this.verticalAlign === 'top') {
|
||||
styles.top = `${pixels}px`
|
||||
} else {
|
||||
|
||||
@ -44,7 +44,7 @@ const NotificationStore = {
|
||||
|
||||
const NotificationsPlugin = {
|
||||
install(Vue, options) {
|
||||
let app = new Vue({
|
||||
const app = new Vue({
|
||||
data: {
|
||||
notificationStore: NotificationStore,
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import VueBootstrapTypeahead from 'vue-bootstrap-typeahead'
|
||||
|
||||
// Global registration
|
||||
//Vue.component('vue-bootstrap-typeahead', VueBootstrapTypeahead)
|
||||
// Vue.component('vue-bootstrap-typeahead', VueBootstrapTypeahead)
|
||||
|
||||
// OR
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
<a
|
||||
:href="`https://elopage.com/s/gradido/sign_in?locale=${$i18n.locale}`"
|
||||
class="nav-link text-lg"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('members_area') }}
|
||||
</a>
|
||||
|
||||
@ -110,7 +110,7 @@ export default {
|
||||
},
|
||||
linkPrefix() {
|
||||
if (this.link.name) {
|
||||
let words = this.link.name.split(' ')
|
||||
const words = this.link.name.split(' ')
|
||||
return words.map((word) => word.substring(0, 1)).join('')
|
||||
}
|
||||
return ''
|
||||
@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
isActive() {
|
||||
if (this.$route && this.$route.path) {
|
||||
let matchingRoute = this.children.find((c) => this.$route.path.startsWith(c.link.path))
|
||||
const matchingRoute = this.children.find((c) => this.$route.path.startsWith(c.link.path))
|
||||
if (matchingRoute !== undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ const SidebarPlugin = {
|
||||
if (options && options.sidebarLinks) {
|
||||
SidebarStore.sidebarLinks = options.sidebarLinks
|
||||
}
|
||||
let app = new Vue({
|
||||
const app = new Vue({
|
||||
data: {
|
||||
sidebarStore: SidebarStore,
|
||||
},
|
||||
|
||||
@ -67,7 +67,7 @@ export default {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value) => {
|
||||
let acceptedValues = ['primary', 'info', 'success', 'warning', 'danger']
|
||||
const acceptedValues = ['primary', 'info', 'success', 'warning', 'danger']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
},
|
||||
@ -102,7 +102,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
findAndActivateTab(title) {
|
||||
let tabToActivate = this.tabs.find((t) => t.title === title)
|
||||
const tabToActivate = this.tabs.find((t) => t.title === title)
|
||||
if (tabToActivate) {
|
||||
this.activateTab(tabToActivate)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ export default {
|
||||
bind: function (el, binding, vnode) {
|
||||
el.clickOutsideEvent = function (event) {
|
||||
// here I check that click was outside the el and his childrens
|
||||
if (!(el == event.target || el.contains(event.target))) {
|
||||
if (!(el === event.target || el.contains(event.target))) {
|
||||
// and if it did, call method provided in attribute value
|
||||
vnode.context[binding.expression](event)
|
||||
}
|
||||
|
||||
@ -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"
|
||||
@ -21,6 +21,7 @@
|
||||
"form": {
|
||||
"attention": "<strong>Achtung!</strong> Bitte überprüfe alle deine Eingaben sehr genau. Du bist alleine Verantwortlich für deine Entscheidungen. Versendete Gradidos können nicht wieder zurück geholt werden.",
|
||||
"cancel":"Abbrechen",
|
||||
"reset": "Zurücksetzen",
|
||||
"close":"schließen",
|
||||
"receiver":"Empfänger",
|
||||
"sender":"Absender",
|
||||
@ -40,14 +41,23 @@
|
||||
"time":"Zeit",
|
||||
"send_now":"Jetzt versenden",
|
||||
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
|
||||
"max_gdd_info":"maximale anzahl GDD zum versenden erreicht!",
|
||||
"max_gdd_info":"Maximale anzahl GDD zum versenden erreicht!",
|
||||
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
|
||||
"thx":"Danke!",
|
||||
"send_success":"Deine Zahlung wurde erfolgreich versendet."
|
||||
"thx":"Danke",
|
||||
"sorry":"Entschuldigung",
|
||||
"send_transaction_success":"Deine Transaktion wurde erfolgreich ausgeführt",
|
||||
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
|
||||
"validation": {
|
||||
"double": "Das Feld {field} muss eine Dezimalzahl mit zwei Nachkommastellen sein",
|
||||
"is-not": "Du kannst Dir selbst keine Gradidos überweisen"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"error":"Fehler"
|
||||
},
|
||||
"transaction":{
|
||||
"show_part": "Die letzten <strong>{count}</strong> Transaktionen",
|
||||
"show_all":"Alle <strong>{count}</strong> Transaktionen ansehen",
|
||||
"nullTransactions":"Du hast noch keine Transaktionen auf deinem Konto.",
|
||||
"more": "mehr"
|
||||
},
|
||||
"site": {
|
||||
@ -113,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."
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,14 +13,15 @@
|
||||
"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",
|
||||
"receiver":"Receiver",
|
||||
"sender":"Sender",
|
||||
@ -39,15 +40,25 @@
|
||||
"at":"at",
|
||||
"time":"Time",
|
||||
"send_now":"Send now",
|
||||
"scann_code":"<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
|
||||
"max_gdd_info":"maximale anzahl GDD zum versenden erreicht!",
|
||||
"send_check":"Bestätige deine Zahlung. Prüfe bitte nochmal alle Daten!",
|
||||
"thx":"THX",
|
||||
"send_success":"Deine Zahlung wurde erfolgreich versendet."
|
||||
"scann_code":"<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
|
||||
"max_gdd_info":"Maximum number of GDDs to be sent has been reached!",
|
||||
"send_check":"Confirm your payment. Please check all data again!",
|
||||
"thx":"Thank you",
|
||||
"sorry":"Sorry",
|
||||
"send_transaction_success":"Your transaction was successfully completed",
|
||||
"send_transaction_error":"Unfortunately, the transaction could not be executed!",
|
||||
"validation": {
|
||||
"double": "The {field} field must be a decimal with two digits",
|
||||
"is-not": "You cannot send Gradidos to yourself"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"error":"Error"
|
||||
},
|
||||
"transaction":{
|
||||
"show_part": "The last <strong>{count}</strong> transactions",
|
||||
"show_all":"View all <strong>{count}</strong> transactions",
|
||||
"show_all":"View all <strong>{count}</strong> transactions.",
|
||||
"show_part": "The last <strong>{count}</strong> transactions.",
|
||||
"nullTransactions":"You don't have any transactions on your account yet.",
|
||||
"more": "more"
|
||||
},
|
||||
"site": {
|
||||
@ -114,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."
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@ import Vue from 'vue'
|
||||
import DashboardPlugin from './plugins/dashboard-plugin'
|
||||
import App from './App.vue'
|
||||
import i18n from './i18n.js'
|
||||
import VeeValidate from './vee-validate.js'
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// eslint-disable-next-line camelcase
|
||||
import { required, email, min, between, double, is_not } from 'vee-validate/dist/rules'
|
||||
|
||||
// store
|
||||
import { store } from './store/store'
|
||||
@ -15,13 +17,52 @@ Vue.use(DashboardPlugin)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth && !store.state.session_id) {
|
||||
if (to.meta.requiresAuth && !store.state.sessionId) {
|
||||
next({ path: '/login' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
configure({
|
||||
defaultMessage: (field, values) => {
|
||||
values._field_ = i18n.t(`fields.${field}`)
|
||||
return i18n.t(`validations.messages.${values._rule_}`, values)
|
||||
},
|
||||
})
|
||||
|
||||
extend('email', {
|
||||
...email,
|
||||
message: (_, values) => i18n.t('validations.messages.email', values),
|
||||
})
|
||||
|
||||
extend('required', {
|
||||
...required,
|
||||
message: (_, values) => i18n.t('validations.messages.required', values),
|
||||
})
|
||||
|
||||
extend('min', {
|
||||
...min,
|
||||
message: (_, values) => i18n.t('validations.messages.min', values),
|
||||
})
|
||||
|
||||
extend('double', {
|
||||
...double,
|
||||
message: (_, values) => i18n.t('form.validation.double', values),
|
||||
})
|
||||
|
||||
extend('between', {
|
||||
...between,
|
||||
message: (_, values) => i18n.t('validations.messages.between', values),
|
||||
})
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
extend('is_not', {
|
||||
// eslint-disable-next-line camelcase
|
||||
...is_not,
|
||||
message: (_, values) => i18n.t('form.validation.is-not', values),
|
||||
})
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
|
||||
@ -3,7 +3,7 @@ import '@/polyfills'
|
||||
// Notifications plugin. Used on Notifications page
|
||||
import Notifications from '@/components/NotificationPlugin'
|
||||
// Validation plugin used to validate forms
|
||||
import { configure } from 'vee-validate'
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// A plugin file where you could register global components used across the app
|
||||
import GlobalComponents from './globalComponents'
|
||||
// A plugin file where you could register global directives
|
||||
@ -14,7 +14,6 @@ import SideBar from '@/components/SidebarPlugin'
|
||||
// element ui language configuration
|
||||
import lang from 'element-ui/lib/locale/lang/en'
|
||||
import locale from 'element-ui/lib/locale'
|
||||
locale.use(lang)
|
||||
|
||||
// vue-bootstrap
|
||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
@ -22,7 +21,6 @@ import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
// asset imports
|
||||
import '@/assets/scss/argon.scss'
|
||||
import '@/assets/vendor/nucleo/css/nucleo.css'
|
||||
import { extend } from 'vee-validate'
|
||||
import * as rules from 'vee-validate/dist/rules'
|
||||
import { messages } from 'vee-validate/dist/locale/en.json'
|
||||
|
||||
@ -40,6 +38,7 @@ import VueMoment from 'vue-moment'
|
||||
import Loading from 'vue-loading-overlay'
|
||||
// import the styles
|
||||
import 'vue-loading-overlay/dist/vue-loading.css'
|
||||
locale.use(lang)
|
||||
|
||||
Object.keys(rules).forEach((rule) => {
|
||||
extend(rule, {
|
||||
|
||||
@ -9,7 +9,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/overview',
|
||||
component: () => import('../views/Pages/KontoOverview.vue'),
|
||||
component: () => import('../views/Pages/AccountOverview.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
@ -21,20 +21,20 @@ const routes = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
//{
|
||||
// {
|
||||
// path: '/profileedit',
|
||||
// component: () => import('../views/Pages/UserProfileEdit.vue'),
|
||||
// meta: {
|
||||
// requiresAuth: true,
|
||||
// },
|
||||
//},
|
||||
//{
|
||||
// },
|
||||
// {
|
||||
// path: '/activity',
|
||||
// component: () => import('../views/Pages/UserProfileActivity.vue'),
|
||||
// meta: {
|
||||
// requiresAuth: true,
|
||||
// },
|
||||
//},
|
||||
// },
|
||||
{
|
||||
path: '/transactions',
|
||||
component: () => import('../views/Pages/UserProfileTransactionList.vue'),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
Vue.use(Vuex)
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
Vue.use(Vuex)
|
||||
|
||||
export const mutations = {
|
||||
language: (state, language) => {
|
||||
@ -10,18 +10,18 @@ export const mutations = {
|
||||
email: (state, email) => {
|
||||
state.email = email
|
||||
},
|
||||
session_id: (state, session_id) => {
|
||||
state.session_id = session_id
|
||||
sessionId: (state, sessionId) => {
|
||||
state.sessionId = sessionId
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
login: ({ dispatch, commit }, data) => {
|
||||
commit('session_id', data.session_id)
|
||||
commit('sessionId', data.sessionId)
|
||||
commit('email', data.email)
|
||||
},
|
||||
logout: ({ commit, state }) => {
|
||||
commit('session_id', null)
|
||||
commit('sessionId', null)
|
||||
commit('email', null)
|
||||
sessionStorage.clear()
|
||||
},
|
||||
@ -34,7 +34,7 @@ export const store = new Vuex.Store({
|
||||
}),
|
||||
],
|
||||
state: {
|
||||
session_id: null,
|
||||
sessionId: null,
|
||||
email: '',
|
||||
language: 'en',
|
||||
modals: false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { mutations, actions } from './store'
|
||||
|
||||
const { language, email, session_id } = mutations
|
||||
const { language, email, sessionId } = mutations
|
||||
const { login, logout } = actions
|
||||
|
||||
describe('Vuex store', () => {
|
||||
@ -21,11 +21,11 @@ describe('Vuex store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('session_id', () => {
|
||||
it('sets the state of session_id', () => {
|
||||
const state = { session_id: null }
|
||||
session_id(state, '1234')
|
||||
expect(state.session_id).toEqual('1234')
|
||||
describe('sessionId', () => {
|
||||
it('sets the state of sessionId', () => {
|
||||
const state = { sessionId: null }
|
||||
sessionId(state, '1234')
|
||||
expect(state.sessionId).toEqual('1234')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -36,17 +36,17 @@ describe('Vuex store', () => {
|
||||
const state = {}
|
||||
|
||||
it('calls two commits', () => {
|
||||
login({ commit, state }, { session_id: 1234, email: 'someone@there.is' })
|
||||
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
|
||||
expect(commit).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('commits session_id', () => {
|
||||
login({ commit, state }, { session_id: 1234, email: 'someone@there.is' })
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'session_id', 1234)
|
||||
it('commits sessionId', () => {
|
||||
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
login({ commit, state }, { session_id: 1234, email: 'someone@there.is' })
|
||||
login({ commit, state }, { sessionId: 1234, email: 'someone@there.is' })
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is')
|
||||
})
|
||||
})
|
||||
@ -60,9 +60,9 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('commits session_id', () => {
|
||||
it('commits sessionId', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'session_id', null)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', null)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { configure, extend } from 'vee-validate'
|
||||
import { required, email, min } from 'vee-validate/dist/rules'
|
||||
import i18n from './i18n'
|
||||
|
||||
configure({
|
||||
defaultMessage: (field, values) => {
|
||||
values._field_ = i18n.t(`fields.${field}`)
|
||||
return i18n.t(`validations.messages.${values._rule_}`, values)
|
||||
},
|
||||
})
|
||||
|
||||
extend('email', {
|
||||
...email,
|
||||
message: (_, values) => i18n.t('validations.messages.email', values),
|
||||
})
|
||||
|
||||
extend('required', {
|
||||
...required,
|
||||
message: (_, values) => i18n.t('validations.messages.required', values),
|
||||
})
|
||||
|
||||
extend('min', {
|
||||
...min,
|
||||
message: (_, values) => i18n.t('validations.messages.min', values),
|
||||
})
|
||||
@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div class="main-content">
|
||||
<template>
|
||||
<div class="main-content">
|
||||
<router-view></router-view>
|
||||
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
|
||||
</div>
|
||||
</template>
|
||||
<div class="wrapper">
|
||||
<div class="main-content">
|
||||
<router-view></router-view>
|
||||
<content-footer v-if="!$route.meta.hideFooter"></content-footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@ -7,7 +7,7 @@ const localVue = global.localVue
|
||||
describe('ContentFooter', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
@ -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/',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,7 +4,11 @@
|
||||
<b-col>
|
||||
<div class="copyright text-center text-lg-center text-muted">
|
||||
© {{ year }}
|
||||
<a :href="`https://gradido.net/${$i18n.locale}`" class="font-weight-bold ml-1">
|
||||
<a
|
||||
:href="`https://gradido.net/${$i18n.locale}`"
|
||||
class="font-weight-bold ml-1"
|
||||
target="_blank"
|
||||
>
|
||||
Gradido-Akademie
|
||||
</a>
|
||||
|
|
||||
@ -39,6 +43,9 @@
|
||||
>
|
||||
{{ $t('whitepaper') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :href="`https://gradido.net/${$i18n.locale}/contact/`" target="_blank">
|
||||
{{ $t('site.navbar.support') }}
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
@ -20,7 +20,7 @@ const transitionStub = () => ({
|
||||
describe('DashboardLayoutGdd', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
@ -28,7 +28,7 @@ describe('DashboardLayoutGdd', () => {
|
||||
$n: jest.fn(),
|
||||
}
|
||||
|
||||
let state = {
|
||||
const state = {
|
||||
user: {
|
||||
name: 'Peter Lustig',
|
||||
balance: 2546,
|
||||
@ -37,12 +37,12 @@ describe('DashboardLayoutGdd', () => {
|
||||
email: 'peter.lustig@example.org',
|
||||
}
|
||||
|
||||
let stubs = {
|
||||
const stubs = {
|
||||
RouterLink: RouterLinkStub,
|
||||
FadeTransition: transitionStub(),
|
||||
}
|
||||
|
||||
let store = new Vuex.Store({
|
||||
const store = new Vuex.Store({
|
||||
state,
|
||||
})
|
||||
|
||||
@ -79,7 +79,7 @@ describe('DashboardLayoutGdd', () => {
|
||||
})
|
||||
|
||||
it('has five items in the navbar', () => {
|
||||
expect(navbar.findAll('ul > li')).toHaveLength(3)
|
||||
expect(navbar.findAll('ul > li')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('has first item "send" in navbar', () => {
|
||||
@ -104,41 +104,41 @@ describe('DashboardLayoutGdd', () => {
|
||||
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/transactions')
|
||||
})
|
||||
|
||||
it('has third item "My profile" in navbar', () => {
|
||||
expect(navbar.findAll('ul > li').at(2).text()).toEqual('site.navbar.my-profil')
|
||||
})
|
||||
|
||||
it.skip('has third item "My profile" linked to profile in navbar', async () => {
|
||||
navbar.findAll('ul > li > a').at(2).trigger('click')
|
||||
await flushPromises()
|
||||
await jest.runAllTimers()
|
||||
await flushPromises()
|
||||
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/profile')
|
||||
})
|
||||
|
||||
//it('has fourth item "Settigs" in navbar', () => {
|
||||
// expect(navbar.findAll('ul > li').at(3).text()).toEqual('site.navbar.settings')
|
||||
//})
|
||||
// it('has third item "My profile" in navbar', () => {
|
||||
// expect(navbar.findAll('ul > li').at(2).text()).toEqual('site.navbar.my-profil')
|
||||
// })
|
||||
//
|
||||
//it.skip('has fourth item "Settings" linked to profileedit in navbar', async () => {
|
||||
// it.skip('has third item "My profile" linked to profile in navbar', async () => {
|
||||
// navbar.findAll('ul > li > a').at(2).trigger('click')
|
||||
// await flushPromises()
|
||||
// await jest.runAllTimers()
|
||||
// await flushPromises()
|
||||
// expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/profile')
|
||||
// })
|
||||
|
||||
// it('has fourth item "Settigs" in navbar', () => {
|
||||
// expect(navbar.findAll('ul > li').at(3).text()).toEqual('site.navbar.settings')
|
||||
// })
|
||||
//
|
||||
// it.skip('has fourth item "Settings" linked to profileedit in navbar', async () => {
|
||||
// navbar.findAll('ul > li > a').at(3).trigger('click')
|
||||
// await flushPromises()
|
||||
// await jest.runAllTimers()
|
||||
// await flushPromises()
|
||||
// expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/profileedit')
|
||||
//})
|
||||
// })
|
||||
|
||||
//it('has fifth item "Activity" in navbar', () => {
|
||||
// it('has fifth item "Activity" in navbar', () => {
|
||||
// expect(navbar.findAll('ul > li').at(4).text()).toEqual('site.navbar.activity')
|
||||
//})
|
||||
// })
|
||||
//
|
||||
//it.skip('has fourth item "Activity" linked to activity in navbar', async () => {
|
||||
// it.skip('has fourth item "Activity" linked to activity in navbar', async () => {
|
||||
// navbar.findAll('ul > li > a').at(4).trigger('click')
|
||||
// await flushPromises()
|
||||
// await jest.runAllTimers()
|
||||
// await flushPromises()
|
||||
// expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/activity')
|
||||
//})
|
||||
// })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,10 +9,10 @@
|
||||
<b-nav-item href="#!" to="/transactions">
|
||||
<b-nav-text class="p-0 text-lg text-muted">{{ $t('transactions') }}</b-nav-text>
|
||||
</b-nav-item>
|
||||
<!--
|
||||
<b-nav-item href="#!" to="/profile">
|
||||
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.my-profil') }}</b-nav-text>
|
||||
</b-nav-item>
|
||||
<!--
|
||||
</b-nav-item>
|
||||
<b-nav-item href="#!" to="/profileedit">
|
||||
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.settings') }}</b-nav-text>
|
||||
</b-nav-item>
|
||||
@ -31,7 +31,10 @@
|
||||
<router-view
|
||||
:balance="balance"
|
||||
:gdt-balance="GdtBalance"
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
@update-balance="updateBalance"
|
||||
@update-transactions="updateTransactions"
|
||||
></router-view>
|
||||
</fade-transition>
|
||||
</div>
|
||||
@ -44,12 +47,19 @@ import PerfectScrollbar from 'perfect-scrollbar'
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import loginAPI from '../../apis/loginAPI'
|
||||
|
||||
import DashboardNavbar from './DashboardNavbar.vue'
|
||||
import ContentFooter from './ContentFooter.vue'
|
||||
// import DashboardContent from './Content.vue';
|
||||
import { FadeTransition } from 'vue2-transitions'
|
||||
import communityAPI from '../../apis/communityAPI'
|
||||
|
||||
function hasElement(className) {
|
||||
return document.getElementsByClassName(className).length > 0
|
||||
}
|
||||
|
||||
function initScrollbar(className) {
|
||||
if (hasElement(className)) {
|
||||
// eslint-disable-next-line no-new
|
||||
new PerfectScrollbar(`.${className}`)
|
||||
} else {
|
||||
// try to init it later in case this component is loaded async
|
||||
@ -59,12 +69,6 @@ function initScrollbar(className) {
|
||||
}
|
||||
}
|
||||
|
||||
import DashboardNavbar from './DashboardNavbar.vue'
|
||||
import ContentFooter from './ContentFooter.vue'
|
||||
// import DashboardContent from './Content.vue';
|
||||
import { FadeTransition } from 'vue2-transitions'
|
||||
import communityAPI from '../../apis/communityAPI'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardNavbar,
|
||||
@ -76,33 +80,32 @@ export default {
|
||||
return {
|
||||
balance: 0,
|
||||
GdtBalance: 0,
|
||||
transactions: [],
|
||||
bookedBalance: 0,
|
||||
transactionCount: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initScrollbar() {
|
||||
let isWindows = navigator.platform.startsWith('Win')
|
||||
const isWindows = navigator.platform.startsWith('Win')
|
||||
if (isWindows) {
|
||||
initScrollbar('sidenav')
|
||||
}
|
||||
},
|
||||
async logout() {
|
||||
const result = await loginAPI.logout(this.$store.state.session_id)
|
||||
await loginAPI.logout(this.$store.state.sessionId)
|
||||
// do we have to check success?
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push('/login')
|
||||
},
|
||||
async loadBalance() {
|
||||
const result = await communityAPI.balance(this.$store.state.session_id)
|
||||
async updateTransactions() {
|
||||
const result = await communityAPI.transactions(this.$store.state.sessionId)
|
||||
if (result.success) {
|
||||
this.balance = result.result.data.balance / 10000
|
||||
} else {
|
||||
// what to do when loading balance fails?
|
||||
}
|
||||
},
|
||||
async loadGDTBalance() {
|
||||
const result = await communityAPI.transactions(this.$store.state.session_id)
|
||||
if (result.success) {
|
||||
this.GdtBalance = result.result.data.gdtSum / 10000
|
||||
this.GdtBalance = Number(result.result.data.gdtSum)
|
||||
this.transactions = result.result.data.transactions
|
||||
this.balance = Number(result.result.data.decay)
|
||||
this.bookedBalance = Number(result.result.data.balance)
|
||||
this.transactionCount = result.result.data.count
|
||||
} else {
|
||||
// what to do when loading balance fails?
|
||||
}
|
||||
@ -115,8 +118,7 @@ export default {
|
||||
this.initScrollbar()
|
||||
},
|
||||
created() {
|
||||
this.loadBalance()
|
||||
this.loadGDTBalance()
|
||||
this.updateTransactions()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -2,10 +2,9 @@
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="header py-1 py-lg-1 pt-lg-3">
|
||||
<b-container>xx</b-container>
|
||||
<b-container>
|
||||
<div class="header-body text-center mb-3">
|
||||
<a href="/login" to="/login">
|
||||
<a href="login" to="login">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 mt-5 mb-5">
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import KontoOverview from './KontoOverview'
|
||||
import AccountOverview from './AccountOverview'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('KontoOverview', () => {
|
||||
describe('AccountOverview', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(KontoOverview, { localVue, mocks })
|
||||
return shallowMount(AccountOverview, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('shallow Mount', () => {
|
||||
@ -35,12 +35,6 @@ describe('KontoOverview', () => {
|
||||
expect(wrapper.find('gdd-table-stub').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('updates transctions data when change-transactions is emitted', async () => {
|
||||
wrapper.find('gdd-table-stub').vm.$emit('change-transactions', [0, 1])
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.vm.transactions).toEqual(expect.arrayContaining([0, 1]))
|
||||
})
|
||||
|
||||
describe('updateBalance method', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.find('gdd-send-stub').vm.$emit('update-balance', {
|
||||
@ -1,12 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<base-header class="pb-6 pb-8 pt-5 pt-md-8 bg-transparent"></base-header>
|
||||
<b-container fluid class="mt--7">
|
||||
<gdd-status
|
||||
:balance="balance"
|
||||
:gdt-balance="GdtBalance"
|
||||
:show-transaction-list="showTransactionList"
|
||||
/>
|
||||
<base-header class="pb-4 pt-2 bg-transparent"></base-header>
|
||||
<b-container fluid class="p-2 mt-5">
|
||||
<gdd-status v-if="showTransactionList" :balance="balance" :gdt-balance="GdtBalance" />
|
||||
<br />
|
||||
<gdd-send
|
||||
:balance="balance"
|
||||
@ -16,34 +12,44 @@
|
||||
/>
|
||||
<hr />
|
||||
<gdd-table
|
||||
:show-transaction-list="showTransactionList"
|
||||
v-if="showTransactionList"
|
||||
:transactions="transactions"
|
||||
@change-transactions="setTransactions"
|
||||
:max="5"
|
||||
:timestamp="timestamp"
|
||||
:transactionCount="transactionCount"
|
||||
@update-transactions="updateTransactions"
|
||||
/>
|
||||
<gdd-table-footer :count="transactionCount" />
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import GddStatus from '../KontoOverview/GddStatus.vue'
|
||||
import GddSend from '../KontoOverview/GddSend.vue'
|
||||
import GddTable from '../KontoOverview/GddTable.vue'
|
||||
import GddStatus from './AccountOverview/GddStatus.vue'
|
||||
import GddSend from './AccountOverview/GddSend.vue'
|
||||
import GddTable from './AccountOverview/GddTable.vue'
|
||||
import GddTableFooter from './AccountOverview/GddTableFooter.vue'
|
||||
|
||||
export default {
|
||||
name: 'Overview',
|
||||
components: {
|
||||
GddStatus,
|
||||
GddSend,
|
||||
GddTable,
|
||||
GddTableFooter,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
transactions: [],
|
||||
showTransactionList: true,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
},
|
||||
props: {
|
||||
balance: { type: Number, default: 0 },
|
||||
GdtBalance: { type: Number, default: 0 },
|
||||
},
|
||||
components: {
|
||||
GddStatus,
|
||||
GddSend,
|
||||
GddTable,
|
||||
transactions: {
|
||||
default: () => [],
|
||||
},
|
||||
transactionCount: { type: Number, default: 0 },
|
||||
},
|
||||
methods: {
|
||||
toggleShowList(bool) {
|
||||
@ -52,8 +58,8 @@ export default {
|
||||
updateBalance(data) {
|
||||
this.$emit('update-balance', data.ammount)
|
||||
},
|
||||
setTransactions(transactions) {
|
||||
this.transactions = transactions
|
||||
updateTransactions() {
|
||||
this.$emit('update-transactions')
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -102,7 +102,7 @@ export default {
|
||||
created() {},
|
||||
watch: {
|
||||
$form: function () {
|
||||
stunden(this.form)
|
||||
this.stunden(this.form)
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
@ -133,16 +133,16 @@ export default {
|
||||
},
|
||||
deleteNewMessage: function (event) {
|
||||
this.form.splice(event, null)
|
||||
this.messages.splice(index, 1)
|
||||
this.messages.splice(this.index, 1)
|
||||
this.index--
|
||||
},
|
||||
submitForm: function (e) {
|
||||
//console.log('submitForm')
|
||||
// console.log('submitForm')
|
||||
this.messages = [{ DaysNumber: '', TextDecoded: '' }]
|
||||
this.submitted = true
|
||||
},
|
||||
textFocus() {
|
||||
//console.log('textFocus TODO')
|
||||
// console.log('textFocus TODO')
|
||||
},
|
||||
newWorkForm() {
|
||||
this.formular = `
|
||||
@ -174,7 +174,7 @@ export default {
|
||||
></textarea>
|
||||
</base-input>
|
||||
</b-col>
|
||||
`
|
||||
`
|
||||
|
||||
// console.log('newWorkForm TODO')
|
||||
const myElement = this.$refs.mydiv
|
||||
129
frontend/src/views/Pages/AccountOverview/GddSend.spec.js
Normal file
129
frontend/src/views/Pages/AccountOverview/GddSend.spec.js
Normal file
@ -0,0 +1,129 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GddSend from './GddSend'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('GddSend', () => {
|
||||
let wrapper
|
||||
|
||||
const state = {
|
||||
user: {
|
||||
balance: 1234,
|
||||
balance_gdt: 9876,
|
||||
},
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state,
|
||||
})
|
||||
|
||||
const mocks = {
|
||||
// $n: jest.fn((n) => n),
|
||||
$t: jest.fn((t) => t),
|
||||
$moment: jest.fn((m) => ({
|
||||
format: () => m,
|
||||
})),
|
||||
$i18n: {
|
||||
locale: jest.fn(() => 'en'),
|
||||
},
|
||||
$n: jest.fn((n) => String(n)),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(GddSend, { localVue, store, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('warning messages', () => {
|
||||
it('has a warning message', () => {
|
||||
expect(wrapper.find('div.alert-default').find('span').text()).toBe('form.attention')
|
||||
})
|
||||
})
|
||||
|
||||
describe('transaction form', () => {
|
||||
describe('email field', () => {
|
||||
it('has an input field of type email', () => {
|
||||
expect(wrapper.find('#input-group-1').find('input').attributes('type')).toBe('email')
|
||||
})
|
||||
|
||||
it('has an envelope icon', () => {
|
||||
expect(wrapper.find('#input-group-1').find('svg').attributes('aria-label')).toBe(
|
||||
'envelope',
|
||||
)
|
||||
})
|
||||
|
||||
it('has a label form.receiver', () => {
|
||||
expect(wrapper.findAll('div.text-left').at(0).text()).toBe('form.receiver')
|
||||
})
|
||||
|
||||
it('has a placeholder "E-Mail"', () => {
|
||||
expect(wrapper.find('#input-group-1').find('input').attributes('placeholder')).toBe(
|
||||
'E-Mail',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ammount field', () => {
|
||||
it('has an input field of type number', () => {
|
||||
expect(wrapper.find('#input-group-2').find('input').attributes('type')).toBe('number')
|
||||
})
|
||||
|
||||
it('has an GDD text icon', () => {
|
||||
expect(wrapper.find('#input-group-2').find('div.h3').text()).toBe('GDD')
|
||||
})
|
||||
|
||||
it('has a label form.amount', () => {
|
||||
expect(wrapper.findAll('div.text-left').at(1).text()).toBe('form.amount')
|
||||
})
|
||||
|
||||
it('has a placeholder "0.01"', () => {
|
||||
expect(wrapper.find('#input-group-2').find('input').attributes('placeholder')).toBe(
|
||||
'0.01',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('message text box', () => {
|
||||
it('has an textarea field', () => {
|
||||
expect(wrapper.find('#input-group-3').find('textarea').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has an chat-right-text icon', () => {
|
||||
expect(wrapper.find('#input-group-3').find('svg').attributes('aria-label')).toBe(
|
||||
'chat right text',
|
||||
)
|
||||
})
|
||||
|
||||
it('has a label form.memo', () => {
|
||||
expect(wrapper.findAll('div.text-left').at(2).text()).toBe('form.memo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancel button', () => {
|
||||
it('has a cancel button', () => {
|
||||
expect(wrapper.find('button[type="reset"]').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has the text "form.cancel"', () => {
|
||||
expect(wrapper.find('button[type="reset"]').text()).toBe('form.reset')
|
||||
})
|
||||
|
||||
it.skip('clears the email field on click', async () => {
|
||||
wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
|
||||
wrapper.find('button[type="reset"]').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.vm.form.email).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gdd-send">
|
||||
<b-row v-show="showTransactionList">
|
||||
<b-col xl="12" md="12">
|
||||
<b-alert show dismissible variant="warning" class="text-center">
|
||||
<span class="alert-text" v-html="$t('form.attention')"></span>
|
||||
<b-alert show dismissible variant="default" class="text-center">
|
||||
<span class="alert-text h3 text-light" v-html="$t('form.attention')"></span>
|
||||
</b-alert>
|
||||
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
|
||||
<!--
|
||||
<b-alert show variant="secondary">
|
||||
<span class="alert-text" v-html="$t('form.scann_code')"></span>
|
||||
<b-col v-show="!scan" lg="12" class="text-right">
|
||||
@ -15,9 +16,9 @@
|
||||
</b-col>
|
||||
|
||||
<div v-if="scan">
|
||||
<!-- <b-row>
|
||||
<b-row>
|
||||
<qrcode-capture @detect="onDetect" capture="user" ></qrcode-capture>
|
||||
</b-row> -->
|
||||
</b-row>
|
||||
|
||||
<qrcode-stream class="mt-3" @decode="onDecode" @detect="onDetect"></qrcode-stream>
|
||||
|
||||
@ -39,6 +40,7 @@
|
||||
</b-alert>
|
||||
</div>
|
||||
</b-alert>
|
||||
-->
|
||||
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<b-form
|
||||
@ -47,83 +49,105 @@
|
||||
@reset="onReset"
|
||||
v-if="show"
|
||||
>
|
||||
<br />
|
||||
<div>
|
||||
<!-- <div>
|
||||
<qrcode-drop-zone id="input-0" v-model="form.img"></qrcode-drop-zone>
|
||||
</div>
|
||||
<br />
|
||||
-->
|
||||
<div>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.receiver') }}</b-col>
|
||||
|
||||
<b-input-group
|
||||
id="input-group-1"
|
||||
label="Empfänger:"
|
||||
label-for="input-1"
|
||||
description="We'll never share your email with anyone else."
|
||||
size="lg"
|
||||
class="mb-3"
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{
|
||||
required: true,
|
||||
email: true,
|
||||
is_not: $store.state.email,
|
||||
}"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<b-input-group-prepend class="p-3 d-none d-md-block">
|
||||
<b-icon icon="envelope" class="display-3"></b-icon>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
id="input-1"
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder="E-Mail"
|
||||
:rules="{ required: true, email: true }"
|
||||
required
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
<b-row>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.receiver') }}</b-col>
|
||||
<b-col v-if="errors" class="text-right p-3 p-sm-1">
|
||||
<span v-for="error in errors" :key="error" class="errors">{{ error }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-input-group
|
||||
id="input-group-1"
|
||||
label="Empfänger:"
|
||||
label-for="input-1"
|
||||
description="We'll never share your email with anyone else."
|
||||
size="lg"
|
||||
class="mb-3"
|
||||
>
|
||||
<b-input-group-prepend class="p-3 d-none d-md-block">
|
||||
<b-icon icon="envelope" class="display-3"></b-icon>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
id="input-1"
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder="E-Mail"
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
</validation-provider>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.amount') }}</b-col>
|
||||
<b-col v-if="balance == form.amount" class="text-right">
|
||||
<b-badge variant="primary">{{ $t('form.max_gdd_info') }}</b-badge>
|
||||
</b-col>
|
||||
<b-input-group
|
||||
id="input-group-2"
|
||||
label="Betrag:"
|
||||
label-for="input-2"
|
||||
size="lg"
|
||||
class="mb-3"
|
||||
<validation-provider
|
||||
:name="$t('form.amount')"
|
||||
:rules="{
|
||||
required: true,
|
||||
double: [2, $i18n.locale === 'de' ? ',' : '.'],
|
||||
between: [0.01, balance],
|
||||
}"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<b-input-group-prepend class="p-2 d-none d-md-block">
|
||||
<div class="h3 pt-3 pr-3">GDD</div>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
id="input-2"
|
||||
v-model="form.amount"
|
||||
type="number"
|
||||
placeholder="0.01"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
:max="balance"
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
|
||||
|
||||
<b-input-group>
|
||||
<b-input-group-prepend class="p-3 d-none d-md-block">
|
||||
<b-icon icon="chat-right-text" class="display-3"></b-icon>
|
||||
</b-input-group-prepend>
|
||||
<b-form-textarea
|
||||
rows="3"
|
||||
v-model="form.memo"
|
||||
class="pl-3"
|
||||
style="font-size: x-large"
|
||||
></b-form-textarea>
|
||||
</b-input-group>
|
||||
<b-row>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.amount') }}</b-col>
|
||||
<b-col v-if="errors" class="text-right p-3 p-sm-1">
|
||||
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-input-group
|
||||
id="input-group-2"
|
||||
label="Betrag:"
|
||||
label-for="input-2"
|
||||
size="lg"
|
||||
class="mb-3"
|
||||
>
|
||||
<b-input-group-prepend class="p-2 d-none d-md-block">
|
||||
<div class="h3 pt-3 pr-3">GDD</div>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
id="input-2"
|
||||
v-model="form.amount"
|
||||
type="number"
|
||||
:lang="$i18n.locale"
|
||||
:placeholder="$n(0.01)"
|
||||
step="0.01"
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
></b-form-input>
|
||||
</b-input-group>
|
||||
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
|
||||
<b-input-group id="input-group-3">
|
||||
<b-input-group-prepend class="p-3 d-none d-md-block">
|
||||
<b-icon icon="chat-right-text" class="display-3"></b-icon>
|
||||
</b-input-group-prepend>
|
||||
<b-form-textarea
|
||||
rows="3"
|
||||
v-model="form.memo"
|
||||
class="pl-3"
|
||||
style="font-size: x-large"
|
||||
></b-form-textarea>
|
||||
</b-input-group>
|
||||
</validation-provider>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-button type="reset" variant="secondary">
|
||||
{{ $t('form.cancel') }}
|
||||
<b-button type="reset" variant="secondary" @click="onReset">
|
||||
{{ $t('form.reset') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
@ -155,7 +179,7 @@
|
||||
</b-list-group-item>
|
||||
|
||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||
{{ ajaxCreateData.memo }}
|
||||
{{ ajaxCreateData.memo ? ajaxCreateData.memo : '-' }}
|
||||
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
|
||||
</b-list-group-item>
|
||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||
@ -178,29 +202,46 @@
|
||||
</b-row>
|
||||
<b-row v-show="row_thx">
|
||||
<b-col>
|
||||
<div class="display-1 p-4">
|
||||
{{ $t('form.thx') }}
|
||||
<hr />
|
||||
{{ $t('form.send_success') }}
|
||||
</div>
|
||||
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
|
||||
<div class="display-2 p-4">
|
||||
{{ $t('form.thx') }}
|
||||
<hr />
|
||||
{{ $t('form.send_transaction_success') }}
|
||||
</div>
|
||||
|
||||
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
|
||||
<hr />
|
||||
<p class="text-center">
|
||||
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
|
||||
</p>
|
||||
</b-card>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row v-show="row_error">
|
||||
<b-col>
|
||||
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
|
||||
<div class="display-2 p-4">
|
||||
{{ $t('form.sorry') }}
|
||||
<hr />
|
||||
{{ $t('form.send_transaction_error') }}
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<b-button variant="success" @click="onReset">{{ $t('form.close') }}</b-button>
|
||||
</p>
|
||||
</b-card>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader'
|
||||
// import { QrcodeStream, QrcodeDropZone } from 'vue-qrcode-reader'
|
||||
import { BIcon } from 'bootstrap-vue'
|
||||
import communityAPI from '../../apis/communityAPI.js'
|
||||
import communityAPI from '../../../apis/communityAPI.js'
|
||||
|
||||
export default {
|
||||
name: 'GddSent',
|
||||
components: {
|
||||
QrcodeStream,
|
||||
QrcodeDropZone,
|
||||
// QrcodeStream,
|
||||
// QrcodeDropZone,
|
||||
BIcon,
|
||||
},
|
||||
props: {
|
||||
@ -209,7 +250,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scan: false,
|
||||
// scan: false,
|
||||
show: true,
|
||||
form: {
|
||||
img: '',
|
||||
@ -227,21 +268,22 @@ export default {
|
||||
send: false,
|
||||
row_check: false,
|
||||
row_thx: false,
|
||||
row_error: false,
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.scan = !this.scan
|
||||
},
|
||||
async onDecode(decodedString) {
|
||||
const arr = JSON.parse(decodedString)
|
||||
this.form.email = arr[0].email
|
||||
this.form.amount = arr[0].amount
|
||||
this.scan = false
|
||||
},
|
||||
// toggle() {
|
||||
// this.scan = !this.scan
|
||||
// },
|
||||
// async onDecode(decodedString) {
|
||||
// const arr = JSON.parse(decodedString)
|
||||
// this.form.email = arr[0].email
|
||||
// this.form.amount = arr[0].amount
|
||||
// this.scan = false
|
||||
// },
|
||||
async onSubmit() {
|
||||
//event.preventDefault()
|
||||
// event.preventDefault()
|
||||
this.ajaxCreateData.email = this.form.email
|
||||
this.ajaxCreateData.amount = this.form.amount
|
||||
const now = new Date(Date.now()).toISOString()
|
||||
@ -250,12 +292,13 @@ export default {
|
||||
this.$emit('toggle-show-list', false)
|
||||
this.row_check = true
|
||||
this.row_thx = false
|
||||
this.row_error = false
|
||||
},
|
||||
async sendTransaction() {
|
||||
const result = await communityAPI.send(
|
||||
this.$store.state.session_id,
|
||||
this.$store.state.sessionId,
|
||||
this.ajaxCreateData.email,
|
||||
this.ajaxCreateData.amount * 10000,
|
||||
this.ajaxCreateData.amount,
|
||||
this.ajaxCreateData.memo,
|
||||
this.ajaxCreateData.target_date,
|
||||
)
|
||||
@ -263,22 +306,25 @@ export default {
|
||||
this.$emit('toggle-show-list', false)
|
||||
this.row_check = false
|
||||
this.row_thx = true
|
||||
this.row_error = false
|
||||
this.$emit('update-balance', { ammount: this.ajaxCreateData.amount })
|
||||
} else {
|
||||
alert('error')
|
||||
this.$emit('toggle-show-list', true)
|
||||
this.row_check = false
|
||||
this.row_thx = false
|
||||
this.row_error = true
|
||||
}
|
||||
},
|
||||
onReset(event) {
|
||||
event.preventDefault()
|
||||
this.form.email = ''
|
||||
this.form.amount = ''
|
||||
this.form.memo = ''
|
||||
this.show = false
|
||||
this.$emit('toggle-show-list', true)
|
||||
this.row_check = false
|
||||
this.row_thx = false
|
||||
this.row_error = false
|
||||
this.$nextTick(() => {
|
||||
this.show = true
|
||||
})
|
||||
@ -294,4 +340,7 @@ video {
|
||||
max-height: 665px;
|
||||
max-width: 665px;
|
||||
}
|
||||
span.errors {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@ -6,11 +6,11 @@ const localVue = global.localVue
|
||||
describe('GddStatus', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$n: jest.fn((n) => n),
|
||||
}
|
||||
|
||||
let propsData = {
|
||||
const propsData = {
|
||||
balance: 1234,
|
||||
GdtBalance: 9876,
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-row v-show="showTransactionList">
|
||||
<b-col xl="6" md="6">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<stats-card
|
||||
type="gradient-red"
|
||||
sub-title="balance_gdd"
|
||||
@ -11,7 +11,7 @@
|
||||
{{ $n(balance) }} GDD
|
||||
</stats-card>
|
||||
</b-col>
|
||||
<b-col xl="6" md="6">
|
||||
<b-col>
|
||||
<stats-card
|
||||
type="gradient-orange"
|
||||
sub-title="balance_gdt"
|
||||
@ -29,7 +29,6 @@
|
||||
export default {
|
||||
name: 'GddStatus',
|
||||
props: {
|
||||
showTransactionList: { type: Boolean, default: true },
|
||||
balance: { type: Number, default: 0 },
|
||||
GdtBalance: { type: Number, default: 0 },
|
||||
},
|
||||
@ -1,28 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-list-group v-show="showTransactionList">
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="item in filteredItems"
|
||||
v-for="item in transactions.slice(0, max)"
|
||||
:key="item.id"
|
||||
style="background-color: #ebebeba3 !important"
|
||||
>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<b-icon
|
||||
v-if="item.type === 'send'"
|
||||
icon="box-arrow-left"
|
||||
class="m-1"
|
||||
icon="arrow-left-circle"
|
||||
class="m-1 text-danger"
|
||||
font-scale="2"
|
||||
style="color: red"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-else
|
||||
icon="box-arrow-right"
|
||||
v-else-if="item.type === 'receive'"
|
||||
icon="arrow-right-circle"
|
||||
class="m-1"
|
||||
font-scale="2"
|
||||
style="color: green"
|
||||
></b-icon>
|
||||
<h1 class="mb-1">
|
||||
{{ $n(item.balance / 10000) }}
|
||||
<b-icon
|
||||
v-else-if="item.type === 'creation'"
|
||||
icon="gift"
|
||||
class="m-1"
|
||||
font-scale="2"
|
||||
style="color: orange"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-else
|
||||
icon="droplet-half"
|
||||
class="m-1"
|
||||
font-scale="2"
|
||||
style="color: orange"
|
||||
></b-icon>
|
||||
<h1 class="">
|
||||
<span v-if="item.type === 'receive' || item.type === 'creation'">+</span>
|
||||
<span v-else>-</span>
|
||||
{{ $n(item.balance) }}
|
||||
<small>GDD</small>
|
||||
</h1>
|
||||
<h2 class="text-muted">{{ item.name }}</h2>
|
||||
@ -72,68 +88,40 @@
|
||||
</b-card>
|
||||
</b-collapse>
|
||||
</b-list-group-item>
|
||||
<b-list-group-item v-show="this.$route.path == '/overview'">
|
||||
<b-alert v-if="count < 5" show variant="secondary">
|
||||
<span class="alert-text" v-html="$t('transaction.show_part', { count: count })"></span>
|
||||
<b-list-group-item>
|
||||
<b-alert v-if="transactions.length === 0" show variant="secondary">
|
||||
<span class="alert-text">{{ $t('transaction.nullTransactions') }}</span>
|
||||
</b-alert>
|
||||
<router-link
|
||||
v-else
|
||||
to="/transactions"
|
||||
v-html="$t('transaction.show_all', { count: count })"
|
||||
></router-link>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import communityAPI from '../../apis/communityAPI'
|
||||
|
||||
export default {
|
||||
name: 'GddTable',
|
||||
props: {
|
||||
showTransactionList: { type: Boolean, default: true },
|
||||
transactions: { default: [] },
|
||||
max: { type: Number, default: 25 },
|
||||
timestamp: { type: Number, default: 0 },
|
||||
transactionCount: { type: Number, default: 0 },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: [],
|
||||
fields: ['balance', 'date', 'memo', 'name', 'transaction_id', 'type', 'details'],
|
||||
items: [],
|
||||
count: 0,
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
const result = await communityAPI.transactions(this.$store.state.session_id)
|
||||
|
||||
if (result.success) {
|
||||
this.items = result.result.data.transactions
|
||||
this.count = result.result.data.count
|
||||
} else {
|
||||
//console.log('error', result)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredItems() {
|
||||
return this.ojectToArray(this.items).reverse()
|
||||
watch: {
|
||||
timestamp: {
|
||||
immediate: true,
|
||||
handler: 'updateTransactions',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
ojectToArray(obj) {
|
||||
let result = new Array(Object.keys(obj).length)
|
||||
Object.entries(obj).forEach((entry) => {
|
||||
const [key, value] = entry
|
||||
result[key] = value
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
rowClass(item, type) {
|
||||
if (!item || type !== 'row') return
|
||||
if (item.type === 'receive') return 'table-success'
|
||||
if (item.type === 'send') return 'table-warning'
|
||||
if (item.type === 'creation') return 'table-primary'
|
||||
updateTransactions() {
|
||||
this.$emit('update-transactions')
|
||||
},
|
||||
},
|
||||
}
|
||||
21
frontend/src/views/Pages/AccountOverview/GddTableFooter.vue
Normal file
21
frontend/src/views/Pages/AccountOverview/GddTableFooter.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-list-group>
|
||||
<b-list-group-item v-if="count > 5">
|
||||
<router-link
|
||||
to="/transactions"
|
||||
v-html="$t('transaction.show_all', { count: count })"
|
||||
></router-link>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GddTableFooter',
|
||||
props: {
|
||||
count: { count: Number },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -68,6 +68,7 @@ export default {
|
||||
if (item.status === 'earned') return 'table-primary'
|
||||
},
|
||||
toogle(item) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const temp =
|
||||
'<b-collapse visible v-bind:id="item.id">xxx <small class="text-muted">porta</small></b-collapse>'
|
||||
},
|
||||
@ -16,7 +16,7 @@
|
||||
<b-row class="justify-content-center">
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="px-lg-5 py-lg-5">
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<base-input
|
||||
|
||||
@ -9,22 +9,22 @@ const localVue = global.localVue
|
||||
describe('Login', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
let state = {
|
||||
const state = {
|
||||
loginfail: false,
|
||||
}
|
||||
|
||||
let store = new Vuex.Store({
|
||||
const store = new Vuex.Store({
|
||||
state,
|
||||
})
|
||||
|
||||
let stubs = {
|
||||
const stubs = {
|
||||
RouterLink: RouterLinkStub,
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="login-form">
|
||||
<!-- Header -->
|
||||
<div class="header p-4">
|
||||
<div class="p-3">
|
||||
<b-container>
|
||||
<div class="text-center mb-7">
|
||||
<div class="text-center mb-7 header">
|
||||
<b-row class="justify-content-center">
|
||||
<b-col xl="5" lg="6" md="8" class="px-2">
|
||||
<h1>Gradido</h1>
|
||||
@ -14,11 +14,11 @@
|
||||
</b-container>
|
||||
</div>
|
||||
<!-- Page content -->
|
||||
<b-container class="mt--8 p-1">
|
||||
<b-container class="mt--8">
|
||||
<b-row class="justify-content-center">
|
||||
<b-col lg="5" md="7">
|
||||
<b-card no-body class="border-0 mb-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="py-lg-4 px-sm-0 px-0 px-md-2 px-lg-4">
|
||||
<b-card-body class="p-4">
|
||||
<div class="text-center text-muted mb-4">
|
||||
<small>{{ $t('login') }}</small>
|
||||
</div>
|
||||
@ -44,24 +44,14 @@
|
||||
v-model="model.password"
|
||||
></base-input>
|
||||
|
||||
<b-alert v-show="loginfail" show variant="warning">
|
||||
<b-alert v-show="loginfail" show dismissible variant="warning">
|
||||
<span class="alert-text bv-example-row">
|
||||
<b-row>
|
||||
<b-col class="col-9 text-left">
|
||||
<b-col class="col-9 text-left text-dark">
|
||||
<strong>
|
||||
Leider konnten wir keinen Account finden mit diesen Daten!
|
||||
</strong>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<a @click="closeAlert">
|
||||
<div>
|
||||
<b-icon-exclamation-triangle-fill
|
||||
class="h2 mb-0"
|
||||
></b-icon-exclamation-triangle-fill>
|
||||
<b-icon-x class="h1 pl-2"></b-icon-x>
|
||||
</div>
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</span>
|
||||
</b-alert>
|
||||
@ -112,13 +102,15 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
let loader = this.$loading.show({
|
||||
// error info ausschalten
|
||||
this.loginfail = false
|
||||
const loader = this.$loading.show({
|
||||
container: this.$refs.submitButton,
|
||||
})
|
||||
const result = await loginAPI.login(this.model.email, this.model.password)
|
||||
if (result.success) {
|
||||
this.$store.dispatch('login', {
|
||||
session_id: result.result.data.session_id,
|
||||
sessionId: result.result.data.session_id,
|
||||
email: this.model.email,
|
||||
})
|
||||
this.$router.push('/overview')
|
||||
@ -128,10 +120,6 @@ export default {
|
||||
this.loginfail = true
|
||||
}
|
||||
},
|
||||
closeAlert() {
|
||||
loader.hide()
|
||||
this.loginfail = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -9,22 +9,22 @@ const localVue = global.localVue
|
||||
describe('Register', () => {
|
||||
let wrapper
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
let state = {
|
||||
const state = {
|
||||
// loginfail: false,
|
||||
}
|
||||
|
||||
let store = new Vuex.Store({
|
||||
const store = new Vuex.Store({
|
||||
state,
|
||||
})
|
||||
|
||||
let stubs = {
|
||||
const stubs = {
|
||||
RouterLink: RouterLinkStub,
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<b-row class="justify-content-center">
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="py-lg-4 px-sm-0 px-0 px-md-2 px-lg-4">
|
||||
<b-card-body class="p-4">
|
||||
<div class="text-center text-muted mb-4">
|
||||
<small>{{ $t('signup') }}</small>
|
||||
</div>
|
||||
@ -110,6 +110,20 @@
|
||||
</base-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-alert
|
||||
v-if="showError"
|
||||
show
|
||||
dismissible
|
||||
variant="warning"
|
||||
@dismissed="closeAlert"
|
||||
>
|
||||
<span class="alert-icon"><i class="ni ni-point"></i></span>
|
||||
<span class="alert-text">
|
||||
<strong>{{ $t('error.error') }}!</strong>
|
||||
{{ messageError }}
|
||||
</span>
|
||||
</b-alert>
|
||||
|
||||
<div
|
||||
class="text-center"
|
||||
v-if="
|
||||
@ -160,6 +174,8 @@ export default {
|
||||
checkPassword: '',
|
||||
passwordVisible: false,
|
||||
submitted: false,
|
||||
showError: false,
|
||||
messageError: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -175,7 +191,7 @@ export default {
|
||||
)
|
||||
if (result.success) {
|
||||
this.$store.dispatch('login', {
|
||||
session_id: result.result.data.session_id,
|
||||
sessionId: result.result.data.session_id,
|
||||
email: this.model.email,
|
||||
})
|
||||
this.model.email = ''
|
||||
@ -184,11 +200,18 @@ export default {
|
||||
this.password = ''
|
||||
this.$router.push('/thx')
|
||||
} else {
|
||||
// todo: Display a proper error message!
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push('/login')
|
||||
this.showError = true
|
||||
this.messageError = result.result.message
|
||||
}
|
||||
},
|
||||
closeAlert() {
|
||||
this.showError = false
|
||||
this.messageError = ''
|
||||
this.model.email = ''
|
||||
this.model.firstname = ''
|
||||
this.model.lastname = ''
|
||||
this.password = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
samePasswords() {
|
||||
@ -209,8 +232,8 @@ export default {
|
||||
return this.model.email !== ''
|
||||
},
|
||||
passwordValidation() {
|
||||
let errors = []
|
||||
for (let condition of this.rules) {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.password)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
|
||||
@ -11,9 +11,9 @@ const router = new VueRouter({ routes })
|
||||
describe('ResetPassword', () => {
|
||||
let wrapper
|
||||
|
||||
let emailVerification = jest.fn()
|
||||
const emailVerification = jest.fn()
|
||||
|
||||
let mocks = {
|
||||
const mocks = {
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
@ -49,13 +49,13 @@ describe('ResetPassword', () => {
|
||||
expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
//describe('Register header', () => {
|
||||
// describe('Register header', () => {
|
||||
// it('has a welcome message', () => {
|
||||
// expect(wrapper.find('div.header').text()).toBe('site.signup.title site.signup.subtitle')
|
||||
// })
|
||||
//})
|
||||
// })
|
||||
|
||||
//describe('links', () => {
|
||||
// describe('links', () => {
|
||||
// it('has a link "Back"', () => {
|
||||
// expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
|
||||
// })
|
||||
@ -63,9 +63,9 @@ describe('ResetPassword', () => {
|
||||
// it('links to /login when clicking "Back"', () => {
|
||||
// expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
|
||||
// })
|
||||
//})
|
||||
// })
|
||||
|
||||
//describe('Register form', () => {
|
||||
// describe('Register form', () => {
|
||||
// it('has a register form', () => {
|
||||
// expect(wrapper.find('form').exists()).toBeTruthy()
|
||||
// })
|
||||
@ -108,7 +108,7 @@ describe('ResetPassword', () => {
|
||||
// })
|
||||
|
||||
// //TODO test different invalid password combinations
|
||||
//})
|
||||
// })
|
||||
|
||||
// TODO test submit button
|
||||
})
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<b-row class="justify-content-center">
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="py-lg-4 px-sm-0 px-0 px-md-2 px-lg-4">
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<b-form-group :label="$t('form.password')">
|
||||
@ -35,11 +35,8 @@
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary">
|
||||
<b-icon
|
||||
:icon="passwordVisible ? 'eye' : 'eye-slash'"
|
||||
@click="togglePasswordVisibility"
|
||||
/>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
@ -104,7 +101,7 @@ export default {
|
||||
passwordVisible: false,
|
||||
submitted: false,
|
||||
authenticated: false,
|
||||
session_id: null,
|
||||
sessionId: null,
|
||||
email: null,
|
||||
}
|
||||
},
|
||||
@ -113,7 +110,7 @@ export default {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
async onSubmit() {
|
||||
const result = await loginAPI.changePassword(this.session_id, this.email, this.password)
|
||||
const result = await loginAPI.changePassword(this.sessionId, this.email, this.password)
|
||||
if (result.success) {
|
||||
this.password = ''
|
||||
this.$router.push('/thx')
|
||||
@ -126,7 +123,7 @@ export default {
|
||||
const result = await loginAPI.loginViaEmailVerificationCode(optin)
|
||||
if (result.success) {
|
||||
this.authenticated = true
|
||||
this.session_id = result.result.data.session_id
|
||||
this.sessionId = result.result.data.session_id
|
||||
this.email = result.result.data.user.email
|
||||
} else {
|
||||
alert(result.result.message)
|
||||
@ -141,8 +138,8 @@ export default {
|
||||
return this.password !== '' && this.checkPassword !== ''
|
||||
},
|
||||
passwordValidation() {
|
||||
let errors = []
|
||||
for (let condition of this.rules) {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.password)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="header pb-8 pt-5 pt-lg-8 d-flex align-items-center profile-header"
|
||||
style="
|
||||
min-height: 600px;
|
||||
background-image: url(img/theme/profile-cover.jpg);
|
||||
background-size: cover;
|
||||
background-position: center top;
|
||||
"
|
||||
>
|
||||
<b-container fluid>
|
||||
<b-container fluid class="d-flex align-items-center">
|
||||
<b-row>
|
||||
<b-col lg="7" md="10">
|
||||
<h1 class="display-2 text-white">Hello {{ this.$store.state.email }}</h1>
|
||||
<p class="text-white mt-0 mb-5">
|
||||
This is your profile page. You can see the progress you've made with your work and
|
||||
manage your projects or assigned tasks
|
||||
</p>
|
||||
<a href="#!" class="btn btn-info">Edit profile</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</b-container>
|
||||
</div>
|
||||
|
||||
<b-container fluid class="mt--6">
|
||||
<b-row>
|
||||
<b-col xl="4" class="order-xl-2 mb-5">
|
||||
<user-card></user-card>
|
||||
</b-col>
|
||||
<b-col xl="8" class="order-xl-1">
|
||||
<edit-profile-form></edit-profile-form>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import EditProfileForm from './UserProfile/EditProfileForm.vue'
|
||||
import UserCard from './UserProfile/UserCard.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditProfileForm,
|
||||
UserCard,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -43,11 +43,11 @@ export default {
|
||||
},
|
||||
onFileChange(fieldName, file) {
|
||||
const { maxSize } = this
|
||||
let imageFile = file[0]
|
||||
const imageFile = file[0]
|
||||
|
||||
//check if user actually selected a file
|
||||
// check if user actually selected a file
|
||||
if (file.length > 0) {
|
||||
let size = imageFile.size / maxSize / maxSize
|
||||
const size = imageFile.size / maxSize / maxSize
|
||||
if (!imageFile.type.match('image.*')) {
|
||||
// check whether the upload is an image
|
||||
this.errorDialog = true
|
||||
@ -58,8 +58,8 @@ export default {
|
||||
this.errorText = 'Your file is too big! Please select an image under 1MB'
|
||||
} else {
|
||||
// Append file into FormData & turn file into image URL
|
||||
let formData = new FormData()
|
||||
let imageURL = URL.createObjectURL(imageFile)
|
||||
const formData = new FormData()
|
||||
const imageURL = URL.createObjectURL(imageFile)
|
||||
formData.append(fieldName, imageFile)
|
||||
// Emit FormData & image URL to the parent component
|
||||
this.$emit('input', { formData, imageURL })
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="heading">
|
||||
{{ $n(this.$store.state.user.balance) }}
|
||||
{{ $n(balance) }}
|
||||
</span>
|
||||
<span class="description">GDD</span>
|
||||
</div>
|
||||
@ -60,6 +60,9 @@ export default {
|
||||
components: {
|
||||
VueQrcode,
|
||||
},
|
||||
props: {
|
||||
balance: { type: Number, default: 0 },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
|
||||
@ -31,8 +31,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import GddWorkTable from '../../views/KontoOverview/GddWorkTable.vue'
|
||||
import GddAddWork2 from '../../views/KontoOverview/GddAddWork2.vue'
|
||||
import GddWorkTable from '../../views/Pages/AccountOverview/GddWorkTable.vue'
|
||||
import GddAddWork2 from '../../views/Pages/AccountOverview/GddAddWork2.vue'
|
||||
|
||||
import * as chartConfigs from '@/components/Charts/config'
|
||||
import LineChart from '@/components/Charts/LineChart'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user