Merge branch 'master' into community_decay_inside

This commit is contained in:
einhornimmond 2021-06-14 17:00:44 +02:00 committed by GitHub
commit 119f344069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
197 changed files with 4619 additions and 6573 deletions

View File

@ -44,19 +44,19 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: true
##########################################################################
# BUILD LOGIN SERVER DOCKER IMAGE ########################################
##########################################################################
- name: login server | Build `test` image
- name: login server | Build `release` image
run: |
docker build --target login_server -t "gradido/login_server:test" -f ./login_server/Dockerfile login_server/
docker save "gradido/login_server:test" > /tmp/login_server.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: docker-login-server-test
path: /tmp/login_server.tar
docker build --target release -t "gradido/login_server:release" -f ./login_server/Dockerfile login_server/
#docker save "gradido/login_server:test" > /tmp/login_server.tar
#- name: Upload Artifact
# uses: actions/upload-artifact@v2
#with:
# name: docker-login-server-test
#path: /tmp/login_server.tar
##############################################################################
# JOB: DOCKER BUILD TEST COMMUNITY SERVER ####################################
@ -102,7 +102,7 @@ jobs:
##########################################################################
- name: mariadb | Build `test` image
run: |
docker build -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./
docker build --target mariadb_server_test -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./
docker save "gradido/mariadb:test" > /tmp/mariadb.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
@ -212,9 +212,138 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 20
min_coverage: 28
token: ${{ github.token }}
##############################################################################
# JOB: UNIT TEST LOGIN-SERVER ###############################################
##############################################################################
unit_test_login_server:
name: Unit tests - Login-Server
runs-on: ubuntu-latest
needs: []
services:
mariadb:
image: gradido/mariadb:test
env:
MARIADB_ALLOW_EMPTY_PASSWORD: 1
MARIADB_USER: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping"
--health-interval=6s
--health-timeout=3s
--health-retries=4
steps:
- name: Debug service
run: echo "$(docker ps)"
- name: Debug container choosing script
run: echo "$(docker container ls | grep mariadb | awk '{ print $1 }')"
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
##########################################################################
# Build Login-Server Test Docker image ###################################
##########################################################################
- name: login server | Build `test` image
run: |
docker build --target test -t "gradido/login_server:test" -f ./login_server/Dockerfile login_server/
##########################################################################
# UNIT TESTS BACKEND LOGIN-SERVER #######################################
##########################################################################
- name: Login-Server | Unit tests
run: |
docker run --network container:$(docker container ls | grep mariadb | awk '{ print $1 }') -v ~/coverage:/code/build_cov/coverage -v $(pwd)/configs/login_server:/etc/grd_login gradido/login_server:test
cp -r ~/coverage ./coverage
##########################################################################
# COVERAGE CHECK BACKEND LOGIN-SERVER ####################################
##########################################################################
- name: backend login | Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Backend Login
type: lcov
result_path: ./coverage/coverage.info
min_coverage: 13
token: ${{ github.token }}
##############################################################################
# JOB: UNIT TEST COMMUNITY-SERVER ###########################################
##############################################################################
unit_test_community_server:
name: Unit tests - Community Server
runs-on: ubuntu-latest
needs: [build_test_community_server]
services:
mariadb:
image: gradido/mariadb:test
env:
MARIADB_ALLOW_EMPTY_PASSWORD: 1
MARIADB_USER: root
# ports:
# - 3306:3306
options: --health-cmd="mysqladmin ping"
--health-interval=5s
--health-timeout=5s
--health-retries=3
steps:
- name: get mariadb container id
run: echo "::set-output name=id::$(docker container ls | grep mariadb | awk '{ print $1 }')"
id: mariadb_container
- name: show networks
run: echo "$(docker network ls)"
- name: get automatic created network
run: echo "::set-output name=id::$(docker network ls | grep github_network | awk '{ print $1 }')"
id: network
- name: Start Login-Server
run: docker run --network ${{ steps.network.outputs.id }} --name=login-server -d gradido/login_server:default
- name: get login-server container id
run: echo "::set-output name=id::$(docker container ls | grep login_server | awk '{ print $1 }')"
id: login_server_container
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# DOWNLOAD DOCKER IMAGE ##################################################
##########################################################################
- name: Download Docker Image (Community-Server)
uses: actions/download-artifact@v2
with:
name: docker-community-server-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/community_server.tar
# for debugging login-server
- name: check login-server
run: docker logs ${{ steps.login_server_container.outputs.id }}
- name: check mariadb
run: docker logs ${{ steps.mariadb_container.outputs.id }}
##########################################################################
# UNIT TESTS BACKEND COMMUNITY-SERVER #######################################
##########################################################################
- name: community server | Unit tests
run: |
docker run --network ${{ steps.network.outputs.id }} -v ~/coverage:/var/www/cakephp/webroot/coverage gradido/community_server:test
cp -r ~/coverage ./coverage
#########################################################################
# COVERAGE CHECK BACKEND COMMUNITY-SERVER ####################################
##########################################################################
- name: backend community | Coverage check
uses: einhornimmond/coverage-check-action@master
with:
report_name: Coverage Backend Community
type: phpunit
result_path: ./coverage/coverage.info
min_coverage: 10
token: ${{ github.token }}
#test:
# runs-on: ubuntu-latest
# steps:
@ -228,4 +357,4 @@ jobs:
# GN_INSTANCE_FOLDER: /tmp/gradio-node-instance
# GN_CONTAINER_NAME: gradido-node-build
# run: docker run gradido-node-test
# timeout-minutes: 2
# timeout-minutes: 2

View File

@ -1,8 +1,8 @@
FROM phpdockerio/php74-fpm
FROM phpdockerio/php74-fpm as community_server
# install php fpm
RUN apt-get update \
&& apt-get -y --no-install-recommends install curl unzip php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql \
&& apt-get -y --no-install-recommends install curl unzip php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql php7.4-xdebug \
&& apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
WORKDIR /var/www/cakephp
@ -13,5 +13,18 @@ COPY ./configs/community_server/app.php ./config/
RUN composer update
RUN composer dump-autoload
######### special for code coverage and testing
FROM community_server as test
RUN apt-get update \
&& apt-get -y --no-install-recommends install php7.4-xdebug \
&& apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
ENV XDEBUG_MODE=coverage
#RUN composer require --dev rregeer/phpunit-coverage-check
#CMD ./vendor/bin/phpunit --coverage-clover=./webroot/coverage/clover.xml
CMD ./vendor/bin/phpunit --coverage-text=./webroot/coverage/coverage.info

View File

