Merge remote-tracking branch 'origin/master' into community_coverage

This commit is contained in:
einhornimmond 2021-06-09 20:46:04 +02:00
commit 8198415bdd
58 changed files with 1325 additions and 622 deletions

View File

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

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($request->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

@ -348,7 +348,7 @@ class AppRequestsController extends AppController
$decay = true;
$transactions = [];
$transactions_from_db = $stateUserTransactionsQuery->toArray();
if(count($transactions_from_db)) {
if($orderDirection == 'DESC') {
$transactions_from_db = array_reverse($transactions_from_db);

View File

@ -391,8 +391,12 @@ class JsonRequestHandlerController extends AppController {
}
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

@ -198,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

@ -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

@ -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
],
],

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

@ -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,27 @@ 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 +109,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, usernameNew) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.usernameNew': usernameNew,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
updateLanguage: async (sessionId, email, language) => {
const payload = {
session_id: sessionId,

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

@ -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",
"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",
@ -49,8 +57,9 @@
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
"validation": {
"double": "Das Feld {field} muss eine Dezimalzahl mit zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen"
}
"is-not": "Du kannst Dir selbst keine Gradidos überweisen"
},
"change_username_info": "Das ändern des Usernamens bedarf mehrerer Schritte."
},
"error": {
"error":"Fehler"
@ -96,7 +105,6 @@
"add_work":"neuer Gemeinschaftsbeitrag"
},
"profil": {
"transactions":"transactions",
"activity": {
"chart":"Gemeinschaftsstunden Chart",
"new":"Neue Gemeinschaftsstunden eintragen",

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",
"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",
@ -50,7 +58,8 @@
"validation": {
"double": "The {field} field must be a decimal with two digits",
"is-not": "You cannot send Gradidos to yourself"
}
},
"change_username_info": "Changing the username requires several steps."
},
"error": {
"error":"Error"

View File

@ -16,25 +16,11 @@ const routes = [
},
{
path: '/profile',
component: () => import('../views/Pages/UserProfile.vue'),
component: () => import('../views/Pages/UserProfileOverview.vue'),
meta: {
requiresAuth: true,
},
},
// {
// path: '/profileedit',
// component: () => import('../views/Pages/UserProfileEdit.vue'),
// meta: {
// requiresAuth: true,
// },
// },
// {
// path: '/activity',
// component: () => import('../views/Pages/UserProfileActivity.vue'),
// meta: {
// requiresAuth: true,
// },
// },
{
path: '/transactions',
component: () => import('../views/Pages/UserProfileTransactionList.vue'),

View File

@ -13,6 +13,18 @@ export const mutations = {
sessionId: (state, sessionId) => {
state.sessionId = sessionId
},
username: (state, username) => {
state.username = username
},
firstName: (state, firstName) => {
state.firstName = firstName
},
lastName: (state, lastName) => {
state.lastName = lastName
},
description: (state, description) => {
state.description = description
},
}
export const actions = {
@ -20,10 +32,18 @@ export const actions = {
commit('sessionId', data.sessionId)
commit('email', data.user.email)
commit('language', data.user.language)
commit('username', data.user.username)
commit('firstName', data.user.first_name)
commit('lastName', data.user.last_name)
commit('description', data.user.description)
},
logout: ({ commit, state }) => {
commit('sessionId', null)
commit('email', null)
commit('username', '')
commit('firstName', '')
commit('lastName', '')
commit('description', '')
sessionStorage.clear()
},
}
@ -39,6 +59,10 @@ export const store = new Vuex.Store({
email: '',
language: null,
modals: false,
firstName: '',
lastName: '',
username: '',
description: '',
},
getters: {},
// Syncronous mutation of the state

View File

@ -40,7 +40,7 @@ describe('Vuex store', () => {
{ commit, state },
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
)
expect(commit).toHaveBeenCalledTimes(3)
expect(commit).toHaveBeenCalledTimes(7)
})
it('commits sessionId', () => {
@ -74,7 +74,7 @@ describe('Vuex store', () => {
it('calls two commits', () => {
logout({ commit, state })
expect(commit).toHaveBeenCalledTimes(2)
expect(commit).toHaveBeenCalledTimes(6)
})
it('commits sessionId', () => {

View File

@ -78,7 +78,7 @@ describe('DashboardLayoutGdd', () => {
})
it('has five items in the navbar', () => {
expect(navbar.findAll('ul > a')).toHaveLength(2)
expect(navbar.findAll('ul > a')).toHaveLength(3)
})
it('has first item "send" in navbar', () => {
@ -103,21 +103,21 @@ describe('DashboardLayoutGdd', () => {
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/transactions')
})
// it('has tree items in the navbar', () => {
// expect(navbar.findAll('ul > li')).toHaveLength(3)
// })
//
// it('has third item "My profile" in navbar', () => {
// expect(navbar.findAll('ul > li').at(2).text()).toEqual('site.navbar.my-profil')
// })
//
// it.skip('has third item "My profile" linked to profile in navbar', async () => {
// navbar.findAll('ul > li > a').at(2).trigger('click')
// await flushPromises()
// await jest.runAllTimers()
// await flushPromises()
// expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/profile')
// })
it('has tree items in the navbar', () => {
expect(navbar.findAll('ul > a')).toHaveLength(3)
})
it('has third item "My profile" in navbar', () => {
expect(navbar.findAll('ul > a').at(2).text()).toEqual('site.navbar.my-profil')
})
it.skip('has third item "My profile" linked to profile in navbar', async () => {
navbar.findAll('ul > a').at(2).trigger('click')
await flushPromises()
await jest.runAllTimers()
await flushPromises()
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/profile')
})
// it('has fourth item "Settigs" in navbar', () => {
// expect(navbar.findAll('ul > li').at(3).text()).toEqual('site.navbar.settings')

View File

@ -1,5 +1,5 @@
<template>
<div class="wrapper">
<div>
<side-bar @logout="logout" :balance="balance" :pending="pending">
<template slot="links">
<sidebar-item
@ -14,18 +14,12 @@
path: '/transactions',
}"
></sidebar-item>
<!--
<b-nav-item href="#!" to="/profile">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.my-profil') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/profileedit">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.settings') }}</b-nav-text>
</b-nav-item>
<b-nav-item href="#!" to="/activity">
<b-nav-text class="p-0 text-lg text-muted">{{ $t('site.navbar.activity') }}</b-nav-text>
</b-nav-item>
-->
<sidebar-item
:link="{
name: $t('site.navbar.my-profil'),
path: '/profile',
}"
></sidebar-item>
</template>
</side-bar>
<div class="main-content">
@ -39,6 +33,7 @@
:transactions="transactions"
:transactionCount="transactionCount"
:pending="pending"
:UserProfileTestData="UserProfileTestData"
@update-balance="updateBalance"
@update-transactions="updateTransactions"
></router-view>
@ -90,6 +85,11 @@ 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: {
@ -106,9 +106,13 @@ export default {
this.$store.dispatch('logout')
this.$router.push('/login')
},
async updateTransactions() {
async updateTransactions(pagination) {
this.pending = true
const result = await communityAPI.transactions(this.$store.state.sessionId)
const result = await communityAPI.transactions(
this.$store.state.sessionId,
pagination.firstPage,
pagination.items,
)
if (result.success) {
this.GdtBalance = Number(result.result.data.gdtSum)
this.transactions = result.result.data.transactions
@ -129,7 +133,7 @@ export default {
this.initScrollbar()
},
created() {
this.updateTransactions()
this.updateTransactions({ firstPage: 1, items: 5 })
},
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<base-nav
container-classes="container-fluid"
class="navbar-top navbar-expand"
class="navbar-expand"
:class="{ 'navbar-dark': type === 'default' }"
>
<!-- Navbar links -->
@ -15,7 +15,7 @@
<span class="pb-2 text-lg font-weight-bold">
{{ $store.state.email }}
</span>
<b-media-body class="ml-2 d-none d-lg-block d-md-block">
<b-media-body class="ml-2">
<span class="avatar">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</span>

View File

@ -1,6 +1,6 @@
<template>
<div>
<b-container fluid class="p-lg-2 mt-lg-1">
<b-container fluid>
<gdd-status
v-if="showContext"
:pending="pending"
@ -31,10 +31,10 @@
<gdd-transaction-list
v-if="showContext"
:transactions="transactions"
:max="5"
:page-size="5"
:timestamp="timestamp"
:transactionCount="transactionCount"
@update-transactions="$emit('update-transactions')"
:transaction-count="transactionCount"
@update-transactions="updateTransactions"
/>
<gdd-transaction-list-footer v-if="showContext" :count="transactionCount" />
</b-container>
@ -116,6 +116,9 @@ export default {
this.transactionData = EMPTY_TRANSACTION_DATA
this.currentTransactionStep = 0
},
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
},
}
</script>

View File

@ -202,16 +202,6 @@ describe('GddTransactionList', () => {
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', () => {
@ -234,5 +224,69 @@ describe('GddTransactionList', () => {
expect(errorHandler).toHaveBeenCalled()
})
})
describe('pagination buttons', () => {
const transactions = Array.from({ length: 42 }, (_, idx) => {
return {
balance: '3.14',
date: '2021-04-29T17:26:40+00:00',
memo: 'Kreiszahl PI',
name: 'Euklid',
transaction_id: idx + 1,
type: 'receive',
}
})
let paginationButtons
beforeEach(async () => {
await wrapper.setProps({
transactions,
transactionCount: 42,
showPagination: true,
})
paginationButtons = wrapper.find('div.pagination-buttons')
})
it('shows the pagination buttons', () => {
expect(paginationButtons.exists()).toBeTruthy()
})
it('has the previous button disabled', () => {
expect(paginationButtons.find('button.previous-page').attributes('disabled')).toBe(
'disabled',
)
})
it('shows the text "1 / 2"', () => {
expect(paginationButtons.find('p.text-center').text()).toBe('1 / 2')
})
it('emits update-transactions when next button is clicked', async () => {
paginationButtons.find('button.next-page').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted('update-transactions')[1]).toEqual([{ firstPage: 2, items: 25 }])
})
it('shows text "2 / 2" when next button is clicked', async () => {
paginationButtons.find('button.next-page').trigger('click')
await wrapper.vm.$nextTick()
expect(paginationButtons.find('p.text-center').text()).toBe('2 / 2')
})
it('has next-button disabled when next button is clicked', async () => {
paginationButtons.find('button.next-page').trigger('click')
await wrapper.vm.$nextTick()
expect(paginationButtons.find('button.next-page').attributes('disabled')).toBe('disabled')
})
it('emits update-transactions when preivous button is clicked after next buton', async () => {
paginationButtons.find('button.next-page').trigger('click')
await wrapper.vm.$nextTick()
paginationButtons.find('button.previous-page').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted('update-transactions')[2]).toEqual([{ firstPage: 1, items: 25 }])
})
})
})
})

View File

@ -2,7 +2,7 @@
<div class="gdd-transaction-list">
<b-list-group>
<b-list-group-item
v-for="item in transactions.slice(0, max)"
v-for="item in transactions"
:key="item.id"
style="background-color: #ebebeba3 !important"
>
@ -66,6 +66,15 @@
</b-card>
</b-collapse>
</b-list-group-item>
<pagination-buttons
v-if="showPagination && transactionCount > pageSize"
:has-next="hasNext"
:has-previous="hasPrevious"
:total-pages="totalPages"
:current-page="currentPage"
@show-next="showNext"
@show-previous="showPrevious"
></pagination-buttons>
<div v-if="transactions.length === 0" class="mt-4 text-center">
<span>{{ $t('transaction.nullTransactions') }}</span>
</div>
@ -74,6 +83,8 @@
</template>
<script>
import PaginationButtons from '../../../components/PaginationButtons'
const iconsByType = {
send: { icon: 'arrow-left-circle', classes: 'text-danger', operator: '-' },
receive: { icon: 'arrow-right-circle', classes: 'gradido-global-color-accent', operator: '+' },
@ -83,11 +94,20 @@ const iconsByType = {
export default {
name: 'gdd-transaction-list',
components: {
PaginationButtons,
},
data() {
return {
currentPage: 1,
}
},
props: {
transactions: { default: () => [] },
max: { type: Number, default: 1000 },
pageSize: { type: Number, default: 25 },
timestamp: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
showPagination: { type: Boolean, default: false },
},
watch: {
timestamp: {
@ -95,9 +115,23 @@ export default {
handler: 'updateTransactions',
},
},
computed: {
hasNext() {
return this.currentPage * this.pageSize < this.transactionCount
},
hasPrevious() {
return this.currentPage > 1
},
totalPages() {
return Math.ceil(this.transactionCount / this.pageSize)
},
},
methods: {
updateTransactions() {
this.$emit('update-transactions')
this.$emit('update-transactions', {
firstPage: this.currentPage,
items: this.pageSize,
})
},
getProperties(item) {
const type = iconsByType[item.type]
@ -112,6 +146,14 @@ export default {
throwError(msg) {
throw new Error(msg)
},
showNext() {
this.currentPage++
this.updateTransactions()
},
showPrevious() {
this.currentPage--
this.updateTransactions()
},
},
}
</script>

View File

@ -1,32 +0,0 @@
<template>
<div>
<!-- Header -->
<div class="header bg-gradient-info py-7 py-lg-3 pt-lg-2">
<b-container>
<div class="header-body text-center mb-7">
<p class="h1">GDD</p>
<p class="h4">Explorer</p>
</div>
</b-container>
<div class="separator separator-bottom separator-skew zindex-100">
<svg
x="0"
y="0"
viewBox="0 0 2560 100"
preserveAspectRatio="none"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<polygon class="fill-default" points="2560 0 2560 100 0 100"></polygon>
</svg>
</div>
</div>
<!-- Page content -->
<div>
<b-card>
<b-card-body>This is some text within a card body.</b-card-body>
</b-card>
</div>
</div>
</template>

View File

@ -1,134 +0,0 @@
<template>
<card style="background-color: #ebebeba3 !important">
<b-form @submit.prevent="updateProfile">
<h6 class="heading-small text-muted mb-4">User information</h6>
<div class="pl-lg-4">
<b-row>
<b-col lg="6">
<base-input
type="text"
label="Username"
placeholder="Username"
v-model="user.username"
></base-input>
</b-col>
<b-col lg="6">
<base-input
type="email"
label="Email address"
placeholder="mike@email.com"
v-model="user.email"
></base-input>
</b-col>
</b-row>
<b-row>
<b-col lg="6">
<base-input
type="text"
label="First Name"
placeholder="First Name"
v-model="user.firstName"
></base-input>
</b-col>
<b-col lg="6">
<base-input
type="text"
label="Last Name"
placeholder="Last Name"
v-model="user.lastName"
></base-input>
</b-col>
</b-row>
</div>
<hr class="my-4" />
<!-- Address -->
<h6 class="heading-small text-muted mb-4">Contact information</h6>
<div class="pl-lg-4">
<b-row>
<b-col md="12">
<base-input
type="text"
label="Address"
placeholder="Home Address"
v-model="user.address"
></base-input>
</b-col>
</b-row>
<b-row>
<b-col lg="4">
<base-input
type="text"
label="City"
placeholder="City"
v-model="user.city"
></base-input>
</b-col>
<b-col lg="4">
<base-input
type="text"
label="Country"
placeholder="Country"
v-model="user.country"
></base-input>
</b-col>
<b-col lg="4">
<base-input
label="Postal Code"
placeholder="ZIP Code"
v-model="user.postalCode"
></base-input>
</b-col>
</b-row>
</div>
<hr class="my-4" />
<!-- Description -->
<h6 class="heading-small text-muted mb-4">About me</h6>
<div class="pl-lg-4">
<b-form-group
label="About Me"
label-class="form-control-label"
class="mb-0"
label-for="about-form-textaria"
>
<!-- <label class="form-control-label">About Me</label> -->
<b-form-textarea
rows="4"
value="A beautiful premium dashboard for BootstrapVue."
id="about-form-textaria"
placeholder="A few words about you ..."
></b-form-textarea>
</b-form-group>
</div>
</b-form>
</card>
</template>
<script>
export default {
data() {
return {
user: {
company: 'Creative Code Inc.',
username: 'michael23',
email: '',
firstName: 'Mike',
lastName: 'Andrew',
address: 'Bld Mihail Kogalniceanu, nr. 8 Bl 1, Sc 1, Ap 09',
city: 'New York',
country: 'USA',
postalCode: '',
aboutMe: `Lamborghini Mercy, Your chick she so thirsty, I'm in that two seat Lambo.`,
},
}
},
methods: {
updateProfile() {
alert('Your data: ' + JSON.stringify(this.user))
},
},
}
</script>
<style></style>

View File

@ -1,72 +0,0 @@
<template>
<div>
<!-- slot for parent component to activate the file changer -->
<div @click="launchFilePicker()">
<slot name="activator"></slot>
</div>
<!-- image input: style is set to hidden and assigned a ref so that it can be triggered -->
<input
type="file"
ref="file"
:name="uploadFieldName"
@change="onFileChange($event.target.name, $event.target.files)"
style="display: none"
/>
<!-- error dialog displays any potential errors -->
<v-dialog v-model="errorDialog" max-width="300">
<v-card>
<v-card-text class="subheading">{{ errorText }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="errorDialog = false" flat>Got it!</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: 'avatar-uploader',
data: () => ({
errorDialog: null,
errorText: '',
uploadFieldName: 'file',
maxSize: 1024,
}),
props: {
// Use "value" here to enable compatibility with v-model
value: Object,
},
methods: {
launchFilePicker() {
this.$refs.file.click()
},
onFileChange(fieldName, file) {
const { maxSize } = this
const imageFile = file[0]
// check if user actually selected a file
if (file.length > 0) {
const size = imageFile.size / maxSize / maxSize
if (!imageFile.type.match('image.*')) {
// check whether the upload is an image
this.errorDialog = true
this.errorText = 'Please choose an image file'
} else if (size > 1) {
// check whether the size is greater than the size limit
this.errorDialog = true
this.errorText = 'Your file is too big! Please select an image under 1MB'
} else {
// Append file into FormData & turn file into image URL
const formData = new FormData()
const imageURL = URL.createObjectURL(imageFile)
formData.append(fieldName, imageFile)
// Emit FormData & image URL to the parent component
this.$emit('input', { formData, imageURL })
}
}
},
},
}
</script>
<style></style>

View File

@ -1,67 +1,41 @@
<template>
<b-card
no-body
class="card-profile"
alt="Image placeholder"
img-top
style="background-color: #ebebeba3 !important"
>
<b-row class="justify-content-center">
<b-col lg="3" class="order-lg-2">
<div class="card-profile-image">
<a href="#">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</a>
<b-card class="bg-transparent">
<div class="w-100 text-center">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
</div>
<b-row>
<b-col>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">
<div>
<span class="heading">
{{ $n(balance) }}
</span>
<span class="description">GDD</span>
</div>
<div>
<span class="heading">{{ transactionCount }}</span>
<span class="description">{{ $t('transactions') }}</span>
</div>
<div>
<span class="heading">--</span>
<span class="description">{{ $t('community') }}</span>
</div>
</div>
</b-col>
</b-row>
<b-card-header class="text-center border-0 pt-8 pt-md-4 pb-0 pb-md-4">
<div class="d-flex justify-content-between">
<br />
</div>
</b-card-header>
<b-card-body class="pt-0">
<b-row>
<b-col>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">
<div>
<span class="heading">--</span>
<span class="description">Transactions</span>
</div>
<div>
<span class="heading">--</span>
<span class="description">Community</span>
</div>
<div>
<span class="heading">
{{ $n(balance, 'decimal') }}
</span>
<span class="description">GDD</span>
</div>
</div>
</b-col>
</b-row>
<div class="text-center">
<h5 class="h3">
{{ this.$store.state.email }}
<span class="font-weight-light"></span>
</h5>
</div>
</b-card-body>
</b-card>
</template>
<script>
import VueQrcode from 'vue-qrcode'
export default {
name: 'profilecard',
name: 'UserCard',
components: {
VueQrcode,
},
props: {
balance: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
},
}
</script>

View File

@ -0,0 +1,108 @@
<template>
<div class="userdata_form">
<b-card
id="userdata_form"
class="bg-transparent"
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-col>
</b-row>
<div>
<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">
{{ form.firstName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-input type="text" v-model="form.firstName"></b-input>
</b-col>
</b-row>
<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.lastname') }}</small>
</b-col>
<b-col v-if="editUserdata" class="col-md-9 col-sm-10">
{{ form.lastName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-input type="text" v-model="form.lastName"></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.description') }}</small>
</b-col>
<b-col v-if="editUserdata" class="col-md-9 col-sm-10">
{{ form.description }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
</div>
</b-container>
</b-card>
</div>
</template>
<script>
import loginAPI from '../../../apis/loginAPI'
export default {
name: 'FormUserData',
props: {
UserProfileTestData: { type: Object },
},
data() {
return {
editUserdata: true,
sessionId: this.$store.state.sessionId,
form: {
firstName: this.$store.state.firstName,
lastName: this.$store.state.lastName,
description: this.$store.state.description,
},
}
},
methods: {
async onSubmit() {
const result = await loginAPI.updateUserInfos(
this.$store.state.sessionId,
this.$store.state.email,
{
firstName: this.form.firstName,
lastName: this.form.lastName,
description: this.form.description,
},
)
if (result.success) {
this.$store.commit('firstName', this.form.firstName)
this.$store.commit('lastName', this.form.lastName)
this.$store.commit('description', this.form.description)
this.editUserdata = true
} else {
alert(result.result.message)
}
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,62 @@
<template>
<b-card id="formusermail" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-container>
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#formusermail" v-if="edit_email" @click="edit_email = !edit_email">
<span>E-Mail {{ $t('form.change') }}</span>
</a>
<div v-else>
<a href="#formusermail" @click="onSubmit">
<span class="mr-4 text-success display-4">{{ $t('form.save') }}</span>
</a>
<a href="#formusermail" @click="edit_email = !edit_email">
<span>
<b>{{ $t('form.cancel') }}</b>
</span>
</a>
</div>
</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>E-Mail</small>
</b-col>
<b-col v-if="edit_email" class="col-md-9 col-sm-10">{{ $store.state.email }}</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-input type="text" v-model="newEmail"></b-input>
</b-col>
</b-row>
</b-container>
</b-card>
</template>
<script>
import loginAPI from '../../../apis/loginAPI'
export default {
name: 'FormUserMail',
data() {
return {
edit_email: true,
newEmail: '',
}
},
methods: {
async onSubmit() {
// console.log(this.data)
const result = await loginAPI.changeEmailProfil(
this.$store.state.sessionId,
this.email,
this.newEmail,
)
if (result.success) {
alert('changePassword success')
} else {
alert(result.result.message)
}
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,95 @@
<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>
</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-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>
</b-container>
</b-card>
</template>
<script>
import loginAPI from '../../../apis/loginAPI'
export default {
name: 'FormUserPasswort',
data() {
return {
edit_pwd: true,
email: null,
password: '',
passwordNew: '',
passwordNew2: '',
}
},
methods: {
async onSubmit() {
// console.log(this.data)
const result = await loginAPI.changePasswordProfile(
this.$store.state.sessionId,
this.email,
this.password,
this.passwordNew,
)
if (result.success) {
alert('changePassword success')
} else {
alert(result.result.message)
}
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,67 @@
<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-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.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>
<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-container>
</b-card>
</template>
<script>
import loginAPI from '../../../apis/loginAPI'
export default {
name: 'FormUsername',
data() {
return {
edit_username: true,
username: '',
}
},
methods: {
async onSubmit() {
// console.log(this.data)
const result = await loginAPI.changeUsernameProfile(this.username)
if (result.success) {
alert('changeUsername success')
} else {
alert(result.result.message)
}
},
},
}
</script>
<style></style>

View File

@ -1,71 +0,0 @@
<template>
<div>
<div class="header pb-7 pt-5"></div>
<b-container fluid class="mt--6">
<div class="display-4 mb-3">{{ $t('site.profil.activity.chart') }}</div>
<b-row>
<b-col>
<div class="chart">
<line-chart :height="350" :chart-data="bigLineChart.chartData"></line-chart>
</div>
</b-col>
</b-row>
<hr />
<div class="display-4 mt-6">{{ $t('site.profil.activity.new') }}</div>
<b-row>
<b-col>
<gdd-add-work-2 />
</b-col>
</b-row>
<hr />
<div class="display-4 mb-3">{{ $t('site.profil.activity.list') }}</div>
<b-row>
<b-col class="mb-5">
{{ $t('community') }}
<gdd-work-table></gdd-work-table>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import GddWorkTable from '../../views/Pages/AccountOverview/GddWorkTable.vue'
import GddAddWork2 from '../../views/Pages/AccountOverview/GddAddWork2.vue'
import * as chartConfigs from '@/components/Charts/config'
import LineChart from '@/components/Charts/LineChart'
export default {
components: {
GddWorkTable,
LineChart,
GddAddWork2,
},
data() {
return {
bigLineChart: {
allData: [
[0, 20, 10, 30, 15, 40, 20, 60, 60],
[0, 20, 5, 25, 10, 30, 35, 60, 40],
[0, 2, 5, 7, 10, 30, 15, 9, 10],
[0, 2, 5, 7, 10, 14, 29, 78, 120],
],
activeIndex: 0,
chartData: {
datasets: [
{
label: 'Gemeinschaftsstunden',
data: [30, 20, 10, 30, 65, 40, 20, 60, 70],
},
],
labels: ['2020 Aug', 'Sep', 'Okt', 'Nov', 'Dez', 'Jan', 'Feb', 'Mär 2021'],
},
extraOptions: chartConfigs.blueChartOptions,
},
}
},
}
</script>
<style></style>

View File

@ -1,23 +0,0 @@
<template>
<div>
<div class="header pb-8 pt-5 pt-lg-8 d-flex align-items-center profile-header"></div>
<b-container fluid class="mt--6">
<b-row>
<b-col xl="12" class="order-xl-1">
<edit-profile-form></edit-profile-form>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import EditProfileForm from './UserProfile/EditProfileForm.vue'
export default {
components: {
EditProfileForm,
},
}
</script>
<style></style>

View File

@ -0,0 +1,29 @@
<template>
<b-container fluid>
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
<form-user-data :UserProfileTestData="UserProfileTestData" />
<form-username />
<form-user-passwort />
</b-container>
</template>
<script>
import UserCard from './UserProfile/UserCard.vue'
import FormUserData from './UserProfile/UserCard_FormUserData.vue'
import FormUsername from './UserProfile/UserCard_FormUsername.vue'
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
export default {
components: {
UserCard,
FormUserData,
FormUsername,
FormUserPasswort,
},
props: {
balance: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 },
UserProfileTestData: { type: Object },
},
}
</script>
<style></style>

View File

@ -1,16 +1,18 @@
<template>
<div>
<div
class="header pb-sm-1 pb-md-7 d-flex align-items-center profile-header"
style="max-height: 200px"
></div>
<gdd-transaction-list
:timestamp="timestamp"
:transactionCount="transactionCount"
:transactions="transactions"
@update-transactions="updateTransactions"
/>
<b-container fluid>
<b-row>
<b-col class="order-xl-1">
<gdd-transaction-list
:timestamp="timestamp"
:transactionCount="transactionCount"
:transactions="transactions"
:show-pagination="true"
@update-transactions="updateTransactions"
/>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
@ -32,8 +34,8 @@ export default {
}
},
methods: {
updateTransactions() {
this.$emit('update-transactions')
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
},
}

View File

@ -170,6 +170,7 @@ FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h")
FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h")
FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h")
FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h")
FILE(GLOB TEST_JSON_INTERFACE "src/cpp/test/JSONInterface/*.cpp" "src/cpp/test/JSONInterface/*.h")
SET(LOCAL_SRCS
${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface} ${COMPILED_PAGES}
@ -179,7 +180,7 @@ SET(LOCAL_SRCS
${PROTO_GRADIDO}
)
SET(LOCAL_TEST_SRC
${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER}
${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER} ${TEST_JSON_INTERFACE}
)
aux_source_directory("src/cpp" LOCAL_SRCS)
@ -204,6 +205,7 @@ if(MSVC)
source_group("Test\\model\\table" FILES ${TEST_MODEL_TABLE})
source_group("Test\\model" FILES ${TEST_MODEL})
source_group("Test\\controller" FILES ${TEST_CONTROLLER})
source_group("Test\\Json-Interface" FILES ${TEST_JSON_INTERFACE})
source_group("Test" FILES ${TEST})
endif()
@ -341,7 +343,7 @@ target_compile_definitions(Gradido_LoginServer_Test PUBLIC "_TEST_BUILD")
target_link_libraries(Gradido_LoginServer_Test ${GRPC_LIBS} )
if(WIN32)
target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} )
target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} libmariadb libprotobuf)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
#TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${GRPC_LIBS} ${PROTOBUF_DEBUG_LIBS})

View File

@ -2,6 +2,7 @@
Poco/1.9.4@pocoproject/stable
libsodium/1.0.18@bincrafters/stable
boost/1.71.0@conan/stable
gtest/1.10.0
[options]
Poco:enable_pagecompiler=True

View File

@ -0,0 +1,92 @@
#include "JsonCheckUsername.h"
#include "Poco/URI.h"
#include "controller/User.h"
#include "lib/DataTypeConverter.h"
Poco::JSON::Object* JsonCheckUsername::handle(Poco::Dynamic::Var params)
{
std::string username;
int group_id = 0;
std::string group_alias;
// if is json object
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
/// Throws a RangeException if the value does not fit
/// into the result variable.
/// Throws a NotImplementedException if conversion is
/// not available for the given type.
/// Throws InvalidAccessException if Var is empty.
auto username_obj = paramJsonObject->get("username");
auto group_id_obj = paramJsonObject->get("group_id");
auto group_alias_obj = paramJsonObject->get("group_alias");
try {
if (!username_obj.isEmpty()) {
username_obj.convert(username);
}
if (!group_id_obj.isEmpty()) {
group_id_obj.convert(group_id);
}
if (!group_alias_obj.isEmpty()) {
group_alias_obj.convert(group_alias);
}
}
catch (Poco::Exception& ex) {
return stateError("Poco Exception", ex.displayText());
}
}
else if (params.isVector()) {
const Poco::URI::QueryParameters queryParams = params.extract<Poco::URI::QueryParameters>();
for (auto it = queryParams.begin(); it != queryParams.end(); it++) {
if (it->first == "username") {
username = it->second;
}
else if (it->first == "group_id") {
DataTypeConverter::strToInt(it->second, group_id);
}
else if (it->first == "group_alias") {
group_alias = it->second;
}
}
}
else {
return stateError("format not implemented", std::string(params.type().name()));
}
if (!group_id && group_alias == "") {
return stateError("no group given");
}
if (!group_id) {
auto groups = controller::Group::load(group_alias);
if (groups.size() > 1) {
return stateError("group is ambiguous");
}
if (!groups.size()) {
return stateError("unknown group");
}
group_id = groups[0]->getModel()->getID();
}
auto group = controller::Group::load(group_id);
if (group.isNull()) {
return stateError("unknown group");
}
auto user = controller::User::create();
user->getModel()->setGroupId(group_id);
if (username == "") {
Poco::JSON::Object* result = new Poco::JSON::Object;
result->set("state", "success");
result->set("group_id", group_id);
return result;
}
if (user->isUsernameAlreadyUsed(username)) {
return stateWarning("username already in use");
}
return stateSuccess();
}

View File

@ -0,0 +1,16 @@
#ifndef __JSON_INTERFACE_JSON_CHECK_USERNAME_
#define __JSON_INTERFACE_JSON_CHECK_USERNAME_
#include "JsonRequestHandler.h"
class JsonCheckUsername : public JsonRequestHandler
{
public:
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
protected:
};
#endif // __JSON_INTERFACE_JSON_CHECK_USERNAME_

View File

@ -135,6 +135,7 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params)
else {
printf("user hasn't valid key pair set\n");
}
Poco::JSON::Array* json_warnings = nullptr;
if (!result) {
try {
auto transaction = model::gradido::Transaction::createTransfer(sender_user, target_pubkey, mTargetGroup, amount, mMemo, mBlockchainType);
@ -149,6 +150,10 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params)
if (errors.size() > 0) {
return stateError("error by signing transaction", errors);
}
if (transaction->warningCount() > 0) {
json_warnings = new Poco::JSON::Array;
json_warnings->add(transaction->getWarningsArray());
}
}
}
catch (Poco::Exception& ex) {
@ -164,6 +169,10 @@ Poco::JSON::Object* JsonCreateTransaction::transfer(Poco::Dynamic::Var params)
return stateError("exception");
}
result = stateSuccess();
if (json_warnings) {
result->set("warnings", json_warnings);
delete json_warnings;
}
}
mm->releaseMemory(target_pubkey);
return result;

View File

@ -7,6 +7,7 @@
#include "JsonAdminEmailVerificationResend.h"
#include "JsonCheckSessionState.h"
#include "JsonCheckUsername.h"
#include "JsonAppLogin.h"
#include "JsonAquireAccessToken.h"
#include "JsonCreateTransaction.h"
@ -74,6 +75,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
else if (url_first_part == "/checkSessionState") {
return new JsonCheckSessionState;
}
else if (url_first_part == "/checkUsername") {
return new JsonCheckUsername;
}
else if (url_first_part == "/createTransaction") {
return new JsonCreateTransaction;
}

View File

@ -58,31 +58,37 @@ Poco::JSON::Object* JsonUnsecureLogin::handle(Poco::Dynamic::Var params)
}
auto user = controller::User::create();
std::string message;
std::string details;
if (email.size()) {
if (!sm->isValid(email, VALIDATE_EMAIL)) {
return stateError("invalid email");
message = "invalid email";
}
if (1 != user->load(email)) {
return stateError("user with email not found", email);
message = "user with email not found";
details = email;
}
}
else if (username.size() > 0) {
if (1 != user->load(username)) {
return stateError("user with username not found", username);
message = "user with username not found";
details = username;
}
email = user->getModel()->getEmail();
}
if (message.size()) {
Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime);
return stateError(message.data(), details);
}
NotificationList pwd_errors;
Poco::JSON::Object* result = new Poco::JSON::Object;
if (!password.size() || !sm->checkPwdValidation(password, &pwd_errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime);
result->set("state", "error");
result->set("msg", pwd_errors.getLastError()->getString(false));
if (pwd_errors.errorCount()) {
result->set("details", pwd_errors.getLastError()->getString(false));
}
result->set("msg", "password incorrect");
return result;
}

View File

@ -108,7 +108,9 @@ namespace controller {
bool User::isUsernameAlreadyUsed(const std::string& username)
{
auto db = getModel();
return db->loadFromDB({ "username", "group_id" }, username, db->getGroupId(), model::table::MYSQL_CONDITION_AND) > 0;
auto results = db->loadMultipleFromDB<model::table::UserTuple>({ "username", "group_id" }, username, db->getGroupId(), model::table::MYSQL_CONDITION_AND);
return results.size() > 0;
}
int User::load(const unsigned char* pubkey_array)

View File

@ -11,6 +11,7 @@
#include "sodium.h"
#include "../SingletonManager/MemoryManager.h"
#include "DataTypeConverter.h"
#include "Warning.h"
JsonRequest::JsonRequest(const std::string& serverHost, int serverPort)
: mServerHost(serverHost), mServerPort(serverPort)
@ -125,11 +126,19 @@ JsonRequestReturn JsonRequest::request(const char* methodName, const Poco::JSON:
return JSON_REQUEST_RETURN_ERROR;
}
else if (stateString == "success") {
auto warnings_obj = object.get("warnings");
if (!warnings_obj.isEmpty()) {
Poco::JSON::Object warnings = *parsedJson.extract<Poco::JSON::Object::Ptr>();
for (auto it = warnings.begin(); it != warnings.end(); it++) {
addWarning(new Warning(it->first, it->second.toString()));
}
}
for (auto it = object.begin(); it != object.end(); it++) {
if (it->first == "state") continue;
std::string index = it->first;
std::string value = it->second.toString();
printf("[JsonRequest] %s: %s\n", index.data(), value.data());
//printf("[JsonRequest] %s: %s\n", index.data(), value.data());
}
}
}
@ -165,95 +174,5 @@ JsonRequestReturn JsonRequest::request(const char* methodName)
return request(methodName, requestJson);
}
#include "Poco/JSON/Stringifier.h"
JsonRequestReturn JsonRequest::requestGRPCRelay(const Poco::Net::NameValueCollection& payload)
{
static const char* functionName = "JsonRequest::requestGRPCRelay";
Poco::JSON::Object requestJson;
for (auto it = payload.begin(); it != payload.end(); it++) {
requestJson.set(it->first, it->second);
}
// send post request via https
// 443 = HTTPS Default
// TODO: adding port into ServerConfig
try {
Profiler phpRequestTime;
Poco::Net::HTTPClientSession httpClientSession(mServerHost, mServerPort);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/hedera_rpc_relay/gRPCProxy.php");
request.setChunkedTransferEncoding(false);
std::ostream& requestStream = httpClientSession.sendRequest(request);
requestJson.stringify(requestStream);
std::stringstream ss;
requestJson.stringify(ss);
auto f = fopen("grpc.txt", "wt");
std::string grpc = ss.str();
fwrite(grpc.data(), grpc.size(), 1, f);
fclose(f);
Poco::Net::HTTPResponse response;
std::istream& request_stream = httpClientSession.receiveResponse(response);
// debugging answer
std::stringstream responseStringStream;
for (std::string line; std::getline(request_stream, line); ) {
responseStringStream << line << std::endl;
}
Poco::Logger& speedLog = Poco::Logger::get("SpeedLog");
speedLog.information("[gRPC relay] php server time: %s", phpRequestTime.string());
// extract parameter from request
Poco::JSON::Parser jsonParser;
Poco::Dynamic::Var parsedJson;
try {
parsedJson = jsonParser.parse(responseStringStream.str());
}
catch (Poco::Exception& ex) {
addError(new ParamError(functionName, "error parsing request answer grpc relay", ex.displayText().data()));
std::string fileName = "response_grpc_";
fileName += ".html";
FILE* f = fopen(fileName.data(), "wt");
std::string responseString = responseStringStream.str();
fwrite(responseString.data(), 1, responseString.size(), f);
fclose(f);
// */
sendErrorsAsEmail(responseStringStream.str());
return JSON_REQUEST_RETURN_PARSE_ERROR;
}
Poco::JSON::Object object = *parsedJson.extract<Poco::JSON::Object::Ptr>();
auto state = object.get("state");
std::string stateString = state.convert<std::string>();
if (stateString == "error") {
addError(new Error(functionName, "php server return error"));
if (!object.isNull("msg")) {
addError(new ParamError(functionName, "msg:", object.get("msg").convert<std::string>().data()));
}
if (!object.isNull("details")) {
addError(new ParamError(functionName, "details:", object.get("details").convert<std::string>().data()));
}
// send copy of errors as email, to have result also in db
sendErrorsAsEmail("", true);
return JSON_REQUEST_RETURN_ERROR;
}
ss.clear();
Poco::JSON::Stringifier::stringify(object, ss);
printf("json request result: %s\n", ss.str().data());
}
catch (Poco::Exception& e) {
addError(new ParamError(functionName, "connect error to php server", e.displayText().data()));
sendErrorsAsEmail();
return JSON_REQUEST_CONNECT_ERROR;
}
return JSON_REQUEST_RETURN_OK;
}

View File

@ -32,7 +32,6 @@ public:
JsonRequestReturn request(const char* methodName, const Poco::Net::NameValueCollection& payload);
JsonRequestReturn request(const char* methodName, const Poco::JSON::Object& payload);
JsonRequestReturn request(const char* methodName);
JsonRequestReturn requestGRPCRelay(const Poco::Net::NameValueCollection& payload);
protected:
int mServerPort;

View File

@ -10,4 +10,10 @@ Notification::Notification(const char* functionName, const std::string& message)
: mFunctionName(functionName), mMessage(message)
{
}
Notification::Notification(const std::string& functionName, const std::string& message)
: mFunctionName(functionName), mMessage(message)
{
}

View File

@ -8,6 +8,7 @@ class Notification
public:
Notification(const char* functionName, const char* message);
Notification(const char* functionName, const std::string& message);
Notification(const std::string& functionName, const std::string& message);
const char* getFunctionName() { return mFunctionName.data(); }
const char* getMessage() { return mMessage.data(); }
@ -16,6 +17,7 @@ public:
virtual bool isError() { return false; }
virtual bool isSuccess() { return false; }
virtual bool isWarning() { return false; }
protected:
std::string mFunctionName;

View File

@ -54,6 +54,11 @@ NotificationList::~NotificationList()
delete mErrorStack.top();
mErrorStack.pop();
}
while (mWarningStack.size() > 0) {
delete mWarningStack.top();
mWarningStack.pop();
}
}
void NotificationList::addError(Notification* error, bool log/* = true */)
@ -61,12 +66,21 @@ void NotificationList::addError(Notification* error, bool log/* = true */)
if (log) {
std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
mLogging.error("%s [ErrorList::addError] %s", dateTimeString, error->getString(false));
mLogging.error("%s [NotificationList::addError] %s", dateTimeString, error->getString(false));
}
mErrorStack.push(error);
}
void NotificationList::addWarning(Warning* warning, bool log/* = true*/)
{
if (log) {
std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
mLogging.warning("%s [NotificationList::addWarning] %s", dateTimeString, warning->getString(false));
}
mWarningStack.push(warning);
}
void NotificationList::addNotification(Notification* notification)
{
mErrorStack.push(notification);
@ -86,6 +100,20 @@ Notification* NotificationList::getLastError()
return error;
}
Warning* NotificationList::getLastWarning()
{
if (mWarningStack.size() == 0) {
return nullptr;
}
Warning* warning = mWarningStack.top();
if (warning) {
mWarningStack.pop();
}
return warning;
}
void NotificationList::clearErrors()
{
while (mErrorStack.size()) {
@ -109,6 +137,17 @@ int NotificationList::getErrors(NotificationList* send)
return iCount;
}
int NotificationList::getWarnings(NotificationList* send)
{
Warning* warning = nullptr;
int iCount = 0;
while (warning = send->getLastWarning()) {
addWarning(warning, false);
iCount++;
}
return iCount;
}
void NotificationList::printErrors()
{
while (mErrorStack.size() > 0) {
@ -134,6 +173,21 @@ std::vector<std::string> NotificationList::getErrorsArray()
return result;
}
std::vector<std::string> NotificationList::getWarningsArray()
{
std::vector<std::string> result;
result.reserve(mWarningStack.size());
while (mWarningStack.size() > 0) {
auto warning = mWarningStack.top();
mWarningStack.pop();
//result->add(error->getString());
result.push_back(warning->getString());
delete warning;
}
return result;
}
std::string NotificationList::getErrorsHtml()
{
std::string res;

View File

@ -11,6 +11,7 @@
#define DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
#include "Error.h"
#include "Warning.h"
#include <stack>
#include "../tasks/CPUTask.h"
@ -28,11 +29,14 @@ public:
// push error, error will be deleted in deconstructor
virtual void addError(Notification* error, bool log = true);
void addNotification(Notification* notification);
virtual void addWarning(Warning* warning, bool log = true);
// return error on top of stack, please delete after using
Notification* getLastError();
Warning* getLastWarning();
inline size_t errorCount() { return mErrorStack.size(); }
inline size_t warningCount() { return mWarningStack.size(); }
// delete all errors
void clearErrors();
@ -41,16 +45,19 @@ public:
return recv->getErrors(send);
}
int getErrors(NotificationList* send);
int getWarnings(NotificationList* send);
void printErrors();
std::string getErrorsHtml();
std::string getErrorsHtmlNewFormat();
std::vector<std::string> getErrorsArray();
std::vector<std::string> getWarningsArray();
void sendErrorsAsEmail(std::string rawHtml = "", bool copy = false);
protected:
std::stack<Notification*> mErrorStack;
std::stack<Warning*> mWarningStack;
// poco logging
Poco::Logger& mLogging;
};

View File

@ -0,0 +1,58 @@
#include "Warning.h"
#include <sstream>
Warning::Warning(const char* functionName, const char* message)
: Notification(functionName, message)
{
}
Warning::Warning(const std::string& functionName, const std::string& message)
: Notification(functionName, message)
{
}
std::string Warning::getString(bool withNewline/* = true*/) const
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage;
if (withNewline) ss << std::endl;
return ss.str();
}
std::string Warning::getHtmlString() const
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage;
return ss.str();
}
ParamWarning::ParamWarning(const char* functionName, const char* message, std::string param)
: Warning(functionName, message), mParam(param)
{
}
ParamWarning::ParamWarning(const char* functionName, const char* message, int param)
: Warning(functionName, message), mParam(std::to_string(param))
{
}
std::string ParamWarning::getString(bool withNewline/* = true*/) const
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage << " " << mParam;
if (withNewline) ss << std::endl;
return ss.str();
}
std::string ParamWarning::getHtmlString() const
{
std::stringstream ss;
ss << mFunctionName << ": " << mMessage << " " << mParam;
return ss.str();
}

View File

@ -0,0 +1,31 @@
#ifndef GRADIDO_LOGIN_SERVER_LIB_WARNING_H
#define GRADIDO_LOGIN_SERVER_LIB_WARNING_H
#include "Notification.h"
class Warning : public Notification
{
public:
Warning(const char* functionName, const char* message);
Warning(const std::string& functionName, const std::string& message);
std::string getString(bool withNewline = true) const;
std::string getHtmlString() const;
virtual bool isWarning() { return true; }
};
class ParamWarning : public Warning
{
public:
ParamWarning(const char* functionName, const char* message, std::string param);
ParamWarning(const char* functionName, const char* message, int param);
std::string getString(bool withNewline = true) const;
std::string getHtmlString() const;
protected:
std::string mParam;
};
#endif //GRADIDO_LOGIN_SERVER_LIB_WARNING_H

View File

@ -561,17 +561,21 @@ namespace model {
Poco::Net::NameValueCollection param;
param.set("transaction", base_64_message);
auto result = json_request.request("putTransaction", param);
if (JSON_REQUEST_RETURN_OK == result) {
json_request.getWarnings(&json_request);
if (JSON_REQUEST_RETURN_OK == result)
{
if (!json_request.errorCount()) {
finishSuccess();
}
else {
getErrors(&json_request);
getErrors(&json_request);
return -1;
}
return 1;
}
json_request.getWarnings(&json_request);
getErrors(&json_request);
return -1;

View File

@ -61,6 +61,12 @@ namespace model {
template<class T1, class T2>
size_t loadFromDB(const std::vector<std::string>& fieldNames, const T1& field1Value, const T2& field2Value, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class Tuple, class T1, class T2>
std::vector<Tuple> loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value,
MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
@ -290,6 +296,43 @@ namespace model {
return resultCount;
}
template<class Tuple, class T1, class T2>
std::vector<Tuple> ModelBase::loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value,
MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
auto cm = ConnectionManager::getInstance();
std::vector<Tuple> results;
if (fieldNames.size() != 2) {
addError(new Error(getTableName(), "error in loadFromDB with 2 different field values, fieldNames count isn't 2"));
return results;
}
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement select = _loadMultipleFromDB(session, fieldNames, conditionType);
select, Poco::Data::Keywords::into(results),
Poco::Data::Keywords::useRef(field1Value), Poco::Data::Keywords::useRef(field2Value);
size_t resultCount = 0;
try {
resultCount = select.execute();
}
catch (Poco::Exception& ex) {
lock();
addError(new ParamError(getTableName(), "mysql error by selecting with 2 different field types", ex.displayText()));
int count = 0;
for (auto it = fieldNames.begin(); it != fieldNames.end(); it++) {
addError(new ParamError(getTableName(), "field name for select: ", *it));
}
//addError(new ParamError(getTableName(), "field name for select: ", fieldName.data()));
unlock();
}
return results;
}
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> ModelBase::loadMultipleFromDB(
const std::vector<std::string>& fieldNames,

View File

@ -0,0 +1,123 @@
#include "gtest/gtest.h"
#include "JSONInterface/JsonCheckUsername.h"
TEST(TestJsonCheckUsername, InvalidGroupAlias)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("group_alias", "robert");
auto result = jsonCall.handle(params);
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "unknown group");
delete result;
}
TEST(TestJsonCheckUsername, InvalidGroupId)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("group_id", "4");
auto result = jsonCall.handle(params);
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "unknown group");
delete result;
}
TEST(TestJsonCheckUsername, ValidGroupAlias)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("group_alias", "gdd1");
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
auto group_id = result->get("group_id");
ASSERT_FALSE(group_id.isEmpty());
ASSERT_TRUE(group_id.isInteger());
int group_id_int = 0;
group_id.convert(group_id_int);
ASSERT_EQ(group_id_int, 1);
delete result;
}
TEST(TestJsonCheckUsername, UsernameWithoutGroup)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("username", "maxi");
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "no group given");
delete result;
}
TEST(TestJsonCheckUsername, ExistingUsername)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("username", "Erfinder");
params->set("group_id", 1);
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "warning");
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "username already in use");
}
TEST(TestJsonCheckUsername, NewUsername)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("username", "Maxi");
params->set("group_id", 1);
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
}
TEST(TestJsonCheckUsername, UsernameExistInOtherGroup)
{
JsonCheckUsername jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("username", "Erfinder");
params->set("group_id", 2);
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
}

