Merge branch 'master' into remove-base-input-example-login-vue

This commit is contained in:
Moriz Wahl 2021-06-14 16:10:43 +02:00
commit a4f2997925
31 changed files with 552 additions and 280 deletions

View File

@ -265,10 +265,10 @@ jobs:
- name: backend login | Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Backend
report_name: Coverage Backend Login
type: lcov
result_path: ./coverage/coverage.info
min_coverage: 6
min_coverage: 13
token: ${{ github.token }}
##############################################################################
@ -335,14 +335,14 @@ jobs:
#########################################################################
# COVERAGE CHECK BACKEND COMMUNITY-SERVER ####################################
##########################################################################
#- name: backend community simplecov | Coverage check
# uses: webcraftmedia/coverage-check-action@master
# with:
# report_name: Coverage Backend
# type: simplecov
# result_path: ./coverage/coverage.info
# min_coverage: 8
# token: ${{ github.token }}
- 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

View File

@ -21,9 +21,10 @@ 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/*
WORKDIR /var/www/cakephp
ENV XDEBUG_MODE=coverage
#RUN composer require --dev rregeer/phpunit-coverage-check
CMD ./vendor/bin/phpunit --coverage-text=./webroot/coverage/coverage.info
#CMD ./vendor/bin/phpunit --coverage-clover=./webroot/coverage/clover.xml
CMD ./vendor/bin/phpunit --coverage-text=./webroot/coverage/coverage.info

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]']);
}

View File

@ -178,24 +178,20 @@ class TransactionsTable extends Table
}
if($prev && $decay == true)
{
if($prev->balance > 0) {
// var_dump($stateUserTransactions);
$current = $su_transaction;
//echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "<br>";
$calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
$balance = floatval($prev->balance - $calculated_decay['balance']);
// skip small decays (smaller than 0,00 GDD)
$balance = floatval($prev->balance - $calculated_decay['balance']);
if(abs($balance) >= 100) {
//echo $interval->format('%R%a days');
//echo "prev balance: " . $prev->balance . ", diff_amount: $diff_amount, summe: " . (-intval($prev->balance - $diff_amount)) . "<br>";
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
'memo' => ''
];
if($balance)
{
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
'memo' => ''
];
}
}
}
@ -207,9 +203,7 @@ class TransactionsTable extends Table
// date
// balance
$transaction = $transaction_indiced[$su_transaction->transaction_id];
/*echo "transaction: <br>";
var_dump($transaction);
echo "<br>";*/
if($su_transaction->transaction_type_id == 1) { // creation
$creation = $transaction->transaction_creation;
$balance = $stateBalancesTable->calculateDecay($creation->amount, $creation->target_date, $transaction->received);
@ -270,14 +264,16 @@ class TransactionsTable extends Table
$duration = $decay_start_date->timeAgoInWords();
}
$balance = floatval($su_transaction->balance - $calculated_decay['balance']);
if($balance > 100) {
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $duration,
'last_decay' => true,
'memo' => ''
];
if($balance)
{
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
'decay_duration' => $duration,
'last_decay' => true,
'memo' => ''
];
}
}
}

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,6 +59,7 @@
"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",

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