@ -60,19 +60,21 @@ Router::scope('/', function (RouteBuilder $routes) {
$whitelist = ['JsonRequestHandler', 'ElopageWebhook', 'AppRequests'];
$ajaxWhitelist = ['TransactionSendCoins', 'TransactionCreations'];
$callerIp = $request->clientIp();
foreach($whitelist as $entry) {
if($request->getParam('controller') === $entry) {
if($entry == 'ElopageWebhook' || $entry == 'AppRequests') {
return true;
}
$allowedIpLocalhost = ['127.0.0.1', 'localhost', '', '::1'];
if(in_array($clientIp, $allowedIpLocalhost)) {
if(in_array($callerIp, $allowedIpLocalhost)) {
return true;
}
$allowedCaller = Configure::read('API.allowedCaller');
$ipPerHost = [];
if($allowedCaller && count($allowedCaller) > 0) {
$callerIp = $request->clientIp();
foreach($allowedCaller as $allowed) {
$ip = gethostbyname($allowed);
$ipPerHost[$allowed] = $ip;

View File

@ -88,19 +88,8 @@ class AppController extends Controller
// load current balance
$session = $this->getRequest()->getSession();
$state_user_id = $session->read('StateUser.id');
if ($state_user_id) {
$stateBalancesTable = TableRegistry::getTableLocator()->get('stateBalances');
$stateBalanceQuery = $stateBalancesTable
->find('all')
->contain(false)
->where(['state_user_id' => $state_user_id]);
if ($stateBalanceQuery->count() == 1) {
//var_dump($stateBalanceEntry->first());
$session->write('StateUser.balance', $stateBalanceQuery->first()->decay);
//echo "stateUser.balance: " . $session->read('StateUser.balance');
}
}
// load error count
if ($state_user_id) {
$stateErrorsTable = TableRegistry::getTableLocator()->get('stateErrors');
@ -111,8 +100,6 @@ class AppController extends Controller
->where(['state_user_id' => $state_user_id]);
$session->write('StateUser.errorCount', $stateErrorQuery->count());
}
//echo "initialize";
// put current page into global for navi
$GLOBALS["passed"] = null;

View File

@ -152,10 +152,7 @@ class AppRequestsController extends AppController
if($result !== true) {
return $this->returnJson($result);
}
$required_fields = $this->checkAndCopyRequiredFields(['target_date'], $params, $data);
if($required_fields !== true) {
return $this->returnJson($required_fields);
}
if(!isset($params['memo']) || strlen($params['memo']) < 5 || strlen($params['memo']) > 150) {
return $this->returnJson(['state' => 'error', 'msg' => 'memo is not set or not in expected range [5;150]']);
}
@ -276,7 +273,8 @@ class AppRequestsController extends AppController
$this->viewBuilder()->setLayout('ajax');
$login_result = $this->requestLogin($session_id, false);
if($login_result !== true) {
return $this->returnJson($login_result);
$this->set('body', $login_result);
return;
}
$session = $this->getRequest()->getSession();
$user = $session->read('StateUser');
@ -285,16 +283,24 @@ class AppRequestsController extends AppController
$state_balance = $state_balances_table->find()->where(['state_user_id' => $user['id']])->first();
if(!$state_balance) {
return $this->returnJson(['state' => 'success', 'balance' => 0]);
}
$now = new FrozenTime();
$body = [
'state' => 'success',
'balance' => $state_balance->amount,
'decay' => $state_balance->partDecay($now),
'decay_date' => $now
];
if(!$state_balance) {
$body = [
'state' => 'success',
'balance' => 0,
'decay' => 0
];
} else {
$body = [
'state' => 'success',
'balance' => $state_balance->amount,
'decay' => $state_balance->partDecay($now),
];
}
$body['decay_date'] = $now;
$this->set('body', $body);
}
@ -302,35 +308,31 @@ class AppRequestsController extends AppController
{
$this->viewBuilder()->setLayout('ajax');
$startTime = microtime(true);
$login_result = $this->requestLogin($session_id, false);
if($login_result !== true) {
return $this->returnJson($login_result);
}
$session = $this->getRequest()->getSession();
$user = $session->read('StateUser');
$stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
$stateBalancesTable->updateBalances($user['id']);
$gdtSum = 0;
$gdtEntries = $this->JsonRequestClient->sendRequestGDT(['email' => $user['email']], 'GdtEntries' . DS . 'sumPerEmailApi');
if('success' == $gdtEntries['state'] && 'success' == $gdtEntries['data']['state']) {
$gdtSum = intval($gdtEntries['data']['sum']);
} else {
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, $user['id'] ? $user['id'] : 0);
}
$stateUserTransactions_total = $stateUserTransactionsTable
->find()
->select(['id'])
->where(['state_user_id' => $user['id']])
->contain([]);
$stateUserTransactionsQuery = $stateUserTransactionsTable
->find()
@ -343,17 +345,18 @@ class AppRequestsController extends AppController
$decay = true;
$transactions = [];
$transactions_from_db = $stateUserTransactionsQuery->toArray();
if($stateUserTransactionsQuery->count() > 0) {
if(count($transactions_from_db)) {
if($orderDirection == 'DESC') {
$transactions_from_db = array_reverse($transactions_from_db);
}
$transactions = $transactionsTable->listTransactionsHumanReadable($transactions_from_db, $user, $decay);
if($orderDirection == 'DESC') {
$transactions = array_reverse($transactions);
}
}
}
$state_balance = $stateBalancesTable->find()->where(['state_user_id' => $user['id']])->first();
@ -361,7 +364,7 @@ class AppRequestsController extends AppController
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => $stateUserTransactions_total->count(),
'count' => $stateUserTransactionsQuery->count(),
'gdtSum' => $gdtSum,
'timeUsed' => microtime(true) - $startTime
];
@ -375,8 +378,8 @@ class AppRequestsController extends AppController
$body['balance'] = $state_balance->amount;
$body['decay'] = $stateBalancesTable->calculateDecay($state_balance->amount, $state_balance->record_date, $now);
}
$this->set('body', $body);
$this->set('body', $body);
}
private function acquireAccessToken($session_id)

View File

@ -340,12 +340,13 @@ class JsonRequestHandlerController extends AppController {
return;
}
$transaction_body = $transaction->getTransactionBody();
$transaction_type_name = $transaction_body->getTransactionTypeName();
$senderUser = null;
if($transaction_type_name === 'transfer') {
$senderUser = $transaction_body->getSpecificTransaction()->getSenderUser();
} else if($transaction_type_name === 'creation') {
$senderUser = $transaction->getFirstSigningUser();
$senderUser = $transaction->getFirstSigningUser();
if($transaction_body != null) {
$transaction_type_name = $transaction_body->getTransactionTypeName();
if($transaction_type_name === 'transfer') {
$senderUser = $transaction_body->getSpecificTransaction()->getSenderUser();
}
}
// send notification email
$noReplyEmail = Configure::read('noReplyEmail');
@ -373,22 +374,29 @@ class JsonRequestHandlerController extends AppController {
private function putTransaction($transactionBase64) {
$transaction = new Transaction($transactionBase64);
//echo "after new transaction<br>";
if($transaction->hasErrors()) {
$this->sendEMailTransactionFailed($transaction, 'parse');
return $this->returnJson(['state' => 'error', 'msg' => 'error parsing transaction', 'details' => $transaction->getErrors()]);
}
//echo "after check on errors<br>";
if(!$transaction->validate()) {
//$transaction_details
$this->sendEMailTransactionFailed($transaction, 'validate');
return $this->returnJsonSaveError($transaction, ['state' => 'error', 'msg' => 'error validate transaction', 'details' => $transaction->getErrors()]);
return $this->returnJsonSaveError($transaction, [
'state' => 'error',
'msg' => 'error validate transaction',
'details' => $transaction->getErrors()
]);
}
//echo "after validate <br>";
if ($transaction->save()) {
$result = ['state' => 'success'];
if($transaction->hasWarnings()) {
$result['warnings'] = $transaction->getWarnings();
}
// success
return $this->returnJson(['state' => 'success']);
return $this->returnJson($result);
} else {
$this->sendEMailTransactionFailed($transaction, 'save');

View File

@ -151,35 +151,6 @@ class StateBalancesController extends AppController
$this->set('gdtSum', $gdtSum);
}
public function ajaxGetBalance($session_id = 0)
{
if(!$session_id) {
return $this->returnJson(['state' => 'error', 'msg' => 'invalid session id']);
}
$login_result = $this->requestLogin($session_id, false);
if($login_result !== true) {
return $this->returnJson($login_result);
}
$session = $this->getRequest()->getSession();
$user = $session->read('StateUser');
$this->StateBalances->updateBalances($user['id']);
$state_balance = $this->StateBalances->find()->where(['state_user_id' => $user['id']])->first();
if(!$state_balance) {
return $this->returnJson(['state' => 'success', 'balance' => 0]);
}
$now = new FrozenTime();
return $this->returnJson([
'state' => 'success',
'balance' => $state_balance->amount,
'decay' => $this->StateBalances->calculateDecay($state_balance->amount, $state_balance->record_date, $now),
'decay_date' => $now
]);
}
public function ajaxGdtOverview()
{

View File

@ -7,6 +7,7 @@ use Model\Transactions\Transaction;
use Model\Transactions\TransactionBody;
use Cake\Core\Configure;
use Cake\I18n\Time;
use Cake\ORM\TableRegistry;
/**
@ -24,7 +25,7 @@ class TransactionsController extends AppController
parent::initialize();
$this->loadComponent('GradidoNumber');
$this->loadComponent('JsonRpcRequestClient');
$this->Auth->allow(['decode']);
$this->Auth->allow(['decode', 'manualTransaction']);
}
/**
@ -170,6 +171,78 @@ class TransactionsController extends AppController
$this->set('transaction', $transaction);
}
public function manualTransaction()
{
if ($this->request->is('post')) {
$data = $this->request->getData();
$type = $data['type'];
$transaction = new \Proto\Gradido\GradidoTransaction();
$transactionBody = new \Proto\Gradido\TransactionBody();
$transactionBody->setMemo($data['memo']);
$created = new \Proto\Gradido\TimestampSeconds();
$now = new Time();
$created->setSeconds($now->getTimestamp());
$transactionBody->setCreated($created);
if($type == "creation") {
$creation = new \Proto\Gradido\GradidoCreation();
$target_date = new \Proto\Gradido\TimestampSeconds();
$target_time = new Time($data['target_date']);
$target_date->setSeconds($target_time->getTimestamp());
$creation->setTargetDate($target_date);
$receiver = new \Proto\Gradido\TransferAmount();
$receiver->setAmount(intval($data['amount']));
$receiver->setPubkey(hex2bin($data['target_public_key']));
$creation->setReceiver($receiver);
$transactionBody->setCreation($creation);
} else if($type == "transfer") {
$transfer = new \Proto\Gradido\GradidoTransfer();
$local_transfer = new \Proto\Gradido\LocalTransfer();
$sender = new \Proto\Gradido\TransferAmount();
$sender->setAmount(intval($data['amount']));
$sender->setPubkey(hex2bin($data['sender_public_key']));
$local_transfer->setSender($sender);
$local_transfer->setReceiver(hex2bin($data['receiver_public_key']));
$transfer->setLocal($local_transfer);
$transactionBody->setTransfer($transfer);
}
$body_bytes = $transactionBody->serializeToString();
$transaction->setBodyBytes($body_bytes);
$protoSigMap = new \Proto\Gradido\SignatureMap();
$sigPairs = $protoSigMap->getSigPair();
//echo "sigPairs: "; var_dump($sigPairs); echo "<br>";
//return null;
// sign with keys
//foreach($keys as $key) {
$sigPair = new \Proto\Gradido\SignaturePair();
$sigPair->setPubKey(hex2bin($data['signer_public_key']));
$signature = sodium_crypto_sign_detached($body_bytes, hex2bin($data['signer_private_key']));
echo "signature: " . bin2hex($signature). "<br>";
$sigPair->setEd25519($signature);
$sigPairs[] = $sigPair;
// SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
// SODIUM_BASE64_VARIANT_ORIGINAL
$transaction->setSigMap($protoSigMap);
//var_dump($protoSigMap);
$transaction_bin = $transaction->serializeToString();
// $url_safe = sodium_bin2base64($transaction_bin, sodium_base64_VARIANT_ORIGINAL);
$base64 = [
//'original' => sodium_bin2base64($transaction_bin, sodium_base64_VARIANT_ORIGINAL),
//'original_nopadding' => sodium_bin2base64($transaction_bin, sodium_base64_VARIANT_ORIGINAL_NO_PADDING),
//'urlsafe' => sodium_bin2base64($transaction_bin, sodium_base64_VARIANT_URLSAFE),
'urlsafe_nopadding' => sodium_bin2base64($transaction_bin, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING),
'php' => base64_encode($transaction_bin)
];
$this->set('base64', $base64);
}
}
public function decode()
{
$this->viewBuilder()->setLayout('frontend');

View File

@ -1,96 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.Key</code>
*/
class Key extends \Google\Protobuf\Internal\Message
{
protected $key;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $ed25519
* ed25519 signature (libsodium default)
* @type string $ed25519_ref10
* ed25519 ref10 signature
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* ed25519 signature (libsodium default)
*
* Generated from protobuf field <code>bytes ed25519 = 2;</code>
* @return string
*/
public function getEd25519()
{
return $this->readOneof(2);
}
/**
* ed25519 signature (libsodium default)
*
* Generated from protobuf field <code>bytes ed25519 = 2;</code>
* @param string $var
* @return $this
*/
public function setEd25519($var)
{
GPBUtil::checkString($var, False);
$this->writeOneof(2, $var);
return $this;
}
/**
* ed25519 ref10 signature
*
* Generated from protobuf field <code>bytes ed25519_ref10 = 3;</code>
* @return string
*/
public function getEd25519Ref10()
{
return $this->readOneof(3);
}
/**
* ed25519 ref10 signature
*
* Generated from protobuf field <code>bytes ed25519_ref10 = 3;</code>
* @param string $var
* @return $this
*/
public function setEd25519Ref10($var)
{
GPBUtil::checkString($var, False);
$this->writeOneof(3, $var);
return $this;
}
/**
* @return string
*/
public function getKey()
{
return $this->whichOneof("key");
}
}

View File

@ -1,85 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.ReceiverAmount</code>
*/
class ReceiverAmount extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>bytes ed25519_receiver_pubkey = 1;</code>
*/
private $ed25519_receiver_pubkey = '';
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
*/
private $amount = 0;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $ed25519_receiver_pubkey
* @type int|string $amount
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>bytes ed25519_receiver_pubkey = 1;</code>
* @return string
*/
public function getEd25519ReceiverPubkey()
{
return $this->ed25519_receiver_pubkey;
}
/**
* Generated from protobuf field <code>bytes ed25519_receiver_pubkey = 1;</code>
* @param string $var
* @return $this
*/
public function setEd25519ReceiverPubkey($var)
{
GPBUtil::checkString($var, False);
$this->ed25519_receiver_pubkey = $var;
return $this;
}
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
* @return int|string
*/
public function getAmount()
{
return $this->amount;
}
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
* @param int|string $var
* @return $this
*/
public function setAmount($var)
{
GPBUtil::checkInt64($var);
$this->amount = $var;
return $this;
}
}

View File

@ -1,119 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.SenderAmount</code>
*/
class SenderAmount extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>bytes ed25519_sender_pubkey = 1;</code>
*/
private $ed25519_sender_pubkey = '';
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
*/
private $amount = 0;
/**
* sender balance after transaction, including perishability
*
* Generated from protobuf field <code>sint64 senderFinalBalance = 3;</code>
*/
private $senderFinalBalance = 0;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $ed25519_sender_pubkey
* @type int|string $amount
* @type int|string $senderFinalBalance
* sender balance after transaction, including perishability
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>bytes ed25519_sender_pubkey = 1;</code>
* @return string
*/
public function getEd25519SenderPubkey()
{
return $this->ed25519_sender_pubkey;
}
/**
* Generated from protobuf field <code>bytes ed25519_sender_pubkey = 1;</code>
* @param string $var
* @return $this
*/
public function setEd25519SenderPubkey($var)
{
GPBUtil::checkString($var, False);
$this->ed25519_sender_pubkey = $var;
return $this;
}
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
* @return int|string
*/
public function getAmount()
{
return $this->amount;
}
/**
* Generated from protobuf field <code>sint64 amount = 2;</code>
* @param int|string $var
* @return $this
*/
public function setAmount($var)
{
GPBUtil::checkInt64($var);
$this->amount = $var;
return $this;
}
/**
* sender balance after transaction, including perishability
*
* Generated from protobuf field <code>sint64 senderFinalBalance = 3;</code>
* @return int|string
*/
public function getSenderFinalBalance()
{
return $this->senderFinalBalance;
}
/**
* sender balance after transaction, including perishability
*
* Generated from protobuf field <code>sint64 senderFinalBalance = 3;</code>
* @param int|string $var
* @return $this
*/
public function setSenderFinalBalance($var)
{
GPBUtil::checkInt64($var);
$this->senderFinalBalance = $var;
return $this;
}
}

View File

@ -1,65 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.SignatureMap</code>
*/
class SignatureMap extends \Google\Protobuf\Internal\Message
{
/**
* Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field <code>repeated .model.messages.gradido.SignaturePair sigPair = 1;</code>
*/
private $sigPair;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\SignaturePair[]|\Google\Protobuf\Internal\RepeatedField $sigPair
* Each signature pair corresponds to a unique Key required to sign the transaction.
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field <code>repeated .model.messages.gradido.SignaturePair sigPair = 1;</code>
* @return \Google\Protobuf\Internal\RepeatedField
*/
public function getSigPair()
{
return $this->sigPair;
}
/**
* Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field <code>repeated .model.messages.gradido.SignaturePair sigPair = 1;</code>
* @param \Model\Messages\Gradido\SignaturePair[]|\Google\Protobuf\Internal\RepeatedField $var
* @return $this
*/
public function setSigPair($var)
{
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Model\Messages\Gradido\SignaturePair::class);
$this->sigPair = $arr;
return $this;
}
}

View File

@ -1,123 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.SignaturePair</code>
*/
class SignaturePair extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>bytes pubKey = 1;</code>
*/
private $pubKey = '';
protected $signature;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $pubKey
* @type string $ed25519
* ed25519 signature (libsodium default)
* @type string $ed25519_ref10
* ed25519 ref10 signature
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>bytes pubKey = 1;</code>
* @return string
*/
public function getPubKey()
{
return $this->pubKey;
}
/**
* Generated from protobuf field <code>bytes pubKey = 1;</code>
* @param string $var
* @return $this
*/
public function setPubKey($var)
{
GPBUtil::checkString($var, False);
$this->pubKey = $var;
return $this;
}
/**
* ed25519 signature (libsodium default)
*
* Generated from protobuf field <code>bytes ed25519 = 2;</code>
* @return string
*/
public function getEd25519()
{
return $this->readOneof(2);
}
/**
* ed25519 signature (libsodium default)
*
* Generated from protobuf field <code>bytes ed25519 = 2;</code>
* @param string $var
* @return $this
*/
public function setEd25519($var)
{
GPBUtil::checkString($var, False);
$this->writeOneof(2, $var);
return $this;
}
/**
* ed25519 ref10 signature
*
* Generated from protobuf field <code>bytes ed25519_ref10 = 3;</code>
* @return string
*/
public function getEd25519Ref10()
{
return $this->readOneof(3);
}
/**
* ed25519 ref10 signature
*
* Generated from protobuf field <code>bytes ed25519_ref10 = 3;</code>
* @param string $var
* @return $this
*/
public function setEd25519Ref10($var)
{
GPBUtil::checkString($var, False);
$this->writeOneof(3, $var);
return $this;
}
/**
* @return string
*/
public function getSignature()
{
return $this->whichOneof("signature");
}
}

View File

@ -1,114 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: StateCreateGroup.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* need signature from this group and from parent (if it isn't zero)
*
* Generated from protobuf message <code>model.messages.gradido.StateCreateGroup</code>
*/
class StateCreateGroup extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>string name = 1;</code>
*/
private $name = '';
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 2;</code>
*/
private $groupPublicKey = null;
/**
* Generated from protobuf field <code>.model.messages.gradido.Key parentGroupPublicKey = 3;</code>
*/
private $parentGroupPublicKey = null;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $name
* @type \Model\Messages\Gradido\Key $groupPublicKey
* @type \Model\Messages\Gradido\Key $parentGroupPublicKey
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\StateCreateGroup::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Generated from protobuf field <code>string name = 1;</code>
* @param string $var
* @return $this
*/
public function setName($var)
{
GPBUtil::checkString($var, True);
$this->name = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 2;</code>
* @return \Model\Messages\Gradido\Key
*/
public function getGroupPublicKey()
{
return $this->groupPublicKey;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 2;</code>
* @param \Model\Messages\Gradido\Key $var
* @return $this
*/
public function setGroupPublicKey($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Key::class);
$this->groupPublicKey = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key parentGroupPublicKey = 3;</code>
* @return \Model\Messages\Gradido\Key
*/
public function getParentGroupPublicKey()
{
return $this->parentGroupPublicKey;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key parentGroupPublicKey = 3;</code>
* @param \Model\Messages\Gradido\Key $var
* @return $this
*/
public function setParentGroupPublicKey($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Key::class);
$this->parentGroupPublicKey = $var;
return $this;
}
}

View File

@ -1,114 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: StateGroupChangeParent.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* need signature from this group and from both parents (if it isn't zero)
*
* Generated from protobuf message <code>model.messages.gradido.StateGroupChangeParent</code>
*/
class StateGroupChangeParent extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 1;</code>
*/
private $groupPublicKey = null;
/**
* Generated from protobuf field <code>.model.messages.gradido.Key newParentGroupPublicKey = 2;</code>
*/
private $newParentGroupPublicKey = null;
/**
* Generated from protobuf field <code>.model.messages.gradido.Key oldParentGroupPublicKey = 3;</code>
*/
private $oldParentGroupPublicKey = null;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\Key $groupPublicKey
* @type \Model\Messages\Gradido\Key $newParentGroupPublicKey
* @type \Model\Messages\Gradido\Key $oldParentGroupPublicKey
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\StateGroupChangeParent::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 1;</code>
* @return \Model\Messages\Gradido\Key
*/
public function getGroupPublicKey()
{
return $this->groupPublicKey;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key groupPublicKey = 1;</code>
* @param \Model\Messages\Gradido\Key $var
* @return $this
*/
public function setGroupPublicKey($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Key::class);
$this->groupPublicKey = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key newParentGroupPublicKey = 2;</code>
* @return \Model\Messages\Gradido\Key
*/
public function getNewParentGroupPublicKey()
{
return $this->newParentGroupPublicKey;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key newParentGroupPublicKey = 2;</code>
* @param \Model\Messages\Gradido\Key $var
* @return $this
*/
public function setNewParentGroupPublicKey($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Key::class);
$this->newParentGroupPublicKey = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key oldParentGroupPublicKey = 3;</code>
* @return \Model\Messages\Gradido\Key
*/
public function getOldParentGroupPublicKey()
{
return $this->oldParentGroupPublicKey;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Key oldParentGroupPublicKey = 3;</code>
* @param \Model\Messages\Gradido\Key $var
* @return $this
*/
public function setOldParentGroupPublicKey($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Key::class);
$this->oldParentGroupPublicKey = $var;
return $this;
}
}

View File

@ -1,101 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* An exact date and time. This is the same data structure as the protobuf Timestamp.proto (see the comments in https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto)
*
* Generated from protobuf message <code>model.messages.gradido.Timestamp</code>
*/
class Timestamp extends \Google\Protobuf\Internal\Message
{
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
*/
private $seconds = 0;
/**
* Number of nanoseconds since the start of the last second
*
* Generated from protobuf field <code>int32 nanos = 2;</code>
*/
private $nanos = 0;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type int|string $seconds
* Number of complete seconds since the start of the epoch
* @type int $nanos
* Number of nanoseconds since the start of the last second
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
* @return int|string
*/
public function getSeconds()
{
return $this->seconds;
}
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
* @param int|string $var
* @return $this
*/
public function setSeconds($var)
{
GPBUtil::checkInt64($var);
$this->seconds = $var;
return $this;
}
/**
* Number of nanoseconds since the start of the last second
*
* Generated from protobuf field <code>int32 nanos = 2;</code>
* @return int
*/
public function getNanos()
{
return $this->nanos;
}
/**
* Number of nanoseconds since the start of the last second
*
* Generated from protobuf field <code>int32 nanos = 2;</code>
* @param int $var
* @return $this
*/
public function setNanos($var)
{
GPBUtil::checkInt32($var);
$this->nanos = $var;
return $this;
}
}

View File

@ -1,67 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: BasicTypes.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* An exact date and time, with a resolution of one second (no nanoseconds).
*
* Generated from protobuf message <code>model.messages.gradido.TimestampSeconds</code>
*/
class TimestampSeconds extends \Google\Protobuf\Internal\Message
{
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
*/
private $seconds = 0;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type int|string $seconds
* Number of complete seconds since the start of the epoch
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\BasicTypes::initOnce();
parent::__construct($data);
}
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
* @return int|string
*/
public function getSeconds()
{
return $this->seconds;
}
/**
* Number of complete seconds since the start of the epoch
*
* Generated from protobuf field <code>int64 seconds = 1;</code>
* @param int|string $var
* @return $this
*/
public function setSeconds($var)
{
GPBUtil::checkInt64($var);
$this->seconds = $var;
return $this;
}
}

View File

@ -1,166 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Transaction.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.Transaction</code>
*/
class Transaction extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>uint64 id = 1;</code>
*/
private $id = 0;
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds received = 2;</code>
*/
private $received = null;
/**
* Generated from protobuf field <code>.model.messages.gradido.SignatureMap sigMap = 3;</code>
*/
private $sigMap = null;
/**
* Generated from protobuf field <code>bytes txHash = 4;</code>
*/
private $txHash = '';
/**
* Generated from protobuf field <code>bytes bodyBytes = 5;</code>
*/
private $bodyBytes = '';
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type int|string $id
* @type \Model\Messages\Gradido\TimestampSeconds $received
* @type \Model\Messages\Gradido\SignatureMap $sigMap
* @type string $txHash
* @type string $bodyBytes
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\Transaction::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>uint64 id = 1;</code>
* @return int|string
*/
public function getId()
{
return $this->id;
}
/**
* Generated from protobuf field <code>uint64 id = 1;</code>
* @param int|string $var
* @return $this
*/
public function setId($var)
{
GPBUtil::checkUint64($var);
$this->id = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds received = 2;</code>
* @return \Model\Messages\Gradido\TimestampSeconds
*/
public function getReceived()
{
return $this->received;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds received = 2;</code>
* @param \Model\Messages\Gradido\TimestampSeconds $var
* @return $this
*/
public function setReceived($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\TimestampSeconds::class);
$this->received = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.SignatureMap sigMap = 3;</code>
* @return \Model\Messages\Gradido\SignatureMap
*/
public function getSigMap()
{
return $this->sigMap;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.SignatureMap sigMap = 3;</code>
* @param \Model\Messages\Gradido\SignatureMap $var
* @return $this
*/
public function setSigMap($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\SignatureMap::class);
$this->sigMap = $var;
return $this;
}
/**
* Generated from protobuf field <code>bytes txHash = 4;</code>
* @return string
*/
public function getTxHash()
{
return $this->txHash;
}
/**
* Generated from protobuf field <code>bytes txHash = 4;</code>
* @param string $var
* @return $this
*/
public function setTxHash($var)
{
GPBUtil::checkString($var, False);
$this->txHash = $var;
return $this;
}
/**
* Generated from protobuf field <code>bytes bodyBytes = 5;</code>
* @return string
*/
public function getBodyBytes()
{
return $this->bodyBytes;
}
/**
* Generated from protobuf field <code>bytes bodyBytes = 5;</code>
* @param string $var
* @return $this
*/
public function setBodyBytes($var)
{
GPBUtil::checkString($var, False);
$this->bodyBytes = $var;
return $this;
}
}

View File

@ -1,193 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: TransactionBody.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.TransactionBody</code>
*/
class TransactionBody extends \Google\Protobuf\Internal\Message
{
/**
* max 150 chars
*
* Generated from protobuf field <code>string memo = 1;</code>
*/
private $memo = '';
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds created = 2;</code>
*/
private $created = null;
protected $data;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type string $memo
* max 150 chars
* @type \Model\Messages\Gradido\TimestampSeconds $created
* @type \Model\Messages\Gradido\StateCreateGroup $createGroup
* @type \Model\Messages\Gradido\StateGroupChangeParent $groupChangeParent
* @type \Model\Messages\Gradido\Transfer $transfer
* @type \Model\Messages\Gradido\TransactionCreation $creation
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\TransactionBody::initOnce();
parent::__construct($data);
}
/**
* max 150 chars
*
* Generated from protobuf field <code>string memo = 1;</code>
* @return string
*/
public function getMemo()
{
return $this->memo;
}
/**
* max 150 chars
*
* Generated from protobuf field <code>string memo = 1;</code>
* @param string $var
* @return $this
*/
public function setMemo($var)
{
GPBUtil::checkString($var, True);
$this->memo = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds created = 2;</code>
* @return \Model\Messages\Gradido\TimestampSeconds
*/
public function getCreated()
{
return $this->created;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds created = 2;</code>
* @param \Model\Messages\Gradido\TimestampSeconds $var
* @return $this
*/
public function setCreated($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\TimestampSeconds::class);
$this->created = $var;
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.StateCreateGroup createGroup = 6;</code>
* @return \Model\Messages\Gradido\StateCreateGroup
*/
public function getCreateGroup()
{
return $this->readOneof(6);
}
/**
* Generated from protobuf field <code>.model.messages.gradido.StateCreateGroup createGroup = 6;</code>
* @param \Model\Messages\Gradido\StateCreateGroup $var
* @return $this
*/
public function setCreateGroup($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\StateCreateGroup::class);
$this->writeOneof(6, $var);
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.StateGroupChangeParent groupChangeParent = 7;</code>
* @return \Model\Messages\Gradido\StateGroupChangeParent
*/
public function getGroupChangeParent()
{
return $this->readOneof(7);
}
/**
* Generated from protobuf field <code>.model.messages.gradido.StateGroupChangeParent groupChangeParent = 7;</code>
* @param \Model\Messages\Gradido\StateGroupChangeParent $var
* @return $this
*/
public function setGroupChangeParent($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\StateGroupChangeParent::class);
$this->writeOneof(7, $var);
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Transfer transfer = 8;</code>
* @return \Model\Messages\Gradido\Transfer
*/
public function getTransfer()
{
return $this->readOneof(8);
}
/**
* Generated from protobuf field <code>.model.messages.gradido.Transfer transfer = 8;</code>
* @param \Model\Messages\Gradido\Transfer $var
* @return $this
*/
public function setTransfer($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\Transfer::class);
$this->writeOneof(8, $var);
return $this;
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TransactionCreation creation = 9;</code>
* @return \Model\Messages\Gradido\TransactionCreation
*/
public function getCreation()
{
return $this->readOneof(9);
}
/**
* Generated from protobuf field <code>.model.messages.gradido.TransactionCreation creation = 9;</code>
* @param \Model\Messages\Gradido\TransactionCreation $var
* @return $this
*/
public function setCreation($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\TransactionCreation::class);
$this->writeOneof(9, $var);
return $this;
}
/**
* @return string
*/
public function getData()
{
return $this->whichOneof("data");
}
}

View File

@ -1,136 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: TransactionCreation.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* need signature from group admin or
* percent of group users another than the receiver
*
* Generated from protobuf message <code>model.messages.gradido.TransactionCreation</code>
*/
class TransactionCreation extends \Google\Protobuf\Internal\Message
{
/**
* 40 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.ReceiverAmount receiverAmount = 1;</code>
*/
private $receiverAmount = null;
/**
* 4 Byte
*
* Generated from protobuf field <code>sint32 ident_hash = 2;</code>
*/
private $ident_hash = 0;
/**
* 8 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds target_date = 3;</code>
*/
private $target_date = null;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\ReceiverAmount $receiverAmount
* 40 Byte
* @type int $ident_hash
* 4 Byte
* @type \Model\Messages\Gradido\TimestampSeconds $target_date
* 8 Byte
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\TransactionCreation::initOnce();
parent::__construct($data);
}
/**
* 40 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.ReceiverAmount receiverAmount = 1;</code>
* @return \Model\Messages\Gradido\ReceiverAmount
*/
public function getReceiverAmount()
{
return $this->receiverAmount;
}
/**
* 40 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.ReceiverAmount receiverAmount = 1;</code>
* @param \Model\Messages\Gradido\ReceiverAmount $var
* @return $this
*/
public function setReceiverAmount($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\ReceiverAmount::class);
$this->receiverAmount = $var;
return $this;
}
/**
* 4 Byte
*
* Generated from protobuf field <code>sint32 ident_hash = 2;</code>
* @return int
*/
public function getIdentHash()
{
return $this->ident_hash;
}
/**
* 4 Byte
*
* Generated from protobuf field <code>sint32 ident_hash = 2;</code>
* @param int $var
* @return $this
*/
public function setIdentHash($var)
{
GPBUtil::checkInt32($var);
$this->ident_hash = $var;
return $this;
}
/**
* 8 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds target_date = 3;</code>
* @return \Model\Messages\Gradido\TimestampSeconds
*/
public function getTargetDate()
{
return $this->target_date;
}
/**
* 8 Byte
*
* Generated from protobuf field <code>.model.messages.gradido.TimestampSeconds target_date = 3;</code>
* @param \Model\Messages\Gradido\TimestampSeconds $var
* @return $this
*/
public function setTargetDate($var)
{
GPBUtil::checkMessage($var, \Model\Messages\Gradido\TimestampSeconds::class);
$this->target_date = $var;
return $this;
}
}

View File

@ -1,85 +0,0 @@
<?php
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: Transfer.proto
namespace Model\Messages\Gradido;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
* Generated from protobuf message <code>model.messages.gradido.Transfer</code>
*/
class Transfer extends \Google\Protobuf\Internal\Message
{
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.SenderAmount senderAmounts = 1;</code>
*/
private $senderAmounts;
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.ReceiverAmount receiverAmounts = 2;</code>
*/
private $receiverAmounts;
/**
* Constructor.
*
* @param array $data {
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\SenderAmount[]|\Google\Protobuf\Internal\RepeatedField $senderAmounts
* @type \Model\Messages\Gradido\ReceiverAmount[]|\Google\Protobuf\Internal\RepeatedField $receiverAmounts
* }
*/
public function __construct($data = NULL) {
\GPBMetadata\Transfer::initOnce();
parent::__construct($data);
}
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.SenderAmount senderAmounts = 1;</code>
* @return \Google\Protobuf\Internal\RepeatedField
*/
public function getSenderAmounts()
{
return $this->senderAmounts;
}
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.SenderAmount senderAmounts = 1;</code>
* @param \Model\Messages\Gradido\SenderAmount[]|\Google\Protobuf\Internal\RepeatedField $var
* @return $this
*/
public function setSenderAmounts($var)
{
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Model\Messages\Gradido\SenderAmount::class);
$this->senderAmounts = $arr;
return $this;
}
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.ReceiverAmount receiverAmounts = 2;</code>
* @return \Google\Protobuf\Internal\RepeatedField
*/
public function getReceiverAmounts()
{
return $this->receiverAmounts;
}
/**
* Generated from protobuf field <code>repeated .model.messages.gradido.ReceiverAmount receiverAmounts = 2;</code>
* @param \Model\Messages\Gradido\ReceiverAmount[]|\Google\Protobuf\Internal\RepeatedField $var
* @return $this
*/
public function setReceiverAmounts($var)
{
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Model\Messages\Gradido\ReceiverAmount::class);
$this->receiverAmounts = $arr;
return $this;
}
}

View File

@ -188,20 +188,28 @@ class TransactionsTable extends Table
if($prev && $decay == true)
{
if($prev->balance > 0) {
$current = $su_transaction;
$current = $su_transaction;
$calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
$balance = floatval($prev->balance - $calculated_decay['balance']);
$balance = floatval($prev->balance - $calculated_decay['balance']);
// skip small decays (smaller than 0,00 GDD)
if(abs($balance) >= 100) {
$final_transaction['decay'] = [
'balance' => $balance,
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds')
];
if($balance)
{
$final_transactions['decay'] = [
'balance' => $balance,
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds')
];
}
}
}
// sender or receiver when user has sended money
// group name if creation
// type: gesendet / empfangen / geschöpft
// transaktion nr / id
// date
// balance
$transaction = $transaction_indiced[$su_transaction->transaction_id];
if($su_transaction->transaction_type_id == 1) { // creation
$creation = $transaction->transaction_creation;
$balance = $stateBalancesTable->calculateDecay($creation->amount, $creation->target_date, $transaction->received);

View File

@ -25,7 +25,7 @@ class Transaction extends TransactionBase {
//$transactionBin = base64_decode($base64Data, true);
//if($transactionBin == false) {
//sodium_base64_VARIANT_URLSAFE_NO_PADDING
if(is_a($base64Data, '\Proto\Gradido\Transaction')) {
if(is_a($base64Data, '\Proto\Gradido\GradidoTransaction')) {
$this->mProtoTransaction = $base64Data;
$this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes());
return;
@ -93,7 +93,11 @@ class Transaction extends TransactionBase {
return $this->mTransactionBody;
}
public function getFirstPublic() {
public function getFirstPublic()
{
if(!$this->mProtoTransaction || !$this->mProtoTransaction->getSigMap()) {
return '';
}
$sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
return $sigPairs[0]->getPubKey();
}
@ -111,6 +115,7 @@ class Transaction extends TransactionBase {
$sigMap = $this->mProtoTransaction->getSigMap();
if(!$sigMap) {
$this->addError('Transaction', 'signature map is zero');
//var_dump($this->mProtoTransaction);
return false;
}
//var_dump($sigMap);
@ -193,8 +198,10 @@ class Transaction extends TransactionBase {
$connection->commit();
$this->mTransactionBody->getSpecificTransaction()->sendNotificationEmail($this->mTransactionBody->getMemo());
$specificTransaction = $this->mTransactionBody->getSpecificTransaction();
$specificTransaction->sendNotificationEmail($this->mTransactionBody->getMemo());
$this->addWarnings($specificTransaction->getWarnings());
return true;
}

View File

@ -6,29 +6,43 @@ use Cake\ORM\TableRegistry;
class TransactionBase {
private $errors = [];
private $warnings = [];
static $tables = [];
public function getErrors() {
return $this->errors;
return $this->errors;
}
public function getWarnings() {
return $this->warnings;
}
public function addError($functionName, $errorName) {
array_push($this->errors, [$functionName => $errorName]);
array_push($this->errors, [$functionName => $errorName]);
}
public function addWarning($functionName, $warningName) {
array_push($this->warnings, [$functionName => $warningName]);
}
public function addErrors($errors) {
$this->errors = array_merge($this->errors, $errors);
$this->errors = array_merge($this->errors, $errors);
}
public function addWarnings($warnings) {
$this->warnings = array_merge($this->warnings, $warnings);
}
public function hasErrors() {
return count($this->errors) > 0;
return count($this->errors) > 0;
}
public function hasWarnings() {
return count($this->warnings) > 0;
}
public static function getTable($tableName) {
if(!isset(self::$tables[$tableName])) {
self::$tables[$tableName] = TableRegistry::getTableLocator()->get($tableName);
}
return self::$tables[$tableName];
if(!isset(self::$tables[$tableName])) {
self::$tables[$tableName] = TableRegistry::getTableLocator()->get($tableName);
}
return self::$tables[$tableName];
}

View File

@ -209,6 +209,7 @@ class TransactionCreation extends TransactionBase {
->send();
} catch(Exception $e) {
// $this->addError('TransactionCreation::sendNotificationEmail', 'error sending notification email: ' . $e->getMessage());
$this->addWarning('TransactionCreation::sendNotificationEmail', 'error sending notification email: ' . $e->getMessage());
return false;
}
return true;

View File

@ -58,7 +58,7 @@ class TransactionTransfer extends TransactionBase {
//$this->addError('TransactionTransfer::validate', 'not implemented yet');
//return false;
//$time = microtime(true);
static $functionName = 'TransactionCreation::validate';
static $functionName = 'TransactionTransfer::validate';
$sigPubHexs = [];
foreach($sigPairs as $sigPair)
@ -204,13 +204,14 @@ class TransactionTransfer extends TransactionBase {
$this->addError('TransactionCreation::sendNotificationEmail', 'to email is empty for user: ' . $receiverUser->id);
return false;
}
$email->setFrom([$serverAdminEmail => $senderUser->getNames() . ' via Gradido Community'])
$noReplyEmail = Configure::read('noReplyEmail');
$email->setFrom([$noReplyEmail => 'Gradido (nicht antworten)'])
->setTo([$receiverUser->email => $receiverUser->getNames()])
->setReplyTo($senderUser->email)
->setSubject(__('Gradidos erhalten'))
->send();
} catch(Exception $e) {
//$this->addError('TransactionTransfer::sendNotificationEmail', 'error sending notification email: ' . $e->getMessage());
$this->addWarning('TransactionTransfer::sendNotificationEmail', 'error sending notification email: ' . $e->getMessage());
return false;
}
return true;

View File

@ -15,7 +15,7 @@ $senderNames = $senderUser->first_name . ' ' . $senderUser->last_name;
<?= $memo ?>
<?= __('Du kannst {0} eine Nachricht schreiben, indem du auf diese E-Mail antwortest', $senderNames); ?>
<?= __('Bitte antworte nicht auf diese E-Mail!'); ?>
<?= __('Mit freundlichen Grüßen'); ?>
Gradido Community Server

View File

@ -0,0 +1,63 @@
<?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.
*/
$this->loadHelper('Form', [
'templates' => 'horizontal_form',
]);
$now = new \DateTime;
?>
<?php if(isset($errors) && count($errors) > 0) : ?>
<div class="alert-color">
<ul>
<?php foreach($errors as $error) : ?>
<li>
<?= var_dump($error); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="action-form">
<p class="form-header"><?= __('Creation Transaction') ?></p>
<div class="form-body">
<?= $this->Form->create() ?>
<?= $this->Form->control('type', ['type' => 'hidden', 'value' => 'creation']) ?>
<?= $this->Form->control('target_public_key', ['type'=> 'text']) ?>
<?= $this->Form->control('target_date', ['type'=> 'text', 'placeholder' => 'yyyy-mm-dd hh:ii:ss', 'default' => $now->format('Y-m-d H:i:s')]) ?>
<?= $this->Form->control('amount', ['type'=> 'number']) ?>
<?= $this->Form->control('memo', ['type'=> 'textarea', 'rows' => '8', 'cols' => 40]) ?>
<?= $this->Form->control('signer_public_key', ['type' => 'text']) ?>
<?= $this->Form->control('signer_private_key', ['type'=> 'text']) ?>
<?= $this->Form->submit(); ?>
<?= $this->Form->end() ?>
</div>
</div>
<div class="action-form">
<p class="form-header"><?= __('Transfer Transaction') ?></p>
<div class="form-body">
<?= $this->Form->create() ?>
<?= $this->Form->control('type', ['type' => 'hidden', 'value' => 'transfer']) ?>
<?= $this->Form->control('sender_public_key', ['type'=> 'text']) ?>
<?= $this->Form->control('receiver_public_key', ['type'=> 'text']) ?>
<?= $this->Form->control('amount', ['type'=> 'number']) ?>
<?= $this->Form->control('memo', ['type'=> 'textarea', 'rows' => '8', 'cols' => 40]) ?>
<?= $this->Form->control('signer_public_key', ['type' => 'text']) ?>
<?= $this->Form->control('signer_private_key', ['type'=> 'text']) ?>
<?= $this->Form->submit(); ?>
<?= $this->Form->end() ?>
</div>
</div>
<?php if(isset($base64)) : ?>
<div>
<ul>
<?php foreach($base64 as $name => $value) : ?>
<li><?= $name ?>: <?= $value ?></li>
<?php endforeach ?>
</ul>
</div>
<?php endif; ?>

View File

@ -23,8 +23,7 @@ class CommunityProfilesFixture extends TestFixture
'state_user_id' => ['type' => 'index', 'columns' => ['state_user_id'], 'length' => []],
],
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
'community_profiles_ibfk_1' => ['type' => 'foreign', 'columns' => ['state_user_id'], 'references' => ['state_users', 'id'], 'update' => 'restrict', 'delete' => 'restrict', 'length' => []],
'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []]
],
'_options' => [
'engine' => 'InnoDB',

View File

@ -0,0 +1,45 @@
<?php
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
/**
* MigrationsFixture
*/
class Migrations2Fixture 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->table = "migrations";
$this->records = [
[
'id' => 1,
'db_version' => 2,
],
];
parent::init();
}
}

View File

@ -6,7 +6,7 @@ use Cake\TestSuite\Fixture\TestFixture;
/**
* StateBalancesFixture
*/
class StateBalancesFixture extends TestFixture
class StateBalancesFixture extends BaseTestFixture
{
/**
* Fields
@ -36,8 +36,11 @@ class StateBalancesFixture extends TestFixture
*/
public function init()
{
$this->records = [
$sql = [
[4, 4, '2021-05-27 17:47:50', '2021-05-27 17:47:50', 28808497],
[5, 1, '2021-05-27 17:47:51', '2021-05-27 17:47:50', 9823963]
];
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
parent::init();
}
}

View File

@ -1,8 +1,6 @@
<?php
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
/**
* TransactionCreationsFixture
*/
@ -19,7 +17,6 @@ class TransactionCreationsFixture extends BaseTestFixture
'transaction_id' => ['type' => 'integer', 'length' => 10, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
'state_user_id' => ['type' => 'integer', 'length' => 10, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
'amount' => ['type' => 'biginteger', 'length' => 20, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
'ident_hash' => ['type' => 'binary', 'length' => 32, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null],
'target_date' => ['type' => 'timestamp', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
@ -37,9 +34,9 @@ class TransactionCreationsFixture extends BaseTestFixture
*/
public function init()
{
$sql = [
[2, 1, 4, 10000000, '0000000000000000000000000000000000000000000000000000000000000000', '2021-01-01 00:00:00'],
[3, 2, 1, 10000000, '0000000000000000000000000000000000000000000000000000000000000000', '2021-01-01 00:00:00']
$sql = [
[2, 1, 4, 10000000, '2021-01-01 00:00:00'],
[3, 2, 1, 10000000, '2021-01-01 00:00:00']
];
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
parent::init();

View File

@ -1,46 +0,0 @@
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\AppController;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\DashboardController Test Case
*
* @uses \App\Controller\DashboardController
*/
class AppControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.StateBalances'
];
public function setUp()
{
parent::setUp();
}
/**
* Test initialize method
*
* @return void
*/
public function testInitialize()
{
$this->session(['StateUser.id' => 1]);
$this->get('/');
$this->assertSession(1200, 'StateUser.balance');
//$this->markTestIncomplete('Not implemented yet.');
}
}

View File

@ -0,0 +1,303 @@
<?php
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\AppRequestsController Test Case
*
* @uses \App\Controller\AppRequestsController
*/
class AppRequestControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.TransactionCreations',
'app.Transactions',
'app.StateUsers',
'app.StateUserTransactions',
'app.StateErrors',
'app.TransactionSignatures',
'app.TransactionSendCoins',
'app.StateBalances',
'app.TransactionTypes',
'app.Migrations'
];
/*public function setUp() {
parent::setUp();
}
*/
/**
* Test ajaxGetBalance method
*
* @return void
*/
public function testGetBalance1()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 1,
'email_checked' => 1,
'public_hex' => 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f'
]
]);
$response = $this->getAndParseWithoutCompare('/api/get-balance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(9100000, $response->balance);
$this->assertLessThan(9100000, $response->decay);
}
public function testGetBalance2()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 3,
'email_checked' => 1,
'public_hex' => '131c7f68dd94b2be4c913400ff7ff4cdc03ac2bda99c2d29edcacb3b065c67e6'
]
]);
$response = $this->getAndParseWithoutCompare('/api/get-balance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(0, $response->balance);
}
public function testGetBalance3()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 4,
'email_checked' => 1,
'public_hex' => 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'
]
]);
$response = $this->getAndParseWithoutCompare('/api/get-balance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(10900000, $response->balance);
$this->assertLessThan(10900000, $response->decay);
}
public function testGetBalanceInvalidSession()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'email_checked' => 1,
'public_hex' => '8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d'
]
]);
$this->getAndParse('/api/get-balance/' . 1211,
['state' => 'not found', 'msg' => 'invalid session']
);
}
public function testGetBalanceInvalidSessionId()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'email_checked' => 1,
'public_hex' => '8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d'
]
]);
$this->getAndParse('/api/get-balance/' ,
['state' => 'not found', 'msg' => 'invalid session']
);
}
/**
* Test ajaxListTransactions method
*
* @return void
*/
public function testListTransactions()
{
//ajaxListTransactions
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transactions' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 1,
'first_name' => 'Dario',
'last_name' => 'Frodo',
'email_checked' => 1,
'email' => 'fördertest@gradido.org',
'public_hex' => '94ae135b93cd9f33752b4e55c41903a3faa13a75bb90bfd411ea1d4a1a5e711f'
]
]);
//echo "balance: $balance";
$expectedResult = '{
"state": "success",
"transactions": [
{
"name": "Gradido Akademie",
"type": "creation",
"transaction_id": 2,
"date": "2021-04-12T00:00:00+00:00",
"target_date": "2021-01-01T00:00:00+00:00",
"creation_amount": 10000000,
"balance": 10000000,
"memo": "AGE Januar 2021"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "send",
"transaction_id": 3,
"date": "2021-04-12T00:00:00+00:00",
"balance": 1000000,
"memo": "test",
"pubkey": "e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "send",
"transaction_id": 4,
"date": "2021-04-14T00:00:00+00:00",
"balance": 100000,
"memo": "test time",
"pubkey": "e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "send",
"transaction_id": 5,
"date": "2021-04-14T09:01:07+00:00",
"balance": 100000,
"memo": "test time",
"pubkey": "e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "receive",
"transaction_id": 6,
"date": "2021-04-14T09:02:28+00:00",
"balance": 100000,
"memo": "test time 3",
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "receive",
"transaction_id": 7,
"date": "2021-04-14T09:28:46+00:00",
"balance": 100000,
"memo": "test login crash",
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000"
},
{
"name": "Samuel Schmied",
"email": "test3.yahoo.com",
"type": "receive",
"transaction_id": 8,
"date": "2021-04-14T09:31:28+00:00",
"balance": 100000,
"memo": "test login crash",
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000"
}
],
"transactionExecutingCount": 0,
"count": 7,
"gdtSum": 180000,
"timeUsed": 0.5575470924377441,
"decay_date": "2021-05-28T09:35:02+00:00",
"balance": 9100000,
"decay": 9100000
}';
$this->getAndParse('/api/list-transactions/', json_decode($expectedResult, true));
}
private function getAndParse($path, $expected)
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$this->disableErrorHandlerMiddleware();
$this->get($path);
// Check that the response was in 2xx - 3xx
$this->assertResponseSuccess();
$json = (object)$this->viewVariable('body');
if(!$json) {
// Check that the response was a 200
$this->assertResponseOk();
$responseBodyString = (string)$this->_response->getBody();
$json = json_decode($responseBodyString);
$this->assertNotFalse($json);
} else {
$responseBodyString = json_encode($json);
}
if(is_array($expected)) {
$dynamic_fields = ['timeUsed', 'decay_date', 'decay', 'gdtSum'];
// copy timeUsed because this value will be variy always
foreach($dynamic_fields as $field) {
if(isset($expected[$field]) && isset($json->$field)) {
$expected[$field] = $json->$field;
}
}
$expected = json_encode($expected);
}
$this->assertEquals($expected, $responseBodyString);
}
private function getAndParseWithoutCompare($path)
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$this->disableErrorHandlerMiddleware();
$this->get($path);
// Check that the response was in 2xx - 3xx
$this->assertResponseSuccess();
$view_body = $this->viewVariable('body');
if($view_body) {
return (object)$view_body;
}
// Check that the response was a 200
$this->assertResponseOk();
$responseBodyString = (string)$this->_response->getBody();
$json = json_decode($responseBodyString, true);
$this->assertNotFalse($json);
return $json;
}
}

