diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a64dc057e..acff97d0e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -441,7 +441,7 @@ jobs:
report_name: Coverage Admin Interface
type: lcov
result_path: ./coverage/lcov.info
- min_coverage: 49
+ min_coverage: 50
token: ${{ github.token }}
##############################################################################
@@ -491,7 +491,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
- min_coverage: 37
+ min_coverage: 39
token: ${{ github.token }}
##############################################################################
diff --git a/admin/package.json b/admin/package.json
index e3c94f5d8..9d70c0b06 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -43,6 +43,7 @@
"vue-jest": "^3.0.7",
"vue-moment": "^4.1.0",
"vue-router": "^3.5.3",
+ "vue-toasted": "^1.1.28",
"vuex": "^3.6.2",
"vuex-persistedstate": "^4.1.0"
},
diff --git a/admin/public/img/brand/green.png b/admin/public/img/brand/green.png
new file mode 100644
index 000000000..bb7f12c39
Binary files /dev/null and b/admin/public/img/brand/green.png differ
diff --git a/admin/src/components/CreationFormular.spec.js b/admin/src/components/CreationFormular.spec.js
index fcdf97cfa..e1bbff1cc 100644
--- a/admin/src/components/CreationFormular.spec.js
+++ b/admin/src/components/CreationFormular.spec.js
@@ -3,6 +3,16 @@ import CreationFormular from './CreationFormular.vue'
const localVue = global.localVue
+const apolloMock = jest.fn().mockResolvedValue({
+ data: {
+ verifyLogin: {
+ name: 'success',
+ id: 0,
+ },
+ },
+})
+const stateCommitMock = jest.fn()
+
const mocks = {
$moment: jest.fn(() => {
return {
@@ -14,6 +24,12 @@ const mocks = {
}),
}
}),
+ $apollo: {
+ query: apolloMock,
+ },
+ $store: {
+ commit: stateCommitMock,
+ },
}
const propsData = {
@@ -39,6 +55,23 @@ describe('CreationFormular', () => {
expect(wrapper.find('.component-creation-formular').exists()).toBeTruthy()
})
+ describe('server sends back moderator data', () => {
+ it('called store commit with mocked data', () => {
+ expect(stateCommitMock).toBeCalledWith('moderator', { name: 'success', id: 0 })
+ })
+ })
+
+ describe('server throws error for moderator data call', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ apolloMock.mockRejectedValue({ message: 'Ouch!' })
+ wrapper = Wrapper()
+ })
+ it('has called store commit with fake data', () => {
+ expect(stateCommitMock).toBeCalledWith('moderator', { id: 0, name: 'Test Moderator' })
+ })
+ })
+
describe('radio buttons to selcet month', () => {
it('has three radio buttons', () => {
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
diff --git a/admin/src/components/CreationFormular.vue b/admin/src/components/CreationFormular.vue
index d6b637152..b6a12433e 100644
--- a/admin/src/components/CreationFormular.vue
+++ b/admin/src/components/CreationFormular.vue
@@ -7,7 +7,6 @@
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
}}
- {{ item }}
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
@@ -24,6 +23,7 @@
@@ -34,6 +34,7 @@
@@ -44,6 +45,7 @@
@@ -52,30 +54,29 @@
-
+
Betrag Auswählen
-
-
- GDD
-
-
-
+
+
+
+
-
+
+
+
+
Text eintragen
@@ -125,6 +126,8 @@
diff --git a/admin/src/components/NavBar.spec.js b/admin/src/components/NavBar.spec.js
index 1d68b16ad..ad3ed54fd 100644
--- a/admin/src/components/NavBar.spec.js
+++ b/admin/src/components/NavBar.spec.js
@@ -3,11 +3,19 @@ import NavBar from './NavBar.vue'
const localVue = global.localVue
+const storeDispatchMock = jest.fn()
+const routerPushMock = jest.fn()
+
const mocks = {
$store: {
state: {
openCreations: 1,
+ token: 'valid-token',
},
+ dispatch: storeDispatchMock,
+ },
+ $router: {
+ push: routerPushMock,
},
}
@@ -27,4 +35,34 @@ describe('NavBar', () => {
expect(wrapper.find('.component-nabvar').exists()).toBeTruthy()
})
})
+
+ describe('wallet', () => {
+ const assignLocationSpy = jest.fn()
+ beforeEach(async () => {
+ await wrapper.findAll('a').at(5).trigger('click')
+ })
+
+ it.skip('changes widnow location to wallet', () => {
+ expect(assignLocationSpy).toBeCalledWith('valid-token')
+ })
+
+ it('dispatches logout to store', () => {
+ expect(storeDispatchMock).toBeCalledWith('logout')
+ })
+ })
+
+ describe('logout', () => {
+ // const assignLocationSpy = jest.fn()
+ beforeEach(async () => {
+ await wrapper.findAll('a').at(6).trigger('click')
+ })
+
+ it('redirects to /logout', () => {
+ expect(routerPushMock).toBeCalledWith('/logout')
+ })
+
+ it('dispatches logout to store', () => {
+ expect(storeDispatchMock).toBeCalledWith('logout')
+ })
+ })
})
diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue
index c52743857..208a6fa70 100644
--- a/admin/src/components/NavBar.vue
+++ b/admin/src/components/NavBar.vue
@@ -1,12 +1,15 @@
- Adminbereich
+
+
+
+ Übersicht |
Usersuche |
Mehrfachschöpfung
{
- this.$store.dispatch('logout')
- this.$router.push('/logout')
- })
- .catch(() => {
- this.$store.dispatch('logout')
- if (this.$router.currentRoute.path !== '/logout') this.$router.push('/logout')
- })
- */
this.$store.dispatch('logout')
this.$router.push('/logout')
},
@@ -56,3 +44,8 @@ export default {
},
}
+
diff --git a/admin/src/components/UserTable.vue b/admin/src/components/UserTable.vue
index 265c2d12e..816d95952 100644
--- a/admin/src/components/UserTable.vue
+++ b/admin/src/components/UserTable.vue
@@ -14,11 +14,11 @@
{{ overlayText.text2 }}
-
+
{{ overlayText.button_cancel }}
@@ -49,7 +49,7 @@
-
+
@@ -68,6 +68,7 @@
:item="row.item"
:creationUserData="creationData"
@update-creation-data="updateCreationData"
+ @update-user-data="updateUserData"
/>
@@ -93,7 +94,7 @@
@@ -105,7 +106,7 @@
@@ -232,6 +233,9 @@ export default {
...data,
}
},
+ updateUserData(rowItem, newCreation) {
+ rowItem.creation = newCreation
+ },
},
}
diff --git a/admin/src/graphql/createPendingCreation.js b/admin/src/graphql/createPendingCreation.js
new file mode 100644
index 000000000..72c3249de
--- /dev/null
+++ b/admin/src/graphql/createPendingCreation.js
@@ -0,0 +1,19 @@
+import gql from 'graphql-tag'
+
+export const createPendingCreation = gql`
+ mutation (
+ $email: String!
+ $amount: Int!
+ $memo: String!
+ $creationDate: String!
+ $moderator: Int!
+ ) {
+ createPendingCreation(
+ email: $email
+ amount: $amount
+ memo: $memo
+ creationDate: $creationDate
+ moderator: $moderator
+ )
+ }
+`
diff --git a/admin/src/graphql/verifyLogin.js b/admin/src/graphql/verifyLogin.js
new file mode 100644
index 000000000..59f5e7eb1
--- /dev/null
+++ b/admin/src/graphql/verifyLogin.js
@@ -0,0 +1,11 @@
+import gql from 'graphql-tag'
+
+export const verifyLogin = gql`
+ query {
+ verifyLogin {
+ firstName
+ lastName
+ id
+ }
+ }
+`
diff --git a/admin/src/main.js b/admin/src/main.js
index 2a2f2d7a1..e6f5a80e1 100644
--- a/admin/src/main.js
+++ b/admin/src/main.js
@@ -21,6 +21,7 @@ import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import moment from 'vue-moment'
+import Toasted from 'vue-toasted'
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
@@ -62,6 +63,18 @@ Vue.use(moment)
Vue.use(VueApollo)
+Vue.use(Toasted, {
+ position: 'top-center',
+ duration: 5000,
+ fullWidth: true,
+ action: {
+ text: 'x',
+ onClick: (e, toastObject) => {
+ toastObject.goAway(0)
+ },
+ },
+})
+
addNavigationGuards(router, store)
new Vue({
diff --git a/admin/src/main.test.js b/admin/src/main.test.js
index c639593d6..bf3df3799 100644
--- a/admin/src/main.test.js
+++ b/admin/src/main.test.js
@@ -3,12 +3,14 @@ import './main'
import CONFIG from './config'
import Vue from 'vue'
+import VueApollo from 'vue-apollo'
import Vuex from 'vuex'
import VueI18n from 'vue-i18n'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import moment from 'vue-moment'
jest.mock('vue')
+jest.mock('vue-apollo')
jest.mock('vuex')
jest.mock('vue-i18n')
jest.mock('vue-moment')
@@ -55,6 +57,10 @@ describe('main', () => {
expect(InMemoryCache).toBeCalled()
})
+ it('calls the VueApollo', () => {
+ expect(VueApollo).toBeCalled()
+ })
+
it('calls Vue', () => {
expect(Vue).toBeCalled()
})
@@ -63,16 +69,16 @@ describe('main', () => {
expect(VueI18n).toBeCalled()
})
- it.skip('calls BootstrapVue', () => {
- expect(BootstrapVue).toBeCalled()
+ it('calls BootstrapVue', () => {
+ expect(Vue.use).toBeCalledWith(BootstrapVue)
})
- it.skip('calls IconsPlugin', () => {
- expect(IconsPlugin).toBeCalled()
+ it('calls IconsPlugin', () => {
+ expect(Vue.use).toBeCalledWith(IconsPlugin)
})
- it.skip('calls Moment', () => {
- expect(moment).toBeCalled()
+ it('calls Moment', () => {
+ expect(Vue.use).toBeCalledWith(moment)
})
it.skip('creates a store', () => {
diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue
index ae0ade7b2..7fae2cdcd 100644
--- a/admin/src/pages/UserSearch.vue
+++ b/admin/src/pages/UserSearch.vue
@@ -32,7 +32,13 @@ export default {
{ key: 'email', label: 'Email' },
{ key: 'firstName', label: 'Firstname' },
{ key: 'lastName', label: 'Lastname' },
- { key: 'creation', label: 'Creation' },
+ {
+ key: 'creation',
+ label: 'Creation',
+ formatter: (value, key, item) => {
+ return String(value)
+ },
+ },
{ key: 'show_details', label: 'Details' },
],
searchResult: [],
diff --git a/admin/src/store/store.js b/admin/src/store/store.js
index 754c559c8..d67537499 100644
--- a/admin/src/store/store.js
+++ b/admin/src/store/store.js
@@ -18,6 +18,9 @@ export const mutations = {
token: (state, token) => {
state.token = token
},
+ moderator: (state, moderator) => {
+ state.moderator = moderator
+ },
}
export const actions = {
@@ -35,7 +38,7 @@ const store = new Vuex.Store({
],
state: {
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
- moderator: 'Dertest Moderator',
+ moderator: { name: 'Dertest Moderator', id: 0 },
openCreations: 0,
},
// Syncronous mutation of the state
diff --git a/admin/yarn.lock b/admin/yarn.lock
index d7960320b..46b5aaa93 100644
--- a/admin/yarn.lock
+++ b/admin/yarn.lock
@@ -12524,6 +12524,11 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
+vue-toasted@^1.1.28:
+ version "1.1.28"
+ resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
+ integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
+
vue@^2.6.11:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
diff --git a/backend/package.json b/backend/package.json
index 375046363..e573a2704 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -29,6 +29,7 @@
"jest": "^27.2.4",
"jsonwebtoken": "^8.5.1",
"module-alias": "^2.2.2",
+ "moment": "^2.29.1",
"mysql2": "^2.3.0",
"nodemailer": "^6.6.5",
"random-bigint": "^0.0.1",
diff --git a/backend/src/graphql/arg/CreatePendingCreationArgs.ts b/backend/src/graphql/arg/CreatePendingCreationArgs.ts
new file mode 100644
index 000000000..d2c17abf1
--- /dev/null
+++ b/backend/src/graphql/arg/CreatePendingCreationArgs.ts
@@ -0,0 +1,19 @@
+import { ArgsType, Field, Int } from 'type-graphql'
+
+@ArgsType()
+export default class CreatePendingCreationArgs {
+ @Field(() => String)
+ email: string
+
+ @Field(() => Int)
+ amount: number
+
+ @Field(() => String)
+ memo: string
+
+ @Field(() => String)
+ creationDate: string
+
+ @Field(() => Int)
+ moderator: number
+}
diff --git a/backend/src/graphql/model/CreatePendingCreation.ts b/backend/src/graphql/model/CreatePendingCreation.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts
index cdb46c954..c7b5806ca 100644
--- a/backend/src/graphql/model/User.ts
+++ b/backend/src/graphql/model/User.ts
@@ -12,6 +12,7 @@ export class User {
*/
constructor(json?: any) {
if (json) {
+ this.id = json.id
this.email = json.email
this.firstName = json.first_name
this.lastName = json.last_name
@@ -24,6 +25,9 @@ export class User {
}
}
+ @Field(() => Number)
+ id: number
+
@Field(() => String)
email: string
diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts
index 4ae259087..f3c9d1516 100644
--- a/backend/src/graphql/resolver/AdminResolver.ts
+++ b/backend/src/graphql/resolver/AdminResolver.ts
@@ -1,8 +1,13 @@
-import { Resolver, Query, Arg, Authorized } from 'type-graphql'
-import { getCustomRepository } from 'typeorm'
+import { Resolver, Query, Arg, Args, Authorized, Mutation } from 'type-graphql'
+import { getCustomRepository, Raw } from 'typeorm'
import { UserAdmin } from '../model/UserAdmin'
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
import { RIGHTS } from '../../auth/RIGHTS'
+import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
+import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation'
+import { UserRepository } from '../../typeorm/repository/User'
+import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
+import moment from 'moment'
@Resolver()
export class AdminResolver {
@@ -11,18 +16,161 @@ export class AdminResolver {
async searchUsers(@Arg('searchText') searchText: string): Promise {
const loginUserRepository = getCustomRepository(LoginUserRepository)
const loginUsers = await loginUserRepository.findBySearchCriteria(searchText)
- const users = loginUsers.map((loginUser) => {
- const user = new UserAdmin()
- user.firstName = loginUser.firstName
- user.lastName = loginUser.lastName
- user.email = loginUser.email
- user.creation = [
- (Math.floor(Math.random() * 50) + 1) * 20,
- (Math.floor(Math.random() * 50) + 1) * 20,
- (Math.floor(Math.random() * 50) + 1) * 20,
- ]
- return user
- })
+ const users = await Promise.all(
+ loginUsers.map(async (loginUser) => {
+ const user = new UserAdmin()
+ user.firstName = loginUser.firstName
+ user.lastName = loginUser.lastName
+ user.email = loginUser.email
+ user.creation = await getUserCreations(loginUser.id)
+ return user
+ }),
+ )
return users
}
+
+ @Mutation(() => [Number])
+ async createPendingCreation(
+ @Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
+ ): Promise {
+ const userRepository = getCustomRepository(UserRepository)
+ const user = await userRepository.findByEmail(email)
+
+ const creations = await getUserCreations(user.id)
+ const creationDateObj = new Date(creationDate)
+ if (isCreationValid(creations, amount, creationDateObj)) {
+ const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
+ const loginPendingTaskAdmin = pendingCreationRepository.create()
+ loginPendingTaskAdmin.userId = user.id
+ loginPendingTaskAdmin.amount = BigInt(amount * 10000)
+ loginPendingTaskAdmin.created = new Date()
+ loginPendingTaskAdmin.date = creationDateObj
+ loginPendingTaskAdmin.memo = memo
+ loginPendingTaskAdmin.moderator = moderator
+
+ pendingCreationRepository.save(loginPendingTaskAdmin)
+ }
+ return await getUserCreations(user.id)
+ }
+}
+
+async function getUserCreations(id: number): Promise {
+ const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01'
+ const dateMonth = moment().format('YYYY-MM') + '-01'
+ const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM') + '-01'
+ const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01'
+
+ const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
+ const createdAmountBeforeLastMonth = await transactionCreationRepository
+ .createQueryBuilder('transaction_creations')
+ .select('SUM(transaction_creations.amount)', 'sum')
+ .where('transaction_creations.state_user_id = :id', { id })
+ .andWhere({
+ targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateBeforeLastMonth,
+ enddate: dateLastMonth,
+ }),
+ })
+ .getRawOne()
+
+ const createdAmountLastMonth = await transactionCreationRepository
+ .createQueryBuilder('transaction_creations')
+ .select('SUM(transaction_creations.amount)', 'sum')
+ .where('transaction_creations.state_user_id = :id', { id })
+ .andWhere({
+ targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateLastMonth,
+ enddate: dateMonth,
+ }),
+ })
+ .getRawOne()
+
+ const createdAmountMonth = await transactionCreationRepository
+ .createQueryBuilder('transaction_creations')
+ .select('SUM(transaction_creations.amount)', 'sum')
+ .where('transaction_creations.state_user_id = :id', { id })
+ .andWhere({
+ targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateMonth,
+ enddate: dateNextMonth,
+ }),
+ })
+ .getRawOne()
+
+ const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
+ const pendingAmountMounth = await pendingCreationRepository
+ .createQueryBuilder('login_pending_tasks_admin')
+ .select('SUM(login_pending_tasks_admin.amount)', 'sum')
+ .where('login_pending_tasks_admin.userId = :id', { id })
+ .andWhere({
+ date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateMonth,
+ enddate: dateNextMonth,
+ }),
+ })
+ .getRawOne()
+
+ const pendingAmountLastMounth = await pendingCreationRepository
+ .createQueryBuilder('login_pending_tasks_admin')
+ .select('SUM(login_pending_tasks_admin.amount)', 'sum')
+ .where('login_pending_tasks_admin.userId = :id', { id })
+ .andWhere({
+ date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateLastMonth,
+ enddate: dateMonth,
+ }),
+ })
+ .getRawOne()
+
+ const pendingAmountBeforeLastMounth = await pendingCreationRepository
+ .createQueryBuilder('login_pending_tasks_admin')
+ .select('SUM(login_pending_tasks_admin.amount)', 'sum')
+ .where('login_pending_tasks_admin.userId = :id', { id })
+ .andWhere({
+ date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
+ date: dateBeforeLastMonth,
+ enddate: dateLastMonth,
+ }),
+ })
+ .getRawOne()
+
+ // COUNT amount from 2 tables
+ const usedCreationBeforeLastMonth =
+ (Number(createdAmountBeforeLastMonth.sum) + Number(pendingAmountBeforeLastMounth.sum)) / 10000
+ const usedCreationLastMonth =
+ (Number(createdAmountLastMonth.sum) + Number(pendingAmountLastMounth.sum)) / 10000
+ const usedCreationMonth =
+ (Number(createdAmountMonth.sum) + Number(pendingAmountMounth.sum)) / 10000
+ return [
+ 1000 - usedCreationBeforeLastMonth,
+ 1000 - usedCreationLastMonth,
+ 1000 - usedCreationMonth,
+ ]
+}
+
+function isCreationValid(creations: number[], amount: number, creationDate: Date) {
+ const dateMonth = moment().format('YYYY-MM')
+ const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM')
+ const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM')
+ const creationDateMonth = moment(creationDate).format('YYYY-MM')
+
+ let openCreation
+ switch (creationDateMonth) {
+ case dateMonth:
+ openCreation = creations[2]
+ break
+ case dateLastMonth:
+ openCreation = creations[1]
+ break
+ case dateBeforeLastMonth:
+ openCreation = creations[0]
+ break
+ default:
+ throw new Error('CreationDate is not in last three months')
+ }
+
+ if (openCreation < amount) {
+ throw new Error(`Open creation (${openCreation}) is less than amount (${amount})`)
+ }
+ return true
}
diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts
index 7f5f7dc43..ce403ac0e 100644
--- a/backend/src/graphql/resolver/UserResolver.ts
+++ b/backend/src/graphql/resolver/UserResolver.ts
@@ -207,6 +207,7 @@ export class UserResolver {
const loginUserRepository = getCustomRepository(LoginUserRepository)
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
const user = new User()
+ user.id = userEntity.id
user.email = userEntity.email
user.firstName = userEntity.firstName
user.lastName = userEntity.lastName
@@ -276,6 +277,7 @@ export class UserResolver {
}
const user = new User()
+ user.id = userEntity.id
user.email = email
user.firstName = loginUser.firstName
user.lastName = loginUser.lastName
diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts
index 28e0e1ce4..e5dfd113b 100644
--- a/backend/src/server/createServer.ts
+++ b/backend/src/server/createServer.ts
@@ -29,7 +29,7 @@ import { elopageWebhook } from '../webhook/elopage'
// TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
-const DB_VERSION = '0004-login_server_data'
+const DB_VERSION = '0005-admin_tables'
const createServer = async (context: any = serverContext): Promise => {
// open mysql connection
diff --git a/backend/src/typeorm/repository/PendingCreation.ts b/backend/src/typeorm/repository/PendingCreation.ts
new file mode 100644
index 000000000..8b49e7ecc
--- /dev/null
+++ b/backend/src/typeorm/repository/PendingCreation.ts
@@ -0,0 +1,5 @@
+import { EntityRepository, Repository } from 'typeorm'
+import { LoginPendingTasksAdmin } from '@entity/LoginPendingTasksAdmin'
+
+@EntityRepository(LoginPendingTasksAdmin)
+export class PendingCreationRepository extends Repository {}
diff --git a/backend/src/typeorm/repository/TransactionCreation.ts b/backend/src/typeorm/repository/TransactionCreation.ts
new file mode 100644
index 000000000..89266838a
--- /dev/null
+++ b/backend/src/typeorm/repository/TransactionCreation.ts
@@ -0,0 +1,5 @@
+import { EntityRepository, Repository } from 'typeorm'
+import { TransactionCreation } from '@entity/TransactionCreation'
+
+@EntityRepository(TransactionCreation)
+export class TransactionCreationRepository extends Repository {}
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 5b74ba7c3..b46bc183d 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -4139,6 +4139,11 @@ module-alias@^2.2.2:
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
+moment@^2.29.1:
+ version "2.29.1"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+ integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
+
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
diff --git a/database/entity/0005-admin_tables/LoginPendingTasksAdmin.ts b/database/entity/0005-admin_tables/LoginPendingTasksAdmin.ts
new file mode 100644
index 000000000..26b92f43b
--- /dev/null
+++ b/database/entity/0005-admin_tables/LoginPendingTasksAdmin.ts
@@ -0,0 +1,25 @@
+import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
+
+@Entity('login_pending_tasks_admin')
+export class LoginPendingTasksAdmin extends BaseEntity {
+ @PrimaryGeneratedColumn('increment', { unsigned: true })
+ id: number
+
+ @Column({ unsigned: true, nullable: false })
+ userId: number
+
+ @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
+ created: Date
+
+ @Column({ type: 'datetime', nullable: false })
+ date: Date
+
+ @Column({ length: 256, nullable: true, default: null })
+ memo: string
+
+ @Column({ type: 'bigint', nullable: false })
+ amount: BigInt
+
+ @Column()
+ moderator: number
+}
diff --git a/database/entity/LoginPendingTasksAdmin.ts b/database/entity/LoginPendingTasksAdmin.ts
new file mode 100644
index 000000000..f766b74dd
--- /dev/null
+++ b/database/entity/LoginPendingTasksAdmin.ts
@@ -0,0 +1 @@
+export { LoginPendingTasksAdmin } from './0005-admin_tables/LoginPendingTasksAdmin'
diff --git a/database/entity/index.ts b/database/entity/index.ts
index 901b4ab0d..92b3875f8 100644
--- a/database/entity/index.ts
+++ b/database/entity/index.ts
@@ -12,6 +12,7 @@ import { TransactionSendCoin } from './TransactionSendCoin'
import { User } from './User'
import { UserSetting } from './UserSetting'
import { UserTransaction } from './UserTransaction'
+import { LoginPendingTasksAdmin } from './LoginPendingTasksAdmin'
export const entities = [
Balance,
@@ -28,4 +29,5 @@ export const entities = [
User,
UserSetting,
UserTransaction,
+ LoginPendingTasksAdmin,
]
diff --git a/database/migrations/0005-admin_tables.ts b/database/migrations/0005-admin_tables.ts
new file mode 100644
index 000000000..6398b22fc
--- /dev/null
+++ b/database/migrations/0005-admin_tables.ts
@@ -0,0 +1,29 @@
+/* MIGRATION FOR ADMIN INTERFACE
+ *
+ * This migration is special since it takes into account that
+ * the database can be setup already but also may not be.
+ * Therefore you will find all `CREATE TABLE` statements with
+ * a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
+ * downgrade function all `DROP TABLE` with a `IF EXISTS`.
+ * This ensures compatibility for existing or non-existing
+ * databases.
+ */
+
+export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) {
+ await queryFn(`
+ CREATE TABLE \`login_pending_tasks_admin\` (
+ \`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ \`userId\` int UNSIGNED DEFAULT 0,
+ \`created\` datetime NOT NULL,
+ \`date\` datetime NOT NULL,
+ \`memo\` text DEFAULT NULL,
+ \`amount\` bigint(20) NOT NULL,
+ \`moderator\` int UNSIGNED DEFAULT 0,
+ PRIMARY KEY (\`id\`)
+ ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
+ `)
+}
+
+export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) {
+ await queryFn(`DROP TABLE \`login_pending_tasks_admin\`;`)
+}