Merge master in here.

This commit is contained in:
elweyn 2023-05-03 14:09:04 +02:00
commit 5d4d7dfa05
175 changed files with 1803 additions and 879 deletions

View File

@ -33,6 +33,7 @@
"bootstrap": "4.3.1",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5",
"date-fns": "^2.29.3",
"dotenv-webpack": "^7.0.3",
"express": "^4.17.1",
"graphql": "^15.6.1",

View File

@ -0,0 +1,183 @@
import { mount } from '@vue/test-utils'
import FederationVisualizeItem from './FederationVisualizeItem.vue'
const localVue = global.localVue
const today = new Date()
const createdDate = new Date()
createdDate.setDate(createdDate.getDate() - 3)
let propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: today,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
const mocks = {
$i18n: {
locale: 'en',
},
}
describe('FederationVisualizeItem', () => {
let wrapper
const Wrapper = () => {
return mount(FederationVisualizeItem, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.federation-visualize-item').exists()).toBe(true)
})
describe('rendering item properties', () => {
it('has the url', () => {
expect(wrapper.find('.row > div:nth-child(2) > div').text()).toBe(
'http://localhost/api/2_0',
)
})
it('has the public key', () => {
expect(wrapper.find('.row > div:nth-child(2) > small').text()).toContain(
'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7'.substring(0, 26),
)
})
describe('verified item', () => {
it('has the check icon', () => {
expect(wrapper.find('svg.bi-check').exists()).toBe(true)
})
it('has the text variant "success"', () => {
expect(wrapper.find('.text-success').exists()).toBe(true)
})
})
describe('not verified item', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: null,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('has the x-circle icon', () => {
expect(wrapper.find('svg.bi-x-circle').exists()).toBe(true)
})
it('has the text variant "danger"', () => {
expect(wrapper.find('.text-danger').exists()).toBe(true)
})
})
// describe('with different locales (de, en, fr, es, nl)', () => {
describe('lastAnnouncedAt', () => {
it('computes the time string for different locales (de, en, fr, es, nl)', () => {
wrapper.vm.$i18n.locale = 'de'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('vor 3 Tagen')
wrapper.vm.$i18n.locale = 'fr'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('il y a 3 jours')
wrapper.vm.$i18n.locale = 'es'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('hace 3 días')
wrapper.vm.$i18n.locale = 'nl'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('3 dagen geleden')
})
describe('lastAnnouncedAt == null', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('computes empty string', async () => {
expect(wrapper.vm.lastAnnouncedAt).toBe('')
})
})
})
describe('createdAt', () => {
it('computes the time string for different locales (de, en, fr, es, nl)', () => {
wrapper.vm.$i18n.locale = 'de'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('vor 3 Tagen')
wrapper.vm.$i18n.locale = 'fr'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('il y a 3 jours')
wrapper.vm.$i18n.locale = 'es'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('hace 3 días')
wrapper.vm.$i18n.locale = 'nl'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('3 dagen geleden')
})
describe('createdAt == null', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: null,
lastErrorAt: null,
createdAt: null,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('computes empty string', async () => {
expect(wrapper.vm.createdAt).toBe('')
})
})
})
})
})
})

View File

@ -0,0 +1,63 @@
<template>
<div class="federation-visualize-item">
<b-row>
<b-col cols="1"><b-icon :icon="icon" :variant="variant" class="mr-4"></b-icon></b-col>
<b-col>
<div>{{ item.url }}</div>
<small>{{ `${item.publicKey.substring(0, 26)}` }}</small>
</b-col>
<b-col cols="2">{{ lastAnnouncedAt }}</b-col>
<b-col cols="2">{{ createdAt }}</b-col>
</b-row>
</div>
</template>
<script>
import { formatDistanceToNow } from 'date-fns'
import { de, en, fr, es, nl } from 'date-fns/locale'
const locales = { en, de, es, fr, nl }
export default {
name: 'FederationVisualizeItem',
props: {
item: { type: Object },
},
data() {
return {
formatDistanceToNow,
locale: this.$i18n.locale,
}
},
computed: {
verified() {
return new Date(this.item.verifiedAt) >= new Date(this.item.lastAnnouncedAt)
},
icon() {
return this.verified ? 'check' : 'x-circle'
},
variant() {
return this.verified ? 'success' : 'danger'
},
lastAnnouncedAt() {
if (this.item.lastAnnouncedAt) {
return formatDistanceToNow(new Date(this.item.lastAnnouncedAt), {
includeSecond: true,
addSuffix: true,
locale: locales[this.locale],
})
}
return ''
},
createdAt() {
if (this.item.createdAt) {
return formatDistanceToNow(new Date(this.item.createdAt), {
includeSecond: true,
addSuffix: true,
locale: locales[this.locale],
})
}
return ''
},
},
}
</script>

View File