View File

@ -51,19 +51,7 @@ class JsonRequestClientComponentTest extends TestCase
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test getLoginServerUrl method
*
* @return void
*/
public function testGetLoginServerUrl()
{
//$this->markTestIncomplete('Not implemented yet.');
$serverUrl = $this->JsonRequestClientComponent->getLoginServerUrl();
$this->assertEquals($serverUrl, 'http://***REMOVED***');
}
/**
/**
* Test is_base64 method
*
* @return void

View File

@ -5,9 +5,9 @@ use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\TransactionJsonRequestHandlerController Test Case
* App\Controller\JsonRequestHandlerController Test Case
*
* @uses \App\Controller\TransactionJsonRequestHandlerController
* @uses \App\Controller\JsonRequestHandlerController
*/
class JsonRequestHandlerControllerTest extends TestCase
{
@ -22,6 +22,7 @@ class JsonRequestHandlerControllerTest extends TestCase
'app.TransactionCreations',
'app.Transactions',
'app.StateUsers',
'app.StateUserTransactions',
'app.StateErrors',
'app.TransactionSignatures',
'app.TransactionSendCoins',
@ -30,13 +31,12 @@ class JsonRequestHandlerControllerTest extends TestCase
];
public $transactions = [
'validCreation' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQJ8wF12eZo3hcMAlAKKJ9WLT-zuSkNmGh7D98UEqH4KoIysnCkXqEya9EBZl9o11_nJ8xmm_nOevuVjR-GfLMQ8qSQoOSGFsbG8gV2VsdCAxMjMSBgiZm4ruBUovCicKIJSuE1uTzZ8zdStOVcQZA6P6oTp1u5C_1BHqHUoaXnEfEKDakwEQtYntlgo',
'validCreation900' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQNVZ8Ae3Zbg3G0wZ840fzKan6N4KtTcSe0KYi17kQwFmsl18oFxXv8_s6j1xXFrIKjy1_1Olq0a7xYLErDMkjwYqORIGCNb5iu4FSi8KJwoglK4TW5PNnzN1K05VxBkDo_qhOnW7kL_UEeodShpecR8QgNHKCBC1ie2WCg',
'validCreation1200' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQEEey5QMAdldoOTP_jTETHgOQriGsixEY0cziQeRfT_J5YtbI_A6AizEYD-JcxmRmXzv1xjjTgsV39Y32ta2CQkqORIGCIeGi-4FSi8KJwoglK4TW5PNnzN1K05VxBkDo_qhOnW7kL_UEeodShpecR8QgOy4CxC1ie2WCg',
'validCreation' => 'CmYKZAog4zad42I86ERtBCTEAT56HXGiZxrj178eeY6_BmXRRfISQDnatUMvitiiP0-sY93JStYPhPKKPU4Vosv_EGrh77BVs48xhPgPj2QHWC3oyuuMh6nN8YNjBQZx20rKvdQ4uwMSRwoMQUdFIE1haSAyMDIxEgYI_c3ChQY6LwolCiD39KSaSsEDefi53ctzHE2exJXm7dFgdfUmcs0l4xefDxDQDxoGCPqbtIQG',
'validCreation900' => 'CmYKZAog9_SkmkrBA3n4ud3LcxxNnsSV5u3RYHX1JnLNJeMXnw8SQCaZHmvmvJOt336E3qst3rn1pptdAR5ZPzePaUT10x0_Yky8FnEiQtMGNy1yT94QErzwQudJZjJwDY2uyK4cTgkSOxIGCKb1vYUGOjEKJwog4zad42I86ERtBCTEAT56HXGiZxrj178eeY6_BmXRRfIQgNHKCBoGCIDMuf8F',
'validCreation1200' => 'CmYKZAog9_SkmkrBA3n4ud3LcxxNnsSV5u3RYHX1JnLNJeMXnw8SQF8jptIrosEyVmCf3WEIGVOK0NR8YCcO0j-s8v2yUyR5BKus0ciT6B7IA5LDtn7eQX6zHjg1v5WlsTiZuOpuNgwSRAoHVG8gbXVjaBIGCL3Jv4UGOjEKJwog4zad42I86ERtBCTEAT56HXGiZxrj178eeY6_BmXRRfIQgOy4CxoGCOG5toQG',
'notBase64' => 'CgpIYWxsbyBXZW-0EgYIyfSG7gV_LwonCiCboKikqwjZfes9xuqgthFH3',
'validTransfer' => 'GmYKZAoggZC9pYXuXx2fv30G6B5p7BjhM3YQTP9Ut0V-t9PvcQ0SQDddHyKzAX3LBV0PuDiPc6lxkUipss5tyuLRpMtFJQnT30tsbYIkA1FXimjMKOoiuLswf4OLLV3bAIYehW-b9AgqYQoFSGFsbG8SBgiJlaPvBUJQCiYKIIGQvaWF7l8dn799BugeaewY4TN2EEz_VLdFfrfT73ENEICfSRImCiDtdleSLxhUgEbMW9DpqIwsykFj3-z_enKEOuGnXrmW8xCAn0k',
'errornusTransfer' => 'ClxGcm9oZXMgTmV1ZXMgSmFociB1bmQgREFOS0UsIGRhc3MgZHUgZGljaCBzbyBlaW5zZXR6dCBmw7xyIEdyYWRpZG8hIEhlcnpsaWNoZSBHcsO8w59lIFRlcmVzYRIGCPjjgvEFQlAKJgogUQwFYeVlGlfWDrkXNN7rHwejoCDJKt+YkYJfbJVyj3EQwIQ9EiYKIPXIRnUhVJ/zCs5+y/VaTBjTIoYizJNwS+JC//xsbQrHEMCEPQ==',
'creationValid' => 'GmYKZAogLtKKHPXhFtg2FUBrxXcVIiHC93SlZW9moOdUD3V21xsSQHpXYAGiVmSfhjB3o7OPx0ZJuPXrDk5eu1_AOhQBODU3KpUqBRA9yMX54S_mvGijGubCNRcMLcm7wiYbyAG-3AkqSwoQZWluIE1vbmF0c2dlaGFsdBIGCKqs5vEFSi8KJwoggZC9pYXuXx2fv30G6B5p7BjhM3YQTP9Ut0V-t9PvcQ0QgNrECRDKyd3uAQ'
'validTransfer' => 'CmYKZAog9_SkmkrBA3n4ud3LcxxNnsSV5u3RYHX1JnLNJeMXnw8SQA0ZVQ9T1qBabzmgDO1NAWNy2J6mlv0YjMP99CiV7bSR0zemt5XoM-kTviR1aTqKggzpSYSyTN5T6gIx2xa-hgkSYwoLTXkgQmlydGhkYXkSBgie0L-FBjJMCkoKJgog9_SkmkrBA3n4ud3LcxxNnsSV5u3RYHX1JnLNJeMXnw8QgIl6EiDjNp3jYjzoRG0EJMQBPnodcaJnGuPXvx55jr8GZdFF8g',
'errornusTransfer' => 'ClxGcm9oZXMgTmV1ZXMgSmFociB1bmQgREFOS0UsIGRhc3MgZHUgZGljaCBzbyBlaW5zZXR6dCBmw7xyIEdyYWRpZG8hIEhlcnpsaWNoZSBHcsO8w59lIFRlcmVzYRIGCPjjgvEFQlAKJgogUQwFYeVlGlfWDrkXNN7rHwejoCDJKt+YkYJfbJVyj3EQwIQ9EiYKIPXIRnUhVJ/zCs5+y/VaTBjTIoYizJNwS+JC//xsbQrHEMCEPQ=='
];
/*public function setUp() {
@ -51,7 +51,7 @@ class JsonRequestHandlerControllerTest extends TestCase
$this->get('/JsonRequestHandler');
$this->assertResponseOk();
$expected = json_encode(['state' => 'error', 'msg' => 'no post']);
$expected = json_encode(['state' => 'error', 'msg' => 'unknown method for get', 'details' => null]);
$this->assertEquals($expected, (string)$this->_response->getBody());
}
@ -87,7 +87,7 @@ class JsonRequestHandlerControllerTest extends TestCase
//$this->post('/TransactionJsonRequestHandler', ['method' => 'putTransaction', 'transaction' => 'CgpIYWxsbyBXZWx0EgYIyfSG7gVKLwonCiCboKikqwjZfes9xuqgthFH3/cHHaWchkUhWiGhQjB23xCg2pMBELWJ7ZYK']);
$this->postAndParse(
['method' => 'foobar', 'transaction' => $this->transactions['validCreation']],
['state' => 'error', 'msg' => 'unknown method', 'details' => 'foobar']
['state' => 'error', 'msg' => 'unknown method for post', 'details' => 'foobar']
);
}
@ -97,7 +97,8 @@ class JsonRequestHandlerControllerTest extends TestCase
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['notBase64']],
['state' => 'error', 'msg' => 'error parsing transaction', 'details' => [
['Transaction' => 'invalid base64 string']
['Transaction' => 'invalid base64 string'],
['base64' => 'CgpIYWxsbyBXZW-0EgYIyfSG7gV_LwonCiCboKikqwjZfes9xuqgthFH3']
]]
);
}
@ -114,9 +115,10 @@ class JsonRequestHandlerControllerTest extends TestCase
public function testToLargeCreationSum()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation900']],
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1000 gr per Month not allowed"}]}'
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1.000 GDD per Month for in target_date not allowed"}]}'
);
}
@ -124,49 +126,36 @@ class JsonRequestHandlerControllerTest extends TestCase
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation1200']],
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1000 gr per Month not allowed"}]}'
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1.000 GDD per Month for in target_date not allowed"}]}'
);
}
public function testValidTransfer()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validTransfer']],
['state' => 'success']
);
}
/*public function testMissingPreviousTransaction()
{
}*/
public function testValidTransaction()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation']],
['state' => 'success']
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validTransfer']],
['state' => 'success']
);
}
public function testValidCreation()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['creationValid']],
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation']],
['state' => 'success']
);
}
private function postAndParse($params, $expected)
{
//$this->enableCsrfToken();
$this->enableCsrfToken();
//$this->enableSecurityToken();
$token = 'my-csrf-token';
$this->cookie('csrfToken', $token);
//$token = 'my-csrf-token';
//$this->cookie('csrfToken', $token);
$this->configRequest([
'headers' => ['Accept' => 'application/json', 'X-CSRF-Token' => $token]
'headers' => ['Accept' => 'application/json']//, 'X-CSRF-Token' => $token]
]);
$this->disableErrorHandlerMiddleware();

View File

@ -27,6 +27,10 @@ use Cake\View\Exception\MissingTemplateException;
*/
class PagesControllerTest extends IntegrationTestCase
{
public $fixtures = [
'app.Migrations2'
];
/**
* testMultipleGet method
*
@ -35,9 +39,12 @@ class PagesControllerTest extends IntegrationTestCase
public function testMultipleGet()
{
$this->get('/');
$this->assertRedirect('account/');
$locations = $this->_response->getHeader('Location');
$this->assertRegExp('%.*/account/$%', $locations[0]);
$this->get('/');
$this->assertRedirect('account/');
$locations = $this->_response->getHeader('Location');
$this->assertRegExp('%.*/account/$%', $locations[0]);
}
/**
@ -64,7 +71,7 @@ class PagesControllerTest extends IntegrationTestCase
$this->get('/pages/not_existing');
$this->assertResponseError();
$this->assertResponseContains('Error');
$this->assertResponseContains('Not Found');
}
/**
@ -78,8 +85,7 @@ class PagesControllerTest extends IntegrationTestCase
$this->get('/pages/not_existing');
$this->assertResponseFailure();
$this->assertResponseContains('Missing Template');
$this->assertResponseContains('Stacktrace');
$this->assertResponseContains('Template file \u0022Pages\/not_existing.ctp\u0022 is missing.');
$this->assertResponseContains('not_existing.ctp');
}

View File

@ -69,142 +69,7 @@ class StateBalancesControllerTest extends TestCase
$this->markTestIncomplete('Not implemented yet.');
}
/**
* Test ajaxGetBalance method
*
* @return void
*/
public function testAjaxGetBalance1()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 1,
'email_checked' => 1,
'public_hex' => 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f'
]
]);
$response = $this->getAndParseWithoutCompare('/state-balances/ajaxGetBalance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(7321825, $response->balance);
$this->assertLessThan(7321825, $response->decay);
}
public function testAjaxGetBalance2()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 3,
'email_checked' => 1,
'public_hex' => '131c7f68dd94b2be4c913400ff7ff4cdc03ac2bda99c2d29edcacb3b065c67e6'
]
]);
$response = $this->getAndParseWithoutCompare('/state-balances/ajaxGetBalance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(0, $response->balance);
}
public function testAjaxGetBalance3()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 4,
'email_checked' => 1,
'public_hex' => 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'
]
]);
$response = $this->getAndParseWithoutCompare('/state-balances/ajaxGetBalance/' . $session_id);
$this->assertEquals('success', $response->state);
$this->assertEquals(9112592, $response->balance);
$this->assertLessThan(9112592, $response->decay);
}
public function testAjaxGetBalanceInvalidSession()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'email_checked' => 1,
'public_hex' => '8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d'
]
]);
//echo "balance: $balance";
$this->getAndParse('/state-balances/ajaxGetBalance/' . 1211,
['state' => 'not found', 'msg' => 'invalid session']
);
}
public function testAjaxGetBalanceInvalidSessionId()
{
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'email_checked' => 1,
'public_hex' => '8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d'
]
]);
//echo "balance: $balance";
$this->getAndParse('/state-balances/ajaxGetBalance' ,
['state' => 'error', 'msg' => 'invalid session id']
);
}
/**
* Test ajaxListTransactions method
*
* @return void
*/
public function testAjaxListTransactions()
{
//ajaxListTransactions
$session_id = rand();
$this->session([
'session_id' => $session_id,
'Transaction' => ['pending' => 0, 'executing' => 0],
'StateUser' => [
'id' => 1,
'first_name' => 'Dario',
'last_name' => 'Frodo',
'email_checked' => 1,
'email' => 'fördertest@gradido.org',
'public_hex' => '94ae135b93cd9f33752b4e55c41903a3faa13a75bb90bfd411ea1d4a1a5e711f'
]
]);
//echo "balance: $balance";
$this->getAndParse('/state-balances/ajaxListTransactions/' . $session_id,
[
'state' => 'success', 'transactions' => [[
'name' => 'Dario Frodo',
'email'=> 'dariofrodo@gmx.de',
'type'=> '',
'transaction_id' => 4,
'date' => '2021-02-19T13:27:14+00:00',
'balance' => 150000001,
'memo' => ''
]],
'transactionExecutingCount' => 0,
'count' => 1,
'gdtSum' => 0,
'timeUsed' => 0.03168010711669922
]
);
}
/**
* Test overviewGdt method

View File

@ -1,174 +0,0 @@
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\JsonRequestHandlerController;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\TransactionJsonRequestHandlerController Test Case
*
* @uses \App\Controller\TransactionJsonRequestHandlerController
*/
class TransactionJsonRequestHandlerControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.TransactionCreations',
'app.Transactions',
'app.StateUsers',
'app.StateErrors',
'app.TransactionSignatures',
'app.TransactionSendCoins',
'app.StateBalances',
'app.TransactionTypes'
];
public $transactions = [
'validCreation' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQJ8wF12eZo3hcMAlAKKJ9WLT-zuSkNmGh7D98UEqH4KoIysnCkXqEya9EBZl9o11_nJ8xmm_nOevuVjR-GfLMQ8qSQoOSGFsbG8gV2VsdCAxMjMSBgiZm4ruBUovCicKIJSuE1uTzZ8zdStOVcQZA6P6oTp1u5C_1BHqHUoaXnEfEKDakwEQtYntlgo',
'validCreation900' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQNVZ8Ae3Zbg3G0wZ840fzKan6N4KtTcSe0KYi17kQwFmsl18oFxXv8_s6j1xXFrIKjy1_1Olq0a7xYLErDMkjwYqORIGCNb5iu4FSi8KJwoglK4TW5PNnzN1K05VxBkDo_qhOnW7kL_UEeodShpecR8QgNHKCBC1ie2WCg',
'validCreation1200' => 'GmYKZAogYbkjwhjLY6ZKjGLzhgEhKDuVd_N00KMVkLoCzcKRKZkSQEEey5QMAdldoOTP_jTETHgOQriGsixEY0cziQeRfT_J5YtbI_A6AizEYD-JcxmRmXzv1xjjTgsV39Y32ta2CQkqORIGCIeGi-4FSi8KJwoglK4TW5PNnzN1K05VxBkDo_qhOnW7kL_UEeodShpecR8QgOy4CxC1ie2WCg',
'notBase64' => 'CgpIYWxsbyBXZW-0EgYIyfSG7gV_LwonCiCboKikqwjZfes9xuqgthFH3',
'validTransfer' => 'GmYKZAoggZC9pYXuXx2fv30G6B5p7BjhM3YQTP9Ut0V-t9PvcQ0SQDddHyKzAX3LBV0PuDiPc6lxkUipss5tyuLRpMtFJQnT30tsbYIkA1FXimjMKOoiuLswf4OLLV3bAIYehW-b9AgqYQoFSGFsbG8SBgiJlaPvBUJQCiYKIIGQvaWF7l8dn799BugeaewY4TN2EEz_VLdFfrfT73ENEICfSRImCiDtdleSLxhUgEbMW9DpqIwsykFj3-z_enKEOuGnXrmW8xCAn0k',
'errornusTransfer' => 'ClxGcm9oZXMgTmV1ZXMgSmFociB1bmQgREFOS0UsIGRhc3MgZHUgZGljaCBzbyBlaW5zZXR6dCBmw7xyIEdyYWRpZG8hIEhlcnpsaWNoZSBHcsO8w59lIFRlcmVzYRIGCPjjgvEFQlAKJgogUQwFYeVlGlfWDrkXNN7rHwejoCDJKt+YkYJfbJVyj3EQwIQ9EiYKIPXIRnUhVJ/zCs5+y/VaTBjTIoYizJNwS+JC//xsbQrHEMCEPQ=='
];
/*public function setUp() {
parent::setUp();
}
*/
public function testWrongMethod()
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$this->get('/TransactionJsonRequestHandler');
$this->assertResponseOk();
$expected = json_encode(['state' => 'error', 'msg' => 'no post']);
$this->assertEquals($expected, (string)$this->_response->getBody());
}
public function testInvalidJson()
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$this->post('/TransactionJsonRequestHandler', '{This isn\'t valid json}');
$this->assertResponseOk();
$expected = json_encode(['state' => 'error', 'msg' => 'parameter error']);
$this->assertEquals($expected, (string)$this->_response->getBody());
}
public function testNotSetTransaction()
{
$this->postAndParse(
['method' => 'putTransaction'],
['state' => 'error', 'msg' => 'parameter error']
);
}
public function testNotSetMethod()
{
$this->postAndParse(
['transaction' => $this->transactions['validCreation']],
['state' => 'error', 'msg' => 'parameter error']
);
}
public function testUnknownMethod()
{
//$this->post('/TransactionJsonRequestHandler', ['method' => 'putTransaction', 'transaction' => 'CgpIYWxsbyBXZWx0EgYIyfSG7gVKLwonCiCboKikqwjZfes9xuqgthFH3/cHHaWchkUhWiGhQjB23xCg2pMBELWJ7ZYK']);
$this->postAndParse(
['method' => 'foobar', 'transaction' => $this->transactions['validCreation']],
['state' => 'error', 'msg' => 'unknown method', 'details' => 'foobar']
);
}
public function testInvalidEncodedTransaction() {
//"msg":"error parsing transaction","details":[{"Transaction":"base64 decode error"}]
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['notBase64']],
['state' => 'error', 'msg' => 'error parsing transaction', 'details' => [
['Transaction' => 'invalid base64 string']
]]
);
}
public function testInvalidTransaction() {
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => base64_encode('Hallo Miau Welt')],
['state' => 'error', 'msg' => 'error parsing transaction', 'details' => [
['Transaction' => 'Error occurred during parsing: Unexpected wire type.']
]]
);
}
public function testToLargeCreationSum()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation900']],
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1000 gr per Month not allowed"}]}'
);
}
public function testToLargeCreation()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation1200']],
'{"state":"error","msg":"error validate transaction","details":[{"TransactionCreation::validate":"Creation more than 1000 gr per Month not allowed"}]}'
);
}
public function testValidTransfer()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validTransfer']],
['state' => 'success']
);
}
/*public function testMissingPreviousTransaction()
{
}*/
public function testValidTransaction()
{
$this->postAndParse(
['method' => 'putTransaction', 'transaction' => $this->transactions['validCreation']],
['state' => 'success']
);
}
private function postAndParse($params, $expected)
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$this->disableErrorHandlerMiddleware();
$this->post('/TransactionJsonRequestHandler', json_encode($params));
// Check that the response was a 200
$this->assertResponseOk();
$responseBodyString = (string)$this->_response->getBody();
$json = json_decode($responseBodyString);
$this->assertNotFalse($json);
if(is_array($expected)) {
$expected = json_encode($expected);
}
$this->assertEquals($expected, $responseBodyString);
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Test\TestCase\Model\Entity;
use App\Model\Entity\Table;
use Cake\TestSuite\TestCase;
/**
* App\Model\Entity\Table Test Case
*/
class TableTest extends TestCase
{
/**
* Test subject
*
* @var \App\Model\Entity\Table
*/
public $Table;
/**
* setUp method
*
* @return void
*/
public function setUp()
{
parent::setUp();
$this->Table = new Table();
}
/**
* tearDown method
*
* @return void
*/
public function tearDown()
{
unset($this->Table);
parent::tearDown();
}
/**
* Test initial setup
*
* @return void
*/
public function testInitialization()
{
$this->markTestIncomplete('Not implemented yet.');
}
}

View File

@ -30,84 +30,12 @@ class TransactionCreationTest extends TestCase
parent::setUp();
}
public function testHashingFunction()
public function testDummy()
{
$pairs = [
"a" => 97,
"b" => 98,
"c" => 99,
"d" => 100,
"aa" => 12513,
"ab" => 12514,
"@" => 64,
".d" => 5988,
"gmx" => 1701624,
"@gmx" => 135919352,
"@gmx.de" => 3742152099,
"***REMOVED***" => 2928827813,
"***REMOVED***" => 1899591683,
"***REMOVED***" => 2089074830,
"maximilian.muster@gradido.net" => 793144931,
"coin-info5@gradido.net" => 1829129963,
"coin-info6@gradido.net" => 1830178539,
"coin-info8@gradido.net" => 1832275691,
"coin-info9@gradido.net" => 1833324267,
"coin-info10@gradido.net" => 3877298078,
"coin-info11@gradido.net" => 3878346654,
"coin-info12@gradido.net" => 3879395230,
"***REMOVED***" => 2089074830,
"***REMOVED***" => 3996757473,
"***REMOVED***" => 3788634614,
"***REMOVED***" => 807797884,
"***REMOVED***" => 1640973721,
"***REMOVED***" => 2025729173,
"***REMOVED***" => 1961122507,
"***REMOVED***" => 362466358,
"***REMOVED***" => 3796728871,
"***REMOVED***" => 807797884,
"***REMOVED***" => 3794905967,
"***REMOVED***" => 3077694284,
"***REMOVED***" => 3246159770,
"***REMOVED***" => 3123402690,
"testneu-11-12-3@gradido.net" => 4092403827,
"***REMOVED***" => 3151414199,
"***REMOVED***" => 3526188273,
"***REMOVED***" => 966804823,
"***REMOVED***" => 1309273258,
"***REMOVED***" => 995978784,
"***REMOVED***" => 310113324,
"***REMOVED***" => 1309273258,
"***REMOVED***" => 530108573,
"***REMOVED***" => 1734855679,
"***REMOVED***" => 767779182,
"***REMOVED***" => 2247491519,
"***REMOVED***" => 3248626267,
"***REMOVED***" => 3516649930,
"***REMOVED***" => 231214190,
"***REMOVED***" => 4247461928,
"***REMOVED***" => 324829839,
"***REMOVED***" => 3046147747,
"***REMOVED***" => 3207307415,
"***REMOVED***" => 728893500,
"***REMOVED***" => 3905254663,
"***REMOVED***" => 3207307415,
"***REMOVED***" => 1155733239,
"***REMOVED***" => 2013046423,
"***REMOVED***" => 4033835283,
"***REMOVED***" => 1945541625,
"***REMOVED***" => 2310715309,
"***REMOVED***" => 1221362064,
"***REMOVED***" => 4161339877
];
foreach($pairs as $email => $cpp_hash) {
$php_hash = TransactionCreation::DRMakeStringHash($email);
// assertEquals(mixed $expected, mixed $actual[, string $message = ''])
if($php_hash != $cpp_hash) {
$this->assertEquals($cpp_hash, $php_hash, "hashes for $email don't match");
}
}
$this->assertEquals(true, true);
}
}

