Merge branch 'master' into login_coverage

This commit is contained in:
Dario Rekowski on RockPI 2021-06-01 10:11:45 +00:00
commit f1855832bc
43 changed files with 619 additions and 227 deletions

View File

@ -212,7 +212,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 19
min_coverage: 20
token: ${{ github.token }}
##############################################################################

View File

@ -4,8 +4,36 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.0.2](https://github.com/gradido/gradido/compare/1.0.1...1.0.2)
- fix: GDD Amount is Always Displayed with Two Digits [`#468`](https://github.com/gradido/gradido/pull/468)
- fix: Date Time Formats [`#469`](https://github.com/gradido/gradido/pull/469)
- Community ipv6 localhost [`#466`](https://github.com/gradido/gradido/pull/466)
- Login html pages autoparse [`#464`](https://github.com/gradido/gradido/pull/464)
- everything I find and fix crash related in login server this week (kw 20) [`#448`](https://github.com/gradido/gradido/pull/448)
- 437 bug mobile transaction list is not easy to read [`#462`](https://github.com/gradido/gradido/pull/462)
- Require memo in send [`#455`](https://github.com/gradido/gradido/pull/455)
- bug: Thx Page Shows Content Dependent of Route From [`#459`](https://github.com/gradido/gradido/pull/459)
- bug: responsive display error on pads fixed [`#461`](https://github.com/gradido/gradido/pull/461)
- [Bug] German "Dir" is written with capital D in send validation bug [`#460`](https://github.com/gradido/gradido/pull/460)
- feat: Save Locale in Database [`#450`](https://github.com/gradido/gradido/pull/450)
- attention! notice in send area removed [`#458`](https://github.com/gradido/gradido/pull/458)
- Remove Error Message encoding [`#456`](https://github.com/gradido/gradido/pull/456)
- bug fix:mobile menu closes on logout, probs value problem on logout f… [`#454`](https://github.com/gradido/gradido/pull/454)
- bug-login password change show hide inserted [`#453`](https://github.com/gradido/gradido/pull/453)
- fix sorting and use total count [`#451`](https://github.com/gradido/gradido/pull/451)
- add dynamic error email if transaction failed [`#452`](https://github.com/gradido/gradido/pull/452)
- ceil the last decay [`#449`](https://github.com/gradido/gradido/pull/449)
- feat: Raise Coverage of Frontend Unit Tets to 18% [`#447`](https://github.com/gradido/gradido/pull/447)
- parse cpsp files automatic in build [`a4a12bb`](https://github.com/gradido/gradido/commit/a4a12bb62b4000e035ff15e17c5a5f5861653ff6)
- translate german html encoded error messages to english and use gettext for automatic translation [`d339627`](https://github.com/gradido/gradido/commit/d33962736d94c1cb7a12ff775bc2c8d7505d646e)
- 100% coverage of GddTransactionList [`96fb245`](https://github.com/gradido/gradido/commit/96fb245821c69f4d321204a663247d5eee60d92f)
#### [1.0.1](https://github.com/gradido/gradido/compare/1.0.0...1.0.1)
> 14 May 2021
- Login crash fix [`#444`](https://github.com/gradido/gradido/pull/444)
- add try catch blocks to prevent login-server from crashing [`22ff220`](https://github.com/gradido/gradido/commit/22ff22072956f8b843037c75c5b16b7ff5d6a2a3)
- fix [`14a4243`](https://github.com/gradido/gradido/commit/14a424347817b1fe6912a113bffd70e55d688112)

View File

@ -65,8 +65,9 @@ Router::scope('/', function (RouteBuilder $routes) {
if($entry == 'ElopageWebhook' || $entry == 'AppRequests') {
return true;
}
if($request->clientIp() == '127.0.0.1' || $request->clientIp() == 'localhost' || $request->clientIp() == '') {
return true;
$allowedIpLocalhost = ['127.0.0.1', 'localhost', '', '::1'];
if(in_array($clientIp, $allowedIpLocalhost)) {
return true;
}
$allowedCaller = Configure::read('API.allowedCaller');
$ipPerHost = [];

View File

@ -156,6 +156,9 @@ class AppRequestsController extends AppController
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]']);
}
$params['transaction_type'] = 'transfer';
$requestAnswear = $this->JsonRequestClient->sendRequest(json_encode($params), '/createTransaction');

View File

@ -237,6 +237,11 @@ class TransactionSendCoinsController extends AppController
$this->set('timeUsed', microtime(true) - $startTime);
return;
}
if($answear_data['msg'] === 'memo is not set or not in expected range [5;150]') {
$this->Flash->error(__('Ein Verwendungszweck zwischen 5 und 150 Zeichen wird benötig!'));
$this->set('timeUsed', microtime(true) - $startTime);
return;
}
} else if($answear_data['state'] === 'not found' && $answear_data['msg'] === 'receiver not found') {
$this->Flash->error(__('Der Empfänger wurde nicht auf dem Login-Server gefunden, hat er sein Konto schon angelegt?'));
$this->set('timeUsed', microtime(true) - $startTime);

View File

@ -4,7 +4,6 @@ module.exports = {
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk',
},
],

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "1.0.1",
"version": "1.0.2",
"private": true,
"scripts": {
"start": "node run/server.js",
@ -29,7 +29,6 @@
"datamaps": "^0.5.9",
"date-fns": "^1.30.1",
"dropzone": "^5.5.1",
"element-ui": "2.4.11",
"es6-promise": "^4.1.1",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.1.0",

View File

@ -49,40 +49,49 @@ a,
.copyright {
color: #5a7b02;
}
gradido-global-color-text {
.font1_2em {
font-size: 1.2em;
}
.font2em {
font-size: 1.5em;
}
.gradido-global-color-text {
color: #3d443b;
}
gradido-global-color-accent {
.gradido-global-color-accent {
color: #047006;
}
gradido-global-color-6e0a9c9e {
.gradido-global-color-6e0a9c9e {
color: #000;
}
gradido-global-color-2d0fb154 {
.gradido-global-color-2d0fb154 {
color: #047006;
}
gradido-global-color-16efe88c {
.gradido-global-color-16efe88c {
color: #7ebc55;
}
gradido-global-color-1939326 {
.gradido-global-color-1939326 {
color: #f6fff6;
}
gradido-global-color-9d79fc1 {
.gradido-global-color-9d79fc1 {
color: #047006;
}
gradido-global-color-6347f4d {
.gradido-global-color-6347f4d {
color: #5a7b02;
}
gradido-global-color-4fbc19a {
.gradido-global-color-4fbc19a {
color: #014034;
}
gradido-global-color-d341874 {
.gradido-global-color-d341874 {
color: #b6d939;
}
gradido-global-color-619d338 {
.gradido-global-color-619d338 {
color: #8ebfb1;
}
gradido-global-color-44819a9 {
.gradido-global-color-44819a9 {
color: #026873;
}
.gradido-global-color-gray {
color: #858383;
}
</style>

View File

@ -1,25 +0,0 @@
<template>
<b-row v-loading="true" id="loading"></b-row>
</template>
<script>
import Vue from 'vue'
import { Loading } from 'element-ui'
Vue.use(Loading.directive)
export default {}
</script>
<style>
#loading {
min-height: 200px;
display: flex;
align-items: center;
}
.el-loading-spinner .path {
stroke: #66615b !important;
}
.el-loading-mask {
background: transparent !important;
}
</style>

View File

@ -10,7 +10,7 @@
<img :src="logo" class="navbar-brand-img" alt="..." />
</div>
<b-row class="text-center">
<b-col>{{ pending ? '—' : $n(balance) }} GDD</b-col>
<b-col>{{ pending ? '—' : $n(balance, 'decimal') }} GDD</b-col>
</b-row>
<slot name="mobile-right">
<ul class="nav align-items-center d-md-none">

View File

@ -20,7 +20,6 @@ import Collapse from './Collapse/Collapse.vue'
import CollapseItem from './Collapse/CollapseItem.vue'
import Modal from './Modal.vue'
import BaseSlider from './BaseSlider.vue'
import LoadingPanel from './LoadingPanel.vue'
import BasePagination from './BasePagination.vue'
@ -48,5 +47,4 @@ export {
BaseButton,
Collapse,
CollapseItem,
LoadingPanel,
}

View File

@ -1,7 +1,8 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
// Load Package Details for some default values
// const pkg = require('../../package')
const pkg = require('../../package')
const environment = {
NODE_ENV: process.env.NODE_ENV,
DEBUG: process.env.NODE_ENV !== 'production' || false,
@ -17,6 +18,7 @@ const server = {
const CONFIG = {
...environment,
...server,
APP_VERSION: pkg.version,
}
export default CONFIG

View File

@ -32,18 +32,51 @@ function loadLocaleMessages() {
}
const numberFormats = {
'en-US': {
currency: {
style: 'currency',
currency: 'GDD',
abbreviate: true,
en: {
decimal: {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
},
'de-DE': {
currency: {
style: 'currency',
currency: 'GDD',
abbreviate: true,
de: {
decimal: {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
},
}
const dateTimeFormats = {
en: {
short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
},
long: {
year: 'numeric',
month: 'short',
day: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: 'numeric',
},
},
de: {
short: {
day: 'numeric',
month: 'numeric',
year: 'numeric',
},
long: {
day: 'numeric',
month: 'short',
year: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: 'numeric',
},
},
}
@ -53,4 +86,5 @@ export default new VueI18n({
fallbackLocale: 'en',
messages: loadLocaleMessages(),
numberFormats,
dateTimeFormats,
})

View File

@ -18,6 +18,7 @@
"de": "Deutsch",
"en": "English"
},
"decay": "Vergänglichkeit",
"form": {
"cancel":"Abbrechen",
"reset": "Zurücksetzen",
@ -84,7 +85,9 @@
},
"thx": {
"title": "Danke!",
"subtitle": "Wir haben dir eine eMail gesendet."
"email": "Wir haben dir eine eMail gesendet.",
"reset": "Dein Passwort wurde geändert.",
"register": "Du bist jetzt regisriert."
},
"overview":{
"account_overview":"Kontoübersicht",

View File

@ -18,6 +18,7 @@
"de": "Deutsch",
"en": "English"
},
"decay": "Decay",
"form": {
"cancel":"Cancel",
"reset": "Reset",
@ -84,7 +85,9 @@
},
"thx": {
"title": "Thank you!",
"subtitle": "We have sent you an email."
"email": "We have sent you an email.",
"reset": "Your password has been changed.",
"register": "You are registred now."
},
"overview":{
"account_overview":"Account overview",

View File

@ -11,10 +11,6 @@ import GlobalDirectives from './globalDirectives'
// Sidebar on the right. Used as a local plugin in DashboardLayout.vue
import SideBar from '@/components/SidebarPlugin'
// element ui language configuration
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
// vue-bootstrap
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
@ -38,7 +34,6 @@ import VueMoment from 'vue-moment'
import Loading from 'vue-loading-overlay'
// import the styles
import 'vue-loading-overlay/dist/vue-loading.css'
locale.use(lang)
Object.keys(rules).forEach((rule) => {
extend(rule, {

View File

@ -13,7 +13,6 @@ import BaseAlert from '@/components/BaseAlert'
import BaseNav from '@/components/Navbar/BaseNav'
import BaseHeader from '@/components/BaseHeader'
import { ValidationProvider, ValidationObserver } from 'vee-validate'
import { Input, Tooltip, Popover } from 'element-ui'
/**
* You can register global components here and use them as a plugin in your main Vue instance
*/
@ -34,11 +33,8 @@ const GlobalComponents = {
Vue.component(Card.name, Card)
Vue.component(Modal.name, Modal)
Vue.component(StatsCard.name, StatsCard)
Vue.component(Input.name, Input)
Vue.component('validation-provider', ValidationProvider)
Vue.component('validation-observer', ValidationObserver)
Vue.use(Tooltip)
Vue.use(Popover)
},
}

View File

@ -16,7 +16,7 @@ const routes = [
},
{
path: '/profile',
component: () => import('../views/Pages/UserProfileCard.vue'),
component: () => import('../views/Pages/UserProfile.vue'),
meta: {
requiresAuth: true,
},
@ -47,8 +47,16 @@ const routes = [
component: () => import('../views/Pages/Login.vue'),
},
{
path: '/thx',
path: '/thx/:comingFrom',
component: () => import('../views/Pages/thx.vue'),
beforeEnter: (to, from, next) => {
const validFrom = ['password', 'reset', 'register']
if (!validFrom.includes(from.path.split('/')[1])) {
next({ path: '/login' })
} else {
next()
}
},
},
{
path: '/password',

View File

@ -28,7 +28,7 @@ describe('AccountOverview', () => {
})
it('has a transactions table', () => {
expect(wrapper.find('gdd-table-stub').exists()).toBeTruthy()
expect(wrapper.find('gdd-transaction-list-stub').exists()).toBeTruthy()
})
})
})

View File

@ -28,7 +28,7 @@
</template>
</gdd-send>
<hr />
<gdd-table
<gdd-transaction-list
v-if="showContext"
:transactions="transactions"
:max="5"
@ -36,15 +36,15 @@
:transactionCount="transactionCount"
@update-transactions="$emit('update-transactions')"
/>
<gdd-table-footer v-if="showContext" :count="transactionCount" />
<gdd-transaction-list-footer v-if="showContext" :count="transactionCount" />
</b-container>
</div>
</template>
<script>
import GddStatus from './AccountOverview/GddStatus.vue'
import GddSend from './AccountOverview/GddSend.vue'
import GddTable from './AccountOverview/GddTable.vue'
import GddTableFooter from './AccountOverview/GddTableFooter.vue'
import GddTransactionList from './AccountOverview/GddTransactionList.vue'
import GddTransactionListFooter from './AccountOverview/GddTransactionListFooter.vue'
import TransactionForm from './AccountOverview/GddSend/TransactionForm.vue'
import TransactionConfirmation from './AccountOverview/GddSend/TransactionConfirmation.vue'
import TransactionResult from './AccountOverview/GddSend/TransactionResult.vue'
@ -62,8 +62,8 @@ export default {
components: {
GddStatus,
GddSend,
GddTable,
GddTableFooter,
GddTransactionList,
GddTransactionListFooter,
TransactionForm,
TransactionConfirmation,
TransactionResult,

View File

@ -16,8 +16,7 @@
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ date }}
{{ $moment(date).format('DD.MM.YYYY - HH:mm:ss') }}
{{ $d($moment(date), 'long') }}
<b-badge variant="primary" pill>{{ $t('form.date') }}</b-badge>
</b-list-group-item>
</b-list-group>

View File

@ -8,9 +8,6 @@ describe('GddSend', () => {
const mocks = {
$t: jest.fn((t) => t),
$moment: jest.fn((m) => ({
format: () => m,
})),
$i18n: {
locale: jest.fn(() => 'en'),
},

View File

@ -84,7 +84,22 @@
style="font-size: xx-large; padding-left: 20px"
></b-form-input>
</b-input-group>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
</validation-provider>
<validation-provider
:rules="{
required: true,
min: 5,
max: 150,
}"
:name="$t('form.memo')"
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
</b-col>
</b-row>
<b-input-group id="input-group-3">
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="chat-right-text" class="display-3"></b-icon>

View File

@ -3,12 +3,12 @@
<b-row>
<b-col>
<b-card style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(balance) }} GDD
{{ pending ? '—' : $n(balance, 'decimal') }} GDD
</b-card>
</b-col>
<b-col>
<b-card class="lg-h2 text-right" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(GdtBalance) }} GDT
{{ pending ? '—' : $n(GdtBalance, 'decimal') }} GDT
</b-card>
</b-col>
</b-row>

View File

@ -0,0 +1,238 @@
import { mount } from '@vue/test-utils'
import GddTransactionList from './GddTransactionList'
const localVue = global.localVue
const errorHandler = jest.fn()
localVue.config.errorHandler = errorHandler
describe('GddTransactionList', () => {
let wrapper
const mocks = {
$n: jest.fn((n) => n),
$t: jest.fn((t) => t),
$d: jest.fn((d) => d),
}
const Wrapper = () => {
return mount(GddTransactionList, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
})
describe('without any properties', () => {
it('renders text saying that there are no transactions', () => {
expect(wrapper.find('div.gdd-transaction-list').text()).toBe('transaction.nullTransactions')
})
})
describe('timestamp property', () => {
it('emits update-transactions when timestamp changes', async () => {
await wrapper.setProps({ timestamp: 0 })
expect(wrapper.emitted('update-transactions')).toBeTruthy()
})
})
describe('with transactions', () => {
beforeEach(async () => {
await wrapper.setProps({
transactions: [
{
balance: '19.93',
date: '2021-05-25T17:38:13+00:00',
memo: 'Alles Gute zum Geburtstag',
name: 'Bob der Baumeister',
transaction_id: 29,
type: 'send',
},
{
balance: '1000',
date: '2021-04-29T15:34:49+00:00',
memo: 'Gut das du da bist!',
name: 'Gradido Akademie',
transaction_id: 3,
type: 'creation',
},
{
balance: '314.98',
date: '2021-04-29T17:26:40+00:00',
memo: 'Für das Fahrrad!',
name: 'Jan Ulrich',
transaction_id: 8,
type: 'receive',
},
{
balance: '1.07',
type: 'decay',
},
],
transactionCount: 12,
})
})
it('renders 4 transactions', () => {
expect(wrapper.findAll('div.gdd-transaction-list-item')).toHaveLength(4)
})
describe('send transactions', () => {
let transaction
beforeEach(() => {
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(0)
})
it('has a bi-arrow-left-circle icon', () => {
expect(transaction.find('svg').classes()).toContain('bi-arrow-left-circle')
})
it('has text-danger color', () => {
expect(transaction.find('svg').classes()).toContain('text-danger')
})
it('shows the amount of transaction', () => {
expect(transaction.findAll('div').at(2).text()).toContain('19.93')
})
it('has a minus operator', () => {
expect(transaction.findAll('div').at(2).text()).toContain('-')
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Bob der Baumeister')
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Tue May 25 2021')
})
})
describe('creation transactions', () => {
let transaction
beforeEach(() => {
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(1)
})
it('has a bi-gift icon', () => {
expect(transaction.find('svg').classes()).toContain('bi-gift')
})
it('has gradido-global-color-accent color', () => {
expect(transaction.find('svg').classes()).toContain('gradido-global-color-accent')
})
it('shows the amount of transaction', () => {
expect(transaction.findAll('div').at(2).text()).toContain('1000')
})
it('has a plus operator', () => {
expect(transaction.findAll('div').at(2).text()).toContain('+')
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Gradido Akademie')
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Thu Apr 29 2021')
})
})
describe('receive transactions', () => {
let transaction
beforeEach(() => {
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(2)
})
it('has a bi-arrow-right-circle icon', () => {
expect(transaction.find('svg').classes()).toContain('bi-arrow-right-circle')
})
it('has gradido-global-color-accent color', () => {
expect(transaction.find('svg').classes()).toContain('gradido-global-color-accent')
})
it('shows the amount of transaction', () => {
expect(transaction.findAll('div').at(2).text()).toContain('314.98')
})
it('has a plus operator', () => {
expect(transaction.findAll('div').at(2).text()).toContain('+')
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Jan Ulrich')
})
it('shows the date of the transaction', () => {
expect(transaction.findAll('div').at(3).text()).toContain('Thu Apr 29 2021')
})
})
describe('decay transactions', () => {
let transaction
beforeEach(() => {
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(3)
})
it('has a bi-droplet-half icon', () => {
expect(transaction.find('svg').classes()).toContain('bi-droplet-half')
})
it('has gradido-global-color-gray color', () => {
expect(transaction.find('svg').classes()).toContain('gradido-global-color-gray')
})
it('shows the amount of transaction', () => {
expect(transaction.findAll('div').at(2).text()).toContain('1.07')
})
it('has a minus operator', () => {
expect(transaction.findAll('div').at(2).text()).toContain('-')
})
it('shows the name of the receiver', () => {
expect(transaction.findAll('div').at(3).text()).toBe('decay')
})
})
describe('max property set to 2', () => {
beforeEach(async () => {
await wrapper.setProps({ max: 2 })
})
it('shows only 2 transactions', () => {
expect(wrapper.findAll('div.gdd-transaction-list-item')).toHaveLength(2)
})
})
})
describe('with invalid transaction type', () => {
beforeEach(async () => {
await wrapper.setProps({
transactions: [
{
balance: '19.93',
date: '2021-05-25T17:38:13+00:00',
memo: 'Alles Gute zum Geburtstag',
name: 'Bob der Baumeister',
transaction_id: 29,
type: 'invalid',
},
],
})
})
it('throws an error', () => {
expect(errorHandler).toHaveBeenCalled()
})
})
})
})

View File

@ -1,52 +1,30 @@
<template>
<div>
<div class="gdd-transaction-list">
<b-list-group>
<b-list-group-item
v-for="item in transactions.slice(0, max)"
:key="item.id"
style="background-color: #ebebeba3 !important"
>
<div class="d-flex w-100 justify-content-between">
<b-icon
v-if="item.type === 'send'"
icon="arrow-left-circle"
class="m-1 text-danger"
font-scale="2"
style="color: red"
></b-icon>
<b-icon
v-else-if="item.type === 'receive'"
icon="arrow-right-circle"
class="m-1"
font-scale="2"
style="color: green"
></b-icon>
<b-icon
v-else-if="item.type === 'creation'"
icon="gift"
class="m-1"
font-scale="2"
style="color: orange"
></b-icon>
<b-icon
v-else
icon="droplet-half"
class="m-1"
font-scale="2"
style="color: orange"
></b-icon>
<h1 class="">
<span v-if="item.type === 'receive' || item.type === 'creation'">+</span>
<span v-else>-</span>
{{ $n(item.balance) }}
<small>GDD</small>
</h1>
<h2 class="text-muted">{{ item.name }}</h2>
<b-button v-b-toggle="'a' + item.transaction_id" variant="secondary">
<b>i</b>
</b-button>
<div class="d-flex gdd-transaction-list-item" v-b-toggle="'a' + item.date + ''">
<div style="width: 8%">
<b-icon :icon="getProperties(item).icon" :class="getProperties(item).class" />
</div>
<div class="font1_2em pr-2 text-right" style="width: 32%">
<span>{{ getProperties(item).operator }}</span>
{{ $n(item.balance, 'decimal') }}
</div>
<div class="font1_2em text-left pl-2" style="width: 55%">
{{ item.name ? item.name : $t('decay') }}
<div v-if="item.date" class="text-sm">{{ $d($moment(item.date), 'long') }}</div>
</div>
<div class="text-right" style="width: 5%">
<b-button class="btn-sm">
<b>i</b>
</b-button>
</div>
</div>
<b-collapse :id="'a' + item.transaction_id" class="mt-2">
<b-collapse :id="'a' + item.date + ''" class="mt-2">
<b-card>
<b-list-group>
<b-list-group-item v-if="item.type === 'send'">
@ -79,16 +57,16 @@
{{ item.memo }}
</b-list-group-item>
</b-list-group>
<b-button v-b-toggle="'collapse-1-inner' + item.transaction_id" variant="secondary">
<b-button v-b-toggle="'collapse-1-inner' + item.date" variant="secondary">
{{ $t('transaction.more') }}
</b-button>
<b-collapse :id="'collapse-1-inner' + item.transaction_id" class="mt-2">
<b-collapse :id="'collapse-1-inner' + item.date" class="mt-2">
<b-card>{{ item }}</b-card>
</b-collapse>
</b-card>
</b-collapse>
</b-list-group-item>
<div v-if="transactions.length === 0" class="mt-lg-4 text-center">
<div v-if="transactions.length === 0" class="mt-4 text-center">
<span>{{ $t('transaction.nullTransactions') }}</span>
</div>
</b-list-group>
@ -96,21 +74,21 @@
</template>
<script>
const iconsByType = {
send: { icon: 'arrow-left-circle', classes: 'text-danger', operator: '-' },
receive: { icon: 'arrow-right-circle', classes: 'gradido-global-color-accent', operator: '+' },
creation: { icon: 'gift', classes: 'gradido-global-color-accent', operator: '+' },
decay: { icon: 'droplet-half', classes: 'gradido-global-color-gray', operator: '-' },
}
export default {
name: 'GddTable',
name: 'gdd-transaction-list',
props: {
transactions: { default: [] },
transactions: { default: () => [] },
max: { type: Number, default: 1000 },
timestamp: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
},
data() {
return {
form: [],
fields: ['balance', 'date', 'memo', 'name', 'transaction_id', 'type', 'details'],
items: [],
}
},
watch: {
timestamp: {
immediate: true,
@ -121,6 +99,19 @@ export default {
updateTransactions() {
this.$emit('update-transactions')
},
getProperties(item) {
const type = iconsByType[item.type]
if (type)
return {
icon: type.icon,
class: type.classes + ' m-mb-1 font2em',
operator: type.operator,
}
this.throwError('no icon to given type')
},
throwError(msg) {
throw new Error(msg)
},
},
}
</script>

View File

@ -0,0 +1,48 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import GddTransactionListFooter from './GddTransactionListFooter'
const localVue = global.localVue
describe('GddTransactionListFooter', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
}
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(GddTransactionListFooter, { localVue, mocks, stubs })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.list-group').exists()).toBeTruthy()
})
it('contains no text', () => {
expect(wrapper.text()).toBe('')
})
})
describe('count property is greater than 5', () => {
beforeEach(async () => {
wrapper.setProps({ count: 6 })
})
it('renders a link to show all', () => {
expect(wrapper.text()).toBe('transaction.show_all')
})
it('links to /transactions', () => {
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/transactions')
})
})
})

View File

@ -13,7 +13,7 @@
<script>
export default {
name: 'GddTableFooter',
name: 'GddTransactionListFooter',
props: {
count: { count: Number },
},

View File

@ -62,7 +62,7 @@ export default {
async onSubmit() {
const result = await loginAPI.sendEmail(this.form.email)
if (result.success) {
this.$router.push({ path: '/thx', params: { id: 'resetmail' } })
this.$router.push('/thx/password')
} else {
alert(result.result)
}

View File

@ -192,7 +192,7 @@ export default {
this.model.firstname = ''
this.model.lastname = ''
this.password = ''
this.$router.push('/thx')
this.$router.push('/thx/register')
} else {
this.showError = true
this.messageError = result.result.message

View File

@ -107,7 +107,13 @@ export default {
const result = await loginAPI.changePassword(this.sessionId, this.email, this.password)
if (result.success) {
this.password = ''
this.$router.push('/thx')
/*
this.$store.dispatch('login', {
sessionId: result.result.data.session_id,
email: result.result.data.user.email,
})
*/
this.$router.push('/thx/reset')
} else {
alert(result.result.message)
}

View File

@ -36,7 +36,7 @@
</div>
<div>
<span class="heading">
{{ $n(balance) }}
{{ $n(balance, 'decimal') }}
</span>
<span class="description">GDD</span>
</div>

View File

@ -1,29 +1,24 @@
<template>
<div>
<div
class="header pb-8 pt-lg-4 d-flex align-items-center profile-header"
class="header pb-sm-1 pb-md-7 d-flex align-items-center profile-header"
style="max-height: 200px"
></div>
<b-container fluid class="mt--6">
<b-row>
<b-col class="order-xl-1">
<gdd-table
:timestamp="timestamp"
:transactionCount="transactionCount"
:transactions="transactions"
@update-transactions="updateTransactions"
/>
</b-col>
</b-row>
</b-container>
<gdd-transaction-list
:timestamp="timestamp"
:transactionCount="transactionCount"
:transactions="transactions"
@update-transactions="updateTransactions"
/>
</div>
</template>
<script>
import GddTable from '../../views/Pages/AccountOverview/GddTable.vue'
import GddTransactionList from './AccountOverview/GddTransactionList.vue'
export default {
components: {
GddTable,
GddTransactionList,
},
props: {
transactions: {

View File

@ -5,12 +5,48 @@
<b-container>
<div class="header-body text-center mb-7">
<p class="h1">{{ $t('site.thx.title') }}</p>
<p class="h4">{{ $t('site.thx.subtitle') }}</p>
<p class="h4">{{ $t(displaySetup.subtitle) }}</p>
<hr />
<b-button to="/login">{{ $t('login') }}</b-button>
<b-button :to="displaySetup.linkTo">{{ $t(displaySetup.button) }}</b-button>
</div>
</b-container>
</div>
<!-- Page content -->
</div>
</template>
<script>
const textFields = {
password: {
subtitle: 'site.thx.email',
button: 'login',
linkTo: '/login',
},
reset: {
subtitle: 'site.thx.reset',
button: 'login',
linkTo: '/login',
},
register: {
subtitle: 'site.thx.register',
button: 'site.login.signin',
linkTo: '/overview',
},
}
export default {
name: 'Thx',
data() {
return {
displaySetup: {},
}
},
methods: {
setDisplaySetup(from) {
this.displaySetup = textFields[this.$route.params.comingFrom]
},
},
created() {
this.setDisplaySetup()
},
}
</script>

View File

@ -1,5 +1,4 @@
import { createLocalVue } from '@vue/test-utils'
import ElementUI from 'element-ui'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import Vuex from 'vuex'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
@ -17,6 +16,8 @@ import VueQrcode from 'vue-qrcode'
import BaseHeader from '@/components/BaseHeader'
import StatsCard from '@/components/Cards/StatsCard.vue'
import VueMoment from 'vue-moment'
import clickOutside from '@/directives/click-ouside.js'
global.localVue = createLocalVue()
@ -28,7 +29,6 @@ Object.keys(rules).forEach((rule) => {
})
})
global.localVue.use(ElementUI)
global.localVue.use(BootstrapVue)
global.localVue.use(Vuex)
global.localVue.use(IconsPlugin)
@ -37,6 +37,7 @@ global.localVue.use(Notifications)
global.localVue.use(SideBar)
global.localVue.use(VueRouter)
global.localVue.use(VueQrcode)
global.localVue.use(VueMoment)
global.localVue.component(BaseInput.name, BaseInput)
global.localVue.component('validation-provider', ValidationProvider)
global.localVue.component('validation-observer', ValidationObserver)

View File

@ -2749,13 +2749,6 @@ async-limiter@~1.0.0:
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
async-validator@~1.8.1:
version "1.8.5"
resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-1.8.5.tgz#dc3e08ec1fd0dddb67e60842f02c0cd1cec6d7f0"
integrity sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==
dependencies:
babel-runtime "6.x"
async@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
@ -2829,7 +2822,7 @@ babel-eslint@^10.0.1, babel-eslint@^10.1.0:
eslint-visitor-keys "^1.0.0"
resolve "^1.12.0"
babel-helper-vue-jsx-merge-props@^2.0.0, babel-helper-vue-jsx-merge-props@^2.0.2:
babel-helper-vue-jsx-merge-props@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==
@ -3059,7 +3052,7 @@ babel-preset-vue@^2.0.2:
babel-plugin-syntax-jsx "^6.18.0"
babel-plugin-transform-vue-jsx "^3.5.0"
babel-runtime@6.x, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-runtime@^6.22.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@ -4916,7 +4909,7 @@ deep-is@^0.1.3, deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
deepmerge@^1.2.0, deepmerge@^1.5.2:
deepmerge@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
@ -5296,18 +5289,6 @@ electron-to-chromium@^1.3.649:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.673.tgz#b4f81c930b388f962b7eba20d0483299aaa40913"
integrity sha512-ms+QR2ckfrrpEAjXweLx6kNCbpAl66DcW//3BZD4BV5KhUgr0RZRce1ON/9J3QyA3JO28nzgb5Xv8DnPr05ILg==
element-ui@2.4.11:
version "2.4.11"
resolved "https://registry.yarnpkg.com/element-ui/-/element-ui-2.4.11.tgz#db6a2d37001b8fe5fff9f176fb58bb3908cfa9c9"
integrity sha512-RtgK0t840NAFTajGMWvylzZRSX1EkZ7V4YgAoBxhv4TtkeMscLuk/IdYOzPdlQq6IN0byx1YVBxCX+u4yYkGvw==
dependencies:
async-validator "~1.8.1"
babel-helper-vue-jsx-merge-props "^2.0.0"
deepmerge "^1.2.0"
normalize-wheel "^1.0.1"
resize-observer-polyfill "^1.5.0"
throttle-debounce "^1.0.1"
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@ -9795,11 +9776,6 @@ normalize-url@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
normalize-wheel@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
integrity sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=
nouislider@^12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/nouislider/-/nouislider-12.1.0.tgz#a4416b4b3357e77e52217f8ecf060eb14a855f59"
@ -12704,11 +12680,6 @@ throat@^5.0.0:
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
throttle-debounce@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz#51853da37be68a155cb6e827b3514a3c422e89cd"
integrity sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==
through2@^2.0.0, through2@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"

View File

@ -139,13 +139,22 @@ FOREACH(proto ${DATAMODEL_HEDERA_PROTOS})
ENDFOREACH(proto)
############################## parse cpsp Files ####################################
IF(WIN32)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
#add_compile_definitions(POCO_NETSSL_WIN)
ENDIF()
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/cpsp GRADIDO_CPSP_PAGE_SRC_PATH)
FILE(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/http_pages GRADIDO_HTTP_PAGES_PATH)
file(MAKE_DIRECTORY ${GRADIDO_HTTP_PAGES_PATH})
FILE(GLOB GRADIDO_HTTP_PAGES_SRC "${GRADIDO_CPSP_PAGE_SRC_PATH}/*.cpsp")
IF(WIN32)
string(REGEX REPLACE "(.*)package/([a-f0-9]*)" "\\1build/\\2/build/bin" POCO_BUILD_BIN "${CONAN_POCO_ROOT}")
find_program(POCO_PAGE_COMPILER cpspc.exe PATHS "${CONAN_POCO_ROOT}")
find_program(POCO_PAGE_COMPILER cpspc.exe PATHS "${POCO_BUILD_BIN}")
ELSE()
find_program(POCO_PAGE_COMPILER cpspc PATHS "${CMAKE_CURRENT_SOURCE_DIR}/build/bin")
ENDIF()
@ -159,15 +168,27 @@ FOREACH(cpsp_file ${GRADIDO_HTTP_PAGES_SRC})
FILE(TO_NATIVE_PATH ${GRADIDO_HTTP_PAGES_PATH}/${cpsp_file_parsed}Page.cpp cpsp_file_parsed_native)
IF(${cpsp_file_native} IS_NEWER_THAN ${cpsp_file_parsed_native})
EXECUTE_PROCESS(
COMMAND
${POCO_PAGE_COMPILER}
--output-dir=${GRADIDO_HTTP_PAGES_PATH}
--header-output-dir=${GRADIDO_HTTP_PAGES_PATH}
--noline
${cpsp_file_native}
RESULT_VARIABLE rv
)
IF(WIN32)
EXECUTE_PROCESS(
COMMAND
${POCO_PAGE_COMPILER}
/output-dir=${GRADIDO_HTTP_PAGES_PATH}
/header-output-dir=${GRADIDO_HTTP_PAGES_PATH}
/noline
${cpsp_file_native}
RESULT_VARIABLE rv
)
ELSE()
EXECUTE_PROCESS(
COMMAND
${POCO_PAGE_COMPILER}
--output-dir=${GRADIDO_HTTP_PAGES_PATH}
--header-output-dir=${GRADIDO_HTTP_PAGES_PATH}
--noline
${cpsp_file_native}
RESULT_VARIABLE rv
)
ENDIF()
# Optional, but that can show the user if something have gone wrong with the proto generation
IF(${rv})
MESSAGE("Generation of HTTP Page return ${rv} for cpsp ${cpsp_file_native}")
@ -244,11 +265,7 @@ if(MSVC)
source_group("Test" FILES ${TEST})
endif()
IF(WIN32)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
#add_compile_definitions(POCO_NETSSL_WIN)
ENDIF()
add_executable(Gradido_LoginServer ${LOCAL_SRCS})

View File

@ -108,6 +108,9 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params)
else {
result = stateError("parameter format unknown");
}
if (mMemo.size() < 5 || mMemo.size() > 150) {
result = stateError("memo is not set or not in expected range [5;150]");
}
if (result) {
mm->releaseMemory(target_pubkey);
return result;

View File

@ -180,31 +180,43 @@ std::vector<Poco::AutoPtr<controller::PendingTask>> PendingTasksManager::getTran
void PendingTasksManager::checkForFinishedTasks(Poco::Timer& timer)
{
static const char* function_name = "PendingTasksManager::checkForFinishedTasks";
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
try {
for (auto map_it = mPendingTasks.begin(); map_it != mPendingTasks.end(); map_it++)
{
auto list = map_it->second;
for (auto list_it = list->begin(); list_it != list->end(); list_it++)
for (auto map_it = mPendingTasks.begin(); map_it != mPendingTasks.end(); map_it++)
{
if ((*list_it)->getModel()->isGradidoTransaction()) {
auto transaction = dynamic_cast<model::gradido::Transaction*>(list_it->get());
auto json = transaction->getModel()->getResultJson();
bool removeIt = false;
if (!json.isNull()) {
auto state = json->get("state");
if (!state.isEmpty() && state.toString() == "success") {
removeIt = true;
auto list = map_it->second;
for (auto list_it = list->begin(); list_it != list->end(); list_it++)
{
if ((*list_it)->getModel()->isGradidoTransaction()) {
auto transaction = dynamic_cast<model::gradido::Transaction*>(list_it->get());
auto json = transaction->getModel()->getResultJson();
bool removeIt = false;
if (!json.isNull()) {
auto state = json->get("state");
if (!state.isEmpty() && state.toString() == "success") {
removeIt = true;
}
}
if (removeIt) {
transaction->deleteFromDB();
list_it = list->erase(list_it);
if (!list->size() || list_it == list->end()) break;
}
}
if (removeIt) {
transaction->deleteFromDB();
list_it = list->erase(list_it);
if (!list->size()) break;
}
}
}
}
catch (Poco::Exception& ex) {
NotificationList errors;
errors.addError(new ParamError(function_name, "poco exception", ex.displayText()));
errors.sendErrorsAsEmail();
} catch(std::exception& ex) {
NotificationList errors;
errors.addError(new ParamError(function_name, "std::exception", ex.what()));
errors.sendErrorsAsEmail();
}
}
Poco::AutoPtr<controller::PendingTask> PendingTasksManager::getPendingTask(int pendingTaskId)

View File

@ -31,7 +31,8 @@ namespace model {
TRANSACTION_VALID_INVALID_AMOUNT,
TRANSACTION_VALID_INVALID_PUBKEY,
TRANSACTION_VALID_INVALID_GROUP_ALIAS,
TRANSACTION_VALID_INVALID_SIGN
TRANSACTION_VALID_INVALID_SIGN,
TRANSACTION_VALID_INVALID_MEMO
};
const char* TransactionValidationToString(TransactionValidation result);

View File

@ -186,6 +186,10 @@ namespace model {
addError(new Error(function_name, "sender and receiver are the same"));
return TRANSACTION_VALID_INVALID_PUBKEY;
}
if (mMemo.size() < 5 || mMemo.size() > 150) {
addError(new Error(function_name, "memo is not set or not in expected range [5;150]"));
return TRANSACTION_VALID_INVALID_MEMO;
}
return TRANSACTION_VALID_OK;
}

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "1.0.1",
"version": "1.0.2",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",