@ -62,8 +62,12 @@ describe('NavBar', () => {
)
})
it('has a link to /federation', () => {
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/federation')
})
it('has a link to /statistic', () => {
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/statistic')
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe('/statistic')
})
})
@ -72,7 +76,7 @@ describe('NavBar', () => {
beforeEach(async () => {
delete window.location
window.location = ''
await wrapper.findAll('.nav-item').at(4).find('a').trigger('click')
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
})
afterEach(() => {
@ -97,7 +101,7 @@ describe('NavBar', () => {
window.location = {
assign: windowLocationMock,
}
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
await wrapper.findAll('.nav-item').at(6).find('a').trigger('click')
})
afterEach(() => {

View File

@ -1,6 +1,6 @@
<template>
<div class="component-nabvar">
<b-navbar toggleable="md" type="dark" variant="success">
<b-navbar toggleable="lg" type="dark" class="bg-dark">
<b-navbar-brand class="mb-2" to="/">
<img src="img/brand/gradido_logo_w.png" class="navbar-brand-img pl-2" alt="..." />
</b-navbar-brand>
@ -19,6 +19,9 @@
<b-nav-item to="/contribution-links">
{{ $t('navbar.automaticContributions') }}
</b-nav-item>
<b-nav-item to="/federation">
{{ $t('navbar.instances') }}
</b-nav-item>
<b-nav-item to="/statistic">{{ $t('navbar.statistic') }}</b-nav-item>
<b-nav-item @click="wallet">{{ $t('navbar.my-account') }}</b-nav-item>
<b-nav-item @click="logout">{{ $t('navbar.logout') }}</b-nav-item>

View File

@ -0,0 +1,17 @@
import gql from 'graphql-tag'
export const getCommunities = gql`
query {
getCommunities {
id
foreign
publicKey
url
lastAnnouncedAt
verifiedAt
lastErrorAt
createdAt
updatedAt
}
}
`

View File

@ -68,6 +68,13 @@
"error": "Fehler",
"expired": "abgelaufen",
"e_mail": "E-Mail",
"federation": {
"createdAt": "Erstellt am",
"gradidoInstances": "Gradido Instanzen",
"lastAnnouncedAt": "letzte Bekanntgabe",
"url": "Url",
"verified": "Verifiziert"
},
"firstname": "Vorname",
"footer": {
"app_version": "App version {version}",
@ -105,6 +112,7 @@
"name": "Name",
"navbar": {
"automaticContributions": "Automatische Beiträge",
"instances": "Instanzen",
"logout": "Abmelden",
"my-account": "Mein Konto",
"statistic": "Statistik",

View File

@ -68,6 +68,13 @@
"error": "Error",
"expired": "expired",
"e_mail": "E-mail",
"federation": {
"createdAt": "Created At ",
"gradidoInstances": "Gradido Instances",
"lastAnnouncedAt": "Last Announced",
"url": "Url",
"verified": "Verified"
},
"firstname": "Firstname",
"footer": {
"app_version": "App version {version}",
@ -105,6 +112,7 @@
"name": "Name",
"navbar": {
"automaticContributions": "Automatic Contributions",
"instances": "Instances",
"logout": "Logout",
"my-account": "My Account",
"statistic": "Statistic",

View File

@ -39,7 +39,7 @@ const mocks = {
const defaultData = () => {
return {
adminListContributions: {
contributionCount: 2,
contributionCount: 30,
contributionList: [
{
id: 1,
@ -407,6 +407,44 @@ describe('CreationConfirm', () => {
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
})
})
describe('change pagination', () => {
it('has pagination buttons', () => {
expect(wrapper.findComponent({ name: 'BPagination' }).exists()).toBe(true)
})
describe('next page', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper.findComponent({ name: 'BPagination' }).vm.$emit('input', 2)
})
it('calls the API again', () => {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 2,
order: 'DESC',
pageSize: 25,
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
})
})
describe('click tab "open" again', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.find('a[data-test="open"]').trigger('click')
})
it('refetches contributions with proper filter and current page = 1', () => {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
statusFilter: ['IN_PROGRESS', 'PENDING'],
})
})
})
})
})
})
})
})

View File

@ -116,6 +116,11 @@ export default {
pageSize: 25,
}
},
watch: {
tabIndex() {
this.currentPage = 1
},
},
methods: {
deleteCreation() {
this.$apollo

View File

@ -0,0 +1,125 @@
import { mount } from '@vue/test-utils'
import FederationVisualize from './FederationVisualize'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { getCommunities } from '@/graphql/getCommunities'
import { toastErrorSpy } from '../../test/testSetup'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue
localVue.use(VueApollo)
const mocks = {
$t: (key) => key,
$d: jest.fn((d) => d),
$i18n: {
locale: 'en',
t: (key) => key,
},
}
const defaultData = () => {
return {
getCommunities: [
{
id: 1776,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: '2023-04-07T12:27:24.037Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.254Z',
updatedAt: null,
__typename: 'Community',
},
{
id: 1775,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/1_1',
lastAnnouncedAt: '2023-04-07T12:27:24.023Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.234Z',
updatedAt: null,
__typename: 'Community',
},
{
id: 1774,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/1_0',
lastAnnouncedAt: '2023-04-07T12:27:24.009Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.218Z',
updatedAt: null,
__typename: 'Community',
},
],
}
}
describe('FederationVisualize', () => {
let wrapper
const getCommunitiesMock = jest.fn()
mockClient.setRequestHandler(
getCommunities,
getCommunitiesMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
const Wrapper = () => {
return mount(FederationVisualize, { localVue, mocks, apolloProvider })
}
describe('mount', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper()
})
describe('server error', () => {
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
describe('sever success', () => {
it('sends query to Apollo when created', () => {
expect(getCommunitiesMock).toBeCalled()
})
it('has a DIV element with the class "federation-visualize"', () => {
expect(wrapper.find('div.federation-visualize').exists()).toBe(true)
})
it('has a refresh button', () => {
expect(wrapper.find('[data-test="federation-communities-refresh-btn"]').exists()).toBe(true)
})
it('renders 3 community list items', () => {
expect(wrapper.findAll('.list-group-item').length).toBe(3)
})
describe('cklicking the refresh button', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.find('[data-test="federation-communities-refresh-btn"]').trigger('click')
})
it('calls the API', async () => {
expect(getCommunitiesMock).toBeCalled()
})
})
})
})
})

View File

@ -0,0 +1,69 @@
<template>
<div class="federation-visualize">
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="h2">{{ $t('federation.gradidoInstances') }}</span>
<b-button>
<b-icon
icon="arrow-clockwise"
font-scale="2"
:animation="animation"
@click="$apollo.queries.GetCommunities.refresh()"
data-test="federation-communities-refresh-btn"
></b-icon>
</b-button>
</div>
<b-list-group>
<b-row>
<b-col cols="1" class="ml-1">{{ $t('federation.verified') }}</b-col>
<b-col class="ml-3">{{ $t('federation.url') }}</b-col>
<b-col cols="2">{{ $t('federation.lastAnnouncedAt') }}</b-col>
<b-col cols="2">{{ $t('federation.createdAt') }}</b-col>
</b-row>
<b-list-group-item
v-for="item in communities"
:key="item.id"
:variant="!item.foreign ? 'primary' : 'warning'"
>
<federation-visualize-item :item="item" />
</b-list-group-item>
</b-list-group>
</div>
</template>
<script>
import { getCommunities } from '@/graphql/getCommunities'
import FederationVisualizeItem from '../components/Fedaration/FederationVisualizeItem.vue'
export default {
name: 'FederationVisualize',
components: {
FederationVisualizeItem,
},
data() {
return {
oldPublicKey: '',
communities: [],
icon: '',
}
},
computed: {
animation() {
return this.$apollo.queries.GetCommunities.loading ? 'spin' : ''
},
},
apollo: {
GetCommunities: {
fetchPolicy: 'network-only',
query() {
return getCommunities
},
update({ getCommunities }) {
this.communities = getCommunities
},
error({ message }) {
this.toastError(message)
},
},
},
}
</script>