View File

@ -214,9 +214,8 @@ return [
'timeout' => 30,
'username' => null,
'password' => null,
'client' => null,
'tls' => null,
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
'className' => 'Smtp',
'tls' => true
],
],
@ -304,11 +303,11 @@ return [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'host' => 'localhost',
'host' => 'mariadb',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
'username' => 'root',
'password' => '',
'database' => 'gradido_community_test',
//'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,

View File

@ -0,0 +1,57 @@
HTTPServer.port = 1200
JSONServer.port = 1201
Gradido.group_id = 1
crypto.server_admin_public = f909a866baec97c5460b8d7a93b72d3d4d20cc45d9f15d78bd83944eb9286b7f
crypto.server_key = a51ef8ac7ef1abf162fb7a65261acd7a
# TODO auto-generate in docker build step
crypto.app_secret = 21ffbbc616fe
# Server admin Passphrase
# nerve execute merit pool talk hockey basic win cargo spin disagree ethics swear price purchase say clutch decrease slow half forest reform cheese able
#
phpServer.url = http://localhost/
phpServer.host = nginx
loginServer.path = http://localhost/account
loginServer.default_locale = de
loginServer.db.host = mariadb
loginServer.db.name = gradido_login_test
loginServer.db.user = root
loginServer.db.password =
loginServer.db.port = 3306
frontend.checkEmailPath = http://localhost/reset
email.disable = true
#email.username =
#email.sender =
#email.admin_receiver =
#email.password =
#email.smtp.url =
#email.smtp.port =
# binary is default, for debugging also json is possible
#hedera.consensus.message_format = json
# TESTNET or MAINNET, TESTNET is default
hedera.nettype = TESTNET
# server setup types: test, staging or production
ServerSetupType=test
dev.default_group = docker
# Session timeout in minutes
#
session.timeout = 15
# Disabling security features for faster develop and testing
unsecure.allow_passwort_via_json_request = 1
unsecure.allow_auto_sign_transactions = 1
unsecure.allow_cors_all = 1
# default disable, passwords must contain a number, a lower character, a high character, special character, and be at least 8 characters long
unsecure.allow_all_passwords = 1

View File

@ -38,7 +38,7 @@ services:
- ./login_server/dependencies:/code/dependencies
- ./login_server/scripts:/code/scripts
- ./configs/login_server:/etc/grd_login
- login_build_3:/code/build
- login_build_3.1:/code/build
#########################################################
@ -47,11 +47,8 @@ services:
community-server:
build:
context: .
target: community_server
dockerfile: ./community_server/Dockerfile
environment:
- DB_PASSWORD=''
- DB_USER='root'
- DB_DATABASE='gradido_community'
depends_on:
- mariadb
networks:
@ -103,4 +100,4 @@ services:
volumes:
frontend_node_modules:
login_build_3:
login_build_3.1:

99
docker-compose.test.yml Normal file
View File

@ -0,0 +1,99 @@
version: "3.4"
services:
#########################################################
## MARIADB ##############################################
#########################################################
mariadb:
build:
context: .
dockerfile: ./mariadb/Dockerfile
target: mariadb_server_test
environment:
- MARIADB_ALLOW_EMPTY_PASSWORD=1
- MARIADB_USER=root
networks:
- internal-net
ports:
- 3306:3306
volumes:
- db_test_vol:/var/lib/mysql
#########################################################
## LOGIN SERVER #########################################
#########################################################
login-server:
build:
context: ./login_server/
target: test
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
depends_on:
- mariadb
networks:
- internal-net
volumes:
- ./logs:/var/log/grd_login
- ./login_server/src:/code/src
- ./configs/login_server:/etc/grd_login
- login_build_cov:/code/build_cov
- ./coverage/login:/code/build_cov/coverage_html
#########################################################
## COMMUNITY SERVER (cakephp with php-fpm) ##############
#########################################################
community-server:
build:
context: .
target: test
dockerfile: ./community_server/Dockerfile
depends_on:
- mariadb
networks:
- internal-net
volumes:
- ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
- ./community_server/src:/var/www/cakephp/src
- ./coverage/community:/var/www/cakephp/webroot/coverage
#########################################################
## PHPMYADMIN ###########################################
#########################################################
phpmyadmin:
image: phpmyadmin
environment:
- PMA_ARBITRARY=1
#restart: always
ports:
- 8074:80
networks:
- internal-net
- external-net
volumes:
- /sessions
#########################################################
## skeema for updating dbs if changes happend ###########
#########################################################
skeema:
build:
context: .
dockerfile: ./skeema/Dockerfile
target: skeema_run
depends_on:
- mariadb
networks:
- internal-net
networks:
external-net:
internal-net:
internal: true
volumes:
db_test_vol:
login_build_cov:

View File

@ -38,7 +38,7 @@ services:
build:
context: .
dockerfile: ./mariadb/Dockerfile
target: mariadb_server
target: mariadb_server_test
environment:
- MARIADB_ALLOW_EMPTY_PASSWORD=1
- MARIADB_USER=root
@ -47,7 +47,7 @@ services:
ports:
- 3306:3306
volumes:
- db_vol:/var/lib/mysql
- db_vol:/var/lib/mysql
#########################################################
## LOGIN SERVER #########################################
@ -80,6 +80,7 @@ services:
depends_on:
- community-server
- login-server
- frontend
ports:
- 80:80
@ -89,6 +90,7 @@ services:
community-server:
build:
context: .
target: community_server
dockerfile: ./community_server/Dockerfile
environment:
- DB_PASSWORD=''

View File

@ -89,6 +89,56 @@ In case of success returns:
nginx was wrong configured.
- `session_id`: can be also negative
## Check username
### Request
`GET http://localhost/login_api/checkUsername?username=<username>&group_id=<group_id>`
`POST http://localhost/login_api/checkUsername`
with
```json
{
"username": "Maxilein",
"group_id": 1,
"group_alias": "gdd1"
}
```
group_id or group_alias, one of both is enough.
group_id is better, because one db request less
### Response
If username is not already taken
```json
{
"state":"success"
}
```
If username is already taken
```json
{
"state":"warning",
"msg":"username already in use"
}
```
If only group_alias was given and group with that alias was found in db
```json
{
"state":"success",
"group_id": 1
}
```
If group_id or group_alias unknown
```json
{
"state":"error",
"msg": "unknown group"
}
```
## Create user
Register a new User

View File

@ -51,6 +51,7 @@
"nouislider": "^12.1.0",
"particles-bg-vue": "1.2.3",
"perfect-scrollbar": "^1.3.0",
"portal-vue": "^2.1.7",
"prettier": "^2.2.1",
"qrcode": "^1.4.4",
"quill": "^1.3.6",
@ -58,12 +59,14 @@
"sweetalert2": "^9.5.4",
"vee-validate": "^3.4.5",
"vue": "^2.6.11",
"vue-bootstrap-toasts": "^1.0.7",
"vue-bootstrap-typeahead": "^0.2.6",
"vue-chartjs": "^3.5.0",
"vue-cli-plugin-i18n": "^1.0.1",
"vue-clickaway": "^2.2.2",
"vue-clipboard2": "^0.3.0",
"vue-flatpickr-component": "^8.1.2",
"vue-focus": "^2.1.0",
"vue-good-table": "^2.21.3",
"vue-i18n": "^8.22.4",
"vue-jest": "^3.0.7",

View File

@ -3,6 +3,7 @@
<div class="">
<particles-bg type="custom" :config="config" :bg="true" />
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
<Toasts></Toasts>
</div>
</div>
</template>

View File

@ -35,7 +35,7 @@ const communityAPI = {
balance: async (sessionId) => {
return apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
},
transactions: async (sessionId, firstPage = 1, items = 1000, order = 'DESC') => {
transactions: async (sessionId, firstPage = 1, items = 5, order = 'DESC') => {
return apiGet(
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
)

View File

@ -78,6 +78,26 @@ const loginAPI = {
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
)
},
getUserInfos: async (sessionId, email) => {
const payload = {
session_id: sessionId,
email: email,
ask: ['user.first_name', 'user.last_name'],
}
return apiPost(CONFIG.LOGIN_API_URL + 'getUserInfos', payload)
},
updateUserInfos: async (sessionId, email, data) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.first_name': data.firstName,
'User.last_name': data.lastName,
'User.description': data.description,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
changePassword: async (sessionId, email, password) => {
const payload = {
session_id: sessionId,
@ -88,6 +108,27 @@ const loginAPI = {
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
changePasswordProfile: async (sessionId, email, password, passwordNew) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.password': password,
'User.passwordNew': passwordNew,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
changeUsernameProfile: async (sessionId, email, username) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.username': username,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
updateLanguage: async (sessionId, email, language) => {
const payload = {
session_id: sessionId,

View File

@ -23,38 +23,95 @@
// Bootstrap (4.1.3) components
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/code";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/input-group";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/code";
@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/input-group";
@import "~bootstrap/scss/jumbotron";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/media";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/jumbotron";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/media";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/popover";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/print";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/toasts";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/variables";
// Utilities
@import "~bootstrap/scss/utilities/align";
@import "~bootstrap/scss/utilities/background";
@import "~bootstrap/scss/utilities/borders";
@import "~bootstrap/scss/utilities/clearfix";
@import "~bootstrap/scss/utilities/display";
@import "~bootstrap/scss/utilities/embed";
@import "~bootstrap/scss/utilities/flex";
@import "~bootstrap/scss/utilities/float";
@import "~bootstrap/scss/utilities/overflow";
@import "~bootstrap/scss/utilities/position";
@import "~bootstrap/scss/utilities/screenreaders";
@import "~bootstrap/scss/utilities/shadows";
@import "~bootstrap/scss/utilities/sizing";
@import "~bootstrap/scss/utilities/spacing";
@import "~bootstrap/scss/utilities/stretched-link";
@import "~bootstrap/scss/utilities/text";
@import "~bootstrap/scss/utilities/visibility";
// Mixins
@import "~bootstrap/scss/mixins/alert";
@import "~bootstrap/scss/mixins/badge";
@import "~bootstrap/scss/mixins/border-radius";
@import "~bootstrap/scss/mixins/box-shadow";
@import "~bootstrap/scss/mixins/breakpoints";
@import "~bootstrap/scss/mixins/buttons";
@import "~bootstrap/scss/mixins/caret";
@import "~bootstrap/scss/mixins/clearfix";
@import "~bootstrap/scss/mixins/deprecate";
@import "~bootstrap/scss/mixins/float";
@import "~bootstrap/scss/mixins/forms";
@import "~bootstrap/scss/mixins/gradients";
@import "~bootstrap/scss/mixins/grid-framework";
@import "~bootstrap/scss/mixins/grid";
@import "~bootstrap/scss/mixins/hover";
@import "~bootstrap/scss/mixins/image";
@import "~bootstrap/scss/mixins/list-group";
@import "~bootstrap/scss/mixins/lists";
@import "~bootstrap/scss/mixins/nav-divider";
@import "~bootstrap/scss/mixins/pagination";
@import "~bootstrap/scss/mixins/reset-text";
@import "~bootstrap/scss/mixins/resize";
@import "~bootstrap/scss/mixins/screen-reader";
@import "~bootstrap/scss/mixins/size";
@import "~bootstrap/scss/mixins/table-row";
@import "~bootstrap/scss/mixins/text-emphasis";
@import "~bootstrap/scss/mixins/text-hide";
@import "~bootstrap/scss/mixins/text-truncate";
@import "~bootstrap/scss/mixins/transition";
@import "~bootstrap/scss/mixins/visibility";
// Argon utilities and components

View File

@ -1,17 +0,0 @@
//
// Circle badge
//
// General styles
.badge-circle {
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
width: 2rem;
height: 2rem;
font-size: .875rem;
}

View File

@ -1,42 +0,0 @@
//
// Dot badge
//
// General styles
.badge-dot {
padding-left: 0;
padding-right: 0;
background: transparent;
font-weight: $font-weight-normal;
font-size: $font-size-sm;
text-transform: none;
strong {
color: $gray-800;
}
i {
display: inline-block;
vertical-align: middle;
width: .375rem;
height: .375rem;
border-radius: 50%;
margin-right: .375rem;
}
&.badge-md {
i {
width: .5rem;
height: .5rem;
}
}
&.badge-lg {
i {
width: .625rem;
height: .625rem;
}
}
}

View File

@ -1,55 +0,0 @@
//
// Badge
//
// General styles
.badge {
text-transform: $badge-text-transfom;
a {
color: $white;
}
}
// Size variations
.badge-md {
padding: .65em 1em;
}
.badge-lg {
padding: .85em 1.375em;
}
// Multiple inline badges
.badge-inline {
margin-right: .625rem;
+ span {
top: 2px;
position: relative;
> a {
text-decoration: underline;
}
}
}
// Badge spacing inside a btn with some text
.btn {
.badge {
&:not(:first-child) {
margin-left: .5rem;
}
&:not(:last-child) {
margin-right: .5rem;
}
}
}

View File

@ -4,7 +4,6 @@
@import "custom/alert";
@import "custom/avatar";
@import "custom/badge";
@import "custom/buttons";
@import "custom/card";
@import "custom/chart";

View File

@ -1,45 +0,0 @@
<template>
<b-badge :variant="type" :pill="rounded" :size="size" :class="{ 'badge-circle': circle }">
<slot>
<i v-if="icon" :class="icon"></i>
</slot>
</b-badge>
</template>
<script>
export default {
name: 'badge',
props: {
tag: {
type: String,
default: 'span',
description: 'Html tag to use for the badge.',
},
rounded: {
type: Boolean,
default: false,
description: 'Whether badge is of pill type',
},
circle: {
type: Boolean,
default: false,
description: 'Whether badge is circle',
},
icon: {
type: String,
default: '',
description: 'Icon name. Will be overwritten by slot if slot is used',
},
type: {
type: String,
default: 'default',
description: 'Badge type (primary|info|danger|default|warning|success)',
},
size: {
type: String,
description: 'Badge size (md, lg)',
default: '',
},
},
}
</script>
<style></style>

View File

@ -1,75 +0,0 @@
<template>
<fade-transition>
<b-alert
v-model="visible"
:variant="type"
:class="[{ 'alert-dismissible': dismissible }]"
role="alert"
>
<slot v-if="!dismissible"></slot>
<template v-else>
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon" data-notify="icon">
<i :class="icon"></i>
</span>
</slot>
</template>
<span class="alert-text"><slot></slot></span>
<slot name="dismiss-icon">
<button
type="button"
class="close"
data-dismiss="alert"
aria-label="Close"
@click="dismissAlert"
>
<span aria-hidden="true">×</span>
</button>
</slot>
</template>
</b-alert>
</fade-transition>
</template>
<script>
import { FadeTransition } from 'vue2-transitions'
export default {
name: 'base-alert',
components: {
FadeTransition,
},
created() {
// console.log('base-alert gesetzt in =>', this.$route.path)
},
props: {
type: {
type: String,
default: 'default',
description: 'Alert type',
},
dismissible: {
type: Boolean,
default: false,
description: 'Whether alert is dismissible (closeable)',
},
icon: {
type: String,
default: '',
description: 'Alert icon to display',
},
},
data() {
return {
visible: true,
}
},
methods: {
dismissAlert() {
this.visible = false
},
},
}
</script>

View File

@ -1,75 +0,0 @@
<template>
<b-button
:type="nativeType"
:disabled="disabled || loading"
@click="handleClick"
class="base-button"
:variant="!outline ? type : `outline-${type}`"
:size="size"
:block="block"
:class="[
{ 'rounded-circle': round },
{ 'btn-wd': wide },
{ 'btn-icon btn-fab': icon },
{ 'btn-link': link },
{ disabled: disabled },
]"
>
<slot name="loading">
<i v-if="loading" class="fas fa-spinner fa-spin"></i>
</slot>
<slot></slot>
</b-button>
</template>
<script>
export default {
name: 'base-button',
props: {
round: Boolean,
icon: Boolean,
block: Boolean,
loading: Boolean,
wide: Boolean,
disabled: Boolean,
type: {
type: String,
default: 'default',
description: 'Button type (primary|secondary|danger etc)',
},
nativeType: {
type: String,
default: 'button',
description: 'Button native type (e.g button, input etc)',
},
size: {
type: String,
default: '',
description: 'Button size (sm|lg)',
},
outline: {
type: Boolean,
description: 'Whether button is outlined (only border has color)',
},
link: {
type: Boolean,
description: 'Whether button is a link (no borders or background)',
},
},
methods: {
handleClick(evt) {
this.$emit('click', evt)
},
},
}
</script>
<style lang="scss">
.base-button {
display: inline-flex;
align-items: center;
justify-content: center;
i {
padding: 0 3px;
}
}
</style>

View File

@ -1,97 +0,0 @@
<template>
<component
:is="tag"
:class="[{ show: isOpen }, `drop${direction}`]"
@click="toggleDropDown"
v-click-outside="closeDropDown"
>
<slot name="title-container" :is-open="isOpen">
<component
:is="titleTag"
class="btn-rotate"
:class="[{ 'dropdown-toggle': hasToggle }, titleClasses]"
:aria-expanded="isOpen"
data-toggle="dropdown"
>
<slot name="title" :is-open="isOpen">
<i :class="icon"></i>
{{ title }}
</slot>
</component>
</slot>
<ul
class="dropdown-menu"
:class="[{ show: isOpen }, { 'dropdown-menu-right': menuOnRight }, menuClasses]"
>
<slot></slot>
</ul>
</component>
</template>
<script>
export default {
name: 'base-dropdown',
props: {
tag: {
type: String,
default: 'div',
description: 'Dropdown html tag (e.g div, ul etc)',
},
titleTag: {
type: String,
default: 'button',
description: 'Dropdown title (toggle) html tag',
},
title: {
type: String,
description: 'Dropdown title',
},
direction: {
type: String,
default: 'down', // up | down
description: 'Dropdown menu direction (up|down)',
},
icon: {
type: String,
description: 'Dropdown icon',
},
titleClasses: {
type: [String, Object, Array],
description: 'Title css classes',
},
menuClasses: {
type: [String, Object],
description: 'Menu css classes',
},
menuOnRight: {
type: Boolean,
description: 'Whether menu should appear on the right',
},
hasToggle: {
type: Boolean,
description: 'Whether dropdown has arrow icon shown',
default: true,
},
},
data() {
return {
isOpen: false,
}
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen
this.$emit('change', this.isOpen)
},
closeDropDown() {
this.isOpen = false
this.$emit('change', false)
},
},
}
</script>
<style lang="scss" scoped>
.dropdown {
cursor: pointer;
user-select: none;
}
</style>

View File

@ -1,22 +0,0 @@
<template>
<div class="header" :class="{ [`bg-${type}`]: type }">
<b-container fluid>
<div class="header-body">
<slot></slot>
</div>
</b-container>
</div>
</template>
<script>
export default {
name: 'base-header',
props: {
type: {
type: String,
default: 'success',
description: 'Header background type',
},
},
}
</script>
<style></style>

View File

@ -1,67 +0,0 @@
<template>
<div>
<b-pagination
first-number
last-number
:per-page="perPage"
:size="size"
:value="value"
@change="(val) => $emit('change', val)"
:align="align"
:total-rows="total"
>
<template v-slot:prev-text>
<a class="page-link" aria-label="Previous">
<span aria-hidden="true">
<i class="fa fa-angle-left" aria-hidden="true"></i>
</span>
</a>
</template>
<template v-slot:next-text>
<a class="page-link" aria-label="Next">
<span aria-hidden="true">
<i class="fa fa-angle-right" aria-hidden="true"></i>
</span>
</a>
</template>
</b-pagination>
</div>
</template>
<script>
export default {
name: 'base-pagination',
props: {
pageCount: {
type: Number,
default: 0,
description: 'Pagination page count. This should be specified in combination with perPage',
},
perPage: {
type: Number,
default: 10,
description: 'Pagination per page. Should be specified with total or pageCount',
},
total: {
type: Number,
default: 0,
description:
'Can be specified instead of pageCount. The page count in this case will be total/perPage',
},
value: {
type: Number,
default: 1,
description: 'Pagination value',
},
size: {
type: String,
default: '',
description: 'Pagination size',
},
align: {
type: String,
default: '',
description: 'Pagination alignment (e.g center|start|end)',
},
},
}
</script>

View File

@ -1,79 +0,0 @@
<template>
<div class="wrapper">
<div :class="`progress-${type}`" v-if="showLabel">
<div class="progress-label">
<slot name="label">
<span>{{ label }}</span>
</slot>
</div>
<div class="progress-percentage">
<slot>
<span>{{ value }}%</span>
</slot>
</div>
</div>
<b-progress :size="size" :class="[progressClasses]" :style="`height: ${height}px`">
<b-progress-bar :class="computedClasses" :value="value"></b-progress-bar>
</b-progress>
</div>
</template>
<script>
export default {
name: 'base-progress',
props: {
striped: {
type: Boolean,
description: 'Whether progress is striped',
},
animated: {
type: Boolean,
description: 'Whether progress is animated (works only with `striped` prop together)',
},
label: {
type: String,
description: 'Progress label (shown on the left above progress)',
},
height: {
type: Number,
default: 3,
description: 'Progress line height',
},
type: {
type: String,
default: 'default',
description: 'Progress type (e.g danger, primary etc)',
},
showLabel: {
type: Boolean,
default: false,
},
progressClasses: {
type: [Array, String],
default: '',
description: 'Progress css classes',
},
size: {
type: String,
default: '',
},
value: {
type: Number,
default: 0,
validator: (value) => {
return value >= 0 && value <= 100
},
description: 'Progress value',
},
},
computed: {
computedClasses() {
return [
{ 'progress-bar-striped': this.striped },
{ 'progress-bar-animated': this.animated },
{ [`bg-${this.type}`]: this.type },
]
},
},
}
</script>
<style></style>

View File

@ -1,92 +0,0 @@
<template>
<div class="slider" :disabled="disabled"></div>
</template>
<script>
import noUiSlider from 'nouislider'
export default {
name: 'base-slider',
props: {
value: {
type: [String, Array, Number],
description: 'slider value',
},
disabled: {
type: Boolean,
default: false,
description: 'whether the slider is disabled',
},
start: {
type: [Number, Array],
default: 0,
description:
'[noUi Slider start](https://refreshless.com/nouislider/slider-options/#section-start)',
},
connect: {
type: [Boolean, Array],
default: () => [true, false],
description:
'[noUi Slider connect](https://refreshless.com/nouislider/slider-options/#section-connect)',
},
range: {
type: Object,
default: () => {
return {
min: 0,
max: 100,
}
},
description:
'[noUi Slider range](https://refreshless.com/nouislider/slider-values/#section-range)',
},
options: {
type: Object,
default: () => {
return {}
},
description: '[noUi Slider options](https://refreshless.com/nouislider/slider-options/)',
},
},
data() {
return {
slider: null,
}
},
methods: {
createSlider() {
noUiSlider.create(this.$el, {
start: this.value || this.start,
connect: Array.isArray(this.value) ? true : this.connect,
range: this.range,
...this.options,
})
const slider = this.$el.noUiSlider
slider.on('slide', () => {
const value = slider.get()
if (value !== this.value) {
this.$emit('input', value)
}
})
},
},
mounted() {
this.createSlider()
},
watch: {
value(newValue, oldValue) {
const slider = this.$el.noUiSlider
const sliderValue = slider.get()
if (newValue !== oldValue && sliderValue !== newValue) {
if (Array.isArray(sliderValue) && Array.isArray(newValue)) {
if (oldValue.length === newValue.length && oldValue.every((v, i) => v === newValue[i])) {
slider.set(newValue)
}
} else {
slider.set(newValue)
}
}
},
},
}
</script>
<style></style>

View File

@ -1,66 +0,0 @@
<template>
<table class="table tablesorter" :class="tableClass">
<thead :class="theadClasses">
<tr>
<slot name="columns" :columns="columns">
<th v-for="column in columns" :key="column">{{ column }}</th>
</slot>
</tr>
</thead>
<tbody :class="tbodyClasses">
<tr v-for="(item, index) in data" :key="index">
<slot :row="item" :index="index">
<td v-for="(column, index) in columns" :key="index">
{{ itemValue(item, column) }}
</td>
</slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'base-table',
props: {
columns: {
type: Array,
default: () => [],
description: 'Table columns',
},
data: {
type: Array,
default: () => [],
description: 'Table data',
},
type: {
type: String, // striped | hover
default: '',
description: 'Whether table is striped or hover type',
},
theadClasses: {
type: String,
default: '',
description: '<thead> css classes',
},
tbodyClasses: {
type: String,
default: '',
description: '<tbody> css classes',
},
},
computed: {
tableClass() {
return this.type && `table-${this.type}`
},
},
methods: {
hasValue(item, column) {
return item[column.toLowerCase()] !== 'undefined'
},
itemValue(item, column) {
return item[column.toLowerCase()]
},
},
}
</script>
<style></style>

View File

@ -1,25 +0,0 @@
<template>
<nav aria-label="breadcrumb">
<b-breadcrumb :class="[{ [`bg-${type}`]: type }, listClasses]">
<slot></slot>
</b-breadcrumb>
</nav>
</template>
<script>
export default {
name: 'breadcrumb',
props: {
type: {
type: String,
default: '',
description: 'Breadcrumb background type',
},
listClasses: {
type: [String, Object],
default: '',
description: 'Breadcrumb list classes',
},
},
}
</script>
<style></style>

View File

@ -1,18 +0,0 @@
<template>
<b-breadcrumb-item :active="active">
<slot></slot>
</b-breadcrumb-item>
</template>
<script>
export default {
name: 'breadcrumb-item',
props: {
active: {
type: Boolean,
default: false,
description: 'Whether breadcrumb item is active',
},
},
}
</script>
<style></style>

View File

@ -1,40 +0,0 @@
<template>
<bread-crumb list-classes="breadcrumb-links breadcrumb-dark">
<bread-crumb-item>
<router-link to="/overview">
<i class="fas fa-home"></i>
</router-link>
</bread-crumb-item>
<bread-crumb-item
v-for="(route, index) in $route.matched.slice()"
:key="route.name"
:active="index === $route.matched.length - 1"
style="display: inline-block"
>
<router-link :to="{ name: route.name }" v-if="index < $route.matched.length - 1">
{{ route.name }}
</router-link>
<span v-else>{{ route.name }}</span>
</bread-crumb-item>
</bread-crumb>
</template>
<script>
import BreadCrumb from './Breadcrumb'
import BreadCrumbItem from './BreadcrumbItem'
export default {
name: 'route-breadcrumb',
components: {
BreadCrumb,
BreadCrumbItem,
},
methods: {
getBreadName(route) {
return route.name
},
},
}
</script>
<style scoped></style>

View File

@ -1,38 +0,0 @@
<template>
<div class="btn-group-toggle" data-toggle="buttons">
<label class="btn" :class="[{ active: value }, buttonClasses]">
<input v-model="model" type="checkbox" checked="" autocomplete="off" />
<slot></slot>
</label>
</div>
</template>
<script>
export default {
name: 'button-checkbox',
props: {
value: {
type: Boolean,
description: 'Checked value',
},
buttonClasses: {
type: [String, Object],
description: 'Inner button css classes',
},
},
model: {
prop: 'value',
event: 'change',
},
computed: {
model: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
},
},
},
}
</script>
<style></style>

View File

@ -1,55 +0,0 @@
<template>
<div class="btn-group-toggle" data-toggle="buttons">
<label
v-for="(option, index) in options"
:key="index"
class="btn"
:class="[{ active: value === option.value }, buttonClasses]"
>
<input
:value="option.value"
v-model="model"
type="radio"
id="option1"
autocomplete="off"
checked=""
/>
{{ option.label }}
</label>
</div>
</template>
<script>
export default {
name: 'button-radio-group',
props: {
options: {
type: Array,
description: 'Radio options. Should be an array of objects {value: "", label: ""}',
default: () => [],
},
value: {
type: String,
description: 'Radio value',
},
buttonClasses: {
type: [String, Object],
description: 'Inner button css classes',
},
},
model: {
prop: 'value',
event: 'change',
},
computed: {
model: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
},
},
},
}
</script>
<style></style>

View File

@ -1,71 +0,0 @@
<template>
<b-card
no-body
:class="[
{ 'card-lift--hover': hover },
{ shadow: shadow },
{ [`shadow-${shadowSize}`]: shadowSize },
{ [`bg-gradient-${gradient}`]: gradient },
{ [`bg-${type}`]: type },
]"
>
<slot name="image"></slot>
<b-card-header :class="headerClasses" v-if="$slots.header">
<slot name="header"></slot>
</b-card-header>
<b-card-body :class="bodyClasses" v-if="!noBody">
<slot></slot>
</b-card-body>
<slot v-if="noBody"></slot>
<b-card-footer :class="footerClasses" v-if="$slots.footer">
<slot name="footer"></slot>
</b-card-footer>
</b-card>
</template>
<script>
export default {
name: 'card',
props: {
type: {
type: String,
description: 'Card type',
},
gradient: {
type: String,
description: 'Card background gradient type (warning,danger etc)',
},
hover: {
type: Boolean,
description: 'Whether card should move on hover',
},
shadow: {
type: Boolean,
description: 'Whether card has shadow',
},
shadowSize: {
type: String,
description: 'Card shadow size',
},
noBody: {
type: Boolean,
default: false,
description: 'Whether card should have wrapper body class',
},
bodyClasses: {
type: [String, Object, Array],
description: 'Card body css classes',
},
headerClasses: {
type: [String, Object, Array],
description: 'Card header css classes',
},
footerClasses: {
type: [String, Object, Array],
description: 'Card footer css classes',
},
},
}
</script>
<style></style>

