mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into feat-previous-balance
This commit is contained in:
commit
c3ebc33ae5
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'jest', 'import'],
|
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'jest', 'import', 'n'],
|
||||||
extends: [
|
extends: [
|
||||||
'standard',
|
'standard',
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
@ -101,6 +101,45 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'import/prefer-default-export': 'off', // TODO
|
'import/prefer-default-export': 'off', // TODO
|
||||||
|
// n
|
||||||
|
'n/handle-callback-err': 'error',
|
||||||
|
'n/no-callback-literal': 'error',
|
||||||
|
'n/no-exports-assign': 'error',
|
||||||
|
'n/no-extraneous-import': 'error',
|
||||||
|
'n/no-extraneous-require': 'error',
|
||||||
|
'n/no-hide-core-modules': 'error',
|
||||||
|
'n/no-missing-import': 'off', // not compatible with typescript
|
||||||
|
'n/no-missing-require': 'error',
|
||||||
|
'n/no-new-require': 'error',
|
||||||
|
'n/no-path-concat': 'error',
|
||||||
|
'n/no-process-exit': 'error',
|
||||||
|
'n/no-unpublished-bin': 'error',
|
||||||
|
'n/no-unpublished-import': 'off', // TODO need to exclude seeds
|
||||||
|
'n/no-unpublished-require': 'error',
|
||||||
|
'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
|
||||||
|
'n/no-unsupported-features/es-builtins': 'error',
|
||||||
|
'n/no-unsupported-features/es-syntax': 'error',
|
||||||
|
'n/no-unsupported-features/node-builtins': 'error',
|
||||||
|
'n/process-exit-as-throw': 'error',
|
||||||
|
'n/shebang': 'error',
|
||||||
|
'n/callback-return': 'error',
|
||||||
|
'n/exports-style': 'error',
|
||||||
|
'n/file-extension-in-import': 'off',
|
||||||
|
'n/global-require': 'error',
|
||||||
|
'n/no-mixed-requires': 'error',
|
||||||
|
'n/no-process-env': 'error',
|
||||||
|
'n/no-restricted-import': 'error',
|
||||||
|
'n/no-restricted-require': 'error',
|
||||||
|
'n/no-sync': 'error',
|
||||||
|
'n/prefer-global/buffer': 'error',
|
||||||
|
'n/prefer-global/console': 'error',
|
||||||
|
'n/prefer-global/process': 'error',
|
||||||
|
'n/prefer-global/text-decoder': 'error',
|
||||||
|
'n/prefer-global/text-encoder': 'error',
|
||||||
|
'n/prefer-global/url': 'error',
|
||||||
|
'n/prefer-global/url-search-params': 'error',
|
||||||
|
'n/prefer-promises/dns': 'error',
|
||||||
|
'n/prefer-promises/fs': 'error',
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
// only for ts files
|
// only for ts files
|
||||||
|
|||||||
@ -22,10 +22,12 @@ module.exports = {
|
|||||||
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
||||||
'@test/(.*)': '<rootDir>/test/$1',
|
'@test/(.*)': '<rootDir>/test/$1',
|
||||||
'@entity/(.*)':
|
'@entity/(.*)':
|
||||||
|
// eslint-disable-next-line n/no-process-env
|
||||||
process.env.NODE_ENV === 'development'
|
process.env.NODE_ENV === 'development'
|
||||||
? '<rootDir>/../database/entity/$1'
|
? '<rootDir>/../database/entity/$1'
|
||||||
: '<rootDir>/../database/build/entity/$1',
|
: '<rootDir>/../database/build/entity/$1',
|
||||||
'@dbTools/(.*)':
|
'@dbTools/(.*)':
|
||||||
|
// eslint-disable-next-line n/no-process-env
|
||||||
process.env.NODE_ENV === 'development'
|
process.env.NODE_ENV === 'development'
|
||||||
? '<rootDir>/../database/src/$1'
|
? '<rootDir>/../database/src/$1'
|
||||||
: '<rootDir>/../database/build/src/$1',
|
: '<rootDir>/../database/build/src/$1',
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
"eslint-import-resolver-typescript": "^3.5.3",
|
"eslint-import-resolver-typescript": "^3.5.3",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-jest": "^27.2.1",
|
"eslint-plugin-jest": "^27.2.1",
|
||||||
|
"eslint-plugin-n": "^15.6.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-promise": "^5.1.0",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
@ -84,5 +85,8 @@
|
|||||||
"ignore": [
|
"ignore": [
|
||||||
"**/*.test.ts"
|
"**/*.test.ts"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export enum RIGHTS {
|
|||||||
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
|
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
|
||||||
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||||
OPEN_CREATIONS = 'OPEN_CREATIONS',
|
OPEN_CREATIONS = 'OPEN_CREATIONS',
|
||||||
|
USER = 'USER',
|
||||||
// Admin
|
// Admin
|
||||||
SEARCH_USERS = 'SEARCH_USERS',
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export const ROLE_USER = new Role('user', [
|
|||||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||||
RIGHTS.OPEN_CREATIONS,
|
RIGHTS.OPEN_CREATIONS,
|
||||||
|
RIGHTS.USER,
|
||||||
])
|
])
|
||||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||||
|
/* eslint-disable n/no-process-env */
|
||||||
|
|
||||||
import { Decimal } from 'decimal.js-light'
|
import { Decimal } from 'decimal.js-light'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ArgsType, Field } from 'type-graphql'
|
|||||||
@ArgsType()
|
@ArgsType()
|
||||||
export default class TransactionSendArgs {
|
export default class TransactionSendArgs {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
identifier: string
|
||||||
|
|
||||||
@Field(() => Decimal)
|
@Field(() => Decimal)
|
||||||
amount: Decimal
|
amount: Decimal
|
||||||
|
|||||||
@ -27,8 +27,6 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
|||||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
||||||
|
|
||||||
import { findUserByEmail } from './UserResolver'
|
|
||||||
|
|
||||||
let mutate: any, query: any, con: any
|
let mutate: any, query: any, con: any
|
||||||
let testEnv: any
|
let testEnv: any
|
||||||
|
|
||||||
@ -84,7 +82,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'wrong@email.com',
|
identifier: 'wrong@email.com',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'test',
|
memo: 'test',
|
||||||
},
|
},
|
||||||
@ -112,22 +110,20 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'stephen@hawking.uk',
|
identifier: 'stephen@hawking.uk',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'test',
|
memo: 'test',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('The recipient account was deleted')],
|
errors: [new GraphQLError('No user to given contact')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', async () => {
|
it('logs the error thrown', () => {
|
||||||
// find peter to check the log
|
expect(logger.error).toBeCalledWith('No user to given contact', 'stephen@hawking.uk')
|
||||||
const user = await findUserByEmail('stephen@hawking.uk')
|
|
||||||
expect(logger.error).toBeCalledWith('The recipient account was deleted', user)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -143,22 +139,23 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'garrick@ollivander.com',
|
identifier: 'garrick@ollivander.com',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'test',
|
memo: 'test',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
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 () => {
|
it('logs the error thrown', () => {
|
||||||
// find peter to check the log
|
expect(logger.error).toBeCalledWith(
|
||||||
const user = await findUserByEmail('garrick@ollivander.com')
|
'No user with this credentials',
|
||||||
expect(logger.error).toBeCalledWith('The recipient account is not activated', user)
|
'garrick@ollivander.com',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -178,7 +175,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'bob@baumeister.de',
|
identifier: 'bob@baumeister.de',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'test',
|
memo: 'test',
|
||||||
},
|
},
|
||||||
@ -202,7 +199,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'test',
|
memo: 'test',
|
||||||
},
|
},
|
||||||
@ -226,7 +223,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 100,
|
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',
|
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({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
memo: 'testing',
|
memo: 'testing',
|
||||||
},
|
},
|
||||||
@ -300,7 +297,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: -50,
|
amount: -50,
|
||||||
memo: 'testing negative',
|
memo: 'testing negative',
|
||||||
},
|
},
|
||||||
@ -323,7 +320,7 @@ describe('send coins', () => {
|
|||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 50,
|
amount: 50,
|
||||||
memo: 'unrepeatable memo',
|
memo: 'unrepeatable memo',
|
||||||
},
|
},
|
||||||
@ -380,7 +377,7 @@ describe('send coins', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 10,
|
amount: 10,
|
||||||
memo: 'first transaction',
|
memo: 'first transaction',
|
||||||
},
|
},
|
||||||
@ -396,7 +393,7 @@ describe('send coins', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 20,
|
amount: 20,
|
||||||
memo: 'second transaction',
|
memo: 'second transaction',
|
||||||
},
|
},
|
||||||
@ -412,7 +409,7 @@ describe('send coins', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 30,
|
amount: 30,
|
||||||
memo: 'third transaction',
|
memo: 'third transaction',
|
||||||
},
|
},
|
||||||
@ -428,7 +425,7 @@ describe('send coins', () => {
|
|||||||
mutate({
|
mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'peter@lustig.de',
|
identifier: 'peter@lustig.de',
|
||||||
amount: 40,
|
amount: 40,
|
||||||
memo: 'fourth transaction',
|
memo: 'fourth transaction',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualT
|
|||||||
|
|
||||||
import { BalanceResolver } from './BalanceResolver'
|
import { BalanceResolver } from './BalanceResolver'
|
||||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
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 { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
export const executeTransaction = async (
|
export const executeTransaction = async (
|
||||||
@ -150,7 +150,6 @@ export const executeTransaction = async (
|
|||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
logger.debug(`prepare Email for transaction received...`)
|
|
||||||
await sendTransactionReceivedEmail({
|
await sendTransactionReceivedEmail({
|
||||||
firstName: recipient.firstName,
|
firstName: recipient.firstName,
|
||||||
lastName: recipient.lastName,
|
lastName: recipient.lastName,
|
||||||
@ -302,10 +301,10 @@ export class TransactionResolver {
|
|||||||
@Authorized([RIGHTS.SEND_COINS])
|
@Authorized([RIGHTS.SEND_COINS])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async sendCoins(
|
async sendCoins(
|
||||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
@Args() { identifier, amount, memo }: TransactionSendArgs,
|
||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`)
|
logger.info(`sendCoins(identifier=${identifier}, amount=${amount}, memo=${memo})`)
|
||||||
if (amount.lte(0)) {
|
if (amount.lte(0)) {
|
||||||
throw new LogError('Amount to send must be positive', amount)
|
throw new LogError('Amount to send must be positive', amount)
|
||||||
}
|
}
|
||||||
@ -314,13 +313,9 @@ export class TransactionResolver {
|
|||||||
const senderUser = getUser(context)
|
const senderUser = getUser(context)
|
||||||
|
|
||||||
// validate recipient user
|
// validate recipient user
|
||||||
const recipientUser = await findUserByEmail(email)
|
const recipientUser = await findUserByIdentifier(identifier)
|
||||||
if (recipientUser.deletedAt) {
|
if (!recipientUser) {
|
||||||
throw new LogError('The recipient account was deleted', recipientUser)
|
throw new LogError('The recipient user was not found', recipientUser)
|
||||||
}
|
|
||||||
const emailContact = recipientUser.emailContact
|
|
||||||
if (!emailContact.emailChecked) {
|
|
||||||
throw new LogError('The recipient account is not activated', recipientUser)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { TransactionLink } from '@entity/TransactionLink'
|
|||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
import { UserContact } from '@entity/UserContact'
|
import { UserContact } from '@entity/UserContact'
|
||||||
import { GraphQLError } from 'graphql'
|
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 { OptInType } from '@enum/OptInType'
|
||||||
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
|
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
|
||||||
@ -46,7 +46,13 @@ import {
|
|||||||
unDeleteUser,
|
unDeleteUser,
|
||||||
sendActivationEmail,
|
sendActivationEmail,
|
||||||
} from '@/seeds/graphql/mutations'
|
} 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 { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||||
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
||||||
@ -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', () => {
|
describe('printTimeDuration', () => {
|
||||||
|
|||||||
@ -72,6 +72,7 @@ import { getTimeDurationObject, printTimeDuration } from '@/util/time'
|
|||||||
|
|
||||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||||
import { getUserCreations } from './util/creations'
|
import { getUserCreations } from './util/creations'
|
||||||
|
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs
|
||||||
const random = require('random-bigint')
|
const random = require('random-bigint')
|
||||||
@ -819,6 +820,12 @@ export class UserResolver {
|
|||||||
|
|
||||||
return true
|
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> {
|
export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||||
|
|||||||
@ -152,7 +152,7 @@ describe('semaphore', () => {
|
|||||||
})
|
})
|
||||||
const bibisTransaction = mutate({
|
const bibisTransaction = mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: { email: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' },
|
variables: { identifier: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' },
|
||||||
})
|
})
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: login,
|
mutation: login,
|
||||||
@ -168,7 +168,7 @@ describe('semaphore', () => {
|
|||||||
})
|
})
|
||||||
const bobsTransaction = mutate({
|
const bobsTransaction = mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: { email: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' },
|
variables: { identifier: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' },
|
||||||
})
|
})
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: login,
|
mutation: login,
|
||||||
|
|||||||
36
backend/src/graphql/resolver/util/findUserByIdentifier.ts
Normal file
36
backend/src/graphql/resolver/util/findUserByIdentifier.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { User as DbUser } from '@entity/User'
|
||||||
|
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||||
|
import { validate, version } from 'uuid'
|
||||||
|
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||||
|
let user: DbUser | undefined
|
||||||
|
if (validate(identifier) && version(identifier) === 4) {
|
||||||
|
user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] })
|
||||||
|
if (!user) {
|
||||||
|
throw new LogError('No user found to given identifier', identifier)
|
||||||
|
}
|
||||||
|
} else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) {
|
||||||
|
const userContact = await DbUserContact.findOne(
|
||||||
|
{
|
||||||
|
email: identifier,
|
||||||
|
emailChecked: true,
|
||||||
|
},
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
if (!userContact) {
|
||||||
|
throw new LogError('No user with this credentials', identifier)
|
||||||
|
}
|
||||||
|
if (!userContact.user) {
|
||||||
|
throw new LogError('No user to given contact', identifier)
|
||||||
|
}
|
||||||
|
user = userContact.user
|
||||||
|
user.emailContact = userContact
|
||||||
|
} else {
|
||||||
|
// last is alias when implemented
|
||||||
|
throw new LogError('Unknown identifier type', identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
// config
|
|
||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
import { startValidateCommunities } from './federation/validateCommunities'
|
import { startValidateCommunities } from './federation/validateCommunities'
|
||||||
import createServer from './server/createServer'
|
import createServer from './server/createServer'
|
||||||
@ -22,5 +20,5 @@ async function main() {
|
|||||||
main().catch((e) => {
|
main().catch((e) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(e)
|
console.error(e)
|
||||||
process.exit(1)
|
throw e
|
||||||
})
|
})
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
|||||||
{ root, args, context, info },
|
{ root, args, context, info },
|
||||||
next,
|
next,
|
||||||
) => {
|
) => {
|
||||||
|
// eslint-disable-next-line n/callback-return
|
||||||
const result = await next()
|
const result = await next()
|
||||||
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
|
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
|
||||||
if (CONFIG.KLICKTIPP) {
|
if (CONFIG.KLICKTIPP) {
|
||||||
|
|||||||
@ -75,8 +75,8 @@ export const sendActivationEmail = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const sendCoins = gql`
|
export const sendCoins = gql`
|
||||||
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
|
mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
|
||||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -340,3 +340,12 @@ export const listContributionMessages = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const user = gql`
|
||||||
|
query ($identifier: String!) {
|
||||||
|
user(identifier: $identifier) {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -61,6 +61,7 @@ ${JSON.stringify(requestContext.response.errors, null, 2)}`)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plugins =
|
const plugins =
|
||||||
|
// eslint-disable-next-line n/no-process-env
|
||||||
process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, logPlugin]
|
process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, logPlugin]
|
||||||
|
|
||||||
export default plugins
|
export default plugins
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { initialize } from '@dbTools/helpers'
|
|
||||||
import { entities } from '@entity/index'
|
import { entities } from '@entity/index'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
@ -40,7 +39,6 @@ export const testEnvironment = async (testLogger: any = logger, testI18n: any =
|
|||||||
const testClient = createTestClient(server.apollo)
|
const testClient = createTestClient(server.apollo)
|
||||||
const mutate = testClient.mutate
|
const mutate = testClient.mutate
|
||||||
const query = testClient.query
|
const query = testClient.query
|
||||||
await initialize()
|
|
||||||
return { mutate, query, con }
|
return { mutate, query, con }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1998,6 +1998,13 @@ buffer@^6.0.3:
|
|||||||
base64-js "^1.3.1"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.2.1"
|
ieee754 "^1.2.1"
|
||||||
|
|
||||||
|
builtins@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9"
|
||||||
|
integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==
|
||||||
|
dependencies:
|
||||||
|
semver "^7.0.0"
|
||||||
|
|
||||||
busboy@^0.3.1:
|
busboy@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
|
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
|
||||||
@ -2937,6 +2944,14 @@ eslint-plugin-es@^3.0.0:
|
|||||||
eslint-utils "^2.0.0"
|
eslint-utils "^2.0.0"
|
||||||
regexpp "^3.0.0"
|
regexpp "^3.0.0"
|
||||||
|
|
||||||
|
eslint-plugin-es@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz#f0822f0c18a535a97c3e714e89f88586a7641ec9"
|
||||||
|
integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==
|
||||||
|
dependencies:
|
||||||
|
eslint-utils "^2.0.0"
|
||||||
|
regexpp "^3.0.0"
|
||||||
|
|
||||||
eslint-plugin-import@^2.27.5:
|
eslint-plugin-import@^2.27.5:
|
||||||
version "2.27.5"
|
version "2.27.5"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65"
|
||||||
@ -2965,6 +2980,20 @@ eslint-plugin-jest@^27.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/utils" "^5.10.0"
|
"@typescript-eslint/utils" "^5.10.0"
|
||||||
|
|
||||||
|
eslint-plugin-n@^15.6.1:
|
||||||
|
version "15.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.6.1.tgz#f7e77f24abb92a550115cf11e29695da122c398c"
|
||||||
|
integrity sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA==
|
||||||
|
dependencies:
|
||||||
|
builtins "^5.0.1"
|
||||||
|
eslint-plugin-es "^4.1.0"
|
||||||
|
eslint-utils "^3.0.0"
|
||||||
|
ignore "^5.1.1"
|
||||||
|
is-core-module "^2.11.0"
|
||||||
|
minimatch "^3.1.2"
|
||||||
|
resolve "^1.22.1"
|
||||||
|
semver "^7.3.8"
|
||||||
|
|
||||||
eslint-plugin-node@^11.1.0:
|
eslint-plugin-node@^11.1.0:
|
||||||
version "11.1.0"
|
version "11.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
|
||||||
@ -6300,7 +6329,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
semver@^7.3.7:
|
semver@^7.0.0, semver@^7.3.7, semver@^7.3.8:
|
||||||
version "7.3.8"
|
version "7.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||||
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
||||||
|
|||||||
@ -4,5 +4,3 @@ DB_USER=root
|
|||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
MIGRATIONS_TABLE=migrations
|
MIGRATIONS_TABLE=migrations
|
||||||
|
|
||||||
TYPEORM_SEEDING_FACTORIES=src/factories/**/*{.ts,.js}
|
|
||||||
|
|||||||
@ -6,5 +6,3 @@ DB_USER=$DB_USER
|
|||||||
DB_PASSWORD=$DB_PASSWORD
|
DB_PASSWORD=$DB_PASSWORD
|
||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
MIGRATIONS_TABLE=migrations
|
MIGRATIONS_TABLE=migrations
|
||||||
|
|
||||||
TYPEORM_SEEDING_FACTORIES=src/factories/**/*{.ts,.js}
|
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
|
|
||||||
const CONFIG = require('./src/config')
|
|
||||||
|
|
||||||
module.export = {
|
|
||||||
name: 'default',
|
|
||||||
type: 'mysql',
|
|
||||||
host: CONFIG.DB_HOST,
|
|
||||||
port: CONFIG.DB_PORT,
|
|
||||||
username: CONFIG.DB_USER,
|
|
||||||
password: CONFIG.DB_PASSWORD,
|
|
||||||
database: CONFIG.DB_DATABASE,
|
|
||||||
seeds: ['src/seeds/**/*{.ts,.js}'],
|
|
||||||
factories: ['src/factories/**/*{.ts,.js}'],
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import CONFIG from './config'
|
|
||||||
import { createPool, PoolConfig } from 'mysql'
|
|
||||||
import { Migration } from 'ts-mysql-migrate'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
const poolConfig: PoolConfig = {
|
|
||||||
host: CONFIG.DB_HOST,
|
|
||||||
port: CONFIG.DB_PORT,
|
|
||||||
user: CONFIG.DB_USER,
|
|
||||||
password: CONFIG.DB_PASSWORD,
|
|
||||||
database: CONFIG.DB_DATABASE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pool?
|
|
||||||
const pool = createPool(poolConfig)
|
|
||||||
|
|
||||||
// Create & Initialize Migrations
|
|
||||||
const migration = new Migration({
|
|
||||||
conn: pool,
|
|
||||||
tableName: CONFIG.MIGRATIONS_TABLE,
|
|
||||||
silent: true,
|
|
||||||
dir: path.join(__dirname, '..', 'migrations'),
|
|
||||||
})
|
|
||||||
|
|
||||||
const initialize = async (): Promise<void> => {
|
|
||||||
await migration.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetDB = async (closePool = false): Promise<void> => {
|
|
||||||
await migration.reset() // use for resetting database
|
|
||||||
if (closePool) pool.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
export { resetDB, pool, migration, initialize }
|
|
||||||
@ -1,18 +1,29 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import prepare from './prepare'
|
import { createDatabase } from './prepare'
|
||||||
import connection from './typeorm/connection'
|
import CONFIG from './config'
|
||||||
import { resetDB, pool, migration } from './helpers'
|
|
||||||
|
import { createPool } from 'mysql'
|
||||||
|
import { Migration } from 'ts-mysql-migrate'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
const run = async (command: string) => {
|
const run = async (command: string) => {
|
||||||
// Database actions not supported by our migration library
|
// Database actions not supported by our migration library
|
||||||
await prepare()
|
await createDatabase()
|
||||||
|
|
||||||
// Database connection for TypeORM
|
|
||||||
const con = await connection()
|
|
||||||
if (!con || !con.isConnected) {
|
|
||||||
throw new Error(`Couldn't open connection to database`)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Initialize Migrations
|
||||||
|
const pool = createPool({
|
||||||
|
host: CONFIG.DB_HOST,
|
||||||
|
port: CONFIG.DB_PORT,
|
||||||
|
user: CONFIG.DB_USER,
|
||||||
|
password: CONFIG.DB_PASSWORD,
|
||||||
|
database: CONFIG.DB_DATABASE,
|
||||||
|
})
|
||||||
|
const migration = new Migration({
|
||||||
|
conn: pool,
|
||||||
|
tableName: CONFIG.MIGRATIONS_TABLE,
|
||||||
|
silent: true,
|
||||||
|
dir: path.join(__dirname, '..', 'migrations'),
|
||||||
|
})
|
||||||
await migration.initialize()
|
await migration.initialize()
|
||||||
|
|
||||||
// Execute command
|
// Execute command
|
||||||
@ -25,14 +36,13 @@ const run = async (command: string) => {
|
|||||||
break
|
break
|
||||||
case 'reset':
|
case 'reset':
|
||||||
// TODO protect from production
|
// TODO protect from production
|
||||||
await resetDB() // use for resetting database
|
await migration.reset()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported command ${command}`)
|
throw new Error(`Unsupported command ${command}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminate connections gracefully
|
// Terminate connections gracefully
|
||||||
await con.close()
|
|
||||||
pool.end()
|
pool.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import Decimal from 'decimal.js-light'
|
|
||||||
|
|
||||||
export interface TransactionContext {
|
|
||||||
typeId: number
|
|
||||||
userId: number
|
|
||||||
balance: Decimal
|
|
||||||
balanceDate: Date
|
|
||||||
amount: Decimal
|
|
||||||
memo: string
|
|
||||||
creationDate?: Date
|
|
||||||
sendReceiverUserId?: number
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
export interface UserContext {
|
|
||||||
pubKey?: Buffer
|
|
||||||
email?: string
|
|
||||||
firstName?: string
|
|
||||||
lastName?: string
|
|
||||||
deletedAt?: Date
|
|
||||||
password?: BigInt
|
|
||||||
privKey?: Buffer
|
|
||||||
emailHash?: Buffer
|
|
||||||
createdAt?: Date
|
|
||||||
emailChecked?: boolean
|
|
||||||
language?: string
|
|
||||||
publisherId?: number
|
|
||||||
passphrase?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerUserContext {
|
|
||||||
username?: string
|
|
||||||
password?: string
|
|
||||||
email?: string
|
|
||||||
role?: string
|
|
||||||
activated?: number
|
|
||||||
lastLogin?: Date
|
|
||||||
created?: Date
|
|
||||||
modified?: Date
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import Decimal from 'decimal.js-light'
|
|
||||||
|
|
||||||
export interface UserInterface {
|
|
||||||
// from user
|
|
||||||
email?: string
|
|
||||||
firstName?: string
|
|
||||||
lastName?: string
|
|
||||||
password?: BigInt
|
|
||||||
pubKey?: Buffer
|
|
||||||
privKey?: Buffer
|
|
||||||
emailHash?: Buffer
|
|
||||||
createdAt?: Date
|
|
||||||
emailChecked?: boolean
|
|
||||||
language?: string
|
|
||||||
deletedAt?: Date
|
|
||||||
publisherId?: number
|
|
||||||
passphrase?: string
|
|
||||||
// from server user
|
|
||||||
serverUserPassword?: string
|
|
||||||
role?: string
|
|
||||||
activated?: number
|
|
||||||
lastLogin?: Date
|
|
||||||
modified?: Date
|
|
||||||
// flag for admin
|
|
||||||
isAdmin?: boolean
|
|
||||||
// flag for balance (creation of 1000 GDD)
|
|
||||||
addBalance?: boolean
|
|
||||||
// balance
|
|
||||||
recordDate?: Date
|
|
||||||
creationDate?: Date
|
|
||||||
amount?: Decimal
|
|
||||||
}
|
|
||||||
@ -1,15 +1,8 @@
|
|||||||
/* PREPARE SCRIPT
|
import { createConnection } from 'mysql2/promise'
|
||||||
*
|
|
||||||
* This file ensures operations our migration library
|
|
||||||
* can not take care of are done.
|
|
||||||
* This applies to all Database Operations like
|
|
||||||
* creating, deleting, renaming Databases.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { createConnection, RowDataPacket } from 'mysql2/promise'
|
|
||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
export default async (): Promise<void> => {
|
export const createDatabase = async (): Promise<void> => {
|
||||||
const con = await createConnection({
|
const con = await createConnection({
|
||||||
host: CONFIG.DB_HOST,
|
host: CONFIG.DB_HOST,
|
||||||
port: CONFIG.DB_PORT,
|
port: CONFIG.DB_PORT,
|
||||||
@ -25,6 +18,8 @@ export default async (): Promise<void> => {
|
|||||||
DEFAULT CHARACTER SET utf8mb4
|
DEFAULT CHARACTER SET utf8mb4
|
||||||
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
/* LEGACY CODE
|
||||||
|
import { RowDataPacket } from 'mysql2/promise'
|
||||||
// Check if old migration table is present, delete if needed
|
// Check if old migration table is present, delete if needed
|
||||||
const [rows] = await con.query(`SHOW TABLES FROM \`${CONFIG.DB_DATABASE}\` LIKE 'migrations';`)
|
const [rows] = await con.query(`SHOW TABLES FROM \`${CONFIG.DB_DATABASE}\` LIKE 'migrations';`)
|
||||||
if ((<RowDataPacket>rows).length > 0) {
|
if ((<RowDataPacket>rows).length > 0) {
|
||||||
@ -37,6 +32,7 @@ export default async (): Promise<void> => {
|
|||||||
console.log('Found and dropped old migrations table')
|
console.log('Found and dropped old migrations table')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
await con.end()
|
await con.end()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { createConnection, Connection } from 'typeorm'
|
|
||||||
import CONFIG from '../config'
|
|
||||||
import { entities } from '../../entity/index'
|
|
||||||
|
|
||||||
const connection = async (): Promise<Connection | null> => {
|
|
||||||
let con = null
|
|
||||||
try {
|
|
||||||
con = await createConnection({
|
|
||||||
name: 'default',
|
|
||||||
type: 'mysql',
|
|
||||||
host: CONFIG.DB_HOST,
|
|
||||||
port: CONFIG.DB_PORT,
|
|
||||||
username: CONFIG.DB_USER,
|
|
||||||
password: CONFIG.DB_PASSWORD,
|
|
||||||
database: CONFIG.DB_DATABASE,
|
|
||||||
entities,
|
|
||||||
synchronize: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return con
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connection
|
|
||||||
@ -4,7 +4,6 @@
|
|||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import connection from '@/typeorm/connection'
|
import connection from '@/typeorm/connection'
|
||||||
import { checkDBVersion } from '@/typeorm/DBVersion'
|
import { checkDBVersion } from '@/typeorm/DBVersion'
|
||||||
import { initialize } from '@dbTools/helpers'
|
|
||||||
import { entities } from '@entity/index'
|
import { entities } from '@entity/index'
|
||||||
import { logger } from './testSetup'
|
import { logger } from './testSetup'
|
||||||
|
|
||||||
@ -42,7 +41,6 @@ export const testEnvironment = async () => {
|
|||||||
logger.fatal('Fatal: Database Version incorrect')
|
logger.fatal('Fatal: Database Version incorrect')
|
||||||
throw new Error('Fatal: Database Version incorrect')
|
throw new Error('Fatal: Database Version incorrect')
|
||||||
}
|
}
|
||||||
await initialize()
|
|
||||||
return { con }
|
return { con }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,9 +5,7 @@
|
|||||||
<b-row class="mt-5">
|
<b-row class="mt-5">
|
||||||
<b-col cols="2"></b-col>
|
<b-col cols="2"></b-col>
|
||||||
<b-col>
|
<b-col>
|
||||||
<div class="h4">
|
<div class="h4">{{ userName ? userName : identifier }}</div>
|
||||||
{{ email }}
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 h5">{{ $t('form.memo') }}</div>
|
<div class="mt-3 h5">{{ $t('form.memo') }}</div>
|
||||||
<div>{{ memo }}</div>
|
<div>{{ memo }}</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
@ -64,9 +62,10 @@ export default {
|
|||||||
name: 'TransactionConfirmationSend',
|
name: 'TransactionConfirmationSend',
|
||||||
props: {
|
props: {
|
||||||
balance: { type: Number, required: true },
|
balance: { type: Number, required: true },
|
||||||
email: { type: String, required: false, default: '' },
|
identifier: { type: String, required: false, default: '' },
|
||||||
amount: { type: Number, required: true },
|
amount: { type: Number, required: true },
|
||||||
memo: { type: String, required: true },
|
memo: { type: String, required: true },
|
||||||
|
userName: { type: String, default: '' },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -2,9 +2,17 @@ import { mount } from '@vue/test-utils'
|
|||||||
import TransactionForm from './TransactionForm'
|
import TransactionForm from './TransactionForm'
|
||||||
import flushPromises from 'flush-promises'
|
import flushPromises from 'flush-promises'
|
||||||
import { SEND_TYPES } from '@/pages/Send'
|
import { SEND_TYPES } from '@/pages/Send'
|
||||||
import DashboardLayout from '@/layouts/DashboardLayout'
|
import { createMockClient } from 'mock-apollo-client'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import { user as userQuery } from '@/graphql/queries'
|
||||||
|
|
||||||
|
const mockClient = createMockClient()
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
localVue.use(VueApollo)
|
||||||
|
|
||||||
describe('TransactionForm', () => {
|
describe('TransactionForm', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
@ -22,6 +30,7 @@ describe('TransactionForm', () => {
|
|||||||
},
|
},
|
||||||
$route: {
|
$route: {
|
||||||
params: {},
|
params: {},
|
||||||
|
query: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +43,24 @@ describe('TransactionForm', () => {
|
|||||||
localVue,
|
localVue,
|
||||||
mocks,
|
mocks,
|
||||||
propsData,
|
propsData,
|
||||||
provide: DashboardLayout.provide,
|
apolloProvider,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userQueryMock = jest.fn()
|
||||||
|
|
||||||
|
mockClient.setRequestHandler(
|
||||||
|
userQuery,
|
||||||
|
userQueryMock.mockRejectedValueOnce({ message: 'Query user name fails!' }).mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
@ -139,7 +162,7 @@ describe('TransactionForm', () => {
|
|||||||
.setValue(' valid@email.com ')
|
.setValue(' valid@email.com ')
|
||||||
await wrapper.find('div[data-test="input-email"]').find('input').trigger('blur')
|
await wrapper.find('div[data-test="input-email"]').find('input').trigger('blur')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.vm.form.email).toBe('valid@email.com')
|
expect(wrapper.vm.form.identifier).toBe('valid@email.com')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -290,12 +313,12 @@ Die ganze Welt bezwingen.“`)
|
|||||||
.find('textarea')
|
.find('textarea')
|
||||||
.setValue('Long enough')
|
.setValue('Long enough')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.vm.form.email).toBe('someone@watches.tv')
|
expect(wrapper.vm.form.identifier).toBe('someone@watches.tv')
|
||||||
expect(wrapper.vm.form.amount).toBe('87.23')
|
expect(wrapper.vm.form.amount).toBe('87.23')
|
||||||
expect(wrapper.vm.form.memo).toBe('Long enough')
|
expect(wrapper.vm.form.memo).toBe('Long enough')
|
||||||
await wrapper.find('button[type="reset"]').trigger('click')
|
await wrapper.find('button[type="reset"]').trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.vm.form.email).toBe('')
|
expect(wrapper.vm.form.identifier).toBe('')
|
||||||
expect(wrapper.vm.form.amount).toBe('')
|
expect(wrapper.vm.form.amount).toBe('')
|
||||||
expect(wrapper.vm.form.memo).toBe('')
|
expect(wrapper.vm.form.memo).toBe('')
|
||||||
})
|
})
|
||||||
@ -321,10 +344,11 @@ Die ganze Welt bezwingen.“`)
|
|||||||
expect(wrapper.emitted('set-transaction')).toEqual([
|
expect(wrapper.emitted('set-transaction')).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
email: 'someone@watches.tv',
|
identifier: 'someone@watches.tv',
|
||||||
amount: 87.23,
|
amount: 87.23,
|
||||||
memo: 'Long enough',
|
memo: 'Long enough',
|
||||||
selected: 'send',
|
selected: 'send',
|
||||||
|
userName: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
@ -346,5 +370,26 @@ Die ganze Welt bezwingen.“`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with gradido ID', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
mocks.$route.query.gradidoID = 'gradido-ID'
|
||||||
|
wrapper = Wrapper()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query for username with success', () => {
|
||||||
|
it('has no email input field', () => {
|
||||||
|
expect(wrapper.find('div[data-test="input-email"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('queries the username', () => {
|
||||||
|
expect(userQueryMock).toBeCalledWith({
|
||||||
|
identifier: 'gradido-ID',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -50,16 +50,24 @@
|
|||||||
<b-col>
|
<b-col>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col cols="12">
|
<b-col cols="12">
|
||||||
<div v-if="radioSelected === sendTypes.send">
|
<div v-if="radioSelected === sendTypes.send && !gradidoID">
|
||||||
<input-email
|
<input-email
|
||||||
:name="$t('form.recipient')"
|
:name="$t('form.recipient')"
|
||||||
:label="$t('form.recipient')"
|
:label="$t('form.recipient')"
|
||||||
:placeholder="$t('form.email')"
|
:placeholder="$t('form.email')"
|
||||||
v-model="form.email"
|
v-model="form.identifier"
|
||||||
:disabled="isBalanceDisabled"
|
:disabled="isBalanceDisabled"
|
||||||
@onValidation="onValidation"
|
@onValidation="onValidation"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="gradidoID" class="mb-4">
|
||||||
|
<b-row>
|
||||||
|
<b-col>{{ $t('form.recipient') }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-col class="font-weight-bold">{{ userName }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="12" lg="6">
|
<b-col cols="12" lg="6">
|
||||||
<input-amount
|
<input-amount
|
||||||
@ -121,6 +129,7 @@ import { SEND_TYPES } from '@/pages/Send'
|
|||||||
import InputEmail from '@/components/Inputs/InputEmail'
|
import InputEmail from '@/components/Inputs/InputEmail'
|
||||||
import InputAmount from '@/components/Inputs/InputAmount'
|
import InputAmount from '@/components/Inputs/InputAmount'
|
||||||
import InputTextarea from '@/components/Inputs/InputTextarea'
|
import InputTextarea from '@/components/Inputs/InputTextarea'
|
||||||
|
import { user as userQuery } from '@/graphql/queries'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TransactionForm',
|
name: 'TransactionForm',
|
||||||
@ -131,20 +140,20 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
balance: { type: Number, default: 0 },
|
balance: { type: Number, default: 0 },
|
||||||
email: { type: String, default: '' },
|
identifier: { type: String, default: '' },
|
||||||
amount: { type: Number, default: 0 },
|
amount: { type: Number, default: 0 },
|
||||||
memo: { type: String, default: '' },
|
memo: { type: String, default: '' },
|
||||||
selected: { type: String, default: 'send' },
|
selected: { type: String, default: 'send' },
|
||||||
},
|
},
|
||||||
inject: ['getTunneledEmail'],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
email: this.email,
|
identifier: this.identifier,
|
||||||
amount: this.amount ? String(this.amount) : '',
|
amount: this.amount ? String(this.amount) : '',
|
||||||
memo: this.memo,
|
memo: this.memo,
|
||||||
},
|
},
|
||||||
radioSelected: this.selected,
|
radioSelected: this.selected,
|
||||||
|
userName: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -152,33 +161,48 @@ export default {
|
|||||||
this.$refs.formValidator.validate()
|
this.$refs.formValidator.validate()
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
if (this.gradidoID) this.form.identifier = this.gradidoID
|
||||||
this.$emit('set-transaction', {
|
this.$emit('set-transaction', {
|
||||||
selected: this.radioSelected,
|
selected: this.radioSelected,
|
||||||
email: this.form.email,
|
identifier: this.form.identifier,
|
||||||
amount: Number(this.form.amount.replace(',', '.')),
|
amount: Number(this.form.amount.replace(',', '.')),
|
||||||
memo: this.form.memo,
|
memo: this.form.memo,
|
||||||
|
userName: this.userName,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onReset(event) {
|
onReset(event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.form.email = ''
|
this.form.identifier = ''
|
||||||
this.form.amount = ''
|
this.form.amount = ''
|
||||||
this.form.memo = ''
|
this.form.memo = ''
|
||||||
this.$refs.formValidator.validate()
|
this.$refs.formValidator.validate()
|
||||||
},
|
if (this.$route.query && !this.$route.query === {}) this.$router.replace({ query: undefined })
|
||||||
setNewRecipientEmail() {
|
|
||||||
this.form.email = this.recipientEmail ? this.recipientEmail : this.form.email
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
apollo: {
|
||||||
recipientEmail() {
|
UserName: {
|
||||||
this.setNewRecipientEmail()
|
query() {
|
||||||
|
return userQuery
|
||||||
|
},
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
variables() {
|
||||||
|
return { identifier: this.gradidoID }
|
||||||
|
},
|
||||||
|
skip() {
|
||||||
|
return !this.gradidoID
|
||||||
|
},
|
||||||
|
update({ user }) {
|
||||||
|
this.userName = `${user.firstName} ${user.lastName}`
|
||||||
|
},
|
||||||
|
error({ message }) {
|
||||||
|
this.toastError(message)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
disabled() {
|
disabled() {
|
||||||
if (
|
if (
|
||||||
this.form.email.length > 5 &&
|
this.form.identifier.length > 5 &&
|
||||||
parseInt(this.form.amount) <= parseInt(this.balance) &&
|
parseInt(this.form.amount) <= parseInt(this.balance) &&
|
||||||
this.form.memo.length > 5 &&
|
this.form.memo.length > 5 &&
|
||||||
this.form.memo.length <= 255
|
this.form.memo.length <= 255
|
||||||
@ -193,15 +217,12 @@ export default {
|
|||||||
sendTypes() {
|
sendTypes() {
|
||||||
return SEND_TYPES
|
return SEND_TYPES
|
||||||
},
|
},
|
||||||
recipientEmail() {
|
gradidoID() {
|
||||||
return this.getTunneledEmail()
|
return this.$route.query && this.$route.query.gradidoID
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
this.setNewRecipientEmail()
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.form.email !== '') this.$refs.formValidator.validate()
|
if (this.form.identifier !== '') this.$refs.formValidator.validate()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -31,15 +31,15 @@
|
|||||||
class="pointer mb-3 bg-white appBoxShadow gradido-border-radius p-3 test-list-group-item"
|
class="pointer mb-3 bg-white appBoxShadow gradido-border-radius p-3 test-list-group-item"
|
||||||
>
|
>
|
||||||
<template #SEND>
|
<template #SEND>
|
||||||
<transaction-send v-bind="transactions[index]" v-on="$listeners" />
|
<transaction-send v-bind="transactions[index]" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #RECEIVE>
|
<template #RECEIVE>
|
||||||
<transaction-receive v-bind="transactions[index]" v-on="$listeners" />
|
<transaction-receive v-bind="transactions[index]" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #CREATION>
|
<template #CREATION>
|
||||||
<transaction-creation v-bind="transactions[index]" v-on="$listeners" />
|
<transaction-creation v-bind="transactions[index]" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #LINK_SUMMARY>
|
<template #LINK_SUMMARY>
|
||||||
|
|||||||
@ -32,11 +32,7 @@
|
|||||||
<b-row>
|
<b-row>
|
||||||
<b-col>
|
<b-col>
|
||||||
<div class="font-weight-bold">
|
<div class="font-weight-bold">
|
||||||
<name
|
<name :linkedUser="transaction.linkedUser" fontColor="text-dark" />
|
||||||
:linkedUser="transaction.linkedUser"
|
|
||||||
v-on="$listeners"
|
|
||||||
fontColor="text-dark"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex mt-3">
|
<div class="d-flex mt-3">
|
||||||
<div class="small">
|
<div class="small">
|
||||||
|
|||||||
@ -3,9 +3,11 @@ import Name from './Name'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$router: {
|
$router: {
|
||||||
push: jest.fn(),
|
push: routerPushMock,
|
||||||
history: {
|
history: {
|
||||||
current: {
|
current: {
|
||||||
fullPath: '/transactions',
|
fullPath: '/transactions',
|
||||||
@ -47,7 +49,7 @@ describe('Name', () => {
|
|||||||
describe('with linked user', () => {
|
describe('with linked user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.setProps({
|
await wrapper.setProps({
|
||||||
linkedUser: { firstName: 'Bibi', lastName: 'Bloxberg', email: 'bibi@bloxberg.de' },
|
linkedUser: { firstName: 'Bibi', lastName: 'Bloxberg', gradidoID: 'gradido-ID' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -64,13 +66,17 @@ describe('Name', () => {
|
|||||||
await wrapper.find('div.gdd-transaction-list-item-name').find('a').trigger('click')
|
await wrapper.find('div.gdd-transaction-list-item-name').find('a').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits set tunneled email', () => {
|
it('pushes router to send', () => {
|
||||||
expect(wrapper.emitted('set-tunneled-email')).toEqual([['bibi@bloxberg.de']])
|
expect(routerPushMock).toBeCalledWith({
|
||||||
|
path: '/send',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pushes the route with query for email', () => {
|
it('pushes query for gradidoID', () => {
|
||||||
expect(mocks.$router.push).toBeCalledWith({
|
expect(routerPushMock).toBeCalledWith({
|
||||||
path: '/send',
|
query: {
|
||||||
|
gradidoID: 'gradido-ID',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<div class="gdd-transaction-list-item-name">
|
<div class="gdd-transaction-list-item-name">
|
||||||
<div v-if="linkedUser && linkedUser.email">
|
<div v-if="linkedUser && linkedUser.gradidoID">
|
||||||
<b-link @click.stop="tunnelEmail" :class="fontColor">
|
<b-link @click.stop="tunnelEmail" :class="fontColor">
|
||||||
{{ itemText }}
|
{{ itemText }}
|
||||||
</b-link>
|
</b-link>
|
||||||
@ -35,8 +35,8 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
tunnelEmail() {
|
tunnelEmail() {
|
||||||
this.$emit('set-tunneled-email', this.linkedUser.email)
|
|
||||||
if (this.$router.history.current.fullPath !== '/send') this.$router.push({ path: '/send' })
|
if (this.$router.history.current.fullPath !== '/send') this.$router.push({ path: '/send' })
|
||||||
|
this.$router.push({ query: { gradidoID: this.linkedUser.gradidoID } })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<name
|
<name
|
||||||
class="font-weight-bold"
|
class="font-weight-bold"
|
||||||
v-on="$listeners"
|
|
||||||
:amount="amount"
|
:amount="amount"
|
||||||
:linkedUser="linkedUser"
|
:linkedUser="linkedUser"
|
||||||
:linkId="linkId"
|
:linkId="linkId"
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<name
|
<name
|
||||||
class="font-weight-bold"
|
class="font-weight-bold"
|
||||||
v-on="$listeners"
|
|
||||||
:amount="amount"
|
:amount="amount"
|
||||||
:linkedUser="linkedUser"
|
:linkedUser="linkedUser"
|
||||||
:linkId="linkId"
|
:linkId="linkId"
|
||||||
|
|||||||
@ -69,8 +69,8 @@ export const createUser = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const sendCoins = gql`
|
export const sendCoins = gql`
|
||||||
mutation($email: String!, $amount: Decimal!, $memo: String!) {
|
mutation($identifier: String!, $amount: Decimal!, $memo: String!) {
|
||||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -144,6 +144,7 @@ export const createContributionMessage = gql`
|
|||||||
export const login = gql`
|
export const login = gql`
|
||||||
mutation($email: String!, $password: String!, $publisherId: Int) {
|
mutation($email: String!, $password: String!, $publisherId: Int) {
|
||||||
login(email: $email, password: $password, publisherId: $publisherId) {
|
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||||
|
gradidoID
|
||||||
email
|
email
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export const transactionsQuery = gql`
|
|||||||
linkedUser {
|
linkedUser {
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
|
gradidoID
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
decay {
|
decay {
|
||||||
@ -269,3 +270,12 @@ export const openCreations = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const user = gql`
|
||||||
|
query($identifier: String!) {
|
||||||
|
user(identifier: $identifier) {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -174,15 +174,6 @@ describe('DashboardLayout', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('set tunneled email', () => {
|
|
||||||
it('updates tunneled email', async () => {
|
|
||||||
await wrapper
|
|
||||||
.findComponent({ ref: 'router-view' })
|
|
||||||
.vm.$emit('set-tunneled-email', 'bibi@bloxberg.de')
|
|
||||||
expect(wrapper.vm.tunneledEmail).toBe('bibi@bloxberg.de')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a component Navbar', () => {
|
it('has a component Navbar', () => {
|
||||||
expect(wrapper.findComponent({ name: 'Navbar' }).exists()).toBe(true)
|
expect(wrapper.findComponent({ name: 'Navbar' }).exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -127,7 +127,6 @@
|
|||||||
:transactions="transactions"
|
:transactions="transactions"
|
||||||
:transactionCount="transactionCount"
|
:transactionCount="transactionCount"
|
||||||
:transactionLinkCount="transactionLinkCount"
|
:transactionLinkCount="transactionLinkCount"
|
||||||
@set-tunneled-email="setTunneledEmail"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #community>
|
<template #community>
|
||||||
@ -149,7 +148,6 @@
|
|||||||
:transactionLinkCount="transactionLinkCount"
|
:transactionLinkCount="transactionLinkCount"
|
||||||
:pending="pending"
|
:pending="pending"
|
||||||
@update-transactions="updateTransactions"
|
@update-transactions="updateTransactions"
|
||||||
@set-tunneled-email="setTunneledEmail"
|
|
||||||
></router-view>
|
></router-view>
|
||||||
</fade-transition>
|
</fade-transition>
|
||||||
</div>
|
</div>
|
||||||
@ -164,7 +162,6 @@
|
|||||||
:transactions="transactions"
|
:transactions="transactions"
|
||||||
:transactionCount="transactionCount"
|
:transactionCount="transactionCount"
|
||||||
:transactionLinkCount="transactionLinkCount"
|
:transactionLinkCount="transactionLinkCount"
|
||||||
@set-tunneled-email="setTunneledEmail"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #empty />
|
<template #empty />
|
||||||
@ -234,18 +231,12 @@ export default {
|
|||||||
transactionLinkCount: 0,
|
transactionLinkCount: 0,
|
||||||
pending: true,
|
pending: true,
|
||||||
visible: false,
|
visible: false,
|
||||||
tunneledEmail: null,
|
|
||||||
hamburger: true,
|
hamburger: true,
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
skeleton: true,
|
skeleton: true,
|
||||||
totalUsers: null,
|
totalUsers: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
getTunneledEmail: () => this.tunneledEmail,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
this.updateTransactions(0)
|
this.updateTransactions(0)
|
||||||
this.getCommunityStatistics()
|
this.getCommunityStatistics()
|
||||||
@ -319,9 +310,6 @@ export default {
|
|||||||
setVisible(bool) {
|
setVisible(bool) {
|
||||||
this.visible = bool
|
this.visible = bool
|
||||||
},
|
},
|
||||||
setTunneledEmail(email) {
|
|
||||||
this.tunneledEmail = email
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const apolloMutationMock = jest.fn()
|
|||||||
apolloMutationMock.mockResolvedValue('success')
|
apolloMutationMock.mockResolvedValue('success')
|
||||||
|
|
||||||
const navigatorClipboardMock = jest.fn()
|
const navigatorClipboardMock = jest.fn()
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ describe('Send', () => {
|
|||||||
$route: {
|
$route: {
|
||||||
query: {},
|
query: {},
|
||||||
},
|
},
|
||||||
|
$router: {
|
||||||
|
push: routerPushMock,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
@ -85,8 +89,8 @@ describe('Send', () => {
|
|||||||
it('shows the transaction formular again', () => {
|
it('shows the transaction formular again', () => {
|
||||||
expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBe(true)
|
expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBe(true)
|
||||||
})
|
})
|
||||||
// TODO:SKIPED at this point, a check must be made in the components ?
|
|
||||||
it.skip('restores the previous data in the formular', () => {
|
it('restores the previous data in the formular', () => {
|
||||||
expect(wrapper.find("input[type='email']").vm.$el.value).toBe('user@example.org')
|
expect(wrapper.find("input[type='email']").vm.$el.value).toBe('user@example.org')
|
||||||
expect(wrapper.find("input[type='text']").vm.$el.value).toBe('23.45')
|
expect(wrapper.find("input[type='text']").vm.$el.value).toBe('23.45')
|
||||||
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
|
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
|
||||||
@ -107,10 +111,11 @@ describe('Send', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'user@example.org',
|
identifier: 'user@example.org',
|
||||||
amount: 23.45,
|
amount: 23.45,
|
||||||
memo: 'Make the best of it!',
|
memo: 'Make the best of it!',
|
||||||
selected: SEND_TYPES.send,
|
selected: SEND_TYPES.send,
|
||||||
|
userName: '',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -162,6 +167,67 @@ describe('Send', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with gradidoID query', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$route.query.gradidoID = 'gradido-ID'
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no email input field', () => {
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'TransactionForm' }).find('input[type="email"]').exists(),
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('submit form', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const transactionForm = wrapper.findComponent({ name: 'TransactionForm' })
|
||||||
|
await transactionForm.find('input[type="text"]').setValue('34.56')
|
||||||
|
await transactionForm.find('textarea').setValue('Make the best of it!')
|
||||||
|
await transactionForm.find('form').trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('steps forward in the dialog', () => {
|
||||||
|
expect(wrapper.findComponent({ name: 'TransactionConfirmationSend' }).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('confirm transaction', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper
|
||||||
|
.findComponent({ name: 'TransactionConfirmationSend' })
|
||||||
|
.find('button.btn-gradido')
|
||||||
|
.trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API', async () => {
|
||||||
|
expect(apolloMutationMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
identifier: 'gradido-ID',
|
||||||
|
amount: 34.56,
|
||||||
|
memo: 'Make the best of it!',
|
||||||
|
selected: SEND_TYPES.send,
|
||||||
|
userName: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resets the gradido ID query in route', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith({
|
||||||
|
query: {
|
||||||
|
gradidoID: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('transaction form link', () => {
|
describe('transaction form link', () => {
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|||||||
@ -11,9 +11,7 @@
|
|||||||
<template #transactionConfirmationSend>
|
<template #transactionConfirmationSend>
|
||||||
<transaction-confirmation-send
|
<transaction-confirmation-send
|
||||||
:balance="balance"
|
:balance="balance"
|
||||||
:email="transactionData.email"
|
v-bind="transactionData"
|
||||||
:amount="transactionData.amount"
|
|
||||||
:memo="transactionData.memo"
|
|
||||||
@send-transaction="sendTransaction"
|
@send-transaction="sendTransaction"
|
||||||
@on-back="onBack"
|
@on-back="onBack"
|
||||||
></transaction-confirmation-send>
|
></transaction-confirmation-send>
|
||||||
@ -21,7 +19,7 @@
|
|||||||
<template #transactionConfirmationLink>
|
<template #transactionConfirmationLink>
|
||||||
<transaction-confirmation-link
|
<transaction-confirmation-link
|
||||||
:balance="balance"
|
:balance="balance"
|
||||||
:email="transactionData.email"
|
:email="transactionData.identifier"
|
||||||
:amount="transactionData.amount"
|
:amount="transactionData.amount"
|
||||||
:memo="transactionData.memo"
|
:memo="transactionData.memo"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@ -62,7 +60,7 @@ import TransactionResultLink from '@/components/GddSend/TransactionResultLink'
|
|||||||
import { sendCoins, createTransactionLink } from '@/graphql/mutations.js'
|
import { sendCoins, createTransactionLink } from '@/graphql/mutations.js'
|
||||||
|
|
||||||
const EMPTY_TRANSACTION_DATA = {
|
const EMPTY_TRANSACTION_DATA = {
|
||||||
email: '',
|
identifier: '',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
memo: '',
|
memo: '',
|
||||||
}
|
}
|
||||||
@ -168,6 +166,7 @@ export default {
|
|||||||
throw new Error(`undefined transactionData.selected : ${this.transactionData.selected}`)
|
throw new Error(`undefined transactionData.selected : ${this.transactionData.selected}`)
|
||||||
}
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
this.$router.push({ query: { gradidoID: undefined } })
|
||||||
},
|
},
|
||||||
onBack() {
|
onBack() {
|
||||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionForm
|
this.currentTransactionStep = TRANSACTION_STEPS.transactionForm
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
:showPagination="true"
|
:showPagination="true"
|
||||||
:pageSize="pageSize"
|
:pageSize="pageSize"
|
||||||
@update-transactions="updateTransactions"
|
@update-transactions="updateTransactions"
|
||||||
v-on="$listeners"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user