View File

@ -45,7 +45,7 @@ describe('router', () => {
describe('routes', () => {
it('has nine routes defined', () => {
expect(routes).toHaveLength(8)
expect(routes).toHaveLength(9)
})
it('has "/overview" as default', async () => {
@ -88,6 +88,13 @@ describe('router', () => {
})
})
describe('federation', () => {
it('loads the "FederationVisualize" page', async () => {
const component = await routes.find((r) => r.path === '/federation').component()
expect(component.default.name).toBe('FederationVisualize')
})
})
describe('not found page', () => {
it('renders the "NotFound" component', async () => {
const component = await routes.find((r) => r.path === '*').component()

View File

@ -31,6 +31,10 @@ const routes = [
path: '*',
component: () => import('@/components/NotFoundPage.vue'),
},
{
path: '/federation',
component: () => import('@/pages/FederationVisualize.vue'),
},
]
export default routes

View File

@ -5038,6 +5038,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.29.3:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"

View File

@ -18,7 +18,9 @@ module.exports = {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: true,
typescript: {
project: ['./tsconfig.json', '**/tsconfig.json'],
},
node: true,
},
},
@ -55,7 +57,7 @@ module.exports = {
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'off',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
@ -71,7 +73,7 @@ module.exports = {
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-default-export': 'off',
'import/no-default-export': 'error',
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
@ -100,7 +102,7 @@ module.exports = {
distinctGroup: true,
},
],
'import/prefer-default-export': 'off', // TODO
'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',
'n/no-callback-literal': 'error',
@ -160,8 +162,8 @@ module.exports = {
'import/unambiguous': 'off',
},
parserOptions: {
tsconfigRootDir: './',
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', '**/tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},

View File

@ -56,26 +56,25 @@
"@types/node": "^16.10.3",
"@types/nodemailer": "^6.4.4",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"apollo-server-testing": "^2.25.2",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.4",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-type-graphql": "^1.0.0",
"faker": "^5.5.3",
"graphql-tag": "^2.12.6",
"jest": "^27.2.4",
"klicktipp-api": "^1.0.2",
"nodemon": "^2.0.7",
"prettier": "^2.3.1",
"prettier": "^2.8.7",
"ts-jest": "^27.0.5",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.14.0",

View File

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import axios from 'axios'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line import/no-relative-parent-imports
@ -64,7 +65,6 @@ export const getKlickTippUser = async (email: string): Promise<any> => {
logger.error(`Could not find subscriber ${email}`)
return false
}
}
return false
}
@ -117,7 +117,12 @@ export const addFieldsToSubscriber = async (
if (isLogin) {
try {
const subscriberId = await klicktippConnector.subscriberSearch(email)
const result = await klicktippConnector.subscriberUpdate(subscriberId, fields, newemail, newsmsnumber)
const result = await klicktippConnector.subscriberUpdate(
subscriberId,
fields,
newemail,
newsmsnumber,
)
logger.info(`Update of subscriber (${email}) has been successful, ${result}`)
return result
} catch (e) {

View File

@ -1,7 +1,7 @@
import { verify, sign } from 'jsonwebtoken'
import CONFIG from '@/config/'
import LogError from '@/server/LogError'
import { CONFIG } from '@/config/'
import { LogError } from '@/server/LogError'
import { CustomJwtPayload } from './CustomJwtPayload'

View File

@ -33,6 +33,7 @@ export enum RIGHTS {
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
OPEN_CREATIONS = 'OPEN_CREATIONS',
USER = 'USER',
// Admin
SEARCH_USERS = 'SEARCH_USERS',
SET_USER_ROLE = 'SET_USER_ROLE',

View File

@ -32,6 +32,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
RIGHTS.OPEN_CREATIONS,
RIGHTS.USER,
])
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights

View File

@ -1,4 +1,4 @@
import CONFIG from './index'
import { CONFIG } from './index'
describe('config/index', () => {
describe('decay start block', () => {

View File

@ -121,7 +121,7 @@ const federation = {
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000,
}
const CONFIG = {
export const CONFIG = {
...constants,
...server,
...database,
@ -132,5 +132,3 @@ const CONFIG = {
...webhook,
...federation,
}
export default CONFIG

View File

@ -4,7 +4,7 @@ import { createTransport } from 'nodemailer'
import { logger, i18n } from '@test/testSetup'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'

View File

@ -5,8 +5,8 @@ import Email from 'email-templates'
import i18n from 'i18n'
import { createTransport } from 'nodemailer'
import CONFIG from '@/config'
import LogError from '@/server/LogError'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export const sendEmailTranslated = async (params: {

View File

@ -8,7 +8,7 @@ import { Decimal } from 'decimal.js-light'
import { testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'
import {

View File

@ -1,6 +1,6 @@
import { Decimal } from 'decimal.js-light'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { decimalSeparatorByLanguage } from '@/util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_CONFIRM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_CREATE = async (
moderator: DbUser,

View File

@ -2,7 +2,8 @@ import { ContributionLink as DbContributionLink } from '@entity/ContributionLink
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_DELETE = async (
moderator: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE = async (
moderator: DbUser,

View File

@ -3,7 +3,8 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_USER_DELETE = async (user: DbUser, moderator: DbUser): Promise<DbEvent> =>
Event(EventType.ADMIN_USER_DELETE, user, moderator).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_USER_ROLE_SET = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_USER_UNDELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_DELETE = async (
user: DbUser,

View File

@ -5,7 +5,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_LINK_REDEEM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_UPDATE = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_ACCOUNT_MULTIREGISTRATION, user, { id: 0 } as DbUser).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_EMAIL_ADMIN_CONFIRMATION = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_EMAIL_CONFIRMATION = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_CONFIRMATION, user, user).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_EMAIL_FORGOT_PASSWORD = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_FORGOT_PASSWORD, user, { id: 0 } as DbUser).save()

View File

@ -3,7 +3,8 @@ import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_CREATE = async (
user: DbUser,

View File

@ -2,7 +2,8 @@ import { Event as DbEvent } from '@entity/Event'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_DELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_REDEEM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_RECEIVE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_SEND = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_USER_ACTIVATE_ACCOUNT = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_ACTIVATE_ACCOUNT, user, user).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_USER_INFO_UPDATE = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_INFO_UPDATE, user, user).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_USER_LOGIN = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_LOGIN, user, user).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_USER_LOGOUT = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_LOGOUT, user, user).save()

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_USER_REGISTER = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_REGISTER, user, user).save()

View File

@ -34,39 +34,3 @@ export const Event = (
event.amount = amount
return event
}
export { EventType }
export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE'
export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE'
export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET'
export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM'
export { EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION } from './EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION'
export { EVENT_EMAIL_ADMIN_CONFIRMATION } from './EVENT_EMAIL_ADMIN_CONFIRMATION'
export { EVENT_EMAIL_CONFIRMATION } from './EVENT_EMAIL_CONFIRMATION'
export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD'
export { EVENT_NEWSLETTER_SUBSCRIBE } from './EVENT_NEWSLETTER_SUBSCRIBE'
export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE'
export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE'
export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM'
export { EVENT_NEWSLETTER_UNSUBSCRIBE } from './EVENT_NEWSLETTER_UNSUBSCRIBE'
export { EVENT_USER_ACTIVATE_ACCOUNT } from './EVENT_USER_ACTIVATE_ACCOUNT'
export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE'
export { EVENT_USER_LOGIN } from './EVENT_USER_LOGIN'
export { EVENT_USER_LOGOUT } from './EVENT_USER_LOGOUT'
export { EVENT_USER_REGISTER } from './EVENT_USER_REGISTER'

View File

@ -0,0 +1,33 @@
export { EventType } from './EventType'
export { Event } from './Event'
export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE'
export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE'
export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET'
export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM'
export { EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION } from './EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION'
export { EVENT_EMAIL_ADMIN_CONFIRMATION } from './EVENT_EMAIL_ADMIN_CONFIRMATION'
export { EVENT_EMAIL_CONFIRMATION } from './EVENT_EMAIL_CONFIRMATION'
export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD'
export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE'
export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE'
export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM'
export { EVENT_USER_ACTIVATE_ACCOUNT } from './EVENT_USER_ACTIVATE_ACCOUNT'
export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE'
export { EVENT_USER_LOGIN } from './EVENT_USER_LOGIN'
export { EVENT_USER_LOGOUT } from './EVENT_USER_LOGOUT'
export { EVENT_USER_REGISTER } from './EVENT_USER_REGISTER'

View File

@ -5,7 +5,7 @@ import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {

View File

@ -5,7 +5,7 @@ import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {

View File

@ -1,7 +1,7 @@
import { IsNull } from '@dbTools/typeorm'
import { Community as DbCommunity } from '@entity/Community'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line camelcase

View File

@ -3,7 +3,7 @@ import { ArgsType, Field, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class AdminCreateContributionArgs {
export class AdminCreateContributionArgs {
@Field(() => String)
email: string

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class AdminUpdateContributionArgs {
export class AdminUpdateContributionArgs {
@Field(() => Int)
id: number

View File

@ -3,7 +3,7 @@ import { ArgsType, Field, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class ContributionArgs {
export class ContributionArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class ContributionLinkArgs {
export class ContributionLinkArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@ import { ArgsType, Field, Int, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class ContributionMessageArgs {
export class ContributionMessageArgs {
@Field(() => Int)
contributionId: number

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class CreateUserArgs {
export class CreateUserArgs {
@Field(() => String)
email: string

View File

@ -4,7 +4,7 @@ import { ArgsType, Field, Int } from 'type-graphql'
import { Order } from '@enum/Order'
@ArgsType()
export default class Paginated {
export class Paginated {
@Field(() => Int, { nullable: true })
currentPage?: number

View File

@ -1,9 +1,9 @@
import { ArgsType, Field, Int } from 'type-graphql'
import SearchUsersFilters from '@arg/SearchUsersFilters'
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
@ArgsType()
export default class SearchUsersArgs {
export class SearchUsersArgs {
@Field(() => String)
searchText: string

View File

@ -1,7 +1,7 @@
import { Field, InputType } from 'type-graphql'
@InputType()
export default class SearchUsersFilters {
export class SearchUsersFilters {
@Field(() => Boolean, { nullable: true, defaultValue: null })
byActivated?: boolean | null

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class TransactionLinkArgs {
export class TransactionLinkArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@
import { Field, InputType } from 'type-graphql'
@InputType()
export default class TransactionLinkFilters {
export class TransactionLinkFilters {
@Field(() => Boolean, { nullable: true })
withDeleted?: boolean

View File

@ -2,9 +2,9 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class TransactionSendArgs {
export class TransactionSendArgs {
@Field(() => String)
email: string
identifier: string
@Field(() => Decimal)
amount: Decimal

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class UnsecureLoginArgs {
export class UnsecureLoginArgs {
@Field(() => String)
email: string

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class UpdateUserInfosArgs {
export class UpdateUserInfosArgs {
@Field({ nullable: true })
firstName?: string

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { User } from '@entity/User'
import { AuthChecker } from 'type-graphql'
@ -9,9 +10,9 @@ import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
import { decode, encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
export const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user
// is rights an inalienable right?
@ -55,5 +56,3 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
return true
}
export default isAuthorized

View File

@ -8,9 +8,7 @@ export class Community {
this.foreign = dbCom.foreign
this.publicKey = dbCom.publicKey.toString()
this.url =
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') +
'api/' +
dbCom.apiVersion
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') + dbCom.apiVersion
this.lastAnnouncedAt = dbCom.lastAnnouncedAt
this.verifiedAt = dbCom.verifiedAt
this.lastErrorAt = dbCom.lastErrorAt

View File

@ -2,7 +2,7 @@ import { ContributionLink as dbContributionLink } from '@entity/ContributionLink
import { Decimal } from 'decimal.js-light'
import { ObjectType, Field, Int } from 'type-graphql'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
@ObjectType()
export class ContributionLink {

View File

@ -47,6 +47,10 @@ export class Transaction {
this.linkId = transaction.contribution
? transaction.contribution.contributionLinkId
: transaction.transactionLinkId || null
this.previousBalance =
(transaction.previousTransaction &&
transaction.previousTransaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN)) ||
new Decimal(0)
}
@Field(() => Int)
@ -70,6 +74,9 @@ export class Transaction {
@Field(() => Date)
balanceDate: Date
@Field(() => Decimal)
previousBalance: Decimal
@Field(() => Decay)
decay: Decay

View File

@ -2,7 +2,7 @@ import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { Decimal } from 'decimal.js-light'
import { ObjectType, Field, Int } from 'type-graphql'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { User } from './User'

View File

@ -36,6 +36,7 @@ describe('CommunityResolver', () => {
let foreignCom1: DbCommunity
let foreignCom2: DbCommunity
let foreignCom3: DbCommunity
describe('with empty list', () => {
it('returns no community entry', async () => {
// const result: Community[] = await query({ query: getCommunities })
@ -56,7 +57,7 @@ describe('CommunityResolver', () => {
homeCom1.foreign = false
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.apiVersion = '1_0'
homeCom1.endPoint = 'http://localhost'
homeCom1.endPoint = 'http://localhost/api'
homeCom1.createdAt = new Date()
await DbCommunity.insert(homeCom1)
@ -64,7 +65,7 @@ describe('CommunityResolver', () => {
homeCom2.foreign = false
homeCom2.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom2.apiVersion = '1_1'
homeCom2.endPoint = 'http://localhost'
homeCom2.endPoint = 'http://localhost/api'
homeCom2.createdAt = new Date()
await DbCommunity.insert(homeCom2)
@ -72,24 +73,24 @@ describe('CommunityResolver', () => {
homeCom3.foreign = false
homeCom3.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom3.apiVersion = '2_0'
homeCom3.endPoint = 'http://localhost'
homeCom3.endPoint = 'http://localhost/api'
homeCom3.createdAt = new Date()
await DbCommunity.insert(homeCom3)
})
it('returns three home-community entries', async () => {
it('returns 3 home-community entries', async () => {
await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: {
getCommunities: [
{
id: 1,
foreign: homeCom1.foreign,
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_0'),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -104,14 +105,14 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 3,
foreign: homeCom3.foreign,
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/2_0'),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
},
],
@ -128,7 +129,7 @@ describe('CommunityResolver', () => {
foreignCom1.foreign = true
foreignCom1.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom1.apiVersion = '1_0'
foreignCom1.endPoint = 'http://remotehost'
foreignCom1.endPoint = 'http://remotehost/api'
foreignCom1.createdAt = new Date()
await DbCommunity.insert(foreignCom1)
@ -136,7 +137,7 @@ describe('CommunityResolver', () => {
foreignCom2.foreign = true
foreignCom2.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom2.apiVersion = '1_1'
foreignCom2.endPoint = 'http://remotehost'
foreignCom2.endPoint = 'http://remotehost/api'
foreignCom2.createdAt = new Date()
await DbCommunity.insert(foreignCom2)
@ -144,24 +145,24 @@ describe('CommunityResolver', () => {
foreignCom3.foreign = true
foreignCom3.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom3.apiVersion = '1_2'
foreignCom3.endPoint = 'http://remotehost'
foreignCom3.endPoint = 'http://remotehost/api'
foreignCom3.createdAt = new Date()
await DbCommunity.insert(foreignCom3)
})
it('returns 3x home and 3x foreign-community entries', async () => {
it('returns 3 home community and 3 foreign community entries', async () => {
await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: {
getCommunities: [
{
id: 1,
foreign: homeCom1.foreign,
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_0'),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -176,25 +177,25 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 3,
foreign: homeCom3.foreign,
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/2_0'),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
},
{
id: 4,
foreign: foreignCom1.foreign,
id: 6,
foreign: foreignCom3.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_0'),
url: expect.stringMatching('http://remotehost/api/1_2'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom1.createdAt.toISOString(),
createdAt: foreignCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -209,14 +210,14 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 6,
foreign: foreignCom3.foreign,
id: 4,
foreign: foreignCom1.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_2'),
url: expect.stringMatching('http://remotehost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom3.createdAt.toISOString(),
createdAt: foreignCom1.createdAt.toISOString(),
updatedAt: null,
},
],

View File

@ -11,7 +11,11 @@ export class CommunityResolver {
@Query(() => [Community])
async getCommunities(): Promise<Community[]> {
const dbCommunities: DbCommunity[] = await DbCommunity.find({
order: { foreign: 'ASC', publicKey: 'ASC', apiVersion: 'ASC' },
order: {
foreign: 'ASC',
createdAt: 'DESC',
lastAnnouncedAt: 'DESC',
},
})
return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom))
}

View File

@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
@ -12,7 +13,7 @@ import { GraphQLError } from 'graphql'
import { cleanDB, testEnvironment, resetToken } from '@test/helpers'
import { logger } from '@test/testSetup'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
login,

View File

@ -3,9 +3,8 @@ import { ContributionLink as DbContributionLink } from '@entity/ContributionLink
import { Decimal } from 'decimal.js-light'
import { Resolver, Args, Arg, Authorized, Mutation, Query, Int, Ctx } from 'type-graphql'
// TODO: this is a strange construct
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
import Paginated from '@arg/Paginated'
import { ContributionLinkArgs } from '@arg/ContributionLinkArgs'
import { Paginated } from '@arg/Paginated'
import { Order } from '@enum/Order'
import { ContributionLink } from '@model/ContributionLink'
import { ContributionLinkList } from '@model/ContributionLinkList'
@ -15,9 +14,9 @@ import {
EVENT_ADMIN_CONTRIBUTION_LINK_CREATE,
EVENT_ADMIN_CONTRIBUTION_LINK_DELETE,
EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import {
CONTRIBUTIONLINK_NAME_MAX_CHARS,

View File

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Event as DbEvent } from '@entity/Event'
import { GraphQLError } from 'graphql'
@ -13,7 +13,7 @@ import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
adminCreateContributionMessage,

View File

@ -6,8 +6,8 @@ import { User as DbUser } from '@entity/User'
import { UserContact as DbUserContact } from '@entity/UserContact'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
import ContributionMessageArgs from '@arg/ContributionMessageArgs'
import Paginated from '@arg/Paginated'
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
import { Paginated } from '@arg/Paginated'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionMessageType } from '@enum/MessageType'
import { Order } from '@enum/Order'
@ -18,9 +18,9 @@ import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import {
EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE,
EVENT_CONTRIBUTION_MESSAGE_CREATE,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
@Resolver()
export class ContributionMessageResolver {
@ -87,7 +87,7 @@ export class ContributionMessageResolver {
.select('cm')
.from(DbContributionMessage, 'cm')
.leftJoinAndSelect('cm.user', 'u')
.where({ contributionId: contributionId })
.where({ contributionId })
.orderBy('cm.createdAt', order)
.limit(pageSize)
.offset((currentPage - 1) * pageSize)

View File

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Contribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Transaction as DbTransaction } from '@entity/Transaction'
@ -32,7 +32,7 @@ import {
sendContributionDeletedEmail,
sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { creations } from '@/seeds/creation/index'
import { creationFactory } from '@/seeds/factory/creation'
import { userFactory } from '@/seeds/factory/user'

View File

@ -8,10 +8,10 @@ import { UserContact } from '@entity/UserContact'
import { Decimal } from 'decimal.js-light'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs'
import AdminUpdateContributionArgs from '@arg/AdminUpdateContributionArgs'
import ContributionArgs from '@arg/ContributionArgs'
import Paginated from '@arg/Paginated'
import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs'
import { AdminUpdateContributionArgs } from '@arg/AdminUpdateContributionArgs'
import { ContributionArgs } from '@arg/ContributionArgs'
import { Paginated } from '@arg/Paginated'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
import { ContributionMessageType } from '@enum/MessageType'
@ -38,9 +38,9 @@ import {
EVENT_ADMIN_CONTRIBUTION_DELETE,
EVENT_ADMIN_CONTRIBUTION_CONFIRM,
EVENT_ADMIN_CONTRIBUTION_DENY,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { calculateDecay } from '@/util/decay'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'

View File

@ -9,7 +9,7 @@ import { GraphQLError } from 'graphql'
import { testEnvironment, cleanDB } from '@test/helpers'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { createUser, setPassword, forgotPassword } from '@/seeds/graphql/mutations'
import { queryOptIn } from '@/seeds/graphql/queries'

View File

@ -1,17 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Resolver, Query, Args, Ctx, Authorized, Arg, Int, Float } from 'type-graphql'
import Paginated from '@arg/Paginated'
import { Paginated } from '@arg/Paginated'
import { Order } from '@enum/Order'
import { GdtEntryList } from '@model/GdtEntryList'
import { apiGet, apiPost } from '@/apis/HttpRequest'
import { RIGHTS } from '@/auth/RIGHTS'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
@Resolver()
export class GdtResolver {

View File

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getConnection } from '@dbTools/typeorm'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'

View File

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { Transaction } from '@entity/Transaction'
@ -18,7 +18,7 @@ import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { cleanDB, testEnvironment, resetToken, resetEntity } from '@test/helpers'
import { logger } from '@test/testSetup'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { creations } from '@/seeds/creation/index'
import { creationFactory } from '@/seeds/factory/creation'
import { transactionLinkFactory } from '@/seeds/factory/transactionLink'

View File

@ -9,9 +9,9 @@ import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query, Int } from 'type-graphql'
import Paginated from '@arg/Paginated'
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
import { Paginated } from '@arg/Paginated'
import { TransactionLinkArgs } from '@arg/TransactionLinkArgs'
import { TransactionLinkFilters } from '@arg/TransactionLinkFilters'
import { ContributionCycleType } from '@enum/ContributionCycleType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
@ -20,7 +20,7 @@ import { ContributionLink } from '@model/ContributionLink'
import { Decay } from '@model/Decay'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@model/User'
import QueryLinkResult from '@union/QueryLinkResult'
import { QueryLinkResult } from '@union/QueryLinkResult'
import { RIGHTS } from '@/auth/RIGHTS'
import {
@ -28,9 +28,9 @@ import {
EVENT_TRANSACTION_LINK_CREATE,
EVENT_TRANSACTION_LINK_DELETE,
EVENT_TRANSACTION_LINK_REDEEM,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { calculateDecay } from '@/util/decay'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
@ -39,7 +39,7 @@ import { calculateBalance } from '@/util/validate'
import { executeTransaction } from './TransactionResolver'
import { getUserCreation, validateContribution } from './util/creations'
import { getLastTransaction } from './util/getLastTransaction'
import transactionLinkList from './util/transactionLinkList'
import { transactionLinkList } from './util/transactionLinkList'
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {

View File

@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Event as DbEvent } from '@entity/Event'
import { Transaction } from '@entity/Transaction'
import { User } from '@entity/User'
@ -14,7 +14,7 @@ import { GraphQLError } from 'graphql'
import { cleanDB, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
confirmContribution,
@ -27,8 +27,6 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { findUserByEmail } from './UserResolver'
let mutate: any, query: any, con: any
let testEnv: any
@ -84,7 +82,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'wrong@email.com',
identifier: 'wrong@email.com',
amount: 100,
memo: 'test',
},
@ -112,22 +110,20 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'stephen@hawking.uk',
identifier: 'stephen@hawking.uk',
amount: 100,
memo: 'test',
},
}),
).toEqual(
expect.objectContaining({
errors: [new GraphQLError('The recipient account was deleted')],
errors: [new GraphQLError('No user to given contact')],
}),
)
})
it('logs the error thrown', async () => {
// find peter to check the log
const user = await findUserByEmail('stephen@hawking.uk')
expect(logger.error).toBeCalledWith('The recipient account was deleted', user)
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('No user to given contact', 'stephen@hawking.uk')
})
})
@ -143,22 +139,23 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'garrick@ollivander.com',
identifier: 'garrick@ollivander.com',
amount: 100,
memo: 'test',
},
}),
).toEqual(
expect.objectContaining({
errors: [new GraphQLError('The recipient account is not activated')],
errors: [new GraphQLError('No user with this credentials')],
}),
)
})
it('logs the error thrown', async () => {
// find peter to check the log
const user = await findUserByEmail('garrick@ollivander.com')
expect(logger.error).toBeCalledWith('The recipient account is not activated', user)
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'No user with this credentials',
'garrick@ollivander.com',
)
})
})
})
@ -178,7 +175,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'bob@baumeister.de',
identifier: 'bob@baumeister.de',
amount: 100,
memo: 'test',
},
@ -202,7 +199,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 100,
memo: 'test',
},
@ -226,7 +223,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 100,
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t',
},
@ -250,7 +247,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 100,
memo: 'testing',
},
@ -300,7 +297,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: -50,
memo: 'testing negative',
},
@ -323,7 +320,7 @@ describe('send coins', () => {
await mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 50,
memo: 'unrepeatable memo',
},
@ -380,7 +377,7 @@ describe('send coins', () => {
mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 10,
memo: 'first transaction',
},
@ -396,7 +393,7 @@ describe('send coins', () => {
mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 20,
memo: 'second transaction',
},
@ -412,7 +409,7 @@ describe('send coins', () => {
mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 30,
memo: 'third transaction',
},
@ -428,7 +425,7 @@ describe('send coins', () => {
mutate({
mutation: sendCoins,
variables: {
email: 'peter@lustig.de',
identifier: 'peter@lustig.de',
amount: 40,
memo: 'fourth transaction',
},

View File

@ -9,14 +9,13 @@ import { User as dbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import Paginated from '@arg/Paginated'
import TransactionSendArgs from '@arg/TransactionSendArgs'
import { Paginated } from '@arg/Paginated'
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
import { Order } from '@enum/Order'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
import { User } from '@model/User'
import { TransactionRepository } from '@repository/Transaction'
import { TransactionLinkRepository } from '@repository/TransactionLink'
import { RIGHTS } from '@/auth/RIGHTS'
@ -24,9 +23,9 @@ import {
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
} from '@/emails/sendEmailVariants'
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Event'
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { communityUser } from '@/util/communityUser'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
@ -35,8 +34,9 @@ import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualT
import { BalanceResolver } from './BalanceResolver'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
import { findUserByEmail } from './UserResolver'
import { findUserByIdentifier } from './util/findUserByIdentifier'
import { getLastTransaction } from './util/getLastTransaction'
import { getTransactionList } from './util/getTransactionList'
export const executeTransaction = async (
amount: Decimal,
@ -149,7 +149,6 @@ export const executeTransaction = async (
} finally {
await queryRunner.release()
}
logger.debug(`prepare Email for transaction received...`)
await sendTransactionReceivedEmail({
firstName: recipient.firstName,
lastName: recipient.lastName,
@ -210,8 +209,7 @@ export class TransactionResolver {
// find transactions
// first page can contain 26 due to virtual decay transaction
const offset = (currentPage - 1) * pageSize
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
const [userTransactions, userTransactionsCount] = await getTransactionList(
user.id,
pageSize,
offset,
@ -276,6 +274,7 @@ export class TransactionResolver {
firstDate || now,
lastDate || now,
self,
(userTransactions.length && userTransactions[0].balance) || new Decimal(0),
),
)
logger.debug(`transactions=${transactions}`)
@ -283,7 +282,7 @@ export class TransactionResolver {
}
// transactions
userTransactions.forEach((userTransaction) => {
userTransactions.forEach((userTransaction: dbTransaction) => {
const linkedUser =
userTransaction.typeId === TransactionTypeId.CREATION
? communityUser
@ -292,6 +291,15 @@ export class TransactionResolver {
})
logger.debug(`TransactionTypeId.CREATION: transactions=${transactions}`)
transactions.forEach((transaction: Transaction) => {
if (transaction.typeId !== TransactionTypeId.DECAY) {
const { balance, previousBalance, amount } = transaction
transaction.decay.decay = new Decimal(
Number(balance) - Number(amount) - Number(previousBalance),
).toDecimalPlaces(2, Decimal.ROUND_HALF_UP)
}
})
// Construct Result
return new TransactionList(await balanceResolver.balance(context), transactions)
}
@ -299,10 +307,10 @@ export class TransactionResolver {
@Authorized([RIGHTS.SEND_COINS])
@Mutation(() => Boolean)
async sendCoins(
@Args() { email, amount, memo }: TransactionSendArgs,
@Args() { identifier, amount, memo }: TransactionSendArgs,
@Ctx() context: Context,
): Promise<boolean> {
logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`)
logger.info(`sendCoins(identifier=${identifier}, amount=${amount}, memo=${memo})`)
if (amount.lte(0)) {
throw new LogError('Amount to send must be positive', amount)
}
@ -311,13 +319,9 @@ export class TransactionResolver {
const senderUser = getUser(context)
// validate recipient user
const recipientUser = await findUserByEmail(email)
if (recipientUser.deletedAt) {
throw new LogError('The recipient account was deleted', recipientUser)
}
const emailContact = recipientUser.emailContact
if (!emailContact.emailChecked) {
throw new LogError('The recipient account is not activated', recipientUser)
const recipientUser = await findUserByIdentifier(identifier)
if (!recipientUser) {
throw new LogError('The recipient user was not found', recipientUser)
}
await executeTransaction(amount, memo, senderUser, recipientUser)

View File

@ -5,13 +5,13 @@
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Event as DbEvent } from '@entity/Event'
import { TransactionLink } from '@entity/TransactionLink'
import { User } from '@entity/User'
import { UserContact } from '@entity/UserContact'
import { GraphQLError } from 'graphql'
import { validate as validateUUID, version as versionUUID } from 'uuid'
import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid'
import { OptInType } from '@enum/OptInType'
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
@ -20,13 +20,13 @@ import { ContributionLink } from '@model/ContributionLink'
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from '@/emails/sendEmailVariants'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
import { encryptPassword } from '@/password/PasswordEncryptor'
import { contributionLinkFactory } from '@/seeds/factory/contributionLink'
@ -46,7 +46,13 @@ import {
unDeleteUser,
sendActivationEmail,
} from '@/seeds/graphql/mutations'
import { verifyLogin, queryOptIn, searchAdminUsers, searchUsers } from '@/seeds/graphql/queries'
import {
verifyLogin,
queryOptIn,
searchAdminUsers,
searchUsers,
user as userQuery,
} from '@/seeds/graphql/queries'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
@ -1420,7 +1426,7 @@ describe('UserResolver', () => {
})
it('changes to gradidoID on login', async () => {
await mutate({ mutation: login, variables: variables })
await mutate({ mutation: login, variables })
const usercontact = await UserContact.findOneOrFail(
{ email: 'bibi@bloxberg.de' },
@ -1441,7 +1447,7 @@ describe('UserResolver', () => {
it('can login after password change', async () => {
resetToken()
expect(await mutate({ mutation: login, variables: variables })).toEqual(
expect(await mutate({ mutation: login, variables })).toEqual(
expect.objectContaining({
data: {
login: {
@ -2298,6 +2304,124 @@ describe('UserResolver', () => {
})
})
})
describe('user', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe('unauthenticated', () => {
it('throws and logs "401 Unauthorized" error', async () => {
await expect(
query({
query: userQuery,
variables: {
identifier: 'identifier',
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
expect(logger.error).toBeCalledWith('401 Unauthorized')
})
})
describe('authenticated', () => {
const uuid = uuidv4()
beforeAll(async () => {
user = await userFactory(testEnv, bibiBloxberg)
await mutate({
mutation: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
})
describe('identifier is no gradido ID and no email', () => {
it('throws and logs "Unknown identifier type" error', async () => {
await expect(
query({
query: userQuery,
variables: {
identifier: 'identifier',
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Unknown identifier type')],
}),
)
expect(logger.error).toBeCalledWith('Unknown identifier type', 'identifier')
})
})
describe('identifier is not found', () => {
it('throws and logs "No user found to given identifier" error', async () => {
await expect(
query({
query: userQuery,
variables: {
identifier: uuid,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user found to given identifier')],
}),
)
expect(logger.error).toBeCalledWith('No user found to given identifier', uuid)
})
})
describe('identifier is found via email', () => {
it('returns user', async () => {
await expect(
query({
query: userQuery,
variables: {
identifier: 'bibi@bloxberg.de',
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
user: {
firstName: 'Bibi',
lastName: 'Bloxberg',
},
},
errors: undefined,
}),
)
})
})
describe('identifier is found via gradidoID', () => {
it('returns user', async () => {
await expect(
query({
query: userQuery,
variables: {
identifier: user.gradidoID,
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
user: {
firstName: 'Bibi',
lastName: 'Bloxberg',
},
},
errors: undefined,
}),
)
})
})
})
})
})
describe('printTimeDuration', () => {

View File

@ -21,11 +21,11 @@ import {
} from 'type-graphql'
import { v4 as uuidv4 } from 'uuid'
import CreateUserArgs from '@arg/CreateUserArgs'
import Paginated from '@arg/Paginated'
import SearchUsersArgs from '@arg/SearchUsersArgs'
import UnsecureLoginArgs from '@arg/UnsecureLoginArgs'
import UpdateUserInfosArgs from '@arg/UpdateUserInfosArgs'
import { CreateUserArgs } from '@arg/CreateUserArgs'
import { Paginated } from '@arg/Paginated'
import { SearchUsersArgs } from '@arg/SearchUsersArgs'
import { UnsecureLoginArgs } from '@arg/UnsecureLoginArgs'
import { UpdateUserInfosArgs } from '@arg/UpdateUserInfosArgs'
import { OptInType } from '@enum/OptInType'
import { Order } from '@enum/Order'
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
@ -38,7 +38,7 @@ import { UserRepository } from '@repository/User'
import { klicktippSignIn } from '@/apis/KlicktippController'
import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
@ -59,12 +59,12 @@ import {
EVENT_ADMIN_USER_ROLE_SET,
EVENT_ADMIN_USER_DELETE,
EVENT_ADMIN_USER_UNDELETE,
} from '@/event/Event'
} from '@/event/Events'
import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddleware'
import { isValidPassword } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { communityDbUser } from '@/util/communityUser'
import { hasElopageBuys } from '@/util/hasElopageBuys'
@ -72,6 +72,7 @@ import { getTimeDurationObject, printTimeDuration } from '@/util/time'
import { FULL_CREATION_AVAILABLE } from './const/const'
import { getUserCreations } from './util/creations'
import { findUserByIdentifier } from './util/findUserByIdentifier'
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs
const random = require('random-bigint')
@ -97,6 +98,7 @@ const newEmailContact = (email: string, userId: number): DbUserContact => {
return emailContact
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const activationLink = (verificationCode: BigInt): string => {
logger.debug(`activationLink(${verificationCode})...`)
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, verificationCode.toString())
@ -819,11 +821,17 @@ export class UserResolver {
return true
}
@Authorized([RIGHTS.USER])
@Query(() => User)
async user(@Arg('identifier') identifier: string): Promise<User> {
return new User(await findUserByIdentifier(identifier))
}
}
export async function findUserByEmail(email: string): Promise<DbUser> {
const dbUserContact = await DbUserContact.findOneOrFail(
{ email: email },
{ email },
{ withDeleted: true, relations: ['user'] },
).catch(() => {
throw new LogError('No user with this credentials', email)
@ -834,7 +842,7 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
}
async function checkEmailExists(email: string): Promise<boolean> {
const userContact = await DbUserContact.findOne({ email: email }, { withDeleted: true })
const userContact = await DbUserContact.findOne({ email }, { withDeleted: true })
if (userContact) {
return true
}

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