mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3508 from gradido/refactor_countOpenPendingTransactions_PendingTransactionState
refactor(federation): move code for checking pending transactions
This commit is contained in:
commit
dee23c6069
@ -1,11 +1,5 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
registerEnumType(PendingTransactionState, {
|
||||
name: 'PendingTransactionState', // this one is mandatory
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
AppDatabase,
|
||||
countOpenPendingTransactions,
|
||||
Community as DbCommunity,
|
||||
PendingTransaction as DbPendingTransaction,
|
||||
Transaction as dbTransaction,
|
||||
@ -14,7 +15,7 @@ import { In, IsNull } from 'typeorm'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
|
||||
import { Order } from '@enum/Order'
|
||||
import { PendingTransactionState } from '@enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionList } from '@model/TransactionList'
|
||||
@ -68,19 +69,7 @@ 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) {
|
||||
if (await countOpenPendingTransactions([sender.gradidoID, recipient.gradidoID]) > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`,
|
||||
)
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
PendingTransactionLoggingView,
|
||||
CommunityLoggingView,
|
||||
UserLoggingView,
|
||||
countOpenPendingTransactions,
|
||||
} from 'database'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
@ -15,7 +16,7 @@ import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0
|
||||
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory'
|
||||
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { LogError } from '@/server/LogError'
|
||||
@ -53,19 +54,7 @@ export async function processXComPendingSendCoins(
|
||||
}
|
||||
)
|
||||
}
|
||||
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) {
|
||||
if (await countOpenPendingTransactions([sender.gradidoID, recipientIdentifier]) > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`,
|
||||
)
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from 'database'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
|
||||
@ -1,14 +1,7 @@
|
||||
import { PendingTransaction, Transaction } from '../entity'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { TransactionLoggingView } from './TransactionLogging.view'
|
||||
|
||||
// TODO: move enum into database, maybe rename database
|
||||
enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
export class PendingTransactionLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: PendingTransaction) {
|
||||
|
||||
@ -2,5 +2,6 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
|
||||
export * from './user'
|
||||
export * from './communities'
|
||||
export * from './pendingTransactions'
|
||||
|
||||
export const LOG4JS_QUERIES_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.queries`
|
||||
|
||||
124
database/src/queries/pendingTransactions.test.ts
Normal file
124
database/src/queries/pendingTransactions.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
PendingTransaction as DbPendingTransaction,
|
||||
User as DbUser,
|
||||
UserContact as DbUserContact,
|
||||
Community as DbCommunity
|
||||
} from '..'
|
||||
import { countOpenPendingTransactions } from './pendingTransactions'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { AppDatabase } from '../AppDatabase'
|
||||
import { userFactory } from '../seeds/factory/user'
|
||||
import { pendingTransactionFactory } from '../seeds/factory/pendingTransaction'
|
||||
import { bibiBloxberg } from '../seeds/users/bibi-bloxberg'
|
||||
import { peterLustig } from '../seeds/users/peter-lustig'
|
||||
import { bobBaumeister } from '../seeds/users/bob-baumeister'
|
||||
import { garrickOllivander } from '../seeds/users/garrick-ollivander'
|
||||
import { describe, expect, it, beforeAll, afterAll } from 'vitest'
|
||||
import { createCommunity } from '../seeds/homeCommunity'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
|
||||
beforeAll(async () => {
|
||||
await db.init()
|
||||
})
|
||||
afterAll(async () => {
|
||||
await db.destroy()
|
||||
})
|
||||
|
||||
|
||||
describe('countOpenPendingTransactions', () => {
|
||||
let bibi: DbUser
|
||||
let peter: DbUser
|
||||
let bob: DbUser
|
||||
let garrick: DbUser
|
||||
beforeAll(async () => {
|
||||
await DbPendingTransaction.clear()
|
||||
await DbUser.clear()
|
||||
await DbUserContact.clear()
|
||||
await DbCommunity.clear()
|
||||
|
||||
await createCommunity(false)
|
||||
|
||||
bibi = await userFactory(bibiBloxberg)
|
||||
peter = await userFactory(peterLustig)
|
||||
bob = await userFactory(bobBaumeister)
|
||||
garrick = await userFactory(garrickOllivander)
|
||||
|
||||
// Bibi -> Peter
|
||||
await pendingTransactionFactory(
|
||||
bibi,
|
||||
peter,
|
||||
new Decimal(10),
|
||||
'Bibi -> Peter new',
|
||||
PendingTransactionState.NEW
|
||||
)
|
||||
await pendingTransactionFactory(
|
||||
bibi,
|
||||
peter,
|
||||
new Decimal(100.01),
|
||||
'Bibi -> Peter settled',
|
||||
PendingTransactionState.SETTLED
|
||||
)
|
||||
|
||||
// Peter -> Bibi
|
||||
await pendingTransactionFactory(
|
||||
peter,
|
||||
bibi,
|
||||
new Decimal(12),
|
||||
'Peter -> Bibi new',
|
||||
PendingTransactionState.NEW
|
||||
)
|
||||
|
||||
// Bob -> Peter
|
||||
await pendingTransactionFactory(
|
||||
bob,
|
||||
peter,
|
||||
new Decimal(17.1),
|
||||
'Bob -> Peter new',
|
||||
PendingTransactionState.NEW
|
||||
)
|
||||
|
||||
})
|
||||
it('should return 0 if called with empty array', async () => {
|
||||
const count = await countOpenPendingTransactions([])
|
||||
expect(count).toBe(0)
|
||||
})
|
||||
|
||||
it('should return 0 if called with unknown gradido id', async () => {
|
||||
const count = await countOpenPendingTransactions([uuidv4()])
|
||||
expect(count).toBe(0)
|
||||
})
|
||||
|
||||
it('should return 0 if there are no pending transactions for the given user', async () => {
|
||||
const count = await countOpenPendingTransactions([garrick.gradidoID])
|
||||
expect(count).toBe(0)
|
||||
})
|
||||
|
||||
it('bibi and peter have two transactions together and peter one additional, should return 3', async () => {
|
||||
const count = await countOpenPendingTransactions([bibi.gradidoID, peter.gradidoID])
|
||||
expect(count).toBe(3)
|
||||
})
|
||||
|
||||
it('peter and bob have one transaction together, peter two additional, should return 3', async () => {
|
||||
const count = await countOpenPendingTransactions([peter.gradidoID, bob.gradidoID])
|
||||
expect(count).toBe(3)
|
||||
})
|
||||
|
||||
it('peter has three transactions, should return 3', async () => {
|
||||
const count = await countOpenPendingTransactions([peter.gradidoID])
|
||||
expect(count).toBe(3)
|
||||
})
|
||||
|
||||
|
||||
it('bibi has two transactions, should return 2', async () => {
|
||||
const count = await countOpenPendingTransactions([bibi.gradidoID])
|
||||
expect(count).toBe(2)
|
||||
})
|
||||
|
||||
it('bob has one transaction, should return 1', async () => {
|
||||
const count = await countOpenPendingTransactions([bob.gradidoID])
|
||||
expect(count).toBe(1)
|
||||
})
|
||||
})
|
||||
18
database/src/queries/pendingTransactions.ts
Normal file
18
database/src/queries/pendingTransactions.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { PendingTransaction as DbPendingTransaction } from '../entity'
|
||||
import { In } from 'typeorm'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
/**
|
||||
* Counts the number of open pending transactions for the given users.
|
||||
* @param users The users gradidoID to count the pending transactions for
|
||||
* @returns The number of open pending transactions
|
||||
*/
|
||||
export async function countOpenPendingTransactions(users: string[]): Promise<number> {
|
||||
const count = await DbPendingTransaction.count({
|
||||
where: [
|
||||
{ userGradidoID: In(users), state: PendingTransactionState.NEW },
|
||||
{ linkedUserGradidoID: In(users), state: PendingTransactionState.NEW },
|
||||
],
|
||||
})
|
||||
return count
|
||||
}
|
||||
@ -3,13 +3,12 @@ import { AppDatabase } from '../AppDatabase'
|
||||
import { aliasExists, findUserByIdentifier } from './user'
|
||||
import { userFactory } from '../seeds/factory/user'
|
||||
import { bibiBloxberg } from '../seeds/users/bibi-bloxberg'
|
||||
import { describe, expect, it, beforeAll, afterAll } from 'vitest'
|
||||
import { describe, expect, it, beforeAll, afterAll, beforeEach, } from 'vitest'
|
||||
import { createCommunity } from '../seeds/homeCommunity'
|
||||
import { peterLustig } from '../seeds/users/peter-lustig'
|
||||
import { bobBaumeister } from '../seeds/users/bob-baumeister'
|
||||
import { getLogger, printLogs, clearLogs } from '../../../config-schema/test/testSetup.vitest'
|
||||
import { LOG4JS_QUERIES_CATEGORY_NAME } from '.'
|
||||
import { beforeEach } from 'node:test'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
const userIdentifierLoggerName = `${LOG4JS_QUERIES_CATEGORY_NAME}.user.findUserByIdentifier`
|
||||
|
||||
23
database/src/seeds/factory/pendingTransaction.ts
Normal file
23
database/src/seeds/factory/pendingTransaction.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { User as DbUser, PendingTransaction as DbPendingTransaction } from '../..'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
export async function pendingTransactionFactory(
|
||||
sender: DbUser,
|
||||
receiver: DbUser,
|
||||
amount: Decimal,
|
||||
memo: string,
|
||||
state: PendingTransactionState,
|
||||
) {
|
||||
const pendingTransaction = new DbPendingTransaction()
|
||||
pendingTransaction.state = state
|
||||
pendingTransaction.memo = memo
|
||||
pendingTransaction.amount = amount
|
||||
pendingTransaction.userId = sender.id
|
||||
pendingTransaction.userGradidoID = sender.gradidoID
|
||||
pendingTransaction.userCommunityUuid = sender.communityUuid!
|
||||
pendingTransaction.linkedUserId = receiver.id
|
||||
pendingTransaction.linkedUserGradidoID = receiver.gradidoID
|
||||
pendingTransaction.linkedUserCommunityUuid = receiver.communityUuid!
|
||||
await pendingTransaction.save()
|
||||
}
|
||||
@ -14,7 +14,9 @@ export const userFactory = async (user: UserInterface): Promise<User> => {
|
||||
let dbUser = new User()
|
||||
dbUser.firstName = user.firstName ?? ''
|
||||
dbUser.lastName = user.lastName ?? ''
|
||||
dbUser.alias = user.alias ?? ''
|
||||
if (user.alias) {
|
||||
dbUser.alias = user.alias
|
||||
}
|
||||
dbUser.language = user.language ?? 'en'
|
||||
dbUser.createdAt = user.createdAt ?? new Date()
|
||||
dbUser.deletedAt = user.deletedAt ?? null
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
registerEnumType(PendingTransactionState, {
|
||||
name: 'PendingTransactionState', // this one is mandatory
|
||||
|
||||
@ -10,7 +10,7 @@ import Decimal from 'decimal.js-light'
|
||||
import { getLogger } from 'log4js'
|
||||
import { Arg, Mutation, Resolver } from 'type-graphql'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { SendCoinsArgsLoggingView } from '../logger/SendCoinsArgsLogging.view'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
@ -20,7 +20,7 @@ import { calculateRecipientBalance } from '../util/calculateRecipientBalance'
|
||||
import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction'
|
||||
import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction'
|
||||
import { storeForeignUser } from '../util/storeForeignUser'
|
||||
|
||||
import { countOpenPendingTransactions } from 'database'
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.SendCoinsResolver`)
|
||||
|
||||
@Resolver()
|
||||
@ -56,19 +56,8 @@ export class SendCoinsResolver {
|
||||
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) {
|
||||
|
||||
if (await countOpenPendingTransactions([args.senderUserUuid, receiverUser.gradidoID]) > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`,
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
Transaction as dbTransaction,
|
||||
} from 'database'
|
||||
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
UserLoggingView,
|
||||
Transaction as dbTransaction,
|
||||
} from 'database'
|
||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
|
||||
6
shared/src/enum/PendingTransactionState.ts
Normal file
6
shared/src/enum/PendingTransactionState.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export enum PendingTransactionState {
|
||||
NEW = 1,
|
||||
PENDING = 2,
|
||||
SETTLED = 3,
|
||||
REVERTED = 4,
|
||||
}
|
||||
@ -2,3 +2,4 @@ export * from './RoleNames'
|
||||
export * from './UserContactType'
|
||||
export * from './PasswordEncryptionType'
|
||||
export * from './OptInType'
|
||||
export * from './PendingTransactionState'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user