mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3215 from gradido/3213-feature-x-com-sendcoins-31-insert-recipient-as-foreign-user-in-users-table-after-x-com-sendcoins
feat(backend): x-com-sendcoins 31: insert recipient as foreign user in users table after x com sendcoins
This commit is contained in:
commit
53b2fe33a0
@ -12,7 +12,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_table',
|
||||
DB_VERSION: '0073-introduce_foreign_user_in_users_table',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
@ -26,4 +26,7 @@ export class SendCoinsArgs {
|
||||
|
||||
@Field(() => String)
|
||||
senderUserName: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
senderAlias?: string | null
|
||||
}
|
||||
|
||||
@ -7,9 +7,9 @@ import { IsPositiveDecimal } from '@/graphql/validator/Decimal'
|
||||
|
||||
@ArgsType()
|
||||
export class TransactionSendArgs {
|
||||
@Field(() => String, { nullable: true })
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
recipientCommunityIdentifier?: string | null | undefined
|
||||
recipientCommunityIdentifier: string
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
|
||||
13
backend/src/graphql/arg/UserArgs.ts
Normal file
13
backend/src/graphql/arg/UserArgs.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { IsString } from 'class-validator'
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class UserArgs {
|
||||
@Field({ nullable: false })
|
||||
@IsString()
|
||||
identifier: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
communityIdentifier?: string
|
||||
}
|
||||
@ -8,6 +8,8 @@ export class User {
|
||||
constructor(user: dbUser | null) {
|
||||
if (user) {
|
||||
this.id = user.id
|
||||
this.foreign = user.foreign
|
||||
this.communityUuid = user.communityUuid
|
||||
this.gradidoID = user.gradidoID
|
||||
this.alias = user.alias
|
||||
if (user.emailContact) {
|
||||
@ -30,6 +32,15 @@ export class User {
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => Boolean)
|
||||
foreign: boolean
|
||||
|
||||
@Field(() => String)
|
||||
communityUuid: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
communityName: string | null
|
||||
|
||||
@Field(() => String)
|
||||
gradidoID: string
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import { Transaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { logger } from '@test/testSetup'
|
||||
@ -71,12 +72,8 @@ let fedForeignCom: DbFederatedCommunity
|
||||
|
||||
describe('send coins', () => {
|
||||
beforeAll(async () => {
|
||||
peter = await userFactory(testEnv, peterLustig)
|
||||
bob = await userFactory(testEnv, bobBaumeister)
|
||||
await userFactory(testEnv, stephenHawking)
|
||||
await userFactory(testEnv, garrickOllivander)
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.communityUuid = '7f474922-b6d8-4b64-8cd0-ebf0a1d875aa'
|
||||
homeCom.communityUuid = uuidv4()
|
||||
homeCom.creationDate = new Date('2000-01-01')
|
||||
homeCom.description = 'homeCom description'
|
||||
homeCom.foreign = false
|
||||
@ -87,7 +84,7 @@ describe('send coins', () => {
|
||||
homeCom = await DbCommunity.save(homeCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.communityUuid = '7f474922-b6d8-4b64-8cd0-cea0a1d875bb'
|
||||
foreignCom.communityUuid = uuidv4()
|
||||
foreignCom.creationDate = new Date('2000-06-06')
|
||||
foreignCom.description = 'foreignCom description'
|
||||
foreignCom.foreign = true
|
||||
@ -98,6 +95,11 @@ describe('send coins', () => {
|
||||
foreignCom.authenticatedAt = new Date('2000-06-12')
|
||||
foreignCom = await DbCommunity.save(foreignCom)
|
||||
|
||||
peter = await userFactory(testEnv, peterLustig)
|
||||
bob = await userFactory(testEnv, bobBaumeister)
|
||||
await userFactory(testEnv, stephenHawking)
|
||||
await userFactory(testEnv, garrickOllivander)
|
||||
|
||||
bobData = {
|
||||
email: 'bob@baumeister.de',
|
||||
password: 'Aa12345_',
|
||||
|
||||
@ -38,7 +38,7 @@ import { calculateBalance } from '@/util/validate'
|
||||
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
||||
|
||||
import { BalanceResolver } from './BalanceResolver'
|
||||
import { isCommunityAuthenticated, isHomeCommunity } from './util/communities'
|
||||
import { getCommunity, getCommunityName, isHomeCommunity } from './util/communities'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { getTransactionList } from './util/getTransactionList'
|
||||
@ -47,6 +47,7 @@ import {
|
||||
processXComPendingSendCoins,
|
||||
} from './util/processXComSendCoins'
|
||||
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
|
||||
import { storeForeignUser } from './util/storeForeignUser'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
export const executeTransaction = async (
|
||||
@ -250,18 +251,50 @@ export class TransactionResolver {
|
||||
// find involved users; I am involved
|
||||
const involvedUserIds: number[] = [user.id]
|
||||
const involvedRemoteUsers: User[] = []
|
||||
userTransactions.forEach((transaction: dbTransaction) => {
|
||||
// userTransactions.forEach((transaction: dbTransaction) => {
|
||||
// use normal for loop because of timing problems with await in forEach-loop
|
||||
for (const transaction of userTransactions) {
|
||||
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
|
||||
involvedUserIds.push(transaction.linkedUserId)
|
||||
}
|
||||
if (!transaction.linkedUserId && transaction.linkedUserGradidoID) {
|
||||
const remoteUser = new User(null)
|
||||
remoteUser.gradidoID = transaction.linkedUserGradidoID
|
||||
remoteUser.firstName = transaction.linkedUserName
|
||||
remoteUser.lastName = '(GradidoID: ' + transaction.linkedUserGradidoID + ')'
|
||||
logger.debug(
|
||||
'search for remoteUser...',
|
||||
transaction.linkedUserCommunityUuid,
|
||||
transaction.linkedUserGradidoID,
|
||||
)
|
||||
const dbRemoteUser = await dbUser.findOne({
|
||||
where: [
|
||||
{
|
||||
foreign: true,
|
||||
communityUuid: transaction.linkedUserCommunityUuid ?? undefined,
|
||||
gradidoID: transaction.linkedUserGradidoID,
|
||||
},
|
||||
],
|
||||
})
|
||||
logger.debug('found dbRemoteUser:', dbRemoteUser)
|
||||
const remoteUser = new User(dbRemoteUser)
|
||||
if (dbRemoteUser === null) {
|
||||
logger.debug('no dbRemoteUser found, init from tx:', transaction)
|
||||
if (transaction.linkedUserCommunityUuid !== null) {
|
||||
remoteUser.communityUuid = transaction.linkedUserCommunityUuid
|
||||
}
|
||||
remoteUser.gradidoID = transaction.linkedUserGradidoID
|
||||
if (transaction.linkedUserName) {
|
||||
remoteUser.firstName = transaction.linkedUserName.slice(
|
||||
0,
|
||||
transaction.linkedUserName.indexOf(' '),
|
||||
)
|
||||
remoteUser.lastName = transaction.linkedUserName?.slice(
|
||||
transaction.linkedUserName.indexOf(' '),
|
||||
transaction.linkedUserName.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
remoteUser.communityName = await getCommunityName(remoteUser.communityUuid)
|
||||
involvedRemoteUsers.push(remoteUser)
|
||||
}
|
||||
})
|
||||
}
|
||||
logger.debug(`involvedUserIds=`, involvedUserIds)
|
||||
logger.debug(`involvedRemoteUsers=`, involvedRemoteUsers)
|
||||
|
||||
@ -336,7 +369,7 @@ export class TransactionResolver {
|
||||
(userTransactions.length && userTransactions[0].balance) || new Decimal(0),
|
||||
),
|
||||
)
|
||||
logger.debug(`transactions=${transactions}`)
|
||||
logger.debug(`transactions=`, transactions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,7 +396,7 @@ export class TransactionResolver {
|
||||
}
|
||||
transactions.push(new Transaction(userTransaction, self, linkedUser))
|
||||
})
|
||||
logger.debug(`TransactionTypeId.CREATION: transactions=${transactions}`)
|
||||
logger.debug(`TransactionTypeId.CREATION: transactions=`, transactions)
|
||||
|
||||
transactions.forEach((transaction: Transaction) => {
|
||||
if (transaction.typeId !== TransactionTypeId.DECAY) {
|
||||
@ -393,11 +426,16 @@ export class TransactionResolver {
|
||||
|
||||
if (!recipientCommunityIdentifier || (await isHomeCommunity(recipientCommunityIdentifier))) {
|
||||
// processing sendCoins within sender and recepient are both in home community
|
||||
// validate recipient user
|
||||
const recipientUser = await findUserByIdentifier(recipientIdentifier)
|
||||
const recipientUser = await findUserByIdentifier(
|
||||
recipientIdentifier,
|
||||
recipientCommunityIdentifier,
|
||||
)
|
||||
if (!recipientUser) {
|
||||
throw new LogError('The recipient user was not found', recipientUser)
|
||||
}
|
||||
if (recipientUser.foreign) {
|
||||
throw new LogError('Found foreign recipient user for a local transaction:', recipientUser)
|
||||
}
|
||||
|
||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||
logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser)
|
||||
@ -407,13 +445,17 @@ export class TransactionResolver {
|
||||
if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) {
|
||||
throw new LogError('X-Community sendCoins disabled per configuration!')
|
||||
}
|
||||
if (!(await isCommunityAuthenticated(recipientCommunityIdentifier))) {
|
||||
const recipCom = await getCommunity(recipientCommunityIdentifier)
|
||||
logger.debug('recipient commuity: ', recipCom)
|
||||
if (recipCom === null) {
|
||||
throw new LogError(
|
||||
'no recipient commuity found for identifier:',
|
||||
recipientCommunityIdentifier,
|
||||
)
|
||||
}
|
||||
if (recipCom !== null && recipCom.authenticatedAt === null) {
|
||||
throw new LogError('recipient commuity is connected, but still not authenticated yet!')
|
||||
}
|
||||
const recipCom = await DbCommunity.findOneOrFail({
|
||||
where: { communityUuid: recipientCommunityIdentifier },
|
||||
})
|
||||
logger.debug('recipient commuity: ', recipCom)
|
||||
let pendingResult: SendCoinsResult
|
||||
let committingResult: SendCoinsResult
|
||||
const creationDate = new Date()
|
||||
@ -438,7 +480,7 @@ export class TransactionResolver {
|
||||
amount,
|
||||
memo,
|
||||
senderUser,
|
||||
pendingResult.recipGradidoID,
|
||||
pendingResult,
|
||||
)
|
||||
logger.debug('processXComCommittingSendCoins result: ', committingResult)
|
||||
if (!committingResult.vote) {
|
||||
@ -451,6 +493,15 @@ export class TransactionResolver {
|
||||
memo,
|
||||
)
|
||||
}
|
||||
// after successful x-com-tx store the recipient as foreign user
|
||||
logger.debug('store recipient as foreign user...')
|
||||
if (await storeForeignUser(recipCom, committingResult)) {
|
||||
logger.info(
|
||||
'X-Com: new foreign user inserted successfully...',
|
||||
recipCom.communityUuid,
|
||||
committingResult.recipGradidoID,
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new LogError(
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { User } from '@entity/User'
|
||||
@ -171,6 +172,8 @@ describe('UserResolver', () => {
|
||||
referrerId: null,
|
||||
contributionLinkId: null,
|
||||
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
||||
communityUuid: null,
|
||||
foreign: false,
|
||||
},
|
||||
])
|
||||
const valUUID = validateUUID(user[0].gradidoID)
|
||||
@ -2500,6 +2503,39 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('user', () => {
|
||||
let homeCom1: DbCommunity
|
||||
let foreignCom1: DbCommunity
|
||||
|
||||
beforeAll(async () => {
|
||||
homeCom1 = DbCommunity.create()
|
||||
homeCom1.foreign = false
|
||||
homeCom1.url = 'http://localhost/api'
|
||||
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
|
||||
homeCom1.privateKey = Buffer.from('privateKey-HomeCommunity')
|
||||
homeCom1.communityUuid = uuidv4() // 'HomeCom-UUID'
|
||||
homeCom1.authenticatedAt = new Date()
|
||||
homeCom1.name = 'HomeCommunity-name'
|
||||
homeCom1.description = 'HomeCommunity-description'
|
||||
homeCom1.creationDate = new Date()
|
||||
await DbCommunity.insert(homeCom1)
|
||||
|
||||
foreignCom1 = DbCommunity.create()
|
||||
foreignCom1.foreign = true
|
||||
foreignCom1.url = 'http://stage-2.gradido.net/api'
|
||||
foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community')
|
||||
foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community')
|
||||
foreignCom1.communityUuid = uuidv4() // 'Stage2-Com-UUID'
|
||||
foreignCom1.authenticatedAt = new Date()
|
||||
foreignCom1.name = 'Stage-2_Community-name'
|
||||
foreignCom1.description = 'Stage-2_Community-description'
|
||||
foreignCom1.creationDate = new Date()
|
||||
await DbCommunity.insert(foreignCom1)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await DbCommunity.clear()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
@ -2546,6 +2582,7 @@ describe('UserResolver', () => {
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: 'identifier_is_no_valid_alias!',
|
||||
communityIdentifier: homeCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -2567,14 +2604,44 @@ describe('UserResolver', () => {
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: uuid,
|
||||
communityIdentifier: homeCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('No user found to given identifier')],
|
||||
errors: [new GraphQLError('No user found to given identifier(s)')],
|
||||
}),
|
||||
)
|
||||
expect(logger.error).toBeCalledWith('No user found to given identifier', uuid)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No user found to given identifier(s)',
|
||||
uuid,
|
||||
homeCom1.communityUuid,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('identifier is found via email, but not matching community', () => {
|
||||
it('returns user', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: 'bibi@bloxberg.de',
|
||||
communityIdentifier: foreignCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError('Found user to given contact, but belongs to other community'),
|
||||
],
|
||||
}),
|
||||
)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Found user to given contact, but belongs to other community',
|
||||
'bibi@bloxberg.de',
|
||||
foreignCom1.communityUuid,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -2585,15 +2652,16 @@ describe('UserResolver', () => {
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: 'bibi@bloxberg.de',
|
||||
communityIdentifier: homeCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
user: {
|
||||
user: expect.objectContaining({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
}),
|
||||
},
|
||||
errors: undefined,
|
||||
}),
|
||||
@ -2608,15 +2676,16 @@ describe('UserResolver', () => {
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: user.gradidoID,
|
||||
communityIdentifier: homeCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
user: {
|
||||
user: expect.objectContaining({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
}),
|
||||
},
|
||||
errors: undefined,
|
||||
}),
|
||||
@ -2631,15 +2700,16 @@ describe('UserResolver', () => {
|
||||
query: userQuery,
|
||||
variables: {
|
||||
identifier: 'bibi',
|
||||
communityIdentifier: homeCom1.communityUuid,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
user: {
|
||||
user: expect.objectContaining({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
}),
|
||||
},
|
||||
errors: undefined,
|
||||
}),
|
||||
|
||||
@ -12,6 +12,7 @@ import i18n from 'i18n'
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, Mutation, Int } from 'type-graphql'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { UserArgs } from '@arg//UserArgs'
|
||||
import { CreateUserArgs } from '@arg/CreateUserArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
@ -64,6 +65,7 @@ import random from 'random-bigint'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
|
||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||
import { getCommunityName, getHomeCommunity } from './util/communities'
|
||||
import { getUserCreations } from './util/creations'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { findUsers } from './util/findUsers'
|
||||
@ -809,8 +811,18 @@ export class UserResolver {
|
||||
|
||||
@Authorized([RIGHTS.USER])
|
||||
@Query(() => User)
|
||||
async user(@Arg('identifier') identifier: string): Promise<User> {
|
||||
return new User(await findUserByIdentifier(identifier))
|
||||
async user(
|
||||
@Args()
|
||||
{ identifier, communityIdentifier }: UserArgs,
|
||||
): Promise<User> {
|
||||
const foundDbUser = await findUserByIdentifier(identifier, communityIdentifier)
|
||||
const modelUser = new User(foundDbUser)
|
||||
if (!foundDbUser.communityUuid) {
|
||||
modelUser.communityName = (await Promise.resolve(getHomeCommunity())).name
|
||||
} else {
|
||||
modelUser.communityName = await getCommunityName(foundDbUser.communityUuid)
|
||||
}
|
||||
return modelUser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,12 @@ export async function isHomeCommunity(communityIdentifier: string): Promise<bool
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHomeCommunity(): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: [{ foreign: false }],
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCommunityUrl(communityIdentifier: string): Promise<string> {
|
||||
const community = await DbCommunity.findOneOrFail({
|
||||
where: [
|
||||
|
||||
@ -6,12 +6,18 @@ import { LogError } from '@/server/LogError'
|
||||
|
||||
import { VALID_ALIAS_REGEX } from './validateAlias'
|
||||
|
||||
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||
export const findUserByIdentifier = async (
|
||||
identifier: string,
|
||||
communityIdentifier?: string,
|
||||
): Promise<DbUser> => {
|
||||
let user: DbUser | null
|
||||
if (validate(identifier) && version(identifier) === 4) {
|
||||
user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] })
|
||||
user = await DbUser.findOne({
|
||||
where: { gradidoID: identifier, communityUuid: communityIdentifier },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)
|
||||
}
|
||||
} else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) {
|
||||
const userContact = await DbUserContact.findOne({
|
||||
@ -27,12 +33,22 @@ export const findUserByIdentifier = async (identifier: string): Promise<DbUser>
|
||||
if (!userContact.user) {
|
||||
throw new LogError('No user to given contact', identifier)
|
||||
}
|
||||
if (userContact.user.communityUuid !== communityIdentifier) {
|
||||
throw new LogError(
|
||||
'Found user to given contact, but belongs to other community',
|
||||
identifier,
|
||||
communityIdentifier,
|
||||
)
|
||||
}
|
||||
user = userContact.user
|
||||
user.emailContact = userContact
|
||||
} else if (VALID_ALIAS_REGEX.exec(identifier)) {
|
||||
user = await DbUser.findOne({ where: { alias: identifier }, relations: ['emailContact'] })
|
||||
user = await DbUser.findOne({
|
||||
where: { alias: identifier, communityUuid: communityIdentifier },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)
|
||||
}
|
||||
} else {
|
||||
throw new LogError('Unknown identifier type', identifier)
|
||||
|
||||
@ -85,6 +85,7 @@ export async function processXComPendingSendCoins(
|
||||
}
|
||||
args.senderUserUuid = sender.gradidoID
|
||||
args.senderUserName = fullName(sender.firstName, sender.lastName)
|
||||
args.senderAlias = sender.alias
|
||||
logger.debug(`X-Com: ready for voteForSendCoins with args=`, args)
|
||||
voteResult = await client.voteForSendCoins(args)
|
||||
logger.debug(`X-Com: returned from voteForSendCoins:`, voteResult)
|
||||
@ -158,7 +159,7 @@ export async function processXComCommittingSendCoins(
|
||||
amount: Decimal,
|
||||
memo: string,
|
||||
sender: dbUser,
|
||||
recipUuid: string,
|
||||
recipient: SendCoinsResult,
|
||||
): Promise<SendCoinsResult> {
|
||||
const sendCoinsResult = new SendCoinsResult()
|
||||
try {
|
||||
@ -170,7 +171,7 @@ export async function processXComCommittingSendCoins(
|
||||
amount,
|
||||
memo,
|
||||
sender,
|
||||
recipUuid,
|
||||
recipient,
|
||||
)
|
||||
// first find pending Tx with given parameters
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
@ -179,7 +180,7 @@ export async function processXComCommittingSendCoins(
|
||||
userName: fullName(sender.firstName, sender.lastName),
|
||||
linkedUserCommunityUuid:
|
||||
receiverCom.communityUuid ?? CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
|
||||
linkedUserGradidoID: recipUuid,
|
||||
linkedUserGradidoID: recipient.recipGradidoID ? recipient.recipGradidoID : undefined,
|
||||
typeId: TransactionTypeId.SEND,
|
||||
state: PendingTransactionState.NEW,
|
||||
balanceDate: creationDate,
|
||||
@ -212,6 +213,7 @@ export async function processXComCommittingSendCoins(
|
||||
if (pendingTx.userName) {
|
||||
args.senderUserName = pendingTx.userName
|
||||
}
|
||||
args.senderAlias = sender.alias
|
||||
logger.debug(`X-Com: ready for settleSendCoins with args=`, args)
|
||||
const acknowledge = await client.settleSendCoins(args)
|
||||
logger.debug(`X-Com: returnd from settleSendCoins:`, acknowledge)
|
||||
@ -235,6 +237,7 @@ export async function processXComCommittingSendCoins(
|
||||
)
|
||||
}
|
||||
sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID
|
||||
sendCoinsResult.recipAlias = recipient.recipAlias
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: `, err)
|
||||
|
||||
75
backend/src/graphql/resolver/util/storeForeignUser.ts
Normal file
75
backend/src/graphql/resolver/util/storeForeignUser.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
export async function storeForeignUser(
|
||||
recipCom: DbCommunity,
|
||||
committingResult: SendCoinsResult,
|
||||
): Promise<boolean> {
|
||||
if (recipCom.communityUuid !== null && committingResult.recipGradidoID !== null) {
|
||||
try {
|
||||
const user = await DbUser.findOne({
|
||||
where: {
|
||||
foreign: true,
|
||||
communityUuid: recipCom.communityUuid,
|
||||
gradidoID: committingResult.recipGradidoID,
|
||||
},
|
||||
})
|
||||
if (!user) {
|
||||
logger.debug(
|
||||
'X-Com: no foreignUser found for:',
|
||||
recipCom.communityUuid,
|
||||
committingResult.recipGradidoID,
|
||||
)
|
||||
let foreignUser = DbUser.create()
|
||||
foreignUser.foreign = true
|
||||
if (committingResult.recipAlias !== null) {
|
||||
foreignUser.alias = committingResult.recipAlias
|
||||
}
|
||||
foreignUser.communityUuid = recipCom.communityUuid
|
||||
if (committingResult.recipFirstName !== null) {
|
||||
foreignUser.firstName = committingResult.recipFirstName
|
||||
}
|
||||
if (committingResult.recipLastName !== null) {
|
||||
foreignUser.lastName = committingResult.recipLastName
|
||||
}
|
||||
foreignUser.gradidoID = committingResult.recipGradidoID
|
||||
foreignUser = await DbUser.save(foreignUser)
|
||||
logger.debug('X-Com: new foreignUser inserted:', foreignUser)
|
||||
|
||||
return true
|
||||
} else if (
|
||||
user.firstName !== committingResult.recipFirstName ||
|
||||
user.lastName !== committingResult.recipLastName ||
|
||||
user.alias !== committingResult.recipAlias
|
||||
) {
|
||||
logger.warn(
|
||||
'X-Com: foreignUser still exists, but with different name or alias:',
|
||||
user,
|
||||
committingResult,
|
||||
)
|
||||
if (committingResult.recipFirstName !== null) {
|
||||
user.firstName = committingResult.recipFirstName
|
||||
}
|
||||
if (committingResult.recipLastName !== null) {
|
||||
user.lastName = committingResult.recipLastName
|
||||
}
|
||||
if (committingResult.recipAlias !== null) {
|
||||
user.alias = committingResult.recipAlias
|
||||
}
|
||||
await DbUser.save(user)
|
||||
logger.debug('update recipient successful.', user)
|
||||
return true
|
||||
} else {
|
||||
logger.debug('X-Com: foreignUser still exists...:', user)
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('X-Com: error in storeForeignUser;', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
import { getHomeCommunity } from '@/graphql/resolver/util/communities'
|
||||
import { setUserRole } from '@/graphql/resolver/util/modifyUserRole'
|
||||
import { createUser, setPassword } from '@/seeds/graphql/mutations'
|
||||
import { UserInterface } from '@/seeds/users/UserInterface'
|
||||
@ -43,6 +44,15 @@ export const userFactory = async (
|
||||
}
|
||||
await dbUser.save()
|
||||
}
|
||||
try {
|
||||
const homeCom = await getHomeCommunity()
|
||||
if (homeCom.communityUuid) {
|
||||
dbUser.communityUuid = homeCom.communityUuid
|
||||
await User.save(dbUser)
|
||||
}
|
||||
} catch (err) {
|
||||
// no homeCommunity exists
|
||||
}
|
||||
|
||||
// get last changes of user from database
|
||||
dbUser = await User.findOneOrFail({
|
||||
|
||||
@ -370,10 +370,14 @@ export const adminListContributionMessages = gql`
|
||||
`
|
||||
|
||||
export const user = gql`
|
||||
query ($identifier: String!) {
|
||||
user(identifier: $identifier) {
|
||||
query ($identifier: String!, $communityIdentifier: String) {
|
||||
user(identifier: $identifier, communityIdentifier: $communityIdentifier) {
|
||||
firstName
|
||||
lastName
|
||||
foreign
|
||||
communityUuid
|
||||
gradidoID
|
||||
alias
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -33,21 +33,23 @@ const communityDbUser: dbUser = {
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
save: function (options?: SaveOptions): Promise<dbUser> {
|
||||
save: function (_options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
remove: function (options?: RemoveOptions): Promise<dbUser> {
|
||||
remove: function (_options?: RemoveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
softRemove: function (options?: SaveOptions): Promise<dbUser> {
|
||||
softRemove: function (_options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
recover: function (options?: SaveOptions): Promise<dbUser> {
|
||||
recover: function (_options?: SaveOptions): Promise<dbUser> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
reload: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
foreign: false,
|
||||
communityUuid: '55555555-4444-4333-2222-11111111',
|
||||
}
|
||||
const communityUser = new User(communityDbUser)
|
||||
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from '../Contribution'
|
||||
import { ContributionMessage } from '../ContributionMessage'
|
||||
import { UserContact } from '../UserContact'
|
||||
import { UserRole } from '../UserRole'
|
||||
|
||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
foreign: boolean
|
||||
|
||||
@Column({
|
||||
name: 'gradido_id',
|
||||
length: 36,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
gradidoID: string
|
||||
|
||||
@Column({
|
||||
name: 'community_uuid',
|
||||
type: 'char',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
communityUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'alias',
|
||||
length: 20,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
alias: string
|
||||
|
||||
@OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user)
|
||||
@JoinColumn({ name: 'email_id' })
|
||||
emailContact: UserContact
|
||||
|
||||
@Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
emailId: number | null
|
||||
|
||||
@Column({
|
||||
name: 'first_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
firstName: string
|
||||
|
||||
@Column({
|
||||
name: 'last_name',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: null,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
lastName: string
|
||||
|
||||
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false })
|
||||
createdAt: Date
|
||||
|
||||
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||
password: BigInt
|
||||
|
||||
@Column({
|
||||
name: 'password_encryption_type',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: false,
|
||||
default: 0,
|
||||
})
|
||||
passwordEncryptionType: number
|
||||
|
||||
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||
language: string
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
hideAmountGDD: boolean
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
hideAmountGDT: boolean
|
||||
|
||||
@OneToMany(() => UserRole, (userRole) => userRole.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
userRoles: UserRole[]
|
||||
|
||||
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||
referrerId?: number | null
|
||||
|
||||
@Column({
|
||||
name: 'contribution_link_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
contributionLinkId?: number | null
|
||||
|
||||
@Column({ name: 'publisher_id', default: 0 })
|
||||
publisherId: number
|
||||
|
||||
@OneToMany(() => Contribution, (contribution) => contribution.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
contributions?: Contribution[]
|
||||
|
||||
@OneToMany(() => ContributionMessage, (message) => message.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
messages?: ContributionMessage[]
|
||||
|
||||
@OneToMany(() => UserContact, (userContact: UserContact) => userContact.user)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
userContacts?: UserContact[]
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { User } from './0069-add_user_roles_table/User'
|
||||
export { User } from './0073-introduce_foreign_user_in_users_table/User'
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE `users` DROP KEY IF EXISTS `gradido_id`;')
|
||||
await queryFn('ALTER TABLE `users` DROP INDEX IF EXISTS `gradido_id`;')
|
||||
await queryFn('ALTER TABLE `users` DROP KEY IF EXISTS `alias`;')
|
||||
await queryFn('ALTER TABLE `users` DROP INDEX IF EXISTS `alias`;')
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `foreign` tinyint(4) NOT NULL DEFAULT 0 AFTER `id`;',
|
||||
)
|
||||
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `community_uuid` char(36) DEFAULT NULL NULL AFTER `gradido_id`;',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD CONSTRAINT uuid_key UNIQUE KEY (`gradido_id`, `community_uuid`);',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `users` ADD CONSTRAINT alias_key UNIQUE KEY (`alias`, `community_uuid`);',
|
||||
)
|
||||
// read the community uuid of the homeCommunity
|
||||
const result = await queryFn(`SELECT c.community_uuid from communities as c WHERE c.foreign = 0`)
|
||||
// and if uuid exists enter the home_community_uuid for all local users
|
||||
if (result && result[0]) {
|
||||
await queryFn(
|
||||
`UPDATE users as u SET u.community_uuid = "${result[0].community_uuid}" WHERE u.foreign = 0 AND u.community_uuid IS NULL`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE `users` DROP KEY IF EXISTS `uuid_key`;')
|
||||
await queryFn('ALTER TABLE `users` DROP KEY IF EXISTS `alias_key`;')
|
||||
|
||||
await queryFn('ALTER TABLE `users` DROP COLUMN IF EXISTS `foreign`;')
|
||||
await queryFn('ALTER TABLE `users` DROP COLUMN IF EXISTS `community_uuid`;')
|
||||
|
||||
await queryFn('ALTER TABLE `users` ADD CONSTRAINT gradido_id UNIQUE KEY (`gradido_id`);')
|
||||
await queryFn('ALTER TABLE `users` ADD CONSTRAINT alias UNIQUE KEY (`alias`);')
|
||||
}
|
||||
@ -4,7 +4,7 @@ import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_table',
|
||||
DB_VERSION: '0073-introduce_foreign_user_in_users_table',
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_table',
|
||||
DB_VERSION: '0073-introduce_foreign_user_in_users_table',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
@ -26,4 +26,7 @@ export class SendCoinsArgs {
|
||||
|
||||
@Field(() => String)
|
||||
senderUserName: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
senderAlias?: string | null
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import { Connection } from '@dbTools/typeorm'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
|
||||
let mutate: ApolloServerTestClient['mutate'], con: Connection
|
||||
let mutate: ApolloServerTestClient['mutate'] // , con: Connection
|
||||
// let query: ApolloServerTestClient['query']
|
||||
|
||||
let testEnv: {
|
||||
@ -35,13 +35,15 @@ beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger)
|
||||
mutate = testEnv.mutate
|
||||
// query = testEnv.query
|
||||
con = testEnv.con
|
||||
// con = testEnv.con
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await cleanDB()
|
||||
await con.destroy()
|
||||
if (!testEnv.con || !testEnv.con.isConnected) {
|
||||
await testEnv.con.close()
|
||||
}
|
||||
})
|
||||
|
||||
describe('SendCoinsResolver', () => {
|
||||
@ -92,6 +94,7 @@ describe('SendCoinsResolver', () => {
|
||||
|
||||
sendUser = DbUser.create()
|
||||
sendUser.alias = 'sendUser-alias'
|
||||
sendUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
sendUser.firstName = 'sendUser-FirstName'
|
||||
sendUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebc'
|
||||
sendUser.lastName = 'sendUser-LastName'
|
||||
@ -106,6 +109,7 @@ describe('SendCoinsResolver', () => {
|
||||
|
||||
recipUser = DbUser.create()
|
||||
recipUser.alias = 'recipUser-alias'
|
||||
recipUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
recipUser.firstName = 'recipUser-FirstName'
|
||||
recipUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebd'
|
||||
recipUser.lastName = 'recipUser-LastName'
|
||||
@ -134,6 +138,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
@ -163,6 +168,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
@ -180,7 +186,7 @@ describe('SendCoinsResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX voted', () => {
|
||||
describe('valid X-Com-TX voted per gradidoID', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
@ -196,6 +202,83 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX voted per alias', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.alias
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX voted per email', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipContact.email
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
@ -235,6 +318,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -255,6 +339,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -284,6 +369,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -317,6 +403,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -350,6 +437,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -370,6 +458,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -399,6 +488,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -432,6 +522,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -465,6 +556,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -489,6 +581,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
@ -518,6 +611,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
@ -551,6 +645,7 @@ describe('SendCoinsResolver', () => {
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
@ -573,7 +668,7 @@ async function newEmailContact(email: string, userId: number): Promise<DbUserCon
|
||||
emailContact.email = email
|
||||
emailContact.userId = userId
|
||||
emailContact.type = 'EMAIL'
|
||||
emailContact.emailChecked = false
|
||||
emailContact.emailChecked = true
|
||||
emailContact.emailOptInTypeId = 1
|
||||
emailContact.emailVerificationCode = '1' + userId
|
||||
return emailContact
|
||||
|
||||
@ -15,6 +15,7 @@ import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTra
|
||||
import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier'
|
||||
import { SendCoinsResult } from '../model/SendCoinsResult'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { storeForeignUser } from '../util/storeForeignUser'
|
||||
|
||||
@Resolver()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@ -34,6 +35,7 @@ export class SendCoinsResolver {
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
args.senderUserName,
|
||||
args.senderAlias,
|
||||
)
|
||||
const result = new SendCoinsResult()
|
||||
// first check if receiver community is correct
|
||||
@ -49,7 +51,10 @@ export class SendCoinsResolver {
|
||||
let receiverUser
|
||||
try {
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
receiverUser = await findUserByIdentifier(
|
||||
args.recipientUserIdentifier,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
} catch (err) {
|
||||
logger.error('Error in findUserByIdentifier:', err)
|
||||
throw new LogError(
|
||||
@ -236,6 +241,16 @@ export class SendCoinsResolver {
|
||||
logger.debug('XCom: settleSendCoins matching pendingTX for settlement...')
|
||||
|
||||
await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx)
|
||||
// after successful x-com-tx store the recipient as foreign user
|
||||
logger.debug('store recipient as foreign user...')
|
||||
if (await storeForeignUser(args)) {
|
||||
logger.info(
|
||||
'X-Com: new foreign user inserted successfully...',
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
)
|
||||
}
|
||||
|
||||
logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`)
|
||||
return true
|
||||
} else {
|
||||
|
||||
62
federation/src/graphql/api/1_0/util/storeForeignUser.ts
Normal file
62
federation/src/graphql/api/1_0/util/storeForeignUser.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
|
||||
export async function storeForeignUser(args: SendCoinsArgs): Promise<boolean> {
|
||||
if (args.senderCommunityUuid !== null && args.senderUserUuid !== null) {
|
||||
try {
|
||||
const user = await DbUser.findOne({
|
||||
where: {
|
||||
foreign: true,
|
||||
communityUuid: args.senderCommunityUuid,
|
||||
gradidoID: args.senderUserUuid,
|
||||
},
|
||||
})
|
||||
if (!user) {
|
||||
logger.debug(
|
||||
'X-Com: no foreignUser found for:',
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
)
|
||||
let foreignUser = DbUser.create()
|
||||
foreignUser.foreign = true
|
||||
if (args.senderAlias) {
|
||||
foreignUser.alias = args.senderAlias
|
||||
}
|
||||
foreignUser.communityUuid = args.senderCommunityUuid
|
||||
if (args.senderUserName !== null) {
|
||||
foreignUser.firstName = args.senderUserName.slice(0, args.senderUserName.indexOf(' '))
|
||||
foreignUser.lastName = args.senderUserName.slice(
|
||||
args.senderUserName.indexOf(' '),
|
||||
args.senderUserName.length,
|
||||
)
|
||||
}
|
||||
foreignUser.gradidoID = args.senderUserUuid
|
||||
foreignUser = await DbUser.save(foreignUser)
|
||||
logger.debug('X-Com: new foreignUser inserted:', foreignUser)
|
||||
|
||||
return true
|
||||
} else if (
|
||||
user.firstName !== args.senderUserName.slice(0, args.senderUserName.indexOf(' ')) ||
|
||||
user.lastName !==
|
||||
args.senderUserName.slice(args.senderUserName.indexOf(' '), args.senderUserName.length) ||
|
||||
user.alias !== args.senderAlias
|
||||
) {
|
||||
logger.warn(
|
||||
'X-Com: foreignUser still exists, but with different name or alias:',
|
||||
user,
|
||||
args,
|
||||
)
|
||||
return false
|
||||
} else {
|
||||
logger.debug('X-Com: foreignUser still exists...:', user)
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('X-Com: error in storeForeignUser;', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -6,12 +6,18 @@ import { LogError } from '@/server/LogError'
|
||||
|
||||
import { VALID_ALIAS_REGEX } from './validateAlias'
|
||||
|
||||
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||
export const findUserByIdentifier = async (
|
||||
identifier: string,
|
||||
communityIdentifier?: string,
|
||||
): Promise<DbUser> => {
|
||||
let user: DbUser | null
|
||||
if (validate(identifier) && version(identifier) === 4) {
|
||||
user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] })
|
||||
user = await DbUser.findOne({
|
||||
where: { gradidoID: identifier, communityUuid: communityIdentifier },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)
|
||||
}
|
||||
} else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) {
|
||||
const userContact = await DbUserContact.findOne({
|
||||
@ -27,12 +33,22 @@ export const findUserByIdentifier = async (identifier: string): Promise<DbUser>
|
||||
if (!userContact.user) {
|
||||
throw new LogError('No user to given contact', identifier)
|
||||
}
|
||||
if (userContact.user.communityUuid !== communityIdentifier) {
|
||||
throw new LogError(
|
||||
'Found user to given contact, but belongs to other community',
|
||||
identifier,
|
||||
communityIdentifier,
|
||||
)
|
||||
}
|
||||
user = userContact.user
|
||||
user.emailContact = userContact
|
||||
} else if (VALID_ALIAS_REGEX.exec(identifier)) {
|
||||
user = await DbUser.findOne({ where: { alias: identifier }, relations: ['emailContact'] })
|
||||
user = await DbUser.findOne({
|
||||
where: { alias: identifier, communityUuid: communityIdentifier },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)
|
||||
}
|
||||
} else {
|
||||
throw new LogError('Unknown identifier type', identifier)
|
||||
|
||||
@ -236,6 +236,7 @@ export default {
|
||||
update({ user, community }) {
|
||||
this.userName = `${user.firstName} ${user.lastName}`
|
||||
this.recipientCommunity.name = community.name
|
||||
this.recipientCommunity.uuid = this.communityUuid
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
|
||||
@ -36,13 +36,24 @@ export default {
|
||||
methods: {
|
||||
async tunnelEmail() {
|
||||
if (this.$route.path !== '/send') await this.$router.push({ path: '/send' })
|
||||
this.$router.push({ query: { gradidoID: this.linkedUser.gradidoID } })
|
||||
this.$router.push({
|
||||
query: {
|
||||
gradidoID: this.linkedUser.gradidoID,
|
||||
communityUuid: this.linkedUser.communityUuid,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
itemText() {
|
||||
return this.linkedUser
|
||||
? this.linkedUser.firstName + ' ' + this.linkedUser.lastName
|
||||
? this.linkedUser.alias
|
||||
? this.linkedUser.alias +
|
||||
(this.linkedUser.communityName ? ' / ' + this.linkedUser.communityName : '')
|
||||
: this.linkedUser.firstName +
|
||||
' ' +
|
||||
this.linkedUser.lastName +
|
||||
(this.linkedUser.communityName ? ' / ' + this.linkedUser.communityName : '')
|
||||
: this.text
|
||||
},
|
||||
},
|
||||
|
||||
@ -40,7 +40,10 @@ export const transactionsQuery = gql`
|
||||
linkedUser {
|
||||
firstName
|
||||
lastName
|
||||
communityUuid
|
||||
communityName
|
||||
gradidoID
|
||||
alias
|
||||
}
|
||||
decay {
|
||||
decay
|
||||
@ -281,6 +284,7 @@ export const user = gql`
|
||||
user(identifier: $identifier) {
|
||||
firstName
|
||||
lastName
|
||||
communityName
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user