View File

@ -152,46 +152,38 @@ int load(int argc, char* argv[]) {
Profiler timeUsed;
// clean up and fill db
std::string tables[] = {
"hedera_accounts",
"hedera_ids",
"crypto_keys",
"hedera_topics",
std::string tables[] = {
"groups",
"node_servers",
"users"
};
for (int i = 0; i < 7; i++) {
for (int i = 0; i < 2; i++) {
runMysql("TRUNCATE " + tables[i]);
runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1");
}
std::stringstream ss;
ss << "INSERT INTO `hedera_ids` (`id`, `shardNum`, `realmNum`, `num`) VALUES "
<< "(1, 0, 0, 37281), "
<< "(2, 0, 0, 21212), "
<< "(3, 0, 0, 212);";
ss << "INSERT INTO `users` (`id`, `email`, `first_name`, `last_name`, `username`, `password`, `pubkey`, `privkey`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`) VALUES "
<< "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 13134558453895551556, 0x146d3fb9e88abc0fca0b0091c1ab1b32b399be037436f340befa8bf004461889, 0x0dcc08960f45f631fe23bc7ddee0724cedc9ec0c861ce30f5091d20ffd96062d08ca215726fb9bd64860c754772e945eea4cc872ed0a36c7b640e8b0bf7a873ec6765fa510711622341347ce2307b5ce, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), "
<< "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', 12910944485867375321, 0x952e215a21d4376b4ac252c4bf41e156e1498e1b6b8ccf2a6826d96712f4f461, 0x4d40bf0860655f728312140dc3741e897bc2d13d00ea80a63e2961046a5a7bd8315397dfb488b89377087bc1a5f4f3af8ffdcf203329ae23ba04be7d38ad3852699d90ff1fc00e5b1ca92b64cc59c01f, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), "
<< "(3, 'Tiger_231@yahoo.com', 'Dieter', 'Schultz', 'Tiger', 13528673707291575501, 0xb539944bf6444a2bfc988244f0c0c9dc326452be9b8a2a43fcd90663719f4f6d, 0x5461fda60b719b65ba00bd6298e48410c4cbf0e89deb13cc784ba8978cf047454e8556ee3eddc8487ee835c33a83163bc8d8babbf2a5c431876bc0a0c114ff0a0d6b57baa12cf8f23c64fb642c862db5, '2020-02-20 16:05:45', 1, 0, 'de', 0, 1), "
<< "(4, 'Nikola_Tesla@email.de', 'Nikola', 'Tesla', 'Erfinder', 15522411320147607375, 0x476b059744f08b0995522b484c90f8d2f47d9b59f4b3c96d9dc0ae6ab7b84979, 0x5277bf044cba4fec64e6f4d38da132755b029161231daefc9a7b4692ad37e05cdd88e0a2c2215baf854dd3a813578c214167af1113607e9f999ca848a7598ba5068e38f2a1afb097e4752a88024d79c8, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(5, 'Elfenhausen@arcor.de', 'Thomas', 'Markuk', 'Elf', 7022671043835614958, 0xb1584e169d60a7e771d3a348235dfd7b5f9e8235fcc26090761a0264b0daa6ff, 0xb46fb7110bf91e28f367aa80f84d1bbd639b6f689f4b0aa28c0f71529232df9bf9ee0fb02fa4c1b9f5a6799c82d119e5646f7231d011517379faaacf6513d973ac3043d4c786490ba62d56d75b86164d, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(6, 'coin-info12@gradido.net', 'coin-info12', 'Test', 'Test Username', 1548398919826089202, 0x4046ae49c1b620f2a321aba0c874fa2bc7ba844ab808bb0eeb18a908d468db14, 0x9522657ecd7456eedf86d065aa087ba7a94a8961a8e4950d044136155d38fe0840f2c0a2876ce055b3eaa6e9ab95c5feba89e535e0434fb2648d94d6e6ec68211aa2ea9e42d1ccd40b6b3c31e41f848e, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1), "
<< "(7, 'AlexWesper@gmail.com', 'Alex', 'Wesper', 'Wespe', 5822761891727948301, 0xb13ede3402abb8f29722b14fec0a2006ae7a3a51fb677cd6a2bbd797ac6905a5, 0x6aa39d7670c64a31639c7d89b874ad929b2eaeb2e5992dbad71b6cea700bf9e3c6cf866d0f0fdc22b44a0ebf51a860799e880ef86266199931dd0a301e5552db44b9b7fa99ed5945652bc7b31eff767c, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1); ";
runMysql(ss.str());
ss.str(std::string());
ss << "INSERT INTO `crypto_keys` (`id`, `private_key`, `public_key`, `crypto_key_type_id`) VALUES "
<< "(1, 0x263f1da712c3b47286b463c2de3784f364f2534d2c34722a3b483c3f3e36476857564f564d476c32d3e342f5ef2763cd23e23a2b429bab62e352f46ba273e2f2, 0xfe5237c2d1ab1361b33163f15634e261c1d217ae32b327cbd88db8ebffedb271, 3), "
<< "(2, 0x721f3e73e3263f1da712c3b47286b463c2de3784f364f2534d2c34722a3b483c3f3e36476857564f564d476c32d3e342f5ef2763cd23e23a2b429bab62e352f46ba273e2f2ef3264fe2452da62bc2739, 0xe3f253d1a2deb25362d2e374baf37bc1d3ef3781cfe1e127f3cd0abcdf372ea6, 1); ";
ss << "INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `description`) VALUES"
<< "(1, 'gdd1', 'Gradido1', 'gdd1.gradido.com', 'Der erste offizielle Gradido Server (zum Testen)'), "
<< "(2, 'gdd_test', 'Gradido Test', 'gdd1.gradido.com', 'Testgroup (zum Testen)'); ";
runMysql(ss.str());
ss.str(std::string());
ss << "INSERT INTO `hedera_accounts` (`id`, `user_id`, `account_hedera_id`, `account_key_id`, `balance`, `network_type`, `updated`) VALUES "
<< "(1, 1, 1, 1, 1000000000000, 1, '2019-09-03 11:13:52'), "
<< "(2, 1, 2, 2, 4312881211, 0, '2019-09-03 11:13:56'); ";
runMysql(ss.str());
ss.str(std::string());
ss << "INSERT INTO `hedera_topics` (`id`, `topic_hedera_id`, `name`, `auto_renew_account_hedera_id`, `auto_renew_period`, `group_id`, `admin_key_id`, `submit_key_id`, `current_timeout`, `sequence_number`, `updated`) VALUES "
<< "(1, 3, 'gdd_test_topic', 1, 0, 1, NULL, NULL, '1999-12-31 23:00:00', 0, '2020-09-14 18:29:04'); ";
runMysql(ss.str());
ss.str(std::string());
std::clog << "after inserting everything in db" << std::endl;
printf("init db in : %s\n", timeUsed.string().data());
fillTests();
for (std::list<Test*>::iterator it = gTests.begin(); it != gTests.end(); it++)
{