@ -98,7 +98,6 @@ const loginAPI = {
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
changePassword: async (sessionId, email, password) => {
const payload = {
session_id: sessionId,
@ -120,12 +119,12 @@ const loginAPI = {
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
changeUsernameProfile: async (sessionId, email, usernameNew) => {
changeUsernameProfile: async (sessionId, email, username) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.usernameNew': usernameNew,
'User.username': username,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)

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

@ -108,7 +108,10 @@
"activity": {
"new":"Neue Gemeinschaftsstunden eintragen",
"list":"Meine Gemeinschaftsstunden Liste"
}
},
"user-data": {
"change-success": "Deine Daten wurden gespeichert."
}
},
"navbar" : {
"my-profil":"Mein Profil",

View File

@ -109,7 +109,10 @@
"activity": {
"new":"Register new community hours",
"list":"My Community Hours List"
}
},
"user-data": {
"change-success": "Your data has been saved."
}
},
"navbar" : {
"my-profil":"My profile",

View File

@ -54,10 +54,14 @@ extend('max', {
extend('gddSendAmount', {
validate(value, { min, max }) {
value = value.replace(',', '.')
return value.match(/^[0-9]+(\.[0-9]{1,2})?$/) && Number(value) >= min && Number(value) <= max
return value.match(/^[0-9]+(\.[0-9]{0,2})?$/) && Number(value) >= min && Number(value) <= max
},
params: ['min', 'max'],
message: (_, values) => i18n.t('form.validation.gddSendAmount', values),
message: (_, values) => {
values.min = i18n.n(values.min)
values.max = i18n.n(values.max)
return i18n.t('form.validation.gddSendAmount', values)
},
})
// eslint-disable-next-line camelcase

View File

@ -4,6 +4,14 @@ import GlobalComponents from './globalComponents'
import GlobalDirectives from './globalDirectives'
import SideBar from '@/components/SidebarPlugin'
import PortalVue from 'portal-vue'
import VueBootstrapToasts from 'vue-bootstrap-toasts'
// vue-bootstrap
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// asset imports
import '@/assets/scss/argon.scss'
import '@/assets/vendor/nucleo/css/nucleo.css'
import * as rules from 'vee-validate/dist/rules'
@ -20,8 +28,6 @@ import VueMoment from 'vue-moment'
import Loading from 'vue-loading-overlay'
import 'vue-loading-overlay/dist/vue-loading.css'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
Object.keys(rules).forEach((rule) => {
extend(rule, {
...rules[rule], // copies rule configuration
@ -34,8 +40,10 @@ export default {
Vue.use(GlobalComponents)
Vue.use(GlobalDirectives)
Vue.use(SideBar)
Vue.use(PortalVue)
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
Vue.use(VueBootstrapToasts)
Vue.use(VueMoment)
Vue.use(VueQrcodeReader)
Vue.use(VueQrcode)

View File

@ -2,6 +2,7 @@
<div>
<side-bar @logout="logout" :balance="balance" :pending="pending">
<template slot="links">
<p></p>
<sidebar-item
:link="{
name: $t('send'),
@ -32,7 +33,6 @@
:transactions="transactions"
:transactionCount="transactionCount"
:pending="pending"
:UserProfileTestData="UserProfileTestData"
@update-balance="updateBalance"
@update-transactions="updateTransactions"
></router-view>
@ -82,11 +82,6 @@ export default {
bookedBalance: 0,
transactionCount: 0,
pending: true,
UserProfileTestData: {
username: 'Mustermax',
desc:
'Max Mustermann seine Beschreibung. Max Mustermann seine Beschreibung. Max Mustermann seine Beschreibung. Max Mustermann seine Beschreibung. ',
},
}
},
methods: {

View File

@ -17,7 +17,6 @@
:email="transactionData.email"
:amount="transactionData.amount"
:memo="transactionData.memo"
:date="transactionData.target_date"
:loading="loading"
@send-transaction="sendTransaction"
@on-reset="onReset"
@ -54,7 +53,6 @@ const EMPTY_TRANSACTION_DATA = {
email: '',
amount: 0,
memo: '',
target_date: '',
}
export default {
@ -96,7 +94,6 @@ export default {
},
methods: {
setTransaction(data) {
data.target_date = new Date(Date.now()).toISOString()
this.transactionData = data
this.currentTransactionStep = 1
},

View File

@ -16,10 +16,6 @@
{{ memo ? memo : '-' }}
<b-badge variant="primary" pill>{{ $t('form.message') }}</b-badge>
</b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center">
{{ $d($moment(date), 'long') }}
<b-badge variant="primary" pill>{{ $t('form.date') }}</b-badge>
</b-list-group-item>
</b-list-group>
</b-col>
</b-row>
@ -42,7 +38,6 @@ export default {
email: { type: String, default: '' },
amount: { type: Number, default: 0 },
memo: { type: String, default: '' },
date: { type: String, default: '' },
loading: { type: Boolean, default: false },
},
}

View File

@ -70,17 +70,15 @@ export default {
},
}
},
created() {},
methods: {
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null
},
async onSubmit() {
const result = await loginAPI.sendEmail(this.form.email)
if (result.success) {
this.$router.push('/thx/password')
} else {
alert(result.result)
}
await loginAPI.sendEmail(this.form.email)
// always give success to avoid email spying
this.$router.push('/thx/password')
},
},
}

View File

@ -139,14 +139,14 @@ export default {
if (result.success) {
this.form.password = ''
/*
this.$store.dispatch('login', {
sessionId: result.result.data.session_id,
email: result.result.data.user.email,
})
*/
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)
this.$toast.error(result.result.message)
}
},
async authenticate() {
@ -157,7 +157,7 @@ export default {
this.sessionId = result.result.data.session_id
this.email = result.result.data.user.email
} else {
alert(result.result.message)
this.$toast.error(result.result.message)
}
},
},

View File

@ -1,28 +0,0 @@
<template>
<div>
<div class="header pb-8 pt-5 pt-lg-8 d-flex align-items-center profile-header">
<b-container fluid></b-container>
</div>
<b-container fluid class="mt--6">
<b-row>
<b-col xl="12" class="order-xl-2 mb-5">
<user-card :balance="balance"></user-card>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import UserCard from './UserProfile/UserCard.vue'
export default {
components: {
UserCard,
},
props: {
balance: { type: Number, default: 0 },
},
}
</script>
<style></style>

View File

@ -6,30 +6,35 @@
style="background-color: #ebebeba3 !important"
>
<b-container>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#userdata_form" v-if="editUserdata" @click="editUserdata = !editUserdata">
<span>{{ $t('form.edit') }}</span>
</a>
<div v-else>
<a href="#userdata_form" @click="onSubmit">
<span class="mr-4 text-success display-4">{{ $t('form.save') }}</span>
</a>
<a href="#userdata_form" @click="editUserdata = !editUserdata">
<span>
<b>{{ $t('form.cancel') }}</b>
</span>
</a>
</div>
<b-row class="text-right">
<b-col class="mb-3">
<b-icon
v-if="showUserData"
@click="showUserData = !showUserData"
class="pointer"
icon="pencil"
>
{{ $t('form.change') }}
</b-icon>
<b-icon
v-else
@click="cancelEdit"
class="pointer"
icon="x-circle"
variant="danger"
></b-icon>
</b-col>
</b-row>
</b-container>
<div>
<b-container>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.firstname') }}</small>
</b-col>
<b-col v-if="editUserdata" class="col-md-9 col-sm-10">
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.firstName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
@ -40,7 +45,7 @@
<b-col class="col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.lastname') }}</small>
</b-col>
<b-col v-if="editUserdata" class="col-md-9 col-sm-10">
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.lastName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
@ -51,14 +56,30 @@
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="editUserdata" class="col-md-9 col-sm-10">
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
{{ form.description }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-col v-else class="col-sm-10 col-md-9">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
</div>
<b-row class="text-right" v-if="!showUserData">
<b-col>
<div class="text-right" ref="submitButton">
<b-button
variant="info"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</b-container>
</b-card>
</div>
@ -68,22 +89,38 @@ import loginAPI from '../../../apis/loginAPI'
export default {
name: 'FormUserData',
props: {
UserProfileTestData: { type: Object },
},
data() {
return {
editUserdata: true,
showUserData: true,
sessionId: this.$store.state.sessionId,
form: {
firstName: this.$store.state.firstName,
lastName: this.$store.state.lastName,
description: this.$store.state.description,
},
loading: true,
}
},
methods: {
async onSubmit() {
cancelEdit() {
this.form.firstName = this.$store.state.firstName
this.form.lastName = this.$store.state.lastName
this.form.description = this.$store.state.description
this.showUserData = true
},
loadSubmitButton() {
if (
this.form.firstName !== this.$store.state.firstName ||
this.form.lastName !== this.$store.state.lastName ||
this.form.description !== this.$store.state.description
) {
this.loading = false
} else {
this.loading = true
}
},
async onSubmit(event) {
event.preventDefault()
const result = await loginAPI.updateUserInfos(
this.$store.state.sessionId,
this.$store.state.email,
@ -97,9 +134,10 @@ export default {
this.$store.commit('firstName', this.form.firstName)
this.$store.commit('lastName', this.form.lastName)
this.$store.commit('description', this.form.description)
this.editUserdata = true
this.showUserData = true
this.$toast.success(this.$t('site.profil.user-data.change-success'))
} else {
alert(result.result.message)
this.$toast.error(result.result.message)
}
},
},

View File

@ -1,62 +1,105 @@
<template>
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-container>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#change_pwd" v-if="edit_pwd" @click="edit_pwd = !edit_pwd">
<span>{{ $t('form.password') }} {{ $t('form.change') }}</span>
</a>
<div v-else>
<a href="#change_pwd" @click="onSubmit">
<span class="mr-4 text-success display-4">{{ $t('form.save') }}</span>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#change_pwd" v-if="edit_pwd" @click="edit_pwd = !edit_pwd">
<span>{{ $t('form.password') }} {{ $t('form.change') }}</span>
<b-icon class="pointer ml-3" icon="pencil" />
</a>
<a href="#change_pwd" @click="edit_pwd = !edit_pwd">
<span>
<b>{{ $t('form.cancel') }}</b>
</span>
</a>
</div>
</b-col>
</b-row>
<div v-if="!edit_pwd">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_old') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input
type="text"
:placeholder="$t('form.password_old')"
v-model="password"
></b-input>
<b-icon
v-else
@click="edit_pwd = !edit_pwd"
class="pointer"
icon="x-circle"
variant="danger"
></b-icon>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input
type="text"
:placeholder="$t('form.password_new')"
v-model="passwordNew"
></b-input>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new_repeat') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input
type="text"
:placeholder="$t('form.password_new_repeat')"
v-model="passwordNew2"
></b-input>
</b-col>
</b-row>
</div>
<div v-if="!edit_pwd">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_old') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input-group>
<b-form-input
class="mb-0"
v-model="password"
name="Password"
:type="passwordVisibleOldPwd ? 'text' : 'password'"
prepend-icon="ni ni-lock-circle-open"
:placeholder="$t('form.password_old')"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordVisibilityOldPwd">
<b-icon :icon="passwordVisibleOldPwd ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input-group>
<b-form-input
class="mb-0"
v-model="passwordNew"
name="Password"
:type="passwordVisibleNewPwd ? 'text' : 'password'"
prepend-icon="ni ni-lock-circle-open"
:placeholder="$t('form.password_new')"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordVisibilityNewPwd">
<b-icon :icon="passwordVisibleNewPwd ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new_repeat') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-input-group>
<b-form-input
class="mb-0"
v-model="passwordNewRepeat"
name="Password"
:type="passwordVisibleNewPwdRepeat ? 'text' : 'password'"
prepend-icon="ni ni-lock-circle-open"
:placeholder="$t('form.password_new_repeat')"
></b-form-input>
<b-input-group-append>
<b-button variant="outline-primary" @click="togglePasswordVisibilityNewPwdRepeat">
<b-icon :icon="passwordVisibleNewPwdRepeat ? 'eye' : 'eye-slash'" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-col>
</b-row>
<b-row class="text-right" v-if="!edit_pwd">
<b-col>
<div class="text-right" ref="submitButton">
<b-button variant="info" @click="onSubmit" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</div>
</b-form>
</b-container>
</b-card>
</template>
@ -71,10 +114,30 @@ export default {
email: null,
password: '',
passwordNew: '',
passwordNew2: '',
passwordNewRepeat: '',
passwordVisibleOldPwd: false,
passwordVisibleNewPwd: false,
passwordVisibleNewPwdRepeat: false,
loading: true,
}
},
methods: {
togglePasswordVisibilityNewPwd() {
this.passwordVisibleNewPwd = !this.passwordVisibleNewPwd
},
togglePasswordVisibilityNewPwdRepeat() {
this.passwordVisibleNewPwdRepeat = !this.passwordVisibleNewPwdRepeat
},
togglePasswordVisibilityOldPwd() {
this.passwordVisibleOldPwd = !this.passwordVisibleOldPwd
},
loadSubmitButton() {
if (this.passwordVisibleNewPwd === this.passwordVisibleNewPwdRepeat) {
this.loading = false
} else {
this.loading = true
}
},
async onSubmit() {
// console.log(this.data)
const result = await loginAPI.changePasswordProfile(

View File

@ -1,42 +1,61 @@
<template>
<b-card id="formusername" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-container>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#formusername" v-if="edit_username" @click="edit_username = !edit_username">
<span>{{ $t('form.username') }} {{ $t('form.change') }}</span>
</a>
<div v-else>
<a href="#formusername" @click="edit_username = !edit_username">
<span>
<b>{{ $t('form.cancel') }}</b>
</span>
</a>
</div>
<b-row class="text-right">
<b-col class="mb-3">
<b-icon
v-if="editUsername"
@click="editUsername = !editUsername"
class="pointer"
icon="pencil"
>
{{ $t('form.change') }}
</b-icon>
<b-icon
v-else
@click="editUsername = !editUsername"
class="pointer"
icon="x-circle"
variant="danger"
></b-icon>
</b-col>
</b-row>
</b-container>
<b-container v-if="editUsername">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.username') }}</small>
</b-col>
<b-col v-if="edit_username" class="col-md-9 col-sm-10">@{{ $store.state.username }}</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
<b-form-input v-model="username" :placeholder="$store.state.username"></b-form-input>
<b-col class="col-md-9 col-sm-10">@{{ username }}</b-col>
</b-row>
</b-container>
<b-container v-else>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.username') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<validation-observer ref="formValidator">
<b-form role="form">
<b-form-input v-model="form.username" :placeholder="username"></b-form-input>
<div>
{{ $t('form.change_username_info') }}
</div>
<div class="text-center" ref="submitButton">
<b-button type="submit" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-form>
</validation-observer>
</b-col>
</b-row>
<b-row class="text-right">
<b-col>
<div class="text-right" ref="submitButton">
<b-button variant="info" @click="onSubmit" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-container>
</b-card>
</template>
@ -47,16 +66,24 @@ export default {
name: 'FormUsername',
data() {
return {
edit_username: true,
username: '',
editUsername: true,
username: this.$store.state.username,
form: {
username: this.$store.state.username,
},
}
},
methods: {
async onSubmit() {
// console.log(this.data)
const result = await loginAPI.changeUsernameProfile(this.username)
const result = await loginAPI.changeUsernameProfile(
this.$store.state.sessionId,
this.$store.state.email,
this.form.username,
)
if (result.success) {
alert('changeUsername success')
this.$store.commit('username', this.form.username)
this.editUserdata = this.editUsername = !this.editUsername
alert('Dein Username wurde geändert.')
} else {
alert(result.result.message)
}

View File

@ -0,0 +1,38 @@
import { shallowMount } from '@vue/test-utils'
import UserProfileOverview from './UserProfileOverview'
const localVue = global.localVue
describe('UserProfileOverview', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
}
const Wrapper = () => {
return shallowMount(UserProfileOverview, { localVue, mocks })
}
describe('shallow Mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('has a user card', () => {
expect(wrapper.findComponent({ name: 'UserCard' }).exists()).toBeTruthy()
})
it('has a user data form', () => {
expect(wrapper.findComponent({ name: 'FormUserData' }).exists()).toBeTruthy()
})
it('has a user name form', () => {
expect(wrapper.findComponent({ name: 'FormUsername' }).exists()).toBeTruthy()
})
it('has a user password form', () => {
expect(wrapper.findComponent({ name: 'FormUserPasswort' }).exists()).toBeTruthy()
})
})
})

View File

@ -1,7 +1,7 @@
<template>
<b-container fluid>
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
<form-user-data :UserProfileTestData="UserProfileTestData" />
<form-user-data />
<form-username />
<form-user-passwort />
</b-container>
@ -22,7 +22,6 @@ export default {
props: {
balance: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
UserProfileTestData: { type: Object },
},
}
</script>

View File

@ -13268,6 +13268,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vue-bootstrap-toasts@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/vue-bootstrap-toasts/-/vue-bootstrap-toasts-1.0.7.tgz#111c38855941e8eb0538e21f41c173e2af67dd53"
integrity sha512-JhurJOAwdNcINQ/QlT701sx0r447YTGpvtxtmZNC9pwDvEqp2I0Pyv15jS4neWwYHkA1gXB42nBsDRcWcj1hlg==
vue-bootstrap-typeahead@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/vue-bootstrap-typeahead/-/vue-bootstrap-typeahead-0.2.6.tgz#8c1999a00bf4bf9fc906bae3a462482482cbc297"

View File

@ -140,6 +140,13 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params)
try {
auto transaction = model::gradido::Transaction::createTransfer(sender_user, target_pubkey, mTargetGroup, amount, mMemo, mBlockchainType);
if (mSession->lastTransactionTheSame(transaction)) {
return stateError("transaction are the same as the last (within 100 seconds)");
}
else {
mSession->setLastTransaction(transaction);
}
if (mAutoSign) {
Poco::JSON::Array errors;
transaction->sign(sender_user);

View File

@ -68,49 +68,49 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
try {
if ( "User.first_name" == name && value.size() > 0) {
if (!value.isString()) {
jsonErrorsArray.add("User.first_name isn't a string");
}
else {
user_model->setFirstName(value.toString());
if ( "User.first_name" == name) {
std::string str_val = validateString(value, "User.first_name", jsonErrorsArray);
if (str_val.size() > 0) {
user_model->setFirstName(str_val);
extractet_values++;
}
}
else if ("User.last_name" == name && value.size() > 0) {
if (!value.isString()) {
jsonErrorsArray.add("User.last_name isn't a string");
}
else {
user_model->setLastName(value.toString());
else if ("User.last_name" == name ) {
std::string str_val = validateString(value, "User.last_name", jsonErrorsArray);
if (str_val.size() > 0) {
user_model->setLastName(str_val);
extractet_values++;
}
}
else if ("User.username" == name && value.size() > 3) {
if (!value.isString()) {
jsonErrorsArray.add("User.username isn't a string");
}
else {
auto new_username = value.toString();
if (user_model->getUsername() != new_username) {
if (user->isUsernameAlreadyUsed(new_username)) {
else if ("User.username" == name) {
std::string str_val = validateString(value, "User.username", jsonErrorsArray);
if (str_val.size() > 0) {
if (user_model->getUsername() != "") {
jsonErrorsArray.add("change username currently not supported!");
}
else if (user_model->getUsername() != str_val) {
if (user->isUsernameAlreadyUsed(str_val)) {
jsonErrorsArray.add("username already used");
}
else {
user_model->setUsername(new_username);
user_model->setUsername(str_val);
extractet_values++;
}
}
}
}
else if ("User.description" == name && value.size() > 3) {
if (!value.isString()) {
jsonErrorsArray.add("description isn't a string");
}
else {
user_model->setDescription(value.toString());
else if ("User.description" == name) {
std::string str_val = validateString(value, "User.description", jsonErrorsArray);
if (str_val.size() > 0) {
user_model->setDescription(str_val);
extractet_values++;
}
}
else if ("User.disabled" == name) {
if (value.isBoolean()) {
@ -130,11 +130,10 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
}
}
else if ("User.language" == name && value.size() > 0) {
if (!value.isString()) {
jsonErrorsArray.add("User.language isn't a string");
}
else {
auto lang = LanguageManager::languageFromString(value.toString());
std::string str_val = validateString(value, "User.language", jsonErrorsArray);
if (str_val.size() > 0) {
auto lang = LanguageManager::languageFromString(str_val);
if (LANG_NULL == lang) {
jsonErrorsArray.add("User.language isn't a valid language");
}
@ -143,12 +142,13 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
extractet_values++;
}
}
}
else if ("User.password" == name && value.size() > 0 && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS) {
if (!value.isString()) {
jsonErrorsArray.add("User.password isn't string");
}
else {
else if ("User.password" == name && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS) {
std::string str_val = validateString(value, "User.password", jsonErrorsArray);
if (str_val.size() > 0) {
NotificationList errors;
if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
jsonErrorsArray.add("User.password isn't valid");
@ -174,7 +174,9 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
}
}
catch (Poco::Exception& ex) {
jsonErrorsArray.add("update parameter invalid");
std::string error_message = "exception by parsing json: ";
error_message += ex.displayText();
jsonErrorsArray.add(error_message);
}
}
if (extractet_values > 0) {
@ -189,4 +191,23 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
result->set("state", "success");
return result;
}
std::string JsonUpdateUserInfos::validateString(Poco::Dynamic::Var value, const char* fieldName, Poco::JSON::Array& errorArray)
{
std::string errorMessage = fieldName;
if (!value.isString()) {
errorMessage += " isn't a string";
errorArray.add(errorMessage);
return "";
}
std::string string_value = value.toString();
if (string_value.size() == 0) {
errorMessage += " is empty";
errorArray.add(errorArray);
return "";
}
return string_value;
}

View File

@ -18,6 +18,8 @@ public:
protected:
std::string validateString(Poco::Dynamic::Var value, const char* fieldName, Poco::JSON::Array& errorArray);
};

View File

@ -919,6 +919,17 @@ bool Session::useOrGeneratePassphrase(const std::string& passphase)
}
*/
bool Session::lastTransactionTheSame(Poco::AutoPtr<model::gradido::Transaction> newTransaction)
{
assert(!newTransaction.isNull());
lock();
if (mLastTransaction.isNull()) {
return false;
}
bool result = mLastTransaction->isTheSameTransaction(newTransaction);
unlock();
return result;
}
bool Session::generateKeys(bool savePrivkey, bool savePassphrase)
{

View File

@ -19,6 +19,8 @@
#include "../controller/EmailVerificationCode.h"
#include "model/gradido/Transaction.h"
#include "Poco/Thread.h"
#include "Poco/Types.h"
#include "Poco/DateTime.h"
@ -163,6 +165,8 @@ public:
// ------------------------ transactions functions ----------------------------
inline void setLastTransaction(Poco::AutoPtr<model::gradido::Transaction> lastTransaction) { lock(); mLastTransaction = lastTransaction; unlock(); }
bool lastTransactionTheSame(Poco::AutoPtr<model::gradido::Transaction> newTransaction);
inline LanguageCatalog* getLanguageCatalog() { return mLanguageCatalog.isNull() ? nullptr : mLanguageCatalog; }
void setLanguage(Languages lang);
@ -188,6 +192,7 @@ protected:
private:
int mHandleId;
Poco::AutoPtr<controller::User> mNewUser;
std::string mPassphrase;
@ -200,6 +205,7 @@ private:
Poco::AutoPtr<controller::EmailVerificationCode> mEmailVerificationCodeObject;
std::shared_mutex mSharedMutex;
Poco::AutoPtr<model::gradido::Transaction> mLastTransaction;
SessionStates mState;

View File

@ -630,6 +630,31 @@ namespace model {
}
bool Transaction::isTheSameTransaction(Poco::AutoPtr<Transaction> other)
{
bool result = false;
auto other_proto = other->getTransactionBody()->getBody();
auto other_created = other_proto->created();
auto own_body_bytes = getTransactionBody()->getBodyBytes();
auto own_body_updated = new proto::gradido::TransactionBody;
own_body_updated->ParseFromString(own_body_bytes);
auto own_created = own_body_updated->mutable_created();
Poco::Int64 timeDiff = other_created.seconds() - own_created->seconds();
*own_created = other_created;
result = own_body_updated->SerializeAsString() == other_proto->SerializeAsString();
delete own_body_updated;
// if they are more than 10 seconds between transaction they consider as not the same
if (abs(timeDiff) > 10) {
return false;
}
return result;
}
/// TASK ////////////////////////
SendTransactionTask::SendTransactionTask(Poco::AutoPtr<Transaction> transaction)

View File

@ -86,6 +86,8 @@ namespace model {
std::string getTransactionAsJson(bool replaceBase64WithHex = false);
inline Poco::AutoPtr<Transaction> getPairedTransaction() { return mPairedTransaction; }
bool isTheSameTransaction(Poco::AutoPtr<Transaction> other);
protected: