mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch 'master' into 3204-release-1_23_3
This commit is contained in:
commit
c9ebb0ff3d
@ -1,4 +1,5 @@
|
||||
CONFIG_VERSION=$BACKEND_CONFIG_VERSION
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
CONFIG_VERSION=v20.2023-09-19
|
||||
|
||||
# Server
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
|
||||
@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 86,
|
||||
lines: 84,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
|
||||
@ -12,14 +12,14 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0071-add-pending_transactions-table',
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_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
|
||||
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v19.2023-08-25',
|
||||
EXPECTED: 'v20.2023-09-19',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
@ -122,8 +122,11 @@ if (
|
||||
}
|
||||
|
||||
const federation = {
|
||||
FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API ?? '1_0',
|
||||
FEDERATION_VALIDATE_COMMUNITY_TIMER:
|
||||
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000,
|
||||
FEDERATION_XCOM_SENDCOINS_ENABLED:
|
||||
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
|
||||
// default value for community-uuid is equal uuid of stage-3
|
||||
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID:
|
||||
process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc',
|
||||
|
||||
@ -5,8 +5,11 @@ import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { SendCoinsArgs } from './model/SendCoinsArgs'
|
||||
import { revertSendCoins } from './query/revertSendCoins'
|
||||
import { voteForSendCoins } from './query/voteForSendCoins'
|
||||
import { SendCoinsResult } from './model/SendCoinsResult'
|
||||
import { revertSendCoins as revertSendCoinsQuery } from './query/revertSendCoins'
|
||||
import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/revertSettledSendCoins'
|
||||
import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins'
|
||||
import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export class SendCoinsClient {
|
||||
@ -20,7 +23,7 @@ export class SendCoinsClient {
|
||||
dbCom.apiVersion
|
||||
}/`
|
||||
this.client = new GraphQLClient(this.endpoint, {
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
jsonSerializer: {
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify,
|
||||
@ -28,66 +31,111 @@ export class SendCoinsClient {
|
||||
})
|
||||
}
|
||||
|
||||
voteForSendCoins = async (args: SendCoinsArgs): Promise<string | undefined> => {
|
||||
async voteForSendCoins(args: SendCoinsArgs): Promise<SendCoinsResult> {
|
||||
logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
logger.debug(`X-Com: SendCoinsClient: voteForSendCoins with args=`, args)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(voteForSendCoins, { args })
|
||||
const { data } = await this.client.rawRequest(voteForSendCoinsQuery, { args })
|
||||
logger.debug(`X-Com: SendCoinsClient: after rawRequest...data:`, data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.voteForSendCoins?.voteForSendCoins) {
|
||||
logger.warn(
|
||||
'X-Com: voteForSendCoins failed with: ',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
data.voteForSendCoins.voteForSendCoins,
|
||||
)
|
||||
return
|
||||
if (!data?.voteForSendCoins?.vote) {
|
||||
logger.debug('X-Com: voteForSendCoins failed with: ', data)
|
||||
return new SendCoinsResult()
|
||||
}
|
||||
logger.debug(
|
||||
'X-Com: voteForSendCoins successful with result=',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
data.voteForSendCoins,
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
||||
return data.voteForSendCoins.voteForSendCoins
|
||||
const result = new SendCoinsResult()
|
||||
result.vote = true
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
result.recipGradidoID = data.voteForSendCoins.recipGradidoID
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
result.recipFirstName = data.voteForSendCoins.recipFirstName
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
result.recipLastName = data.voteForSendCoins.recipLastName
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
result.recipAlias = data.voteForSendCoins.recipAlias
|
||||
logger.debug('X-Com: voteForSendCoins successful with result=', result)
|
||||
return result
|
||||
} catch (err) {
|
||||
throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
revertSendCoins = async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
async revertSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
logger.debug(`X-Com: SendCoinsClient: revertSendCoins with args=`, args)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(revertSendCoins, { args })
|
||||
const { data } = await this.client.rawRequest(revertSendCoinsQuery, { args })
|
||||
logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.revertSendCoins?.revertSendCoins) {
|
||||
if (!data?.revertSendCoins) {
|
||||
logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint)
|
||||
return false
|
||||
}
|
||||
logger.debug(`X-Com: revertSendCoins successful from endpoint=${this.endpoint}`)
|
||||
logger.debug(
|
||||
`X-Com: SendCoinsClient: revertSendCoins successful from endpoint=${this.endpoint}`,
|
||||
)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
logger.error(
|
||||
`X-Com: SendCoinsClient: revertSendCoins failed for endpoint=${this.endpoint}`,
|
||||
err,
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
commitSendCoins = async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
logger.debug(`X-Com: commitSendCoins against endpoint='${this.endpoint}'...`)
|
||||
async settleSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
logger.debug(`X-Com: SendCoinsClient: settleSendCoins with args=`, args)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(commitSendCoins, { args })
|
||||
const { data } = await this.client.rawRequest(settleSendCoinsQuery, { args })
|
||||
logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.commitSendCoins?.acknowledged) {
|
||||
logger.warn('X-Com: commitSendCoins without response data from endpoint', this.endpoint)
|
||||
if (!data?.settleSendCoins) {
|
||||
logger.warn(
|
||||
'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint',
|
||||
this.endpoint,
|
||||
)
|
||||
return false
|
||||
}
|
||||
logger.debug(`X-Com: commitSendCoins successful from endpoint=${this.endpoint}`)
|
||||
logger.debug(
|
||||
`X-Com: SendCoinsClient: settleSendCoins successful from endpoint=${this.endpoint}`,
|
||||
)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(`X-Com: commitSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
throw new LogError(
|
||||
`X-Com: SendCoinsClient: settleSendCoins failed for endpoint=${this.endpoint}`,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async revertSettledSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
logger.debug(`X-Com: SendCoinsClient: revertSettledSendCoins with args=`, args)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(revertSettledSendCoinsQuery, { args })
|
||||
logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.revertSettledSendCoins) {
|
||||
logger.warn(
|
||||
'X-Com: SendCoinsClient: revertSettledSendCoins without response data from endpoint',
|
||||
this.endpoint,
|
||||
)
|
||||
return false
|
||||
}
|
||||
logger.debug(
|
||||
`X-Com: SendCoinsClient: revertSettledSendCoins successful from endpoint=${this.endpoint}`,
|
||||
)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(
|
||||
`X-Com: SendCoinsClient: revertSettledSendCoins failed for endpoint=${this.endpoint}`,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@ -4,10 +4,10 @@ import { ArgsType, Field } from 'type-graphql'
|
||||
@ArgsType()
|
||||
export class SendCoinsArgs {
|
||||
@Field(() => String)
|
||||
communityReceiverIdentifier: string
|
||||
recipientCommunityUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userReceiverIdentifier: string
|
||||
recipientUserIdentifier: string
|
||||
|
||||
@Field(() => String)
|
||||
creationDate: string
|
||||
@ -19,11 +19,11 @@ export class SendCoinsArgs {
|
||||
memo: string
|
||||
|
||||
@Field(() => String)
|
||||
communitySenderIdentifier: string
|
||||
senderCommunityUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userSenderIdentifier: string
|
||||
senderUserUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userSenderName: string
|
||||
senderUserName: string
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
@ObjectType()
|
||||
export class SendCoinsResult {
|
||||
constructor() {
|
||||
this.vote = false
|
||||
@ -9,9 +9,15 @@ export class SendCoinsResult {
|
||||
@Field(() => Boolean)
|
||||
vote: boolean
|
||||
|
||||
@Field(() => String)
|
||||
receiverFirstName: string
|
||||
@Field(() => String, { nullable: true })
|
||||
recipGradidoID: string | null
|
||||
|
||||
@Field(() => String)
|
||||
receiverLastName: string
|
||||
@Field(() => String, { nullable: true })
|
||||
recipFirstName: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipLastName: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipAlias: string | null
|
||||
}
|
||||
|
||||
@ -1,25 +1,30 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
revertSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
/*
|
||||
mutation (
|
||||
$communityReceiverIdentifier: String!
|
||||
$userReceiverIdentifier: String!
|
||||
$recipientCommunityUuid: String!
|
||||
$recipientUserIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$communitySenderIdentifier: String!
|
||||
$userSenderIdentifier: String!
|
||||
$userSenderName: String!
|
||||
$senderCommunityUuid: String!
|
||||
$senderUserUuid: String!
|
||||
$senderUserName: String!
|
||||
) {
|
||||
revertSendCoins(
|
||||
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||
userReceiverIdentifier: $userReceiverIdentifier
|
||||
recipientCommunityUuid: $recipientCommunityUuid
|
||||
recipientUserIdentifier: $recipientUserIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communitySenderIdentifier: $communitySenderIdentifier
|
||||
userSenderIdentifier: $userSenderIdentifier
|
||||
userSenderName: $userSenderName
|
||||
senderCommunityUuid: $senderCommunityUuid
|
||||
senderUserUuid: $senderUserUuid
|
||||
senderUserName: $senderUserName
|
||||
)
|
||||
}
|
||||
`
|
||||
*/
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSettledSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
revertSettledSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
/*
|
||||
|
||||
mutation (
|
||||
$recipientCommunityUuid: String!
|
||||
$recipientUserIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$senderCommunityUuid: String!
|
||||
$senderUserUuid: String!
|
||||
$senderUserName: String!
|
||||
) {
|
||||
revertSettledSendCoins(
|
||||
recipientCommunityUuid: $recipientCommunityUuid
|
||||
recipientUserIdentifier: $recipientUserIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
senderCommunityUuid: $senderCommunityUuid
|
||||
senderUserUuid: $senderUserUuid
|
||||
senderUserName: $senderUserName
|
||||
)
|
||||
}
|
||||
*/
|
||||
30
backend/src/federation/client/1_0/query/settleSendCoins.ts
Normal file
30
backend/src/federation/client/1_0/query/settleSendCoins.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const settleSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
settleSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
/*
|
||||
mutation (
|
||||
$recipientCommunityUuid: String!
|
||||
$recipientUserIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$senderCommunityUuid: String!
|
||||
$senderUserUuid: String!
|
||||
$senderUserName: String!
|
||||
) {
|
||||
settleSendCoins(
|
||||
recipientCommunityUuid: $recipientCommunityUuid
|
||||
recipientUserIdentifier: $recipientUserIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
senderCommunityUuid: $senderCommunityUuid
|
||||
senderUserUuid: $senderUserUuid
|
||||
senderUserName: $senderUserName
|
||||
)
|
||||
}
|
||||
*/
|
||||
@ -1,25 +1,13 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const voteForSendCoins = gql`
|
||||
mutation (
|
||||
$communityReceiverIdentifier: String!
|
||||
$userReceiverIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$communitySenderIdentifier: String!
|
||||
$userSenderIdentifier: String!
|
||||
$userSenderName: String!
|
||||
) {
|
||||
voteForSendCoins(
|
||||
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||
userReceiverIdentifier: $userReceiverIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communitySenderIdentifier: $communitySenderIdentifier
|
||||
userSenderIdentifier: $userSenderIdentifier
|
||||
userSenderName: $userSenderName
|
||||
)
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
voteForSendCoins(data: $args) {
|
||||
vote
|
||||
recipGradidoID
|
||||
recipFirstName
|
||||
recipLastName
|
||||
recipAlias
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
WAIT_ON_PENDING = 2,
|
||||
PENDING = 3,
|
||||
WAIT_ON_CONFIRM = 4,
|
||||
CONFIRMED = 5,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
|
||||
registerEnumType(PendingTransactionState, {
|
||||
|
||||
@ -5,24 +5,26 @@ import { KlickTipp } from './KlickTipp'
|
||||
|
||||
@ObjectType()
|
||||
export class User {
|
||||
constructor(user: dbUser) {
|
||||
this.id = user.id
|
||||
this.gradidoID = user.gradidoID
|
||||
this.alias = user.alias
|
||||
if (user.emailContact) {
|
||||
this.emailChecked = user.emailContact.emailChecked
|
||||
constructor(user: dbUser | null) {
|
||||
if (user) {
|
||||
this.id = user.id
|
||||
this.gradidoID = user.gradidoID
|
||||
this.alias = user.alias
|
||||
if (user.emailContact) {
|
||||
this.emailChecked = user.emailContact.emailChecked
|
||||
}
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
this.deletedAt = user.deletedAt
|
||||
this.createdAt = user.createdAt
|
||||
this.language = user.language
|
||||
this.publisherId = user.publisherId
|
||||
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
|
||||
this.klickTipp = null
|
||||
this.hasElopage = null
|
||||
this.hideAmountGDD = user.hideAmountGDD
|
||||
this.hideAmountGDT = user.hideAmountGDT
|
||||
}
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
this.deletedAt = user.deletedAt
|
||||
this.createdAt = user.createdAt
|
||||
this.language = user.language
|
||||
this.publisherId = user.publisherId
|
||||
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
|
||||
this.klickTipp = null
|
||||
this.hasElopage = null
|
||||
this.hideAmountGDD = user.hideAmountGDD
|
||||
this.hideAmountGDT = user.hideAmountGDT
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
|
||||
@ -165,6 +165,7 @@ export class TransactionLinkResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
// const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } })
|
||||
const user = getUser(context)
|
||||
|
||||
if (code.match(/^CL-/)) {
|
||||
@ -271,6 +272,11 @@ export class TransactionLinkResolver {
|
||||
transaction.typeId = TransactionTypeId.CREATION
|
||||
transaction.memo = contribution.memo
|
||||
transaction.userId = contribution.userId
|
||||
/* local transaction will not carry homeComUuid for local users
|
||||
if (homeCom.communityUuid) {
|
||||
transaction.userCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
*/
|
||||
transaction.userGradidoID = user.gradidoID
|
||||
transaction.userName = fullName(user.firstName, user.lastName)
|
||||
transaction.previous = lastTransaction ? lastTransaction.id : null
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { Connection, In } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
@ -12,7 +17,11 @@ import { GraphQLError } from 'graphql'
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { logger } from '@test/testSetup'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { EventType } from '@/event/Events'
|
||||
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
import { SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import {
|
||||
confirmContribution,
|
||||
@ -46,7 +55,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
await con.close()
|
||||
await con.destroy() // close()
|
||||
})
|
||||
|
||||
let bobData: any
|
||||
@ -56,12 +65,38 @@ let user: User[]
|
||||
let bob: User
|
||||
let peter: User
|
||||
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
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.creationDate = new Date('2000-01-01')
|
||||
homeCom.description = 'homeCom description'
|
||||
homeCom.foreign = false
|
||||
homeCom.name = 'homeCom name'
|
||||
homeCom.privateKey = Buffer.from('homeCom privateKey')
|
||||
homeCom.publicKey = Buffer.from('homeCom publicKey')
|
||||
homeCom.url = 'homeCom url'
|
||||
homeCom = await DbCommunity.save(homeCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.communityUuid = '7f474922-b6d8-4b64-8cd0-cea0a1d875bb'
|
||||
foreignCom.creationDate = new Date('2000-06-06')
|
||||
foreignCom.description = 'foreignCom description'
|
||||
foreignCom.foreign = true
|
||||
foreignCom.name = 'foreignCom name'
|
||||
foreignCom.privateKey = Buffer.from('foreignCom privateKey')
|
||||
foreignCom.publicKey = Buffer.from('foreignCom publicKey')
|
||||
foreignCom.url = 'foreignCom_url'
|
||||
foreignCom.authenticatedAt = new Date('2000-06-12')
|
||||
foreignCom = await DbCommunity.save(foreignCom)
|
||||
|
||||
bobData = {
|
||||
email: 'bob@baumeister.de',
|
||||
@ -91,6 +126,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'wrong@email.com',
|
||||
amount: 100,
|
||||
memo: 'test test',
|
||||
@ -119,6 +155,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'stephen@hawking.uk',
|
||||
amount: 100,
|
||||
memo: 'test test',
|
||||
@ -148,6 +185,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'garrick@ollivander.com',
|
||||
amount: 100,
|
||||
memo: 'test test',
|
||||
@ -184,6 +222,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'bob@baumeister.de',
|
||||
amount: 100,
|
||||
memo: 'test test',
|
||||
@ -207,6 +246,7 @@ describe('send coins', () => {
|
||||
const { errors: errorObjects } = await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 100,
|
||||
memo: 'Test',
|
||||
@ -238,6 +278,7 @@ describe('send coins', () => {
|
||||
const { errors: errorObjects } = await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: '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',
|
||||
@ -270,6 +311,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 100,
|
||||
memo: 'testing',
|
||||
@ -319,6 +361,7 @@ describe('send coins', () => {
|
||||
const { errors: errorObjects } = await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: -50,
|
||||
memo: 'testing negative',
|
||||
@ -350,6 +393,7 @@ describe('send coins', () => {
|
||||
await mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 50,
|
||||
memo: 'unrepeatable memo',
|
||||
@ -456,6 +500,7 @@ describe('send coins', () => {
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: peter?.gradidoID,
|
||||
amount: 10,
|
||||
memo: 'send via gradido ID',
|
||||
@ -496,6 +541,7 @@ describe('send coins', () => {
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'bob',
|
||||
amount: 6.66,
|
||||
memo: 'send via alias',
|
||||
@ -558,12 +604,75 @@ describe('send coins', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('X-Com send coins via gradido ID', () => {
|
||||
beforeAll(async () => {
|
||||
CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED = true
|
||||
fedForeignCom = DbFederatedCommunity.create()
|
||||
fedForeignCom.apiVersion = '1_0'
|
||||
fedForeignCom.foreign = true
|
||||
fedForeignCom.publicKey = Buffer.from('foreignCom publicKey')
|
||||
fedForeignCom.endPoint = 'http://foreignCom_url/api'
|
||||
fedForeignCom.lastAnnouncedAt = new Date('2000-06-09')
|
||||
fedForeignCom.verifiedAt = new Date('2000-06-10')
|
||||
fedForeignCom = await DbFederatedCommunity.save(fedForeignCom)
|
||||
|
||||
jest
|
||||
.spyOn(SendCoinsClient.prototype, 'voteForSendCoins')
|
||||
.mockImplementation(async (args: SendCoinsArgs): Promise<SendCoinsResult> => {
|
||||
logger.debug('mock of voteForSendCoins...', args)
|
||||
return Promise.resolve({
|
||||
vote: true,
|
||||
recipFirstName: peter.firstName,
|
||||
recipLastName: peter.lastName,
|
||||
recipGradidoID: args.recipientUserIdentifier,
|
||||
recipAlias: peter.alias,
|
||||
})
|
||||
})
|
||||
|
||||
jest
|
||||
.spyOn(SendCoinsClient.prototype, 'settleSendCoins')
|
||||
.mockImplementation(async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
logger.debug('mock of settleSendCoins...', args)
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: bobData,
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sends the coins', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: foreignCom.communityUuid,
|
||||
recipientIdentifier: peter?.gradidoID,
|
||||
amount: 10,
|
||||
memo: 'x-com send via gradido ID',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
sendCoins: true,
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('more transactions to test semaphore', () => {
|
||||
it('sends the coins four times in a row', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 10,
|
||||
memo: 'first transaction',
|
||||
@ -580,6 +689,7 @@ describe('send coins', () => {
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 20,
|
||||
memo: 'second transaction',
|
||||
@ -596,6 +706,7 @@ describe('send coins', () => {
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 30,
|
||||
memo: 'third transaction',
|
||||
@ -612,6 +723,7 @@ describe('send coins', () => {
|
||||
mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'peter@lustig.de',
|
||||
amount: 40,
|
||||
memo: 'fourth transaction',
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getConnection, In, IsNull } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
@ -12,17 +14,20 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
|
||||
import { Order } from '@enum/Order'
|
||||
import { PendingTransactionState } from '@enum/PendingTransactionState'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionList } from '@model/TransactionList'
|
||||
import { User } from '@model/User'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { CONFIG } from '@/config'
|
||||
import {
|
||||
sendTransactionLinkRedeemedEmail,
|
||||
sendTransactionReceivedEmail,
|
||||
} from '@/emails/sendEmailVariants'
|
||||
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
@ -33,9 +38,14 @@ import { calculateBalance } from '@/util/validate'
|
||||
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
||||
|
||||
import { BalanceResolver } from './BalanceResolver'
|
||||
import { isCommunityAuthenticated, isHomeCommunity } from './util/communities'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { getTransactionList } from './util/getTransactionList'
|
||||
import {
|
||||
processXComCommittingSendCoins,
|
||||
processXComPendingSendCoins,
|
||||
} from './util/processXComSendCoins'
|
||||
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
@ -51,6 +61,24 @@ export const executeTransaction = async (
|
||||
try {
|
||||
logger.info('executeTransaction', amount, memo, sender, recipient)
|
||||
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: sender.gradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: sender.gradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`,
|
||||
)
|
||||
}
|
||||
|
||||
if (sender.id === recipient.id) {
|
||||
throw new LogError('Sender and Recipient are the same', sender.id)
|
||||
}
|
||||
@ -221,12 +249,21 @@ export class TransactionResolver {
|
||||
|
||||
// find involved users; I am involved
|
||||
const involvedUserIds: number[] = [user.id]
|
||||
const involvedRemoteUsers: User[] = []
|
||||
userTransactions.forEach((transaction: dbTransaction) => {
|
||||
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 + ')'
|
||||
involvedRemoteUsers.push(remoteUser)
|
||||
}
|
||||
})
|
||||
logger.debug(`involvedUserIds=${involvedUserIds}`)
|
||||
logger.debug(`involvedUserIds=`, involvedUserIds)
|
||||
logger.debug(`involvedRemoteUsers=`, involvedRemoteUsers)
|
||||
|
||||
// We need to show the name for deleted users for old transactions
|
||||
const involvedDbUsers = await dbUser.find({
|
||||
@ -235,7 +272,7 @@ export class TransactionResolver {
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
const involvedUsers = involvedDbUsers.map((u) => new User(u))
|
||||
logger.debug(`involvedUsers=${involvedUsers}`)
|
||||
logger.debug(`involvedUsers=`, involvedUsers)
|
||||
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
@ -305,10 +342,25 @@ export class TransactionResolver {
|
||||
|
||||
// transactions
|
||||
userTransactions.forEach((userTransaction: dbTransaction) => {
|
||||
/*
|
||||
const linkedUser =
|
||||
userTransaction.typeId === TransactionTypeId.CREATION
|
||||
? communityUser
|
||||
: involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
|
||||
*/
|
||||
let linkedUser: User | undefined
|
||||
if (userTransaction.typeId === TransactionTypeId.CREATION) {
|
||||
linkedUser = communityUser
|
||||
logger.debug('CREATION-linkedUser=', linkedUser)
|
||||
} else if (userTransaction.linkedUserId) {
|
||||
linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
|
||||
logger.debug('local linkedUser=', linkedUser)
|
||||
} else if (userTransaction.linkedUserCommunityUuid) {
|
||||
linkedUser = involvedRemoteUsers.find(
|
||||
(u) => u.gradidoID === userTransaction.linkedUserGradidoID,
|
||||
)
|
||||
logger.debug('remote linkedUser=', linkedUser)
|
||||
}
|
||||
transactions.push(new Transaction(userTransaction, self, linkedUser))
|
||||
})
|
||||
logger.debug(`TransactionTypeId.CREATION: transactions=${transactions}`)
|
||||
@ -330,23 +382,87 @@ export class TransactionResolver {
|
||||
@Mutation(() => Boolean)
|
||||
async sendCoins(
|
||||
@Args()
|
||||
{ /* recipientCommunityIdentifier, */ recipientIdentifier, amount, memo }: TransactionSendArgs,
|
||||
{ recipientCommunityIdentifier, recipientIdentifier, amount, memo }: TransactionSendArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
logger.info(
|
||||
`sendCoins(recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`,
|
||||
logger.debug(
|
||||
`sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`,
|
||||
)
|
||||
|
||||
const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } })
|
||||
const senderUser = getUser(context)
|
||||
|
||||
// validate recipient user
|
||||
const recipientUser = await findUserByIdentifier(recipientIdentifier)
|
||||
if (!recipientUser) {
|
||||
throw new LogError('The recipient user was not found', recipientUser)
|
||||
}
|
||||
if (!recipientCommunityIdentifier || (await isHomeCommunity(recipientCommunityIdentifier))) {
|
||||
// processing sendCoins within sender and recepient are both in home community
|
||||
// validate recipient user
|
||||
const recipientUser = await findUserByIdentifier(recipientIdentifier)
|
||||
if (!recipientUser) {
|
||||
throw new LogError('The recipient user was not found', recipientUser)
|
||||
}
|
||||
|
||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||
logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser)
|
||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||
logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser)
|
||||
} else {
|
||||
// processing a x-community sendCoins
|
||||
logger.debug('X-Com: processing a x-community transaction...')
|
||||
if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) {
|
||||
throw new LogError('X-Community sendCoins disabled per configuration!')
|
||||
}
|
||||
if (!(await isCommunityAuthenticated(recipientCommunityIdentifier))) {
|
||||
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()
|
||||
|
||||
try {
|
||||
pendingResult = await processXComPendingSendCoins(
|
||||
recipCom,
|
||||
homeCom,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
senderUser,
|
||||
recipientIdentifier,
|
||||
)
|
||||
logger.debug('processXComPendingSendCoins result: ', pendingResult)
|
||||
if (pendingResult.vote && pendingResult.recipGradidoID) {
|
||||
logger.debug('vor processXComCommittingSendCoins... ')
|
||||
committingResult = await processXComCommittingSendCoins(
|
||||
recipCom,
|
||||
homeCom,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
senderUser,
|
||||
pendingResult.recipGradidoID,
|
||||
)
|
||||
logger.debug('processXComCommittingSendCoins result: ', committingResult)
|
||||
if (!committingResult.vote) {
|
||||
logger.fatal('FATAL ERROR: on processXComCommittingSendCoins for', committingResult)
|
||||
throw new LogError(
|
||||
'FATAL ERROR: on processXComCommittingSendCoins with ',
|
||||
recipientCommunityIdentifier,
|
||||
recipientIdentifier,
|
||||
amount.toString(),
|
||||
memo,
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new LogError(
|
||||
'ERROR: on processXComCommittingSendCoins with ',
|
||||
recipientCommunityIdentifier,
|
||||
recipientIdentifier,
|
||||
amount.toString(),
|
||||
memo,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
@ -48,9 +49,21 @@ describe('semaphore', () => {
|
||||
let bibisTransactionLinkCode = ''
|
||||
let bibisOpenContributionId = -1
|
||||
let bobsOpenContributionId = -1
|
||||
let homeCom: DbCommunity
|
||||
|
||||
beforeAll(async () => {
|
||||
const now = new Date()
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.communityUuid = 'homeCom-UUID'
|
||||
homeCom.creationDate = new Date('2000-01-01')
|
||||
homeCom.description = 'homeCom description'
|
||||
homeCom.foreign = false
|
||||
homeCom.name = 'homeCom name'
|
||||
homeCom.privateKey = Buffer.from('homeCom privateKey')
|
||||
homeCom.publicKey = Buffer.from('homeCom publicKey')
|
||||
homeCom.url = 'homeCom url'
|
||||
homeCom = await DbCommunity.save(homeCom)
|
||||
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
await userFactory(testEnv, peterLustig)
|
||||
await userFactory(testEnv, bobBaumeister)
|
||||
@ -157,6 +170,7 @@ describe('semaphore', () => {
|
||||
const bibisTransaction = mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'bob@baumeister.de',
|
||||
amount: '50',
|
||||
memo: 'Das ist für dich, Bob',
|
||||
@ -177,6 +191,7 @@ describe('semaphore', () => {
|
||||
const bobsTransaction = mutate({
|
||||
mutation: sendCoins,
|
||||
variables: {
|
||||
recipientCommunityIdentifier: homeCom.communityUuid,
|
||||
recipientIdentifier: 'bibi@bloxberg.de',
|
||||
amount: '50',
|
||||
memo: 'Das ist für dich, Bibi',
|
||||
|
||||
53
backend/src/graphql/resolver/util/communities.ts
Normal file
53
backend/src/graphql/resolver/util/communities.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
|
||||
export async function isHomeCommunity(communityIdentifier: string): Promise<boolean> {
|
||||
const homeCommunity = await DbCommunity.findOne({
|
||||
where: [
|
||||
{ foreign: false, communityUuid: communityIdentifier },
|
||||
{ foreign: false, name: communityIdentifier },
|
||||
{ foreign: false, url: communityIdentifier },
|
||||
],
|
||||
})
|
||||
if (homeCommunity) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCommunityUrl(communityIdentifier: string): Promise<string> {
|
||||
const community = await DbCommunity.findOneOrFail({
|
||||
where: [
|
||||
{ communityUuid: communityIdentifier },
|
||||
{ name: communityIdentifier },
|
||||
{ url: communityIdentifier },
|
||||
],
|
||||
})
|
||||
return community.url
|
||||
}
|
||||
|
||||
export async function isCommunityAuthenticated(communityIdentifier: string): Promise<boolean> {
|
||||
const community = await DbCommunity.findOne({
|
||||
where: [
|
||||
{ communityUuid: communityIdentifier },
|
||||
{ name: communityIdentifier },
|
||||
{ url: communityIdentifier },
|
||||
],
|
||||
})
|
||||
if (community?.authenticatedAt) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCommunityName(communityIdentifier: string): Promise<string> {
|
||||
const community = await DbCommunity.findOne({
|
||||
where: [{ communityUuid: communityIdentifier }, { url: communityIdentifier }],
|
||||
})
|
||||
if (community?.name) {
|
||||
return community.name
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
// eslint-disable-next-line camelcase
|
||||
import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
|
||||
import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory'
|
||||
@ -16,28 +17,45 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
||||
import { fullName } from '@/util/utilities'
|
||||
|
||||
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
|
||||
|
||||
export async function processXComPendingSendCoins(
|
||||
receiverFCom: DbFederatedCommunity,
|
||||
receiverCom: DbCommunity,
|
||||
senderCom: DbCommunity,
|
||||
creationDate: Date,
|
||||
amount: Decimal,
|
||||
memo: string,
|
||||
sender: dbUser,
|
||||
recipient: dbUser,
|
||||
): Promise<boolean> {
|
||||
recipientIdentifier: string,
|
||||
): Promise<SendCoinsResult> {
|
||||
let voteResult: SendCoinsResult
|
||||
try {
|
||||
logger.debug(
|
||||
`XCom: processXComPendingSendCoins...`,
|
||||
receiverFCom,
|
||||
receiverCom,
|
||||
senderCom,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
sender,
|
||||
recipient,
|
||||
recipientIdentifier,
|
||||
)
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: sender.gradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: sender.gradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: recipientIdentifier, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: recipientIdentifier, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`,
|
||||
)
|
||||
}
|
||||
// first calculate the sender balance and check if the transaction is allowed
|
||||
const senderBalance = await calculateSenderBalance(sender.id, amount.mul(-1), creationDate)
|
||||
if (!senderBalance) {
|
||||
@ -45,44 +63,56 @@ export async function processXComPendingSendCoins(
|
||||
}
|
||||
logger.debug(`X-Com: calculated senderBalance = `, senderBalance)
|
||||
|
||||
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
||||
where: {
|
||||
publicKey: receiverCom.publicKey,
|
||||
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
|
||||
},
|
||||
})
|
||||
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
||||
// eslint-disable-next-line camelcase
|
||||
if (client instanceof V1_0_SendCoinsClient) {
|
||||
const args = new SendCoinsArgs()
|
||||
args.communityReceiverIdentifier = receiverCom.communityUuid
|
||||
? receiverCom.communityUuid
|
||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
||||
args.userReceiverIdentifier = recipient.gradidoID
|
||||
if (receiverCom.communityUuid) {
|
||||
args.recipientCommunityUuid = receiverCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipientIdentifier
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = amount
|
||||
args.memo = memo
|
||||
args.communitySenderIdentifier = senderCom.communityUuid
|
||||
? senderCom.communityUuid
|
||||
: 'homeCom-UUID'
|
||||
args.userSenderIdentifier = sender.gradidoID
|
||||
args.userSenderName = fullName(sender.firstName, sender.lastName)
|
||||
if (senderCom.communityUuid) {
|
||||
args.senderCommunityUuid = senderCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sender.gradidoID
|
||||
args.senderUserName = fullName(sender.firstName, sender.lastName)
|
||||
logger.debug(`X-Com: ready for voteForSendCoins with args=`, args)
|
||||
const recipientName = await client.voteForSendCoins(args)
|
||||
logger.debug(`X-Com: returnd from voteForSendCoins:`, recipientName)
|
||||
if (recipientName) {
|
||||
voteResult = await client.voteForSendCoins(args)
|
||||
logger.debug(`X-Com: returned from voteForSendCoins:`, voteResult)
|
||||
if (voteResult.vote) {
|
||||
logger.debug(`X-Com: prepare pendingTransaction for sender...`)
|
||||
// writing the pending transaction on receiver-side was successfull, so now write the sender side
|
||||
try {
|
||||
const pendingTx = DbPendingTransaction.create()
|
||||
pendingTx.amount = amount.mul(-1)
|
||||
pendingTx.balance = senderBalance ? senderBalance.balance : new Decimal(0)
|
||||
pendingTx.balance = senderBalance.balance
|
||||
pendingTx.balanceDate = creationDate
|
||||
pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0)
|
||||
pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null
|
||||
pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid
|
||||
? receiverCom.communityUuid
|
||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
||||
pendingTx.linkedUserGradidoID = recipient.gradidoID
|
||||
pendingTx.linkedUserName = recipientName
|
||||
if (receiverCom.communityUuid) {
|
||||
pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid
|
||||
}
|
||||
if (voteResult.recipGradidoID) {
|
||||
pendingTx.linkedUserGradidoID = voteResult.recipGradidoID
|
||||
}
|
||||
if (voteResult.recipFirstName && voteResult.recipLastName) {
|
||||
pendingTx.linkedUserName = fullName(voteResult.recipFirstName, voteResult.recipLastName)
|
||||
}
|
||||
pendingTx.memo = memo
|
||||
pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null
|
||||
pendingTx.state = PendingTransactionState.NEW
|
||||
pendingTx.typeId = TransactionTypeId.SEND
|
||||
if (senderCom.communityUuid) pendingTx.userCommunityUuid = senderCom.communityUuid
|
||||
pendingTx.userId = sender.id
|
||||
pendingTx.userGradidoID = sender.gradidoID
|
||||
pendingTx.userName = fullName(sender.firstName, sender.lastName)
|
||||
logger.debug(`X-Com: initialized sender pendingTX=`, pendingTx)
|
||||
@ -107,10 +137,131 @@ export async function processXComPendingSendCoins(
|
||||
)
|
||||
}
|
||||
logger.debug(`voteForSendCoins()-1_0... successfull`)
|
||||
} else {
|
||||
logger.error(
|
||||
`X-Com: break with error on writing pendingTransaction for recipient...`,
|
||||
voteResult,
|
||||
)
|
||||
}
|
||||
return voteResult
|
||||
}
|
||||
} catch (err) {
|
||||
throw new LogError(`Error:`, err)
|
||||
}
|
||||
return new SendCoinsResult()
|
||||
}
|
||||
|
||||
export async function processXComCommittingSendCoins(
|
||||
receiverCom: DbCommunity,
|
||||
senderCom: DbCommunity,
|
||||
creationDate: Date,
|
||||
amount: Decimal,
|
||||
memo: string,
|
||||
sender: dbUser,
|
||||
recipUuid: string,
|
||||
): Promise<SendCoinsResult> {
|
||||
const sendCoinsResult = new SendCoinsResult()
|
||||
try {
|
||||
logger.debug(
|
||||
`XCom: processXComCommittingSendCoins...`,
|
||||
receiverCom,
|
||||
senderCom,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
sender,
|
||||
recipUuid,
|
||||
)
|
||||
// first find pending Tx with given parameters
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: senderCom.communityUuid ?? 'homeCom-UUID',
|
||||
userGradidoID: sender.gradidoID,
|
||||
userName: fullName(sender.firstName, sender.lastName),
|
||||
linkedUserCommunityUuid:
|
||||
receiverCom.communityUuid ?? CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
|
||||
linkedUserGradidoID: recipUuid,
|
||||
typeId: TransactionTypeId.SEND,
|
||||
state: PendingTransactionState.NEW,
|
||||
balanceDate: creationDate,
|
||||
memo,
|
||||
})
|
||||
if (pendingTx) {
|
||||
logger.debug(`X-Com: find pending Tx for settlement:`, pendingTx)
|
||||
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
||||
where: {
|
||||
publicKey: receiverCom.publicKey,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
|
||||
},
|
||||
})
|
||||
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
||||
// eslint-disable-next-line camelcase
|
||||
if (client instanceof V1_0_SendCoinsClient) {
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid
|
||||
? pendingTx.linkedUserCommunityUuid
|
||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
||||
if (pendingTx.linkedUserGradidoID) {
|
||||
args.recipientUserIdentifier = pendingTx.linkedUserGradidoID
|
||||
}
|
||||
args.creationDate = pendingTx.balanceDate.toISOString()
|
||||
args.amount = pendingTx.amount.mul(-1)
|
||||
args.memo = pendingTx.memo
|
||||
args.senderCommunityUuid = pendingTx.userCommunityUuid
|
||||
args.senderUserUuid = pendingTx.userGradidoID
|
||||
if (pendingTx.userName) {
|
||||
args.senderUserName = pendingTx.userName
|
||||
}
|
||||
logger.debug(`X-Com: ready for settleSendCoins with args=`, args)
|
||||
const acknowledge = await client.settleSendCoins(args)
|
||||
logger.debug(`X-Com: returnd from settleSendCoins:`, acknowledge)
|
||||
if (acknowledge) {
|
||||
// settle the pending transaction on receiver-side was successfull, so now settle the sender side
|
||||
try {
|
||||
sendCoinsResult.vote = await settlePendingSenderTransaction(
|
||||
senderCom,
|
||||
sender,
|
||||
pendingTx,
|
||||
)
|
||||
if (sendCoinsResult.vote) {
|
||||
if (pendingTx.linkedUserName) {
|
||||
sendCoinsResult.recipFirstName = pendingTx.linkedUserName.slice(
|
||||
0,
|
||||
pendingTx.linkedUserName.indexOf(' '),
|
||||
)
|
||||
sendCoinsResult.recipLastName = pendingTx.linkedUserName.slice(
|
||||
pendingTx.linkedUserName.indexOf(' '),
|
||||
pendingTx.linkedUserName.length,
|
||||
)
|
||||
}
|
||||
sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: `, err)
|
||||
// revert the existing pending transaction on receiver side
|
||||
let revertCount = 0
|
||||
logger.debug(`X-Com: first try to revertSetteledSendCoins of receiver`)
|
||||
do {
|
||||
if (await client.revertSettledSendCoins(args)) {
|
||||
logger.debug(
|
||||
`revertSettledSendCoins()-1_0... successfull after revertCount=`,
|
||||
revertCount,
|
||||
)
|
||||
// treat revertingSettledSendCoins as an error of the whole sendCoins-process
|
||||
throw new LogError('Error in settle sender pending transaction: `, err')
|
||||
}
|
||||
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
|
||||
throw new LogError(
|
||||
`Error in reverting receiver pending transaction even after revertCount=`,
|
||||
revertCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error:`, err)
|
||||
sendCoinsResult.vote = false
|
||||
}
|
||||
return true
|
||||
return sendCoinsResult
|
||||
}
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
|
||||
import { getLastTransaction } from './getLastTransaction'
|
||||
|
||||
export async function settlePendingSenderTransaction(
|
||||
homeCom: DbCommunity,
|
||||
senderUser: DbUser,
|
||||
pendingTx: DbPendingTransaction,
|
||||
): Promise<boolean> {
|
||||
// TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!!
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
logger.debug(`start Transaction for write-access...`)
|
||||
|
||||
try {
|
||||
logger.info('X-Com: settlePendingSenderTransaction:', homeCom, senderUser, pendingTx)
|
||||
|
||||
// ensure that no other pendingTx with the same sender or recipient exists
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) {
|
||||
throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient')
|
||||
}
|
||||
|
||||
const lastTransaction = await getLastTransaction(senderUser.id)
|
||||
|
||||
if (lastTransaction?.id !== pendingTx.previous) {
|
||||
throw new LogError(
|
||||
`X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`,
|
||||
)
|
||||
}
|
||||
|
||||
// transfer the pendingTx to the transactions table
|
||||
const transactionSend = new dbTransaction()
|
||||
transactionSend.typeId = pendingTx.typeId
|
||||
transactionSend.memo = pendingTx.memo
|
||||
transactionSend.userId = pendingTx.userId
|
||||
transactionSend.userGradidoID = pendingTx.userGradidoID
|
||||
transactionSend.userName = pendingTx.userName
|
||||
transactionSend.linkedUserId = pendingTx.linkedUserId
|
||||
transactionSend.linkedUserCommunityUuid = pendingTx.linkedUserCommunityUuid
|
||||
transactionSend.linkedUserGradidoID = pendingTx.linkedUserGradidoID
|
||||
transactionSend.linkedUserName = pendingTx.linkedUserName
|
||||
transactionSend.amount = pendingTx.amount
|
||||
const sendBalance = await calculateSenderBalance(
|
||||
senderUser.id,
|
||||
pendingTx.amount,
|
||||
pendingTx.balanceDate,
|
||||
)
|
||||
if (!sendBalance) {
|
||||
throw new LogError(`Sender has not enough GDD or amount is < 0', sendBalance`)
|
||||
}
|
||||
transactionSend.balance = sendBalance?.balance ?? new Decimal(0)
|
||||
transactionSend.balanceDate = pendingTx.balanceDate
|
||||
transactionSend.decay = sendBalance.decay.decay // pendingTx.decay
|
||||
transactionSend.decayStart = sendBalance.decay.start // pendingTx.decayStart
|
||||
transactionSend.previous = pendingTx.previous
|
||||
transactionSend.linkedTransactionId = pendingTx.linkedTransactionId
|
||||
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
||||
logger.debug(`send Transaction inserted: ${dbTransaction}`)
|
||||
|
||||
// and mark the pendingTx in the pending_transactions table as settled
|
||||
pendingTx.state = PendingTransactionState.SETTLED
|
||||
await queryRunner.manager.save(DbPendingTransaction, pendingTx)
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit send Transaction successful...`)
|
||||
|
||||
/*
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
recipient,
|
||||
sender,
|
||||
transactionReceive,
|
||||
transactionReceive.amount,
|
||||
)
|
||||
*/
|
||||
// trigger to send transaction via dlt-connector
|
||||
// void sendTransactionsToDltConnector()
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('X-Com: send Transaction was not successful', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
releaseLock()
|
||||
}
|
||||
/*
|
||||
void sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
email: recipient.emailContact.email,
|
||||
language: recipient.language,
|
||||
senderFirstName: sender.firstName,
|
||||
senderLastName: sender.lastName,
|
||||
senderEmail: sender.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
})
|
||||
if (transactionLink) {
|
||||
void sendTransactionLinkRedeemedEmail({
|
||||
firstName: sender.firstName,
|
||||
lastName: sender.lastName,
|
||||
email: sender.emailContact.email,
|
||||
language: sender.language,
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
senderEmail: recipient.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
logger.info(`finished executeTransaction successfully`)
|
||||
} finally {
|
||||
releaseLock()
|
||||
}
|
||||
*/
|
||||
return true
|
||||
}
|
||||
34
backend/src/seeds/community/index.ts
Normal file
34
backend/src/seeds/community/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
export async function writeHomeCommunityEntry(): Promise<void> {
|
||||
try {
|
||||
// check for existing homeCommunity entry
|
||||
let homeCom = await DbCommunity.findOne({ where: { foreign: false } })
|
||||
if (homeCom) {
|
||||
// simply update the existing entry, but it MUST keep the ID and UUID because of possible relations
|
||||
homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey
|
||||
// homeCom.privateKey = keyPair.secretKey
|
||||
homeCom.url = 'http://localhost/api/'
|
||||
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||
await DbCommunity.save(homeCom)
|
||||
} else {
|
||||
// insert a new homecommunity entry including a new ID and a new but ensured unique UUID
|
||||
homeCom = new DbCommunity()
|
||||
homeCom.foreign = false
|
||||
homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey
|
||||
// homeCom.privateKey = keyPair.secretKey
|
||||
homeCom.communityUuid = uuidv4() // await newCommunityUuid()
|
||||
homeCom.url = 'http://localhost/api/'
|
||||
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||
homeCom.creationDate = new Date()
|
||||
await DbCommunity.insert(homeCom)
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Seeding: Error writing HomeCommunity-Entry`) // : ${err}`)
|
||||
}
|
||||
}
|
||||
@ -78,9 +78,31 @@ export const sendActivationEmail = gql`
|
||||
}
|
||||
`
|
||||
|
||||
/*
|
||||
export const sendCoins = gql`
|
||||
mutation ($recipientIdentifier: String!, $amount: Decimal!, $memo: String!) {
|
||||
sendCoins(recipientIdentifier: $recipientIdentifier, amount: $amount, memo: $memo)
|
||||
mutation ($identifier: String!, $amount: Decimal!, $memo: String!, $communityIdentifier: String) {
|
||||
sendCoins(
|
||||
identifier: $identifier
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communityIdentifier: $communityIdentifier
|
||||
)
|
||||
}
|
||||
`
|
||||
*/
|
||||
export const sendCoins = gql`
|
||||
mutation (
|
||||
$recipientCommunityIdentifier: String!
|
||||
$recipientIdentifier: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
) {
|
||||
sendCoins(
|
||||
recipientCommunityIdentifier: $recipientCommunityIdentifier
|
||||
recipientIdentifier: $recipientIdentifier
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { CONFIG } from '@/config'
|
||||
import { createServer } from '@/server/createServer'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { writeHomeCommunityEntry } from './community'
|
||||
import { contributionLinks } from './contributionLink/index'
|
||||
import { creations } from './creation/index'
|
||||
import { contributionLinkFactory } from './factory/contributionLink'
|
||||
@ -57,6 +58,9 @@ const run = async () => {
|
||||
await cleanDB()
|
||||
logger.info('##seed## clean database successful...')
|
||||
|
||||
// seed home community
|
||||
await writeHomeCommunityEntry()
|
||||
|
||||
// seed the standard users
|
||||
for (const user of users) {
|
||||
await userFactory(seedClient, user)
|
||||
|
||||
@ -58,6 +58,8 @@ const virtualLinkTransaction = (
|
||||
userName: null,
|
||||
linkedUserGradidoID: null,
|
||||
linkedUserName: null,
|
||||
userCommunityUuid: null,
|
||||
linkedUserCommunityUuid: null,
|
||||
}
|
||||
return new Transaction(linkDbTransaction, user)
|
||||
}
|
||||
@ -92,6 +94,8 @@ const virtualDecayTransaction = (
|
||||
userName: null,
|
||||
linkedUserGradidoID: null,
|
||||
linkedUserName: null,
|
||||
userCommunityUuid: null,
|
||||
linkedUserCommunityUuid: null,
|
||||
}
|
||||
return new Transaction(decayDbTransaction, user)
|
||||
}
|
||||
|
||||
@ -0,0 +1,163 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Contribution } from '../Contribution'
|
||||
import { DltTransaction } from '../DltTransaction'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ type: 'int', unsigned: true, unique: true, nullable: true, default: null })
|
||||
previous: number | null
|
||||
|
||||
@Column({ name: 'type_id', unsigned: true, nullable: false })
|
||||
typeId: number
|
||||
|
||||
@Column({
|
||||
name: 'transaction_link_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
transactionLinkId?: number | null
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: false,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
amount: Decimal
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: false,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
balance: Decimal
|
||||
|
||||
@Column({
|
||||
name: 'balance_date',
|
||||
type: 'datetime',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
nullable: false,
|
||||
})
|
||||
balanceDate: Date
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: false,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
decay: Decimal
|
||||
|
||||
@Column({
|
||||
name: 'decay_start',
|
||||
type: 'datetime',
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
decayStart: Date | null
|
||||
|
||||
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||
memo: string
|
||||
|
||||
@Column({ name: 'creation_date', type: 'datetime', nullable: true, default: null })
|
||||
creationDate: Date | null
|
||||
|
||||
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||
userId: number
|
||||
|
||||
@Column({
|
||||
name: 'user_community_uuid',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
userCommunityUuid: string | null
|
||||
|
||||
@Column({
|
||||
name: 'user_gradido_id',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: false,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
userGradidoID: string
|
||||
|
||||
@Column({
|
||||
name: 'user_name',
|
||||
type: 'varchar',
|
||||
length: 512,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
userName: string | null
|
||||
|
||||
@Column({
|
||||
name: 'linked_user_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
linkedUserId?: number | null
|
||||
|
||||
@Column({
|
||||
name: 'linked_user_community_uuid',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
linkedUserCommunityUuid: string | null
|
||||
|
||||
@Column({
|
||||
name: 'linked_user_gradido_id',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
linkedUserGradidoID: string | null
|
||||
|
||||
@Column({
|
||||
name: 'linked_user_name',
|
||||
type: 'varchar',
|
||||
length: 512,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
linkedUserName: string | null
|
||||
|
||||
@Column({
|
||||
name: 'linked_transaction_id',
|
||||
type: 'int',
|
||||
unsigned: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
linkedTransactionId?: number | null
|
||||
|
||||
@OneToOne(() => Contribution, (contribution) => contribution.transaction)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
|
||||
contribution?: Contribution | null
|
||||
|
||||
@OneToOne(() => DltTransaction, (dlt) => dlt.transactionId)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
|
||||
dltTransaction?: DltTransaction | null
|
||||
|
||||
@OneToOne(() => Transaction)
|
||||
@JoinColumn({ name: 'previous' })
|
||||
previousTransaction?: Transaction | null
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { Transaction } from './0070-add_dlt_transactions_table/Transaction'
|
||||
export { Transaction } from './0072-add_communityuuid_to_transactions_table/Transaction'
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
/* MIGRATION TO add users that have a transaction but do not exist */
|
||||
|
||||
/* 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 `transactions` ADD COLUMN `user_community_uuid` char(36) DEFAULT NULL NULL AFTER `user_id`;',
|
||||
)
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` ADD COLUMN `linked_user_community_uuid` char(36) DEFAULT NULL NULL AFTER `linked_user_id`;',
|
||||
)
|
||||
/* the migration of the HomeCom-UUID for local users in the transactions table will be skipped
|
||||
and be solved with the future users table migration for treating home- and foreign-users including
|
||||
homeCom- and foreignCom-UUIDs
|
||||
|
||||
// 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 sender and recipient of each still existing transaction
|
||||
if (result && result[0]) {
|
||||
await queryFn(
|
||||
`UPDATE transactions as t SET t.user_community_uuid = "${result[0].community_uuid}" WHERE t.user_id IS NOT NULL AND t.user_community_uuid IS NULL`,
|
||||
)
|
||||
await queryFn(
|
||||
`UPDATE transactions as t SET t.linked_user_community_uuid = "${result[0].community_uuid}" WHERE t.linked_user_id IS NOT NULL AND t.linked_user_community_uuid IS NULL`,
|
||||
)
|
||||
}
|
||||
// leads to an error in case of empty communties table during CD/CI-pipeline-tests
|
||||
await queryFn(
|
||||
'ALTER TABLE `transactions` MODIFY COLUMN `user_community_uuid` char(36) NOT NULL AFTER `user_id`;',
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_community_uuid`;')
|
||||
await queryFn('ALTER TABLE `transactions` DROP COLUMN `linked_user_community_uuid`;')
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
CONFIG_VERSION=v3.2023-04-26
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
|
||||
@ -4,7 +4,7 @@ import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0071-add-pending_transactions-table',
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_table',
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||
|
||||
@ -67,7 +67,7 @@ services:
|
||||
context: ./mariadb
|
||||
target: mariadb_server
|
||||
environment:
|
||||
- MARIADB_ALLOW_EMPTY_PASSWORD=1
|
||||
- MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1
|
||||
- MARIADB_USER=root
|
||||
networks:
|
||||
- internal-net
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
CONFIG_VERSION=$FEDERATION_CONFIG_VERSION
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
CONFIG_VERSION=v2.2023-08-24
|
||||
|
||||
LOG_LEVEL=$LOG_LEVEL
|
||||
# this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use
|
||||
|
||||
@ -6,7 +6,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 76,
|
||||
lines: 77,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
|
||||
@ -16,7 +16,9 @@
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/uuid": "8.3.4",
|
||||
"apollo-server-express": "^2.25.2",
|
||||
"await-semaphore": "0.1.3",
|
||||
"class-validator": "^0.13.2",
|
||||
"cors": "2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
@ -27,7 +29,8 @@
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"log4js": "^6.7.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"type-graphql": "^1.1.1"
|
||||
"type-graphql": "^1.1.1",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "4.17.12",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
Decimal.set({
|
||||
@ -9,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0071-add-pending_transactions-table',
|
||||
DB_VERSION: '0072-add_communityuuid_to_transactions_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
|
||||
@ -53,9 +54,14 @@ const federation = {
|
||||
FEDERATION_API: process.env.FEDERATION_API || '1_0',
|
||||
FEDERATION_PORT: process.env.FEDERATION_PORT || 5010,
|
||||
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null,
|
||||
FEDERATION_TRADING_LEVEL: {
|
||||
RECEIVER_COMMUNITY_URL: 'https://stage3.gradido.net/api/',
|
||||
SEND_COINS: true,
|
||||
AMOUNT: 100,
|
||||
},
|
||||
}
|
||||
|
||||
const CONFIG = {
|
||||
export const CONFIG = {
|
||||
...constants,
|
||||
...server,
|
||||
...database,
|
||||
|
||||
12
federation/src/graphql/api/1_0/const/const.ts
Normal file
12
federation/src/graphql/api/1_0/const/const.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
export const MAX_CREATION_AMOUNT = new Decimal(1000)
|
||||
export const FULL_CREATION_AVAILABLE = [
|
||||
MAX_CREATION_AMOUNT,
|
||||
MAX_CREATION_AMOUNT,
|
||||
MAX_CREATION_AMOUNT,
|
||||
]
|
||||
export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100
|
||||
export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5
|
||||
export const MEMO_MAX_CHARS = 255
|
||||
export const MEMO_MIN_CHARS = 5
|
||||
@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
WAIT_ON_PENDING = 2,
|
||||
PENDING = 3,
|
||||
WAIT_ON_CONFIRM = 4,
|
||||
CONFIRMED = 5,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
|
||||
registerEnumType(PendingTransactionState, {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
import { Field, InputType } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
@InputType()
|
||||
export class SendCoinsArgs {
|
||||
@Field(() => String)
|
||||
communityReceiverIdentifier: string
|
||||
recipientCommunityUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userReceiverIdentifier: string
|
||||
recipientUserIdentifier: string
|
||||
|
||||
@Field(() => String)
|
||||
creationDate: string
|
||||
@ -19,11 +19,11 @@ export class SendCoinsArgs {
|
||||
memo: string
|
||||
|
||||
@Field(() => String)
|
||||
communitySenderIdentifier: string
|
||||
senderCommunityUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userSenderIdentifier: string
|
||||
senderUserUuid: string
|
||||
|
||||
@Field(() => String)
|
||||
userSenderName: string
|
||||
senderUserName: string
|
||||
}
|
||||
|
||||
23
federation/src/graphql/api/1_0/model/SendCoinsResult.ts
Normal file
23
federation/src/graphql/api/1_0/model/SendCoinsResult.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class SendCoinsResult {
|
||||
constructor() {
|
||||
this.vote = false
|
||||
}
|
||||
|
||||
@Field(() => Boolean)
|
||||
vote: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipGradidoID: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipFirstName: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipLastName: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
recipAlias: string | null
|
||||
}
|
||||
@ -4,11 +4,12 @@ import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '@/server/createServer'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import CONFIG from '@/config'
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
|
||||
let query: any
|
||||
|
||||
// to do: We need a setup for the tests that closes the connection
|
||||
let con: any
|
||||
let con: Connection
|
||||
|
||||
CONFIG.FEDERATION_API = '1_0'
|
||||
|
||||
|
||||
@ -4,11 +4,14 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import CONFIG from '@/config'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
import { fullName } from '@/graphql/util/fullName'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { logger } from '@test/testSetup'
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
|
||||
let mutate: ApolloServerTestClient['mutate'], con: Connection
|
||||
// let query: ApolloServerTestClient['query']
|
||||
@ -21,18 +24,18 @@ let testEnv: {
|
||||
|
||||
CONFIG.FEDERATION_API = '1_0'
|
||||
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
let sendUser: DbUser
|
||||
let sendContact: DbUserContact
|
||||
let recipUser: DbUser
|
||||
let recipContact: DbUserContact
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger)
|
||||
mutate = testEnv.mutate
|
||||
// query = testEnv.query
|
||||
con = testEnv.con
|
||||
|
||||
// const server = await createServer()
|
||||
// con = server.con
|
||||
// query = createTestClient(server.apollo).query
|
||||
// mutate = createTestClient(server.apollo).mutate
|
||||
// DbCommunity.clear()
|
||||
// DbUser.clear()
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
@ -43,115 +46,102 @@ afterAll(async () => {
|
||||
|
||||
describe('SendCoinsResolver', () => {
|
||||
const voteForSendCoinsMutation = `
|
||||
mutation (
|
||||
$communityReceiverIdentifier: String!
|
||||
$userReceiverIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$communitySenderIdentifier: String!
|
||||
$userSenderIdentifier: String!
|
||||
$userSenderName: String!
|
||||
) {
|
||||
voteForSendCoins(
|
||||
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||
userReceiverIdentifier: $userReceiverIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communitySenderIdentifier: $communitySenderIdentifier
|
||||
userSenderIdentifier: $userSenderIdentifier
|
||||
userSenderName: $userSenderName
|
||||
)
|
||||
}
|
||||
`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
voteForSendCoins(data: $args) {
|
||||
vote
|
||||
recipGradidoID
|
||||
recipFirstName
|
||||
recipLastName
|
||||
recipAlias
|
||||
}
|
||||
}`
|
||||
const settleSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
settleSendCoins(data: $args)
|
||||
}`
|
||||
const revertSendCoinsMutation = `
|
||||
mutation (
|
||||
$communityReceiverIdentifier: String!
|
||||
$userReceiverIdentifier: String!
|
||||
$creationDate: String!
|
||||
$amount: Decimal!
|
||||
$memo: String!
|
||||
$communitySenderIdentifier: String!
|
||||
$userSenderIdentifier: String!
|
||||
$userSenderName: String!
|
||||
) {
|
||||
revertSendCoins(
|
||||
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||
userReceiverIdentifier: $userReceiverIdentifier
|
||||
creationDate: $creationDate
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
communitySenderIdentifier: $communitySenderIdentifier
|
||||
userSenderIdentifier: $userSenderIdentifier
|
||||
userSenderName: $userSenderName
|
||||
)
|
||||
}
|
||||
`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
revertSendCoins(data: $args)
|
||||
}`
|
||||
const revertSettledSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
revertSettledSendCoins(data: $args)
|
||||
}`
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDB()
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.foreign = false
|
||||
homeCom.url = 'homeCom-url'
|
||||
homeCom.name = 'homeCom-Name'
|
||||
homeCom.description = 'homeCom-Description'
|
||||
homeCom.creationDate = new Date()
|
||||
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||
homeCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
await DbCommunity.insert(homeCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.foreign = true
|
||||
foreignCom.url = 'foreignCom-url'
|
||||
foreignCom.name = 'foreignCom-Name'
|
||||
foreignCom.description = 'foreignCom-Description'
|
||||
foreignCom.creationDate = new Date()
|
||||
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||
foreignCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
await DbCommunity.insert(foreignCom)
|
||||
|
||||
sendUser = DbUser.create()
|
||||
sendUser.alias = 'sendUser-alias'
|
||||
sendUser.firstName = 'sendUser-FirstName'
|
||||
sendUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebc'
|
||||
sendUser.lastName = 'sendUser-LastName'
|
||||
await DbUser.insert(sendUser)
|
||||
|
||||
sendContact = await newEmailContact('send.user@email.de', sendUser.id)
|
||||
sendContact = await DbUserContact.save(sendContact)
|
||||
|
||||
sendUser.emailContact = sendContact
|
||||
sendUser.emailId = sendContact.id
|
||||
await DbUser.save(sendUser)
|
||||
|
||||
recipUser = DbUser.create()
|
||||
recipUser.alias = 'recipUser-alias'
|
||||
recipUser.firstName = 'recipUser-FirstName'
|
||||
recipUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebd'
|
||||
recipUser.lastName = 'recipUser-LastName'
|
||||
await DbUser.insert(recipUser)
|
||||
|
||||
recipContact = await newEmailContact('recip.user@email.de', recipUser.id)
|
||||
recipContact = await DbUserContact.save(recipContact)
|
||||
|
||||
recipUser.emailContact = recipContact
|
||||
recipUser.emailId = recipContact.id
|
||||
await DbUser.save(recipUser)
|
||||
})
|
||||
|
||||
describe('voteForSendCoins', () => {
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
let sendUser: DbUser
|
||||
let recipUser: DbUser
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDB()
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.foreign = false
|
||||
homeCom.url = 'homeCom-url'
|
||||
homeCom.name = 'homeCom-Name'
|
||||
homeCom.description = 'homeCom-Description'
|
||||
homeCom.creationDate = new Date()
|
||||
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||
homeCom.communityUuid = 'homeCom-UUID'
|
||||
await DbCommunity.insert(homeCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.foreign = true
|
||||
foreignCom.url = 'foreignCom-url'
|
||||
foreignCom.name = 'foreignCom-Name'
|
||||
foreignCom.description = 'foreignCom-Description'
|
||||
foreignCom.creationDate = new Date()
|
||||
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||
foreignCom.communityUuid = 'foreignCom-UUID'
|
||||
await DbCommunity.insert(foreignCom)
|
||||
|
||||
sendUser = DbUser.create()
|
||||
sendUser.alias = 'sendUser-alias'
|
||||
sendUser.firstName = 'sendUser-FirstName'
|
||||
sendUser.gradidoID = 'sendUser-GradidoID'
|
||||
sendUser.lastName = 'sendUser-LastName'
|
||||
await DbUser.insert(sendUser)
|
||||
|
||||
recipUser = DbUser.create()
|
||||
recipUser.alias = 'recipUser-alias'
|
||||
recipUser.firstName = 'recipUser-FirstName'
|
||||
recipUser.gradidoID = 'recipUser-GradidoID'
|
||||
recipUser.lastName = 'recipUser-LastName'
|
||||
await DbUser.insert(recipUser)
|
||||
})
|
||||
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: 'invalid foreignCom',
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: new Date().toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('voteForSendCoins with wrong communityReceiverIdentifier')],
|
||||
errors: [new GraphQLError('voteForSendCoins with wrong recipientCommunityUuid')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -160,25 +150,29 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: 'invalid recipient',
|
||||
creationDate: new Date().toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'voteForSendCoins with unknown userReceiverIdentifier in the community=',
|
||||
'voteForSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -189,24 +183,34 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX voted', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: new Date().toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: 'recipUser-FirstName recipUser-LastName',
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -215,83 +219,50 @@ describe('SendCoinsResolver', () => {
|
||||
})
|
||||
|
||||
describe('revertSendCoins', () => {
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
let sendUser: DbUser
|
||||
let recipUser: DbUser
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDB()
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.foreign = false
|
||||
homeCom.url = 'homeCom-url'
|
||||
homeCom.name = 'homeCom-Name'
|
||||
homeCom.description = 'homeCom-Description'
|
||||
homeCom.creationDate = new Date()
|
||||
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||
homeCom.communityUuid = 'homeCom-UUID'
|
||||
await DbCommunity.insert(homeCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.foreign = true
|
||||
foreignCom.url = 'foreignCom-url'
|
||||
foreignCom.name = 'foreignCom-Name'
|
||||
foreignCom.description = 'foreignCom-Description'
|
||||
foreignCom.creationDate = new Date()
|
||||
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||
foreignCom.communityUuid = 'foreignCom-UUID'
|
||||
await DbCommunity.insert(foreignCom)
|
||||
|
||||
sendUser = DbUser.create()
|
||||
sendUser.alias = 'sendUser-alias'
|
||||
sendUser.firstName = 'sendUser-FirstName'
|
||||
sendUser.gradidoID = 'sendUser-GradidoID'
|
||||
sendUser.lastName = 'sendUser-LastName'
|
||||
await DbUser.insert(sendUser)
|
||||
|
||||
recipUser = DbUser.create()
|
||||
recipUser.alias = 'recipUser-alias'
|
||||
recipUser.firstName = 'recipUser-FirstName'
|
||||
recipUser.gradidoID = 'recipUser-GradidoID'
|
||||
recipUser.lastName = 'recipUser-LastName'
|
||||
await DbUser.insert(recipUser)
|
||||
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: 'invalid foreignCom',
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('revertSendCoins with wrong communityReceiverIdentifier')],
|
||||
errors: [new GraphQLError('revertSendCoins with wrong recipientCommunityUuid')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -300,25 +271,29 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: 'invalid recipient',
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'revertSendCoins with unknown userReceiverIdentifier in the community=',
|
||||
'revertSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -329,19 +304,23 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX reverted', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
@ -353,4 +332,249 @@ describe('SendCoinsResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('settleSendCoins', () => {
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('settleSendCoins with wrong recipientCommunityUuid')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'settleSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX settled', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
settleSendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('revertSettledSendCoins', () => {
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('revertSettledSendCoins with wrong recipientCommunityUuid')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'revertSettledSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX settled', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.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)
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
revertSettledSendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function newEmailContact(email: string, userId: number): Promise<DbUserContact> {
|
||||
const emailContact = new DbUserContact()
|
||||
emailContact.email = email
|
||||
emailContact.userId = userId
|
||||
emailContact.type = 'EMAIL'
|
||||
emailContact.emailChecked = false
|
||||
emailContact.emailOptInTypeId = 1
|
||||
emailContact.emailVerificationCode = '1' + userId
|
||||
return emailContact
|
||||
}
|
||||
|
||||
@ -1,78 +1,108 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { Args, Mutation, Resolver } from 'type-graphql'
|
||||
import { Arg, Args, Mutation, Resolver } from 'type-graphql'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { calculateRecipientBalance } from '@/graphql/util/calculateRecipientBalance'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { calculateRecipientBalance } from '../util/calculateRecipientBalance'
|
||||
import { fullName } from '@/graphql/util/fullName'
|
||||
import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction'
|
||||
// import { checkTradingLevel } from '@/graphql/util/checkTradingLevel'
|
||||
import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction'
|
||||
import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier'
|
||||
import { SendCoinsResult } from '../model/SendCoinsResult'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@Resolver()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export class SendCoinsResolver {
|
||||
@Mutation(() => String)
|
||||
@Mutation(() => SendCoinsResult)
|
||||
async voteForSendCoins(
|
||||
@Args()
|
||||
{
|
||||
communityReceiverIdentifier,
|
||||
userReceiverIdentifier,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
communitySenderIdentifier,
|
||||
userSenderIdentifier,
|
||||
userSenderName,
|
||||
}: SendCoinsArgs,
|
||||
): Promise<string | null> {
|
||||
logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`)
|
||||
let result: string | null = null
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
): Promise<SendCoinsResult> {
|
||||
logger.debug(
|
||||
`voteForSendCoins() via apiVersion=1_0 ...`,
|
||||
args.recipientCommunityUuid,
|
||||
args.recipientUserIdentifier,
|
||||
args.creationDate,
|
||||
args.amount.toString(),
|
||||
args.memo,
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
args.senderUserName,
|
||||
)
|
||||
const result = new SendCoinsResult()
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: communityReceiverIdentifier,
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`voteForSendCoins with wrong communityReceiverIdentifier`,
|
||||
communityReceiverIdentifier,
|
||||
`voteForSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
}
|
||||
// second check if receiver user exists in this community
|
||||
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
||||
if (!receiverUser) {
|
||||
let receiverUser
|
||||
try {
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
} catch (err) {
|
||||
logger.error('Error in findUserByIdentifier:', err)
|
||||
throw new LogError(
|
||||
`voteForSendCoins with unknown userReceiverIdentifier in the community=`,
|
||||
`voteForSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
}
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`,
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const txDate = new Date(creationDate)
|
||||
const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate)
|
||||
const txDate = new Date(args.creationDate)
|
||||
const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate)
|
||||
const pendingTx = DbPendingTransaction.create()
|
||||
pendingTx.amount = amount
|
||||
pendingTx.balance = receiveBalance ? receiveBalance.balance : new Decimal(0)
|
||||
pendingTx.amount = args.amount
|
||||
pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount
|
||||
pendingTx.balanceDate = txDate
|
||||
pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
||||
pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
||||
pendingTx.creationDate = new Date()
|
||||
pendingTx.linkedUserCommunityUuid = communitySenderIdentifier
|
||||
pendingTx.linkedUserGradidoID = userSenderIdentifier
|
||||
pendingTx.linkedUserName = userSenderName
|
||||
pendingTx.memo = memo
|
||||
pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid
|
||||
pendingTx.linkedUserGradidoID = args.senderUserUuid
|
||||
pendingTx.linkedUserName = args.senderUserName
|
||||
pendingTx.memo = args.memo
|
||||
pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
||||
pendingTx.state = PendingTransactionState.NEW
|
||||
pendingTx.typeId = TransactionTypeId.RECEIVE
|
||||
pendingTx.userId = receiverUser.id
|
||||
pendingTx.userCommunityUuid = communityReceiverIdentifier
|
||||
pendingTx.userGradidoID = userReceiverIdentifier
|
||||
pendingTx.userCommunityUuid = args.recipientCommunityUuid
|
||||
pendingTx.userGradidoID = receiverUser.gradidoID
|
||||
pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName)
|
||||
|
||||
await DbPendingTransaction.insert(pendingTx)
|
||||
result = pendingTx.userName
|
||||
result.vote = true
|
||||
result.recipFirstName = receiverUser.firstName
|
||||
result.recipLastName = receiverUser.lastName
|
||||
result.recipAlias = receiverUser.alias
|
||||
result.recipGradidoID = receiverUser.gradidoID
|
||||
logger.debug(`voteForSendCoins()-1_0... successfull`)
|
||||
} catch (err) {
|
||||
throw new LogError(`Error in voteForSendCoins: `, err)
|
||||
@ -82,49 +112,43 @@ export class SendCoinsResolver {
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revertSendCoins(
|
||||
@Args()
|
||||
{
|
||||
communityReceiverIdentifier,
|
||||
userReceiverIdentifier,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
communitySenderIdentifier,
|
||||
userSenderIdentifier,
|
||||
userSenderName,
|
||||
}: SendCoinsArgs,
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: communityReceiverIdentifier,
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`revertSendCoins with wrong communityReceiverIdentifier`,
|
||||
communityReceiverIdentifier,
|
||||
`revertSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
}
|
||||
// second check if receiver user exists in this community
|
||||
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
||||
if (!receiverUser) {
|
||||
let receiverUser
|
||||
try {
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
} catch (err) {
|
||||
logger.error('Error in findUserByIdentifier:', err)
|
||||
throw new LogError(
|
||||
`revertSendCoins with unknown userReceiverIdentifier in the community=`,
|
||||
`revertSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
}
|
||||
try {
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: communityReceiverIdentifier,
|
||||
userGradidoID: userReceiverIdentifier,
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userGradidoID: receiverUser.gradidoID,
|
||||
state: PendingTransactionState.NEW,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(creationDate),
|
||||
linkedUserCommunityUuid: communitySenderIdentifier,
|
||||
linkedUserGradidoID: userSenderIdentifier,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
})
|
||||
logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx)
|
||||
if (pendingTx && pendingTx.amount.toString() === amount.toString()) {
|
||||
if (pendingTx && pendingTx.amount.toString() === args.amount.toString()) {
|
||||
logger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
||||
try {
|
||||
await pendingTx.remove()
|
||||
@ -135,21 +159,21 @@ export class SendCoinsResolver {
|
||||
} else {
|
||||
logger.debug(
|
||||
'XCom: revertSendCoins NOT matching pendingTX for remove:',
|
||||
pendingTx?.amount,
|
||||
amount,
|
||||
pendingTx?.amount.toString(),
|
||||
args.amount.toString(),
|
||||
)
|
||||
throw new LogError(
|
||||
`Can't find in revertSendCoins the pending receiver TX for args=`,
|
||||
communityReceiverIdentifier,
|
||||
userReceiverIdentifier,
|
||||
args.recipientCommunityUuid,
|
||||
args.recipientUserIdentifier,
|
||||
PendingTransactionState.NEW,
|
||||
TransactionTypeId.RECEIVE,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
communitySenderIdentifier,
|
||||
userSenderIdentifier,
|
||||
userSenderName,
|
||||
args.creationDate,
|
||||
args.amount,
|
||||
args.memo,
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
args.senderUserName,
|
||||
)
|
||||
}
|
||||
logger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
@ -158,4 +182,146 @@ export class SendCoinsResolver {
|
||||
throw new LogError(`Error in revertSendCoins: `, err)
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async settleSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(
|
||||
`settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${
|
||||
args.recipientCommunityUuid
|
||||
}, userGradidoID=${args.recipientUserIdentifier}, balanceDate=${
|
||||
args.creationDate
|
||||
},amount=${args.amount.valueOf()}, memo=${args.memo}, linkedUserCommunityUuid = ${
|
||||
args.senderCommunityUuid
|
||||
}, userSenderIdentifier=${args.senderUserUuid}, userSenderName=${args.senderUserName}`,
|
||||
)
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`settleSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
}
|
||||
let receiverUser
|
||||
try {
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
} catch (err) {
|
||||
logger.error('Error in findUserByIdentifier:', err)
|
||||
throw new LogError(
|
||||
`settleSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
}
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userGradidoID: receiverUser.gradidoID,
|
||||
state: PendingTransactionState.NEW,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
})
|
||||
logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString())
|
||||
if (
|
||||
pendingTx &&
|
||||
pendingTx.amount.toString() === args.amount.toString() &&
|
||||
pendingTx.memo === args.memo
|
||||
) {
|
||||
logger.debug('XCom: settleSendCoins matching pendingTX for settlement...')
|
||||
|
||||
await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx)
|
||||
logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`)
|
||||
return true
|
||||
} else {
|
||||
logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...')
|
||||
throw new LogError(
|
||||
`Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`,
|
||||
args.recipientCommunityUuid,
|
||||
args.recipientUserIdentifier,
|
||||
PendingTransactionState.NEW,
|
||||
TransactionTypeId.RECEIVE,
|
||||
args.creationDate,
|
||||
args.amount,
|
||||
args.memo,
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
args.senderUserName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revertSettledSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`)
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`revertSettledSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
}
|
||||
let receiverUser
|
||||
try {
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
} catch (err) {
|
||||
logger.error('Error in findUserByIdentifier:', err)
|
||||
throw new LogError(
|
||||
`revertSettledSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
}
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userGradidoID: args.recipientUserIdentifier,
|
||||
state: PendingTransactionState.SETTLED,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
})
|
||||
logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx)
|
||||
if (
|
||||
pendingTx &&
|
||||
pendingTx.amount.toString() === args.amount.toString() &&
|
||||
pendingTx.memo === args.memo
|
||||
) {
|
||||
logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...')
|
||||
try {
|
||||
await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx)
|
||||
logger.debug('XCom: revertSettledSendCoins pendingTX successfully')
|
||||
} catch (err) {
|
||||
throw new LogError('Error in revertSettledSendCoins of receiver: ', err)
|
||||
}
|
||||
} else {
|
||||
logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...')
|
||||
throw new LogError(
|
||||
`Can't find in revertSettledSendCoins the pending receiver TX for args=`,
|
||||
args.recipientCommunityUuid,
|
||||
args.recipientUserIdentifier,
|
||||
PendingTransactionState.SETTLED,
|
||||
TransactionTypeId.RECEIVE,
|
||||
args.creationDate,
|
||||
args.amount,
|
||||
args.memo,
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
args.senderUserName,
|
||||
)
|
||||
}
|
||||
logger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { calculateDecay } from '@/graphql/util/decay'
|
||||
import { getLastTransaction } from '@/graphql/util/getLastTransaction'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { getLastTransaction } from './getLastTransaction'
|
||||
import { calculateDecay } from './decay'
|
||||
import { Decay } from '../api/1_0/model/Decay'
|
||||
import { Decay } from '../model/Decay'
|
||||
|
||||
export async function calculateRecipientBalance(
|
||||
userId: number,
|
||||
@ -0,0 +1,158 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
import { getLastTransaction } from '@/graphql/util/getLastTransaction'
|
||||
import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK'
|
||||
|
||||
export async function revertSettledReceiveTransaction(
|
||||
homeCom: DbCommunity,
|
||||
receiverUser: DbUser,
|
||||
pendingTx: DbPendingTransaction,
|
||||
): Promise<boolean> {
|
||||
// TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!!
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
logger.debug(`start Transaction for write-access...`)
|
||||
|
||||
try {
|
||||
logger.info('X-Com: revertSettledReceiveTransaction:', homeCom, receiverUser, pendingTx)
|
||||
|
||||
// ensure that no other pendingTx with the same sender or recipient exists
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
{ userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED },
|
||||
{
|
||||
linkedUserGradidoID: pendingTx.linkedUserGradidoID!,
|
||||
state: PendingTransactionState.NEW,
|
||||
},
|
||||
{
|
||||
linkedUserGradidoID: pendingTx.linkedUserGradidoID!,
|
||||
state: PendingTransactionState.SETTLED,
|
||||
},
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
{ userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.SETTLED },
|
||||
{ linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) {
|
||||
throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient')
|
||||
}
|
||||
|
||||
const lastTransaction = await getLastTransaction(receiverUser.id)
|
||||
logger.debug(`LastTransaction vs PendingTransaction`)
|
||||
logger.debug(`balance:`, lastTransaction?.balance.toString(), pendingTx.balance.toString())
|
||||
logger.debug(
|
||||
`balanceDate:`,
|
||||
lastTransaction?.balanceDate.toISOString(),
|
||||
pendingTx.balanceDate.toISOString(),
|
||||
)
|
||||
logger.debug(`GradidoID:`, lastTransaction?.userGradidoID, pendingTx.userGradidoID)
|
||||
logger.debug(`Name:`, lastTransaction?.userName, pendingTx.userName)
|
||||
logger.debug(`amount:`, lastTransaction?.amount.toString(), pendingTx.amount.toString())
|
||||
logger.debug(`memo:`, lastTransaction?.memo, pendingTx.memo)
|
||||
logger.debug(
|
||||
`linkedUserGradidoID:`,
|
||||
lastTransaction?.linkedUserGradidoID,
|
||||
pendingTx.linkedUserGradidoID,
|
||||
)
|
||||
logger.debug(`linkedUserName:`, lastTransaction?.linkedUserName, pendingTx.linkedUserName)
|
||||
// now the last Tx must be the equivalant to the pendingTX
|
||||
if (
|
||||
lastTransaction &&
|
||||
lastTransaction.balance.comparedTo(pendingTx.balance) === 0 &&
|
||||
lastTransaction.balanceDate.toISOString() === pendingTx.balanceDate.toISOString() &&
|
||||
lastTransaction.userGradidoID === pendingTx.userGradidoID &&
|
||||
lastTransaction.userName === pendingTx.userName &&
|
||||
lastTransaction.amount.comparedTo(pendingTx.amount) === 0 &&
|
||||
lastTransaction.memo === pendingTx.memo &&
|
||||
lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID &&
|
||||
lastTransaction.linkedUserName === pendingTx.linkedUserName
|
||||
) {
|
||||
await queryRunner.manager.remove(dbTransaction, lastTransaction)
|
||||
logger.debug(`X-Com: revert settlement receive Transaction removed:`, lastTransaction)
|
||||
// and mark the pendingTx in the pending_transactions table as reverted
|
||||
pendingTx.state = PendingTransactionState.REVERTED
|
||||
await queryRunner.manager.save(DbPendingTransaction, pendingTx)
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit revert settlement recipient Transaction successful...`)
|
||||
} else {
|
||||
// TODO: if the last TX is not equivelant to pendingTX, the transactions must be corrected in EXPERT-MODE
|
||||
throw new LogError(
|
||||
`X-Com: missmatching transaction order for revert settlement!`,
|
||||
lastTransaction,
|
||||
pendingTx,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
recipient,
|
||||
sender,
|
||||
transactionReceive,
|
||||
transactionReceive.amount,
|
||||
)
|
||||
*/
|
||||
// trigger to send transaction via dlt-connector
|
||||
// void sendTransactionsToDltConnector()
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('X-Com: revert settlement recipient Transaction was not successful', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
releaseLock()
|
||||
}
|
||||
/*
|
||||
void sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
email: recipient.emailContact.email,
|
||||
language: recipient.language,
|
||||
senderFirstName: sender.firstName,
|
||||
senderLastName: sender.lastName,
|
||||
senderEmail: sender.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
})
|
||||
if (transactionLink) {
|
||||
void sendTransactionLinkRedeemedEmail({
|
||||
firstName: sender.firstName,
|
||||
lastName: sender.lastName,
|
||||
email: sender.emailContact.email,
|
||||
language: sender.language,
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
senderEmail: recipient.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
logger.info(`finished executeTransaction successfully`)
|
||||
} finally {
|
||||
releaseLock()
|
||||
}
|
||||
*/
|
||||
return true
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
import { getLastTransaction } from '@/graphql/util/getLastTransaction'
|
||||
import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK'
|
||||
import { calculateRecipientBalance } from './calculateRecipientBalance'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
export async function settlePendingReceiveTransaction(
|
||||
homeCom: DbCommunity,
|
||||
receiverUser: DbUser,
|
||||
pendingTx: DbPendingTransaction,
|
||||
): Promise<boolean> {
|
||||
// TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!!
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
logger.debug(`start Transaction for write-access...`)
|
||||
|
||||
try {
|
||||
logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx)
|
||||
|
||||
// ensure that no other pendingTx with the same sender or recipient exists
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
const openReceiverPendingTx = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) {
|
||||
throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient')
|
||||
}
|
||||
|
||||
const lastTransaction = await getLastTransaction(receiverUser.id)
|
||||
|
||||
if (lastTransaction !== null && lastTransaction.id !== pendingTx.previous) {
|
||||
throw new LogError(
|
||||
`X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`,
|
||||
)
|
||||
}
|
||||
|
||||
// transfer the pendingTx to the transactions table
|
||||
const transactionReceive = new dbTransaction()
|
||||
transactionReceive.typeId = pendingTx.typeId
|
||||
transactionReceive.memo = pendingTx.memo
|
||||
transactionReceive.userId = pendingTx.userId
|
||||
transactionReceive.userCommunityUuid = pendingTx.userCommunityUuid
|
||||
transactionReceive.userGradidoID = pendingTx.userGradidoID
|
||||
transactionReceive.userName = pendingTx.userName
|
||||
transactionReceive.linkedUserId = pendingTx.linkedUserId
|
||||
transactionReceive.linkedUserCommunityUuid = pendingTx.linkedUserCommunityUuid
|
||||
transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID
|
||||
transactionReceive.linkedUserName = pendingTx.linkedUserName
|
||||
transactionReceive.amount = pendingTx.amount
|
||||
const receiveBalance = await calculateRecipientBalance(
|
||||
receiverUser.id,
|
||||
pendingTx.amount,
|
||||
pendingTx.balanceDate,
|
||||
)
|
||||
transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount
|
||||
transactionReceive.balanceDate = pendingTx.balanceDate
|
||||
transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
||||
transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
||||
transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
||||
transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId
|
||||
await queryRunner.manager.insert(dbTransaction, transactionReceive)
|
||||
logger.debug(`receive Transaction inserted: ${dbTransaction}`)
|
||||
|
||||
// and mark the pendingTx in the pending_transactions table as settled
|
||||
pendingTx.state = PendingTransactionState.SETTLED
|
||||
await queryRunner.manager.save(DbPendingTransaction, pendingTx)
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit recipient Transaction successful...`)
|
||||
|
||||
/*
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
recipient,
|
||||
sender,
|
||||
transactionReceive,
|
||||
transactionReceive.amount,
|
||||
)
|
||||
*/
|
||||
// trigger to send transaction via dlt-connector
|
||||
// void sendTransactionsToDltConnector()
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('X-Com: recipient Transaction was not successful', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
releaseLock()
|
||||
}
|
||||
/*
|
||||
void sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
email: recipient.emailContact.email,
|
||||
language: recipient.language,
|
||||
senderFirstName: sender.firstName,
|
||||
senderLastName: sender.lastName,
|
||||
senderEmail: sender.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
})
|
||||
if (transactionLink) {
|
||||
void sendTransactionLinkRedeemedEmail({
|
||||
firstName: sender.firstName,
|
||||
lastName: sender.lastName,
|
||||
email: sender.emailContact.email,
|
||||
language: sender.language,
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
senderEmail: recipient.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
logger.info(`finished executeTransaction successfully`)
|
||||
} finally {
|
||||
releaseLock()
|
||||
}
|
||||
*/
|
||||
return true
|
||||
}
|
||||
4
federation/src/graphql/util/TRANSACTIONS_LOCK.ts
Normal file
4
federation/src/graphql/util/TRANSACTIONS_LOCK.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Semaphore } from 'await-semaphore'
|
||||
|
||||
const CONCURRENT_TRANSACTIONS = 1
|
||||
export const TRANSACTIONS_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS)
|
||||
25
federation/src/graphql/util/checkTradingLevel.ts
Normal file
25
federation/src/graphql/util/checkTradingLevel.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import CONFIG from '@/config'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
export async function checkTradingLevel(homeCom: DbCommunity, amount: Decimal): Promise<boolean> {
|
||||
const tradingLevel = CONFIG.FEDERATION_TRADING_LEVEL
|
||||
if (homeCom.url !== tradingLevel.RECEIVER_COMMUNITY_URL) {
|
||||
logger.warn(
|
||||
`X-Com: tradingLevel allows to receive coins only with url ${tradingLevel.RECEIVER_COMMUNITY_URL}`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
if (!tradingLevel.SEND_COINS) {
|
||||
logger.warn(`X-Com: tradingLevel disable general x-com sendcoin actions!`)
|
||||
return false
|
||||
}
|
||||
if (new Decimal(tradingLevel.AMOUNT) < amount) {
|
||||
logger.warn(
|
||||
`X-Com: tradingLevel only allows to receive coins lower than amount of ${tradingLevel.AMOUNT}`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
42
federation/src/graphql/util/findUserByIdentifier.ts
Normal file
42
federation/src/graphql/util/findUserByIdentifier.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
import { validate, version } from 'uuid'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { VALID_ALIAS_REGEX } from './validateAlias'
|
||||
|
||||
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||
let user: DbUser | null
|
||||
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({
|
||||
where: {
|
||||
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 if (VALID_ALIAS_REGEX.exec(identifier)) {
|
||||
user = await DbUser.findOne({ where: { alias: identifier }, relations: ['emailContact'] })
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
}
|
||||
} else {
|
||||
throw new LogError('Unknown identifier type', identifier)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
39
federation/src/graphql/util/validateAlias.ts
Normal file
39
federation/src/graphql/util/validateAlias.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Raw } from '@dbTools/typeorm'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const VALID_ALIAS_REGEX = /^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/
|
||||
|
||||
const RESERVED_ALIAS = [
|
||||
'admin',
|
||||
'email',
|
||||
'gast',
|
||||
'gdd',
|
||||
'gradido',
|
||||
'guest',
|
||||
'home',
|
||||
'root',
|
||||
'support',
|
||||
'temp',
|
||||
'tmp',
|
||||
'tmp',
|
||||
'user',
|
||||
'usr',
|
||||
'var',
|
||||
]
|
||||
|
||||
export const validateAlias = async (alias: string): Promise<boolean> => {
|
||||
if (alias.length < 3) throw new LogError('Given alias is too short', alias)
|
||||
if (alias.length > 20) throw new LogError('Given alias is too long', alias)
|
||||
if (!alias.match(VALID_ALIAS_REGEX)) throw new LogError('Invalid characters in alias', alias)
|
||||
if (RESERVED_ALIAS.includes(alias.toLowerCase()))
|
||||
throw new LogError('Alias is not allowed', alias)
|
||||
const aliasInUse = await DbUser.find({
|
||||
where: { alias: Raw((a) => `LOWER(${a}) = "${alias.toLowerCase()}"`) },
|
||||
})
|
||||
if (aliasInUse.length !== 0) {
|
||||
throw new LogError('Alias already in use', alias)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import createServer from './server/createServer'
|
||||
import { createServer } from './server/createServer'
|
||||
|
||||
// config
|
||||
import CONFIG from './config'
|
||||
import { CONFIG } from './config'
|
||||
|
||||
async function main() {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -13,7 +13,7 @@ import cors from './cors'
|
||||
import plugins from './plugins'
|
||||
|
||||
// config
|
||||
import CONFIG from '@/config'
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
// graphql
|
||||
import schema from '@/graphql/schema'
|
||||
@ -33,7 +33,7 @@ import { Logger } from 'log4js'
|
||||
|
||||
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection }
|
||||
|
||||
const createServer = async (
|
||||
export const createServer = async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// context: any = serverContext,
|
||||
logger: Logger = apolloLogger,
|
||||
|
||||
37
federation/test/extensions.ts
Normal file
37
federation/test/extensions.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
expect.extend({
|
||||
decimalEqual(received, value) {
|
||||
const pass = new Decimal(value).equals(received.toString())
|
||||
if (pass) {
|
||||
return {
|
||||
message: () => `expected ${received} to not equal ${value}`,
|
||||
pass: true,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () => `expected ${received} to equal ${value}`,
|
||||
pass: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
decimalEqual(value: number): R
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
interface Expect extends CustomMatchers {}
|
||||
interface Matchers<R> extends CustomMatchers<R> {}
|
||||
interface InverseAsymmetricMatchers extends CustomMatchers {}
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import { entities } from '@entity/index'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import createServer from '@/server/createServer'
|
||||
import { createServer } from '@/server/createServer'
|
||||
|
||||
import { logger } from './testSetup'
|
||||
|
||||
|
||||
@ -53,13 +53,17 @@
|
||||
// "@model/*": ["src/graphql/model/*"],
|
||||
"@repository/*": ["src/typeorm/repository/*"],
|
||||
"@test/*": ["test/*"],
|
||||
/* common */
|
||||
// "@common/*": ["../common/src/*"],
|
||||
// "@email/*": ["../common/scr/email/*"],
|
||||
// "@event/*": ["../common/src/event/*"],
|
||||
/* external */
|
||||
"@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"],
|
||||
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
||||
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
||||
},
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */
|
||||
"typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
|
||||
@ -1057,6 +1057,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||
|
||||
"@types/uuid@8.3.4":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
"@types/ws@^7.0.0":
|
||||
version "7.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
|
||||
@ -1529,6 +1534,11 @@ available-typed-arrays@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
await-semaphore@0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3"
|
||||
integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==
|
||||
|
||||
babel-jest@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444"
|
||||
@ -5437,16 +5447,16 @@ utils-merge@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
uuid@8.3.2, uuid@^8.0.0:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^3.1.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^8.0.0:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#########################################################################################################
|
||||
# mariadb server
|
||||
#########################################################################################################
|
||||
FROM mariadb/server:10.5 as mariadb_server
|
||||
FROM mariadb:10.5 as mariadb_server
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user