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,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'jest', 'import'],
|
||||
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'jest', 'import', 'n'],
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
@ -101,6 +101,45 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'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: [
|
||||
// only for ts files
|
||||
|
||||
@ -22,10 +22,12 @@ module.exports = {
|
||||
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
||||
'@test/(.*)': '<rootDir>/test/$1',
|
||||
'@entity/(.*)':
|
||||
// eslint-disable-next-line n/no-process-env
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '<rootDir>/../database/entity/$1'
|
||||
: '<rootDir>/../database/build/entity/$1',
|
||||
'@dbTools/(.*)':
|
||||
// eslint-disable-next-line n/no-process-env
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '<rootDir>/../database/src/$1'
|
||||
: '<rootDir>/../database/build/src/$1',
|
||||
|
||||
@ -65,6 +65,7 @@
|
||||
"eslint-import-resolver-typescript": "^3.5.3",
|
||||
"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",
|
||||
@ -84,5 +85,8 @@
|
||||
"ignore": [
|
||||
"**/*.test.ts"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,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',
|
||||
|
||||
@ -34,6 +34,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
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||
/* eslint-disable n/no-process-env */
|
||||
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
@ -4,7 +4,7 @@ import { ArgsType, Field } from 'type-graphql'
|
||||
@ArgsType()
|
||||
export default class TransactionSendArgs {
|
||||
@Field(() => String)
|
||||
email: string
|
||||
identifier: string
|
||||
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@ -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',
|
||||
},
|
||||
|
||||
@ -36,7 +36,7 @@ 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'
|
||||
|
||||
export const executeTransaction = async (
|
||||
@ -150,7 +150,6 @@ export const executeTransaction = async (
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
logger.debug(`prepare Email for transaction received...`)
|
||||
await sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
@ -302,10 +301,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)
|
||||
}
|
||||
@ -314,13 +313,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)
|
||||
|
||||
@ -11,7 +11,7 @@ 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'
|
||||
@ -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'
|
||||
@ -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', () => {
|
||||
|
||||
@ -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')
|
||||
@ -819,6 +820,12 @@ 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> {
|
||||
|
||||
@ -152,7 +152,7 @@ describe('semaphore', () => {
|
||||
})
|
||||
const bibisTransaction = mutate({
|
||||
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({
|
||||
mutation: login,
|
||||
@ -168,7 +168,7 @@ describe('semaphore', () => {
|
||||
})
|
||||
const bobsTransaction = mutate({
|
||||
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({
|
||||
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 */
|
||||
|
||||
// config
|
||||
import CONFIG from './config'
|
||||
import { startValidateCommunities } from './federation/validateCommunities'
|
||||
import createServer from './server/createServer'
|
||||
@ -22,5 +20,5 @@ async function main() {
|
||||
main().catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
throw e
|
||||
})
|
||||
|
||||
@ -28,6 +28,7 @@ export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||
{ root, args, context, info },
|
||||
next,
|
||||
) => {
|
||||
// eslint-disable-next-line n/callback-return
|
||||
const result = await next()
|
||||
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
|
||||
if (CONFIG.KLICKTIPP) {
|
||||
|
||||
@ -75,8 +75,8 @@ export const sendActivationEmail = gql`
|
||||
`
|
||||
|
||||
export const sendCoins = gql`
|
||||
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
|
||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
||||
mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
|
||||
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 =
|
||||
// eslint-disable-next-line n/no-process-env
|
||||
process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, logPlugin]
|
||||
|
||||
export default plugins
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { initialize } from '@dbTools/helpers'
|
||||
import { entities } from '@entity/index'
|
||||
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 mutate = testClient.mutate
|
||||
const query = testClient.query
|
||||
await initialize()
|
||||
return { mutate, query, con }
|
||||
}
|
||||
|
||||
|
||||
@ -1998,6 +1998,13 @@ buffer@^6.0.3:
|
||||
base64-js "^1.3.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:
|
||||
version "0.3.1"
|
||||
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"
|
||||
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:
|
||||
version "2.27.5"
|
||||
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:
|
||||
"@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:
|
||||
version "11.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
||||
|
||||
@ -4,5 +4,3 @@ DB_USER=root
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=gradido_community
|
||||
MIGRATIONS_TABLE=migrations
|
||||
|
||||
TYPEORM_SEEDING_FACTORIES=src/factories/**/*{.ts,.js}
|
||||
|
||||
@ -6,5 +6,3 @@ DB_USER=$DB_USER
|
||||
DB_PASSWORD=$DB_PASSWORD
|
||||
DB_DATABASE=gradido_community
|
||||
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 prepare from './prepare'
|
||||
import connection from './typeorm/connection'
|
||||
import { resetDB, pool, migration } from './helpers'
|
||||
import { createDatabase } from './prepare'
|
||||
import CONFIG from './config'
|
||||
|
||||
import { createPool } from 'mysql'
|
||||
import { Migration } from 'ts-mysql-migrate'
|
||||
import path from 'path'
|
||||
|
||||
const run = async (command: string) => {
|
||||
// Database actions not supported by our migration library
|
||||
await prepare()
|
||||
|
||||
// Database connection for TypeORM
|
||||
const con = await connection()
|
||||
if (!con || !con.isConnected) {
|
||||
throw new Error(`Couldn't open connection to database`)
|
||||
}
|
||||
await createDatabase()
|
||||
|
||||
// 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()
|
||||
|
||||
// Execute command
|
||||
@ -25,14 +36,13 @@ const run = async (command: string) => {
|
||||
break
|
||||
case 'reset':
|
||||
// TODO protect from production
|
||||
await resetDB() // use for resetting database
|
||||
await migration.reset()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported command ${command}`)
|
||||
}
|
||||
|
||||
// Terminate connections gracefully
|
||||
await con.close()
|
||||
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
|
||||
*
|
||||
* 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 } from 'mysql2/promise'
|
||||
|
||||
import { createConnection, RowDataPacket } from 'mysql2/promise'
|
||||
import CONFIG from './config'
|
||||
|
||||
export default async (): Promise<void> => {
|
||||
export const createDatabase = async (): Promise<void> => {
|
||||
const con = await createConnection({
|
||||
host: CONFIG.DB_HOST,
|
||||
port: CONFIG.DB_PORT,
|
||||
@ -25,6 +18,8 @@ export default async (): Promise<void> => {
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
||||
|
||||
/* LEGACY CODE
|
||||
import { RowDataPacket } from 'mysql2/promise'
|
||||
// Check if old migration table is present, delete if needed
|
||||
const [rows] = await con.query(`SHOW TABLES FROM \`${CONFIG.DB_DATABASE}\` LIKE 'migrations';`)
|
||||
if ((<RowDataPacket>rows).length > 0) {
|
||||
@ -37,6 +32,7 @@ export default async (): Promise<void> => {
|
||||
console.log('Found and dropped old migrations table')
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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 connection from '@/typeorm/connection'
|
||||
import { checkDBVersion } from '@/typeorm/DBVersion'
|
||||
import { initialize } from '@dbTools/helpers'
|
||||
import { entities } from '@entity/index'
|
||||
import { logger } from './testSetup'
|
||||
|
||||
@ -42,7 +41,6 @@ export const testEnvironment = async () => {
|
||||
logger.fatal('Fatal: Database Version incorrect')
|
||||
throw new Error('Fatal: Database Version incorrect')
|
||||
}
|
||||
await initialize()
|
||||
return { con }
|
||||
}
|
||||
|
||||
|
||||
@ -5,9 +5,7 @@
|
||||
<b-row class="mt-5">
|
||||
<b-col cols="2"></b-col>
|
||||
<b-col>
|
||||
<div class="h4">
|
||||
{{ email }}
|
||||
</div>
|
||||
<div class="h4">{{ userName ? userName : identifier }}</div>
|
||||
<div class="mt-3 h5">{{ $t('form.memo') }}</div>
|
||||
<div>{{ memo }}</div>
|
||||
</b-col>
|
||||
@ -64,9 +62,10 @@ export default {
|
||||
name: 'TransactionConfirmationSend',
|
||||
props: {
|
||||
balance: { type: Number, required: true },
|
||||
email: { type: String, required: false, default: '' },
|
||||
identifier: { type: String, required: false, default: '' },
|
||||
amount: { type: Number, required: true },
|
||||
memo: { type: String, required: true },
|
||||
userName: { type: String, default: '' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -2,9 +2,17 @@ import { mount } from '@vue/test-utils'
|
||||
import TransactionForm from './TransactionForm'
|
||||
import flushPromises from 'flush-promises'
|
||||
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
|
||||
localVue.use(VueApollo)
|
||||
|
||||
describe('TransactionForm', () => {
|
||||
let wrapper
|
||||
@ -22,6 +30,7 @@ describe('TransactionForm', () => {
|
||||
},
|
||||
$route: {
|
||||
params: {},
|
||||
query: {},
|
||||
},
|
||||
}
|
||||
|
||||
@ -34,10 +43,24 @@ describe('TransactionForm', () => {
|
||||
localVue,
|
||||
mocks,
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
@ -139,7 +162,7 @@ describe('TransactionForm', () => {
|
||||
.setValue(' valid@email.com ')
|
||||
await wrapper.find('div[data-test="input-email"]').find('input').trigger('blur')
|
||||
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')
|
||||
.setValue('Long enough')
|
||||
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.memo).toBe('Long enough')
|
||||
await wrapper.find('button[type="reset"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.form.email).toBe('')
|
||||
expect(wrapper.vm.form.identifier).toBe('')
|
||||
expect(wrapper.vm.form.amount).toBe('')
|
||||
expect(wrapper.vm.form.memo).toBe('')
|
||||
})
|
||||
@ -321,10 +344,11 @@ Die ganze Welt bezwingen.“`)
|
||||
expect(wrapper.emitted('set-transaction')).toEqual([
|
||||
[
|
||||
{
|
||||
email: 'someone@watches.tv',
|
||||
identifier: 'someone@watches.tv',
|
||||
amount: 87.23,
|
||||
memo: 'Long enough',
|
||||
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-row>
|
||||
<b-col cols="12">
|
||||
<div v-if="radioSelected === sendTypes.send">
|
||||
<div v-if="radioSelected === sendTypes.send && !gradidoID">
|
||||
<input-email
|
||||
:name="$t('form.recipient')"
|
||||
:label="$t('form.recipient')"
|
||||
:placeholder="$t('form.email')"
|
||||
v-model="form.email"
|
||||
v-model="form.identifier"
|
||||
:disabled="isBalanceDisabled"
|
||||
@onValidation="onValidation"
|
||||
/>
|
||||
</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 cols="12" lg="6">
|
||||
<input-amount
|
||||
@ -121,6 +129,7 @@ import { SEND_TYPES } from '@/pages/Send'
|
||||
import InputEmail from '@/components/Inputs/InputEmail'
|
||||
import InputAmount from '@/components/Inputs/InputAmount'
|
||||
import InputTextarea from '@/components/Inputs/InputTextarea'
|
||||
import { user as userQuery } from '@/graphql/queries'
|
||||
|
||||
export default {
|
||||
name: 'TransactionForm',
|
||||
@ -131,20 +140,20 @@ export default {
|
||||
},
|
||||
props: {
|
||||
balance: { type: Number, default: 0 },
|
||||
email: { type: String, default: '' },
|
||||
identifier: { type: String, default: '' },
|
||||
amount: { type: Number, default: 0 },
|
||||
memo: { type: String, default: '' },
|
||||
selected: { type: String, default: 'send' },
|
||||
},
|
||||
inject: ['getTunneledEmail'],
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
email: this.email,
|
||||
identifier: this.identifier,
|
||||
amount: this.amount ? String(this.amount) : '',
|
||||
memo: this.memo,
|
||||
},
|
||||
radioSelected: this.selected,
|
||||
userName: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -152,33 +161,48 @@ export default {
|
||||
this.$refs.formValidator.validate()
|
||||
},
|
||||
onSubmit() {
|
||||
if (this.gradidoID) this.form.identifier = this.gradidoID
|
||||
this.$emit('set-transaction', {
|
||||
selected: this.radioSelected,
|
||||
email: this.form.email,
|
||||
identifier: this.form.identifier,
|
||||
amount: Number(this.form.amount.replace(',', '.')),
|
||||
memo: this.form.memo,
|
||||
userName: this.userName,
|
||||
})
|
||||
},
|
||||
onReset(event) {
|
||||
event.preventDefault()
|
||||
this.form.email = ''
|
||||
this.form.identifier = ''
|
||||
this.form.amount = ''
|
||||
this.form.memo = ''
|
||||
this.$refs.formValidator.validate()
|
||||
},
|
||||
setNewRecipientEmail() {
|
||||
this.form.email = this.recipientEmail ? this.recipientEmail : this.form.email
|
||||
if (this.$route.query && !this.$route.query === {}) this.$router.replace({ query: undefined })
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
recipientEmail() {
|
||||
this.setNewRecipientEmail()
|
||||
apollo: {
|
||||
UserName: {
|
||||
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: {
|
||||
disabled() {
|
||||
if (
|
||||
this.form.email.length > 5 &&
|
||||
this.form.identifier.length > 5 &&
|
||||
parseInt(this.form.amount) <= parseInt(this.balance) &&
|
||||
this.form.memo.length > 5 &&
|
||||
this.form.memo.length <= 255
|
||||
@ -193,15 +217,12 @@ export default {
|
||||
sendTypes() {
|
||||
return SEND_TYPES
|
||||
},
|
||||
recipientEmail() {
|
||||
return this.getTunneledEmail()
|
||||
gradidoID() {
|
||||
return this.$route.query && this.$route.query.gradidoID
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setNewRecipientEmail()
|
||||
},
|
||||
mounted() {
|
||||
if (this.form.email !== '') this.$refs.formValidator.validate()
|
||||
if (this.form.identifier !== '') this.$refs.formValidator.validate()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -31,15 +31,15 @@
|
||||
class="pointer mb-3 bg-white appBoxShadow gradido-border-radius p-3 test-list-group-item"
|
||||
>
|
||||
<template #SEND>
|
||||
<transaction-send v-bind="transactions[index]" v-on="$listeners" />
|
||||
<transaction-send v-bind="transactions[index]" />
|
||||
</template>
|
||||
|
||||
<template #RECEIVE>
|
||||
<transaction-receive v-bind="transactions[index]" v-on="$listeners" />
|
||||
<transaction-receive v-bind="transactions[index]" />
|
||||
</template>
|
||||
|
||||
<template #CREATION>
|
||||
<transaction-creation v-bind="transactions[index]" v-on="$listeners" />
|
||||
<transaction-creation v-bind="transactions[index]" />
|
||||
</template>
|
||||
|
||||
<template #LINK_SUMMARY>
|
||||
|
||||
@ -32,11 +32,7 @@
|
||||
<b-row>
|
||||
<b-col>
|
||||
<div class="font-weight-bold">
|
||||
<name
|
||||
:linkedUser="transaction.linkedUser"
|
||||
v-on="$listeners"
|
||||
fontColor="text-dark"
|
||||
/>
|
||||
<name :linkedUser="transaction.linkedUser" fontColor="text-dark" />
|
||||
</div>
|
||||
<div class="d-flex mt-3">
|
||||
<div class="small">
|
||||
|
||||
@ -3,9 +3,11 @@ import Name from './Name'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const routerPushMock = jest.fn()
|
||||
|
||||
const mocks = {
|
||||
$router: {
|
||||
push: jest.fn(),
|
||||
push: routerPushMock,
|
||||
history: {
|
||||
current: {
|
||||
fullPath: '/transactions',
|
||||
@ -47,7 +49,7 @@ describe('Name', () => {
|
||||
describe('with linked user', () => {
|
||||
beforeEach(async () => {
|
||||
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')
|
||||
})
|
||||
|
||||
it('emits set tunneled email', () => {
|
||||
expect(wrapper.emitted('set-tunneled-email')).toEqual([['bibi@bloxberg.de']])
|
||||
it('pushes router to send', () => {
|
||||
expect(routerPushMock).toBeCalledWith({
|
||||
path: '/send',
|
||||
})
|
||||
})
|
||||
|
||||
it('pushes the route with query for email', () => {
|
||||
expect(mocks.$router.push).toBeCalledWith({
|
||||
path: '/send',
|
||||
it('pushes query for gradidoID', () => {
|
||||
expect(routerPushMock).toBeCalledWith({
|
||||
query: {
|
||||
gradidoID: 'gradido-ID',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="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">
|
||||
{{ itemText }}
|
||||
</b-link>
|
||||
@ -35,8 +35,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
tunnelEmail() {
|
||||
this.$emit('set-tunneled-email', this.linkedUser.email)
|
||||
if (this.$router.history.current.fullPath !== '/send') this.$router.push({ path: '/send' })
|
||||
this.$router.push({ query: { gradidoID: this.linkedUser.gradidoID } })
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
<div>
|
||||
<name
|
||||
class="font-weight-bold"
|
||||
v-on="$listeners"
|
||||
:amount="amount"
|
||||
:linkedUser="linkedUser"
|
||||
:linkId="linkId"
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
<div>
|
||||
<name
|
||||
class="font-weight-bold"
|
||||
v-on="$listeners"
|
||||
:amount="amount"
|
||||
:linkedUser="linkedUser"
|
||||
:linkId="linkId"
|
||||
|
||||
@ -69,8 +69,8 @@ export const createUser = gql`
|
||||
`
|
||||
|
||||
export const sendCoins = gql`
|
||||
mutation($email: String!, $amount: Decimal!, $memo: String!) {
|
||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
||||
mutation($identifier: String!, $amount: Decimal!, $memo: String!) {
|
||||
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
|
||||
}
|
||||
`
|
||||
|
||||
@ -144,6 +144,7 @@ export const createContributionMessage = gql`
|
||||
export const login = gql`
|
||||
mutation($email: String!, $password: String!, $publisherId: Int) {
|
||||
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||
gradidoID
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
|
||||
@ -39,6 +39,7 @@ export const transactionsQuery = gql`
|
||||
linkedUser {
|
||||
firstName
|
||||
lastName
|
||||
gradidoID
|
||||
email
|
||||
}
|
||||
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', () => {
|
||||
expect(wrapper.findComponent({ name: 'Navbar' }).exists()).toBe(true)
|
||||
})
|
||||
|
||||
@ -127,7 +127,6 @@
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
/>
|
||||
</template>
|
||||
<template #community>
|
||||
@ -149,7 +148,6 @@
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
:pending="pending"
|
||||
@update-transactions="updateTransactions"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
></router-view>
|
||||
</fade-transition>
|
||||
</div>
|
||||
@ -164,7 +162,6 @@
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
/>
|
||||
</template>
|
||||
<template #empty />
|
||||
@ -234,18 +231,12 @@ export default {
|
||||
transactionLinkCount: 0,
|
||||
pending: true,
|
||||
visible: false,
|
||||
tunneledEmail: null,
|
||||
hamburger: true,
|
||||
darkMode: false,
|
||||
skeleton: true,
|
||||
totalUsers: null,
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
getTunneledEmail: () => this.tunneledEmail,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateTransactions(0)
|
||||
this.getCommunityStatistics()
|
||||
@ -319,9 +310,6 @@ export default {
|
||||
setVisible(bool) {
|
||||
this.visible = bool
|
||||
},
|
||||
setTunneledEmail(email) {
|
||||
this.tunneledEmail = email
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -10,6 +10,7 @@ const apolloMutationMock = jest.fn()
|
||||
apolloMutationMock.mockResolvedValue('success')
|
||||
|
||||
const navigatorClipboardMock = jest.fn()
|
||||
const routerPushMock = jest.fn()
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -38,6 +39,9 @@ describe('Send', () => {
|
||||
$route: {
|
||||
query: {},
|
||||
},
|
||||
$router: {
|
||||
push: routerPushMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -85,8 +89,8 @@ describe('Send', () => {
|
||||
it('shows the transaction formular again', () => {
|
||||
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='text']").vm.$el.value).toBe('23.45')
|
||||
expect(wrapper.find('textarea').vm.$el.value).toBe('Make the best of it!')
|
||||
@ -107,10 +111,11 @@ describe('Send', () => {
|
||||
expect.objectContaining({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
email: 'user@example.org',
|
||||
identifier: 'user@example.org',
|
||||
amount: 23.45,
|
||||
memo: 'Make the best of it!',
|
||||
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', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -11,9 +11,7 @@
|
||||
<template #transactionConfirmationSend>
|
||||
<transaction-confirmation-send
|
||||
:balance="balance"
|
||||
:email="transactionData.email"
|
||||
:amount="transactionData.amount"
|
||||
:memo="transactionData.memo"
|
||||
v-bind="transactionData"
|
||||
@send-transaction="sendTransaction"
|
||||
@on-back="onBack"
|
||||
></transaction-confirmation-send>
|
||||
@ -21,7 +19,7 @@
|
||||
<template #transactionConfirmationLink>
|
||||
<transaction-confirmation-link
|
||||
:balance="balance"
|
||||
:email="transactionData.email"
|
||||
:email="transactionData.identifier"
|
||||
:amount="transactionData.amount"
|
||||
:memo="transactionData.memo"
|
||||
:loading="loading"
|
||||
@ -62,7 +60,7 @@ import TransactionResultLink from '@/components/GddSend/TransactionResultLink'
|
||||
import { sendCoins, createTransactionLink } from '@/graphql/mutations.js'
|
||||
|
||||
const EMPTY_TRANSACTION_DATA = {
|
||||
email: '',
|
||||
identifier: '',
|
||||
amount: 0,
|
||||
memo: '',
|
||||
}
|
||||
@ -168,6 +166,7 @@ export default {
|
||||
throw new Error(`undefined transactionData.selected : ${this.transactionData.selected}`)
|
||||
}
|
||||
this.loading = false
|
||||
this.$router.push({ query: { gradidoID: undefined } })
|
||||
},
|
||||
onBack() {
|
||||
this.currentTransactionStep = TRANSACTION_STEPS.transactionForm
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
@update-transactions="updateTransactions"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user