View File

@ -1,58 +0,0 @@
<template>
<card class="card-stats" :show-footer-line="true">
<b-row>
<b-col>
<slot>
<h5 class="card-title text-uppercase text-muted mb-0" v-if="title">
{{ title }}
</h5>
<span class="h2 font-weight-bold mb-0" v-if="subTitle">
{{ subTitle }}
</span>
</slot>
</b-col>
<b-col cols="auto" v-if="$slots.icon || icon">
<slot name="icon">
<div
class="icon icon-shape text-white rounded-circle shadow"
:class="[`bg-${type}`, iconClasses]"
>
<i :class="icon"></i>
</div>
</slot>
</b-col>
<b-col cols="auto" v-if="$slots.img || img">
<slot name="img">
<img :src="img" width="80" />
</slot>
</b-col>
</b-row>
<p class="mt-3 mb-0 text-sm">
<slot name="footer"></slot>
</p>
</card>
</template>
<script>
import Card from './Card.vue'
export default {
name: 'stats-card',
components: {
Card,
},
props: {
type: {
type: String,
default: 'primary',
},
icon: String,
img: String,
title: String,
subTitle: String,
iconClasses: [String, Array],
},
}
</script>
<style></style>

View File

@ -1,30 +0,0 @@
import { Bar, mixins } from 'vue-chartjs'
import globalOptionsMixin from '@/components/Charts/globalOptionsMixin'
export default {
name: 'bar-chart',
extends: Bar,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({}),
},
},
data() {
return {
ctx: null,
}
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions)
}
},
{ immediate: true },
)
},
}

View File

@ -1,29 +0,0 @@
import { Line, mixins } from 'vue-chartjs'
import globalOptionsMixin from '@/components/Charts/globalOptionsMixin'
export default {
name: 'line-chart',
extends: Line,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({}),
},
},
data() {
return {
ctx: null,
}
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions)
}
},
{ immediate: true },
)
},
}

View File

@ -1,234 +0,0 @@
import { parseOptions } from '@/components/Charts/optionHelpers'
import Chart from 'chart.js'
export const Charts = {
mode: 'light', // (themeMode) ? themeMode : 'light';
fonts: {
base: 'Open Sans',
},
colors: {
gray: {
100: '#f6f9fc',
200: '#e9ecef',
300: '#dee2e6',
400: '#ced4da',
500: '#adb5bd',
600: '#8898aa',
700: '#525f7f',
800: '#32325d',
900: '#212529',
},
theme: {
default: '#172b4d',
primary: '#5e72e4',
secondary: '#f4f5f7',
info: '#11cdef',
success: '#2dce89',
danger: '#f5365c',
warning: '#fb6340',
},
black: '#12263F',
white: '#FFFFFF',
transparent: 'transparent',
},
}
function chartOptions() {
const { colors, mode, fonts } = Charts
// Options
const options = {
defaults: {
global: {
responsive: true,
maintainAspectRatio: false,
defaultColor: mode === 'dark' ? colors.gray[700] : colors.gray[600],
defaultFontColor: mode === 'dark' ? colors.gray[700] : colors.gray[600],
defaultFontFamily: fonts.base,
defaultFontSize: 13,
layout: {
padding: 0,
},
legend: {
display: false,
position: 'bottom',
labels: {
usePointStyle: true,
padding: 16,
},
},
elements: {
point: {
radius: 0,
backgroundColor: colors.theme.primary,
},
line: {
tension: 0.4,
borderWidth: 4,
borderColor: colors.theme.primary,
backgroundColor: colors.transparent,
borderCapStyle: 'rounded',
},
rectangle: {
backgroundColor: colors.theme.warning,
},
arc: {
backgroundColor: colors.theme.primary,
borderColor: mode === 'dark' ? colors.gray[800] : colors.white,
borderWidth: 4,
},
},
tooltips: {
enabled: true,
mode: 'index',
intersect: false,
},
},
pie: {
tooltips: {
mode: 'point',
},
},
doughnut: {
tooltips: {
mode: 'point',
},
cutoutPercentage: 83,
legendCallback: function (chart) {
const data = chart.data
let content = ''
data.labels.forEach(function (label, index) {
const bgColor = data.datasets[0].backgroundColor[index]
content += '<span class="chart-legend-item">'
content +=
'<i class="chart-legend-indicator" style="background-color: ' + bgColor + '"></i>'
content += label
content += '</span>'
})
return content
},
},
},
}
// yAxes
Chart.scaleService.updateScaleDefaults('linear', {
gridLines: {
borderDash: [2],
borderDashOffset: [2],
color: mode === 'dark' ? colors.gray[900] : colors.gray[200],
drawBorder: false,
drawTicks: true,
zeroLineWidth: 1,
zeroLineColor: mode === 'dark' ? colors.gray[900] : colors.gray[200],
zeroLineBorderDash: [2],
zeroLineBorderDashOffset: [2],
},
ticks: {
beginAtZero: true,
padding: 10,
callback: function (value) {
if (!(value % 10)) {
return value
}
},
},
})
// xAxes
Chart.scaleService.updateScaleDefaults('category', {
gridLines: {
drawBorder: false,
drawOnChartArea: false,
drawTicks: false,
lineWidth: 1,
zeroLineWidth: 1,
},
ticks: {
padding: 20,
},
maxBarThickness: 10,
})
return options
}
let initialized = false
export function initGlobalOptions() {
if (initialized) {
return
}
parseOptions(Chart, chartOptions())
initialized = true
}
export const basicOptions = {
maintainAspectRatio: false,
legend: {
display: false,
},
responsive: true,
}
export const blueChartOptions = {
scales: {
yAxes: [
{
gridLines: {
color: Charts.colors.gray[700],
zeroLineColor: Charts.colors.gray[700],
},
},
],
},
}
export const lineChartOptionsBlue = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest',
},
responsive: true,
scales: {
yAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.0)',
zeroLineColor: 'transparent',
},
ticks: {
suggestedMin: 60,
suggestedMax: 125,
padding: 20,
fontColor: '#9e9e9e',
},
},
],
xAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.1)',
zeroLineColor: 'transparent',
},
ticks: {
padding: 20,
fontColor: '#9e9e9e',
},
},
],
},
}

View File

@ -1,7 +0,0 @@
import { initGlobalOptions } from '@/components/Charts/config'
import './roundedCornersExtension'
export default {
mounted() {
initGlobalOptions()
},
}

View File

@ -1,10 +0,0 @@
// Parse global options
export function parseOptions(parent, options) {
for (const item in options) {
if (typeof options[item] !== 'object') {
parent[item] = options[item]
} else {
parseOptions(parent[item], options[item])
}
}
}

View File

@ -1,126 +0,0 @@
//
// Chart extension for making the bars rounded
// Code from: https://codepen.io/jedtrow/full/ygRYgo
//
import Chart from 'chart.js'
Chart.elements.Rectangle.prototype.draw = function () {
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
const cornerRadius = 6
if (!vm.horizontal) {
// bar
left = vm.x - vm.width / 2
right = vm.x + vm.width / 2
top = vm.y
bottom = vm.base
signX = 1
signY = bottom > top ? 1 : -1
borderSkipped = vm.borderSkipped || 'bottom'
} else {
// horizontal bar
left = vm.base
right = vm.x
top = vm.y - vm.height / 2
bottom = vm.y + vm.height / 2
signX = right > left ? 1 : -1
signY = 1
borderSkipped = vm.borderSkipped || 'left'
}
// Canvas doesn't allow us to stroke inside the width so we can
// 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.
const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom))
borderWidth = borderWidth > barSize ? barSize : borderWidth
const halfStroke = borderWidth / 2
// Adjust borderWidth when bar top position is near vm.base(zero).
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
bottom = borderBottom
}
// not become a horizontal line?
if (borderTop !== borderBottom) {
left = borderLeft
right = borderRight
}
}
ctx.beginPath()
ctx.fillStyle = vm.backgroundColor
ctx.strokeStyle = vm.borderColor
ctx.lineWidth = borderWidth
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
const corners = [
[left, bottom],
[left, top],
[right, top],
[right, bottom],
]
// Find first (starting) corner with fallback to 'bottom'
const borders = ['bottom', 'left', 'top', 'right']
let startCorner = borders.indexOf(borderSkipped, 0)
if (startCorner === -1) {
startCorner = 0
}
function cornerAt(index) {
return corners[(startCorner + index) % 4]
}
// Draw rectangle from 'startCorner'
let corner = cornerAt(0)
ctx.moveTo(corner[0], corner[1])
for (let i = 1; i < 4; i++) {
corner = cornerAt(i)
let nextCornerId = i + 1
if (nextCornerId === 4) {
nextCornerId = 0
}
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
// Fix radius being too large
if (radius > height / 2) {
radius = height / 2
}
if (radius > width / 2) {
radius = width / 2
}
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius)
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
ctx.lineTo(x + radius, y + height)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
}
ctx.fill()
if (borderWidth) {
ctx.stroke()
}
}

View File

@ -1,28 +0,0 @@
import { mount } from '@vue/test-utils'
import CloseButton from './CloseButton'
const localVue = global.localVue
describe('CloseButton', () => {
let wrapper
const propsData = {
target: 'Target',
expanded: false,
}
const Wrapper = () => {
return mount(CloseButton, { localVue, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('emmits click event', () => {
wrapper.find('.navbar-toggler').trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})
})

View File

@ -1,36 +0,0 @@
<template>
<button
type="button"
class="navbar-toggler"
data-toggle="collapse"
@click="handleClick"
:data-target="`#${target}`"
:aria-controls="target"
:aria-expanded="expanded"
aria-label="Toggle navigation"
>
<span></span>
<span></span>
</button>
</template>
<script>
export default {
name: 'close-button',
props: {
target: {
type: [String, Number],
description: 'Close button target element',
},
expanded: {
type: Boolean,
description: 'Whether button is expanded (aria-expanded attribute)',
},
},
methods: {
handleClick(evt) {
this.$emit('click', evt)
},
},
}
</script>
<style></style>

View File

@ -1,78 +0,0 @@
<template>
<div
class="custom-control custom-checkbox"
:class="[{ disabled: disabled }, { [`custom-checkbox-${type}`]: type }, inlineClass]"
>
<input
:id="cbId"
class="custom-control-input"
:class="inputClasses"
type="checkbox"
:disabled="disabled"
v-model="model"
/>
<label :for="cbId" class="custom-control-label">
<slot>
<span v-if="inline">&nbsp;</span>
</slot>
</label>
</div>
</template>
<script>
export default {
name: 'base-checkbox',
model: {
prop: 'checked',
},
props: {
checked: {
type: [Array, Boolean],
description: 'Whether checkbox is checked',
},
disabled: {
type: Boolean,
description: 'Whether checkbox is disabled',
},
inline: {
type: Boolean,
description: 'Whether checkbox is inline',
},
inputClasses: {
type: [String, Object, Array],
description: 'Checkbox input classes',
},
type: {
type: String,
description: 'Checkbox type (e.g info, danger etc)',
},
},
data() {
return {
cbId: '',
touched: false,
}
},
computed: {
model: {
get() {
return this.checked
},
set(check) {
if (!this.touched) {
this.touched = true
}
this.$emit('input', check)
},
},
inlineClass() {
if (this.inline) {
return `form-check-inline`
}
return ''
},
},
created() {
this.cbId = Math.random().toString(16).slice(2)
},
}
</script>

View File

@ -1,187 +0,0 @@
<template>
<validation-provider
:rules="rules"
:name="name"
v-bind="$attrs"
v-slot="{ errors, valid, invalid, validated }"
>
<b-form-group>
<slot name="label">
<label v-if="label" :class="labelClasses">
{{ label }}
</label>
</slot>
<div
:class="[
{ 'input-group': hasIcon },
{ focused: focused },
{ 'input-group-alternative': alternative },
{ 'has-label': label || $slots.label },
inputGroupClasses,
]"
>
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend">
<span class="input-group-text">
<slot name="prepend">
<i :class="prependIcon"></i>
</slot>
</span>
</div>
<slot v-bind="slotData">
<input
:value="value"
:type="type"
v-on="listeners"
v-bind="$attrs"
:valid="valid"
:required="required"
class="form-control"
:class="[
{ 'is-valid': valid && validated && successMessage },
{ 'is-invalid': invalid && validated },
inputClasses,
]"
/>
</slot>
<div v-if="appendIcon || $slots.append" class="input-group-append">
<span class="input-group-text">
<slot name="append">
<i :class="appendIcon"></i>
</slot>
</span>
</div>
<slot name="infoBlock"></slot>
</div>
<slot name="success">
<div class="valid-feedback" v-if="valid && validated && successMessage">
{{ successMessage }}
</div>
</slot>
<slot name="error">
<div v-if="errors[0]" class="invalid-feedback" style="display: block">
{{ errors[0] }}
</div>
</slot>
</b-form-group>
</validation-provider>
</template>
<script>
export default {
inheritAttrs: false,
name: 'base-input',
props: {
required: {
type: Boolean,
description: 'Whether input is required (adds an asterix *)',
},
group: {
type: Boolean,
description: 'Whether input is an input group (manual override in special cases)',
},
alternative: {
type: Boolean,
description: 'Whether input is of alternative layout',
},
label: {
type: String,
description: 'Input label (text before input)',
},
error: {
type: String,
description: 'Input error (below input)',
},
successMessage: {
type: String,
description: 'Input success message',
default: '',
},
labelClasses: {
type: String,
description: 'Input label css classes',
default: 'form-control-label',
},
inputClasses: {
type: String,
description: 'Input css classes',
},
inputGroupClasses: {
type: String,
description: 'Input group css classes',
},
value: {
type: [String, Number],
description: 'Input value',
},
type: {
type: String,
description: 'Input type',
default: 'text',
},
appendIcon: {
type: String,
description: 'Append icon (right)',
},
prependIcon: {
type: String,
description: 'Prepend icon (left)',
},
rules: {
type: [String, Array, Object],
description: 'Vee validate validation rules',
default: '',
},
name: {
type: String,
description: 'Input name (used for validation)',
default: '',
},
},
data() {
return {
focused: false,
}
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
focus: this.onFocus,
blur: this.onBlur,
}
},
slotData() {
return {
focused: this.focused,
error: this.error,
...this.listeners,
}
},
hasIcon() {
const { append, prepend } = this.$slots
return (
append !== undefined ||
prepend !== undefined ||
this.appendIcon !== undefined ||
this.prependIcon !== undefined ||
this.group
)
},
},
methods: {
updateValue(evt) {
const value = evt.target.value
this.$emit('input', value)
},
onFocus(evt) {
this.focused = true
this.$emit('focus', evt)
},
onBlur(evt) {
this.focused = false
this.$emit('blur', evt)
},
},
}
</script>
<style></style>

View File

@ -1,64 +0,0 @@
<template>
<div class="custom-control custom-radio" :class="[inlineClass, { disabled: disabled }]">
<input
:id="cbId"
class="custom-control-input"
type="radio"
:disabled="disabled"
:value="name"
v-model="model"
/>
<label :for="cbId" class="custom-control-label">
<slot>
<span v-if="inline">&nbsp;</span>
</slot>
</label>
</div>
</template>
<script>
export default {
name: 'base-radio',
props: {
name: {
type: [String, Number],
description: 'Radio label',
},
disabled: {
type: Boolean,
description: 'Whether radio is disabled',
},
value: {
type: [String, Boolean],
description: 'Radio value',
},
inline: {
type: Boolean,
description: 'Whether radio is inline',
},
},
data() {
return {
cbId: '',
}
},
computed: {
model: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
},
},
inlineClass() {
if (this.inline) {
return `form-check-inline`
}
return ''
},
},
created() {
this.cbId = Math.random().toString(16).slice(2)
},
}
</script>

View File

@ -1,127 +0,0 @@
<template>
<slide-y-up-transition :duration="animationDuration">
<b-modal
class="modal fade"
ref="app-modal"
:size="size"
:hide-header="!$slots.header"
:modal-class="[{ 'modal-mini': type === 'mini' }, ...modalClasses]"
@mousedown.self="closeModal"
tabindex="-1"
role="dialog"
centered
@close="closeModal"
@hide="closeModal"
:header-class="headerClasses"
:footer-class="footerClasses"
:content-class="[gradient ? `bg-gradient-${gradient}` : '', ...modalContentClasses]"
:body-class="bodyClasses"
:aria-hidden="!show"
>
<template v-slot:modal-header>
<slot name="header"></slot>
<slot name="close-button">
<button
type="button"
class="close"
v-if="showClose"
@click="closeModal"
data-dismiss="modal"
aria-label="Close"
>
<span :aria-hidden="!show">×</span>
</button>
</slot>
</template>
<slot />
<template v-slot:modal-footer>
<slot name="footer"></slot>
</template>
</b-modal>
</slide-y-up-transition>
</template>
<script>
import { SlideYUpTransition } from 'vue2-transitions'
export default {
name: 'modal',
components: {
SlideYUpTransition,
},
props: {
show: Boolean,
showClose: {
type: Boolean,
default: true,
},
type: {
type: String,
default: '',
validator(value) {
const acceptedValues = ['', 'notice', 'mini']
return acceptedValues.indexOf(value) !== -1
},
description: 'Modal type (notice|mini|"") ',
},
modalClasses: {
type: [Object, String],
description: 'Modal dialog css classes',
},
size: {
type: String,
description: 'Modal size',
validator(value) {
const acceptedValues = ['', 'sm', 'lg']
return acceptedValues.indexOf(value) !== -1
},
},
modalContentClasses: {
type: [Object, String],
description: 'Modal dialog content css classes',
},
gradient: {
type: String,
description: 'Modal gradient type (danger, primary etc)',
},
headerClasses: {
type: [Object, String],
description: 'Modal Header css classes',
},
bodyClasses: {
type: [Object, String],
description: 'Modal Body css classes',
},
footerClasses: {
type: [Object, String],
description: 'Modal Footer css classes',
},
animationDuration: {
type: Number,
default: 500,
description: 'Modal transition duration',
},
},
methods: {
closeModal() {
this.$emit('update:show', false)
this.$emit('close')
},
},
watch: {
show(val) {
if (val) {
this.$refs['app-modal'].show()
} else {
this.$refs['app-modal'].hide()
}
},
},
}
</script>
<style>
.modal-backdrop {
background-color: rgba(0, 0, 0, 0.6) !important;
}
</style>

View File

@ -1,80 +0,0 @@
<template>
<!--Notice modal-->
<modal
:show.sync="$store.state.modals"
modal-classes="modal-danger"
modal-content-classes="bg-gradient-danger"
>
<h6 slot="header" class="modal-title">Your attention is required</h6>
<div class="py-3 text-center">
<form ref="form" @submit.stop.prevent="handleSubmit">
<b-form-group
label="Name"
label-for="name-input"
invalid-feedback="Name is required"
:state="nameState"
>
<b-form-input id="name-input" v-model="name" :state="nameState" required></b-form-input>
</b-form-group>
</form>
</div>
<template slot="footer">
<base-button type="white">Ok</base-button>
<base-button type="link" class="ml-auto" @click="$store.state.modals = false">
abbrechen
</base-button>
</template>
</modal>
</template>
<script>
export default {
name: 'modal',
data() {
return {
name: '',
nameState: null,
submittedNames: [],
}
},
/* Modal */
checkFormValidity() {
const valid = this.$refs.form.checkValidity()
this.nameState = valid
return valid
},
resetModal() {
this.name = ''
this.nameState = null
},
handleOk(bvModalEvt) {
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
this.handleSubmit()
},
handleSubmit() {
// Exit when the form isn't valid
if (!this.checkFormValidity()) {
return
}
// Push the name to submitted names
this.submittedNames.push(this.name)
this.$store.state.modals = false
this.$store.commit('loginAsAdmin')
this.$router.push('/AdminOverview')
// Hide the modal manually
this.$nextTick(() => {
this.$bvModal.hide('modal-prevent-closing')
})
},
}
</script>
<style>
.modal-backdrop {
background-color: rgba(0, 0, 0, 0.6) !important;
}
</style>

View File

@ -1,120 +0,0 @@
<template>
<b-navbar toggleable :class="classes">
<div :class="containerClasses">
<slot name="brand"></slot>
<slot name="toggle-button">
<button
class="navbar-toggler collapsed"
v-if="hasMenu"
type="button"
@click="toggleMenu"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-bar navbar-kebab"></span>
<span class="navbar-toggler-bar navbar-kebab"></span>
<span class="navbar-toggler-bar navbar-kebab"></span>
</button>
</slot>
<b-navbar-toggle target="nav-text-collapse" @click.stop="toggleMenu"></b-navbar-toggle>
<b-collapse
is-nav
id="nav-text-collapse"
class="navbar-custom-collapse collapse"
:class="menuClasses"
:visible="show"
v-click-outside="closeMenu"
>
<slot :close-menu="closeMenu"></slot>
</b-collapse>
</div>
</b-navbar>
</template>
<script>
export default {
name: 'base-nav',
props: {
show: {
type: Boolean,
default: false,
description:
'Whether navbar menu is shown (valid for viewports < specified by `expand` prop)',
},
transparent: {
type: Boolean,
default: false,
description: 'Whether navbar is transparent',
},
expand: {
type: String,
default: 'lg',
description: 'Breakpoint where nav should expand',
},
menuClasses: {
type: [String, Object, Array],
default: '',
description: 'Navbar menu (items) classes. Can be used to align menu items to the right/left',
},
containerClasses: {
type: [String, Object, Array],
default: 'container',
description:
'Container classes. Can be used to control container classes (contains both navbar brand and menu items)',
},
type: {
type: String,
default: '',
validator(value) {
return [
'',
'dark',
'success',
'danger',
'warning',
'white',
'primary',
'light',
'info',
'vue',
].includes(value)
},
description: 'Navbar color type',
},
},
model: {
prop: 'show',
event: 'change',
},
computed: {
classes() {
const color = `bg-${this.type}`
const classes = [
{ 'navbar-transparent': this.transparent },
{ [`navbar-expand-${this.expand}`]: this.expand },
]
if (this.position) {
classes.push(`navbar-${this.position}`)
}
if (!this.transparent) {
classes.push(color)
}
return classes
},
hasMenu() {
return this.$slots.default
},
},
methods: {
toggleMenu() {
this.$emit('change', !this.show)
},
closeMenu() {
this.$emit('change', false)
},
},
}
</script>
<style></style>

View File

@ -1,194 +0,0 @@
<template>
<div
@click="tryClose"
data-notify="container"
class="alert alert-notify alert-dismissible"
:class="[{ 'alert-with-icon': icon }, verticalAlign, horizontalAlign, alertType]"
role="alert"
:style="customPosition"
data-notify-position="top-center"
>
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon" data-notify="icon">
<i :class="icon"></i>
</span>
</slot>
</template>
<span class="alert-text">
<span v-if="title" class="title">
<b>
{{ title }}
<br />
</b>
</span>
<span v-if="message" v-html="message"></span>
<content-render v-if="!message && component" :component="component"></content-render>
</span>
<slot name="dismiss-icon">
<button type="button" class="close" data-dismiss="alert" aria-label="Close" @click="close">
<span aria-hidden="true">×</span>
</button>
</slot>
</div>
</template>
<script>
export default {
name: 'notification',
components: {
contentRender: {
props: ['component'],
render: function (createElement) {
return createElement(this.component)
},
},
},
props: {
message: String,
title: {
type: String,
description: 'Notification title',
},
icon: {
type: String,
description: 'Notification icon',
},
verticalAlign: {
type: String,
default: 'top',
validator: (value) => {
const acceptedValues = ['top', 'bottom']
return acceptedValues.indexOf(value) !== -1
},
description: 'Vertical alignment of notification (top|bottom)',
},
horizontalAlign: {
type: String,
default: 'right',
validator: (value) => {
const acceptedValues = ['left', 'center', 'right']
return acceptedValues.indexOf(value) !== -1
},
description: 'Horizontal alignment of notification (left|center|right)',
},
type: {
type: String,
default: 'info',
validator: (value) => {
const acceptedValues = ['default', 'info', 'primary', 'danger', 'warning', 'success']
return acceptedValues.indexOf(value) !== -1
},
description:
'Notification type of notification (default|info|primary|danger|warning|success)',
},
timeout: {
type: Number,
default: 5000,
validator: (value) => {
return value >= 0
},
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)',
},
timestamp: {
type: Date,
default: () => new Date(),
description:
'Notification timestamp (used internally to handle notification removal correctly)',
},
component: {
type: [Object, Function],
description: 'Custom content component. Cane be a `.vue` component or render function',
},
showClose: {
type: Boolean,
default: true,
description: 'Whether to show close button',
},
closeOnClick: {
type: Boolean,
default: true,
description: "Whether to close notification when clicking it' body",
},
clickHandler: {
type: Function,
description: 'Custom notification click handler',
},
},
data() {
return {
elmHeight: 0,
}
},
computed: {
hasIcon() {
return this.icon && this.icon.length > 0
},
alertType() {
return `alert-${this.type}`
},
customPosition() {
const initialMargin = 20
const alertHeight = this.elmHeight + 10
let sameAlertsCount = this.$notifications.state.filter((alert) => {
return (
alert.horizontalAlign === this.horizontalAlign &&
alert.verticalAlign === this.verticalAlign &&
alert.timestamp <= this.timestamp
)
}).length
if (this.$notifications.settings.overlap) {
sameAlertsCount = 1
}
const pixels = (sameAlertsCount - 1) * alertHeight + initialMargin
const styles = {}
if (this.verticalAlign === 'top') {
styles.top = `${pixels}px`
} else {
styles.bottom = `${pixels}px`
}
return styles
},
},
methods: {
close() {
this.$emit('close', this.timestamp)
},
tryClose(evt) {
if (this.clickHandler) {
this.clickHandler(evt, this)
}
if (this.closeOnClick) {
this.close()
}
},
},
mounted() {
this.elmHeight = this.$el.clientHeight
if (this.timeout) {
setTimeout(this.close, this.timeout)
}
},
}
</script>
<style lang="scss">
.notifications .alert {
position: fixed;
z-index: 10000;
&[data-notify='container'] {
max-width: 500px;
}
&.center {
margin: 0 auto;
}
&.left {
left: 20px;
}
&.right {
right: 20px;
}
}
</style>

View File

@ -1,52 +0,0 @@
<template>
<div class="notifications">
<slide-y-up-transition :duration="transitionDuration" group mode="out-in">
<notification
v-for="notification in notifications"
v-bind="notification"
:clickHandler="notification.onClick"
:key="notification.timestamp.getTime()"
@close="removeNotification"
></notification>
</slide-y-up-transition>
</div>
</template>
<script>
import Notification from './Notification.vue'
import { SlideYUpTransition } from 'vue2-transitions'
export default {
components: {
SlideYUpTransition,
Notification,
},
props: {
transitionDuration: {
type: Number,
default: 200,
},
overlap: {
type: Boolean,
default: false,
},
},
data() {
return {
notifications: this.$notifications.state,
}
},
methods: {
removeNotification(timestamp) {
this.$notifications.removeNotification(timestamp)
},
},
created() {
this.$notifications.settings.overlap = this.overlap
},
watch: {
overlap: function (newVal) {
this.$notifications.settings.overlap = newVal
},
},
}
</script>

View File

@ -1,66 +0,0 @@
import Notifications from './Notifications.vue'
const NotificationStore = {
state: [], // here the notifications will be added
settings: {
overlap: false,
verticalAlign: 'top',
horizontalAlign: 'right',
type: 'info',
timeout: 5000,
closeOnClick: true,
showClose: true,
},
setOptions(options) {
this.settings = Object.assign(this.settings, options)
},
removeNotification(timestamp) {
const indexToDelete = this.state.findIndex((n) => n.timestamp === timestamp)
if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1)
}
},
addNotification(notification) {
if (typeof notification === 'string' || notification instanceof String) {
notification = { message: notification }
}
notification.timestamp = new Date()
notification.timestamp.setMilliseconds(
notification.timestamp.getMilliseconds() + this.state.length,
)
notification = Object.assign({}, this.settings, notification)
this.state.push(notification)
},
notify(notification) {
if (Array.isArray(notification)) {
notification.forEach((notificationInstance) => {
this.addNotification(notificationInstance)
})
} else {
this.addNotification(notification)
}
},
}
const NotificationsPlugin = {
install(Vue, options) {
const app = new Vue({
data: {
notificationStore: NotificationStore,
},
methods: {
notify(notification) {
this.notificationStore.notify(notification)
},
},
})
Vue.prototype.$notify = app.notify
Vue.prototype.$notifications = app.notificationStore
Vue.component('Notifications', Notifications)
if (options) {
NotificationStore.setOptions(options)
}
},
}
export default NotificationsPlugin

View File

@ -0,0 +1,53 @@
import { mount } from '@vue/test-utils'
import PaginationButtons from './PaginationButtons'
const localVue = global.localVue
describe('PaginationButtons', () => {
let wrapper
const Wrapper = () => {
return mount(PaginationButtons, { localVue })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.pagination-buttons').exists()).toBeTruthy()
})
it('has previous page button disabled by default', () => {
expect(wrapper.find('button.previous-page').attributes('disabled')).toBe('disabled')
})
it('has bext page button disabled by default', () => {
expect(wrapper.find('button.next-page').attributes('disabled')).toBe('disabled')
})
it('shows the text "1 / 1" by default"', () => {
expect(wrapper.find('p.text-center').text()).toBe('1 / 1')
})
describe('with active buttons', () => {
beforeEach(async () => {
await wrapper.setProps({
hasNext: true,
hasPrevious: true,
})
})
it('emits show-previous when previous page button is clicked', () => {
wrapper.find('button.previous-page').trigger('click')
expect(wrapper.emitted('show-previous')).toBeTruthy()
})
it('emits show-next when next page button is clicked', () => {
wrapper.find('button.next-page').trigger('click')
expect(wrapper.emitted('show-next')).toBeTruthy()
})
})
})
})

View File

@ -0,0 +1,30 @@
<template>
<div class="pagination-buttons">
<b-row class="m-4">
<b-col class="text-right">
<b-button class="previous-page" :disabled="!hasPrevious" @click="$emit('show-previous')">
<b-icon icon="chevron-left" variant="primary"></b-icon>
</b-button>
</b-col>
<b-col cols="2">
<p class="text-center pt-2">{{ currentPage }} / {{ totalPages }}</p>
</b-col>
<b-col>
<b-button class="next-page" :disabled="!hasNext" @click="$emit('show-next')">
<b-icon icon="chevron-right" variant="primary"></b-icon>
</b-button>
</b-col>
</b-row>
</div>
</template>
<script>
export default {
name: 'PaginationButtons',
props: {
hasNext: { type: Boolean, default: false },
hasPrevious: { type: Boolean, default: false },
totalPages: { type: Number, default: 1 },
currentPage: { type: Number, default: 1 },
},
}
</script>

View File

@ -1,81 +0,0 @@
<template>
<vue-bootstrap-typeahead v-model="query" :data="users" @change="getUser" />
</template>
<script>
import VueBootstrapTypeahead from 'vue-bootstrap-typeahead'
// Global registration
// Vue.component('vue-bootstrap-typeahead', VueBootstrapTypeahead)
// OR
// Local registration
export default {
name: 'SearchUser',
components: {
VueBootstrapTypeahead,
},
data() {
return {
user: '',
users: [
'Bob',
'Alice',
'Bernd',
'Dario',
'Alex',
'Pauls',
'Ulf',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illnois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
],
}
},
methods: {
getUser() {
alert(this.data.user)
},
},
}
</script>

View File

@ -41,6 +41,19 @@ describe('SideBar', () => {
expect(wrapper.find('#sidenav-main').exists()).toBeTruthy()
})
describe('navbar button', () => {
it('has a navbar button', () => {
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
})
it('calls showSidebar when clicked', async () => {
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
wrapper.find('button.navbar-toggler').trigger('click')
await wrapper.vm.$nextTick()
expect(spy).toHaveBeenCalledWith(true)
})
})
describe('balance', () => {
it('shows em-dash as balance while loading', () => {
expect(wrapper.find('div.row.text-center').text()).toBe('— GDD')
@ -55,19 +68,6 @@ describe('SideBar', () => {
})
})
describe('navbar button', () => {
it('has a navbar button', () => {
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
})
it('calls showSidebar when clicked', async () => {
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
wrapper.find('button.navbar-toggler').trigger('click')
await wrapper.vm.$nextTick()
expect(spy).toHaveBeenCalledWith(true)
})
})
describe('close siedbar', () => {
it('calls closeSidebar when clicked', async () => {
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')

View File

@ -14,13 +14,11 @@
</b-row>
<slot name="mobile-right">
<ul class="nav align-items-center d-md-none">
<a slot="title-container" class="nav-link" role="button">
<div class="media align-items-center">
<span class="avatar avatar-sm">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</span>
</div>
</a>
<div class="media align-items-center">
<span class="avatar avatar-sm">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</span>
</div>
</ul>
</slot>
<slot></slot>
@ -71,15 +69,15 @@
</template>
<script>
import NavbarToggleButton from '@/components/NavbarToggleButton'
import VueQrcode from 'vue-qrcode'
import LanguageSwitch from '@/components/LanguageSwitch.vue'
import VueQrcode from 'vue-qrcode'
export default {
name: 'sidebar',
components: {
NavbarToggleButton,
VueQrcode,
LanguageSwitch,
VueQrcode,
},
props: {
logo: {

View File

@ -10,12 +10,10 @@ const SidebarStore = {
},
toggleMinimize() {
document.body.classList.toggle('sidebar-mini')
// we simulate the window Resize so the charts will get updated in realtime.
const simulateWindowResize = setInterval(() => {
window.dispatchEvent(new Event('resize'))
}, 180)
// we stop the simulation of Window Resize after the animations are completed
setTimeout(() => {
clearInterval(simulateWindowResize)
}, 1000)

View File

@ -1,33 +0,0 @@
<template>
<div
class="tab-pane"
v-show="active"
:id="id || title"
:class="{ active: active }"
:aria-expanded="active"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'tab-pane',
props: ['title', 'id'],
inject: ['addTab', 'removeTab'],
data() {
return {
active: false,
}
},
mounted() {
this.addTab(this)
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
}
this.removeTab(this)
},
}
</script>
<style></style>

View File

@ -1,155 +0,0 @@
<template>
<div>
<div
:class="[
{ 'col-md-4': vertical && !tabNavWrapperClasses },
{ 'col-12': centered && !tabNavWrapperClasses },
tabNavWrapperClasses,
]"
>
<b-nav
class="nav-pills"
role="tablist"
:class="[
`nav-pills-${type}`,
{ 'flex-column': vertical },
{ 'justify-content-center': centered },
tabNavClasses,
]"
>
<b-nav-item
v-for="tab in tabs"
class="active"
data-toggle="tab"
role="tablist"
:active="tab.active"
:key="tab.id"
:href="`#${tab.id}`"
@click.prevent="activateTab(tab)"
:aria-expanded="tab.active"
>
<tab-item-content :tab="tab"></tab-item-content>
</b-nav-item>
</b-nav>
</div>
<div
class="tab-content"
:class="[
{ 'tab-space': !vertical },
{ 'col-md-8': vertical && !tabContentClasses },
tabContentClasses,
]"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'tabs',
components: {
TabItemContent: {
props: ['tab'],
render(h) {
return h('div', [this.tab.$slots.title || this.tab.title])
},
},
},
provide() {
return {
addTab: this.addTab,
removeTab: this.removeTab,
}
},
props: {
type: {
type: String,
default: 'primary',
validator: (value) => {
const acceptedValues = ['primary', 'info', 'success', 'warning', 'danger']
return acceptedValues.indexOf(value) !== -1
},
},
activeTab: {
type: String,
default: '',
description: 'Active tab name',
},
tabNavWrapperClasses: {
type: [String, Object],
default: '',
description: 'ul wrapper css classes',
},
tabNavClasses: {
type: [String, Object],
default: '',
description: 'ul css classes',
},
tabContentClasses: {
type: [String, Object],
default: '',
description: 'tab content css classes',
},
vertical: Boolean,
centered: Boolean,
value: String,
},
data() {
return {
tabs: [],
}
},
methods: {
findAndActivateTab(title) {
const tabToActivate = this.tabs.find((t) => t.title === title)
if (tabToActivate) {
this.activateTab(tabToActivate)
}
},
activateTab(tab) {
if (this.handleClick) {
this.handleClick(tab)
}
this.deactivateTabs()
tab.active = true
},
deactivateTabs() {
this.tabs.forEach((tab) => {
tab.active = false
})
},
addTab(tab) {
const index = this.$slots.default.indexOf(tab.$vnode)
if (!this.activeTab && index === 0) {
tab.active = true
}
if (this.activeTab === tab.name) {
tab.active = true
}
this.tabs.splice(index, 0, tab)
},
removeTab(tab) {
const tabs = this.tabs
const index = tabs.indexOf(tab)
if (index > -1) {
tabs.splice(index, 1)
}
},
},
mounted() {
this.$nextTick(() => {
if (this.value) {
this.findAndActivateTab(this.value)
}
})
},
watch: {
value(newVal) {
this.findAndActivateTab(newVal)
},
},
}
</script>
<style scoped></style>

View File

@ -1,50 +1,8 @@
import BaseCheckbox from './Inputs/BaseCheckbox.vue'
import BaseAlert from './BaseAlert.vue'
import BaseRadio from './Inputs/BaseRadio.vue'
import BaseInput from './Inputs/BaseInput.vue'
import Badge from './Badge'
import BaseProgress from './BaseProgress.vue'
import BaseButton from './BaseButton.vue'
import BaseDropdown from './BaseDropdown.vue'
import BaseTable from './BaseTable.vue'
import Card from './Cards/Card.vue'
import StatsCard from './Cards/StatsCard.vue'
import BaseNav from './Navbar/BaseNav'
import NavbarToggleButton from './Navbar/NavbarToggleButton'
import TabPane from './Tabs/Tab.vue'
import Tabs from './Tabs/Tabs.vue'
import Collapse from './Collapse/Collapse.vue'
import CollapseItem from './Collapse/CollapseItem.vue'
import Modal from './Modal.vue'
import BaseSlider from './BaseSlider.vue'
import BasePagination from './BasePagination.vue'
import SidebarPlugin from './SidebarPlugin'
export {
BaseCheckbox,
Badge,
BaseAlert,
BaseProgress,
BasePagination,
BaseRadio,
BaseInput,
Card,
StatsCard,
BaseTable,
BaseDropdown,
SidebarPlugin,
BaseNav,
NavbarToggleButton,
TabPane,
Tabs,
Modal,
BaseSlider,
BaseButton,
Collapse,
CollapseItem,
}
export { SidebarPlugin, NavbarToggleButton, Collapse, CollapseItem }

View File

@ -20,17 +20,25 @@
},
"decay": "Vergänglichkeit",
"form": {
"cancel":"Abbrechen",
"cancel": "Abbrechen",
"reset": "Zurücksetzen",
"close":"schließen",
"close": "schließen",
"edit": "bearbeiten",
"save": "speichern",
"receiver":"Empfänger",
"sender":"Absender",
"username":"Username",
"firstname":"Vorname",
"lastname":"Nachname",
"description": "Beschreibung",
"email":"E-Mail",
"email_repeat":"eMail wiederholen",
"password":"Passwort",
"password_repeat":"Passwort wiederholen",
"passwordRepeat":"Passwort wiederholen",
"password_old":"altes Passwort",
"password_new":"neues Passwort",
"password_new_repeat":"neues Passwort wiederholen",
"change": "ändern",
"amount":"Betrag",
"memo":"Nachricht für den Empfänger",
"message":"Nachricht",
@ -48,9 +56,10 @@
"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",
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen"
}
},
"change_username_info": "Das ändern des Usernamens bedarf mehrerer Schritte."
},
"error": {
"error":"Fehler"
@ -96,12 +105,13 @@
"add_work":"neuer Gemeinschaftsbeitrag"
},
"profil": {
"transactions":"transactions",
"activity": {
"chart":"Gemeinschaftsstunden Chart",
"new":"Neue Gemeinschaftsstunden eintragen",
"list":"Meine Gemeinschaftsstunden Liste"
}
},
"user-data": {
"change-success": "Deine Daten wurden gespeichert."
}
},
"navbar" : {
"my-profil":"Mein Profil",

View File

@ -1,7 +1,7 @@
{
"message": "hello gradido !!",
"welcome":"Welcome!",
"community": "Gemeinschaft",
"community": "Community",
"logout":"Logout",
"login":"Login",
"signup": "Sign up",
@ -23,14 +23,22 @@
"cancel":"Cancel",
"reset": "Reset",
"close":"Close",
"edit": "Edit",
"save": "save",
"receiver":"Receiver",
"sender":"Sender",
"username":"Username",
"firstname":"Firstname",
"lastname":"Lastname",
"description": "Description",
"email":"Email",
"email_repeat":"Repeat Email",
"password":"Password",
"password_repeat":"Repeat password",
"passwordRepeat":"Repeat password",
"password_old":"Old password",
"password_new":"New password",
"password_new_repeat":"Repeat new password",
"change": "change",
"amount":"Amount",
"memo":"Message for the recipient",
"message":"Message",
@ -48,9 +56,10 @@
"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",
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits",
"is-not": "You cannot send Gradidos to yourself"
}
},
"change_username_info": "Changing the username requires several steps."
},
"error": {
"error":"Error"
@ -98,10 +107,12 @@
"profil": {
"transactions":"transactions",
"activity": {
"chart":"Community Hours Chart",
"new":"Register new community hours",
"list":"My Community Hours List"
}
},
"user-data": {
"change-success": "Your data has been saved."
}
},
"navbar" : {
"my-profil":"My profile",

Some files were not shown because too many files have changed in this diff Show More