diff --git a/backend/src/graphql/arg/TransactionLinkArgs.ts b/backend/src/graphql/arg/TransactionLinkArgs.ts new file mode 100644 index 000000000..5ccb967d3 --- /dev/null +++ b/backend/src/graphql/arg/TransactionLinkArgs.ts @@ -0,0 +1,14 @@ +import { ArgsType, Field } from 'type-graphql' +import Decimal from 'decimal.js-light' + +@ArgsType() +export default class TransactionLinkArgs { + @Field(() => Decimal) + amount: Decimal + + @Field(() => String) + memo: string + + @Field(() => Boolean, { nullable: true }) + showEmail?: boolean +} diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts new file mode 100644 index 000000000..51790502d --- /dev/null +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -0,0 +1,14 @@ +import { transactionLinkCode } from './TransactionLinkResolver' + +describe('transactionLinkCode', () => { + const date = new Date() + + it('returns a string of length 96', () => { + expect(transactionLinkCode(date)).toHaveLength(96) + }) + + it('returns a string that ends with the hex value of date', () => { + const regexp = new RegExp(date.getTime().toString(16) + '$') + expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) + }) +}) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts new file mode 100644 index 000000000..920e18569 --- /dev/null +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { Resolver, Args, Authorized, Ctx, Mutation } from 'type-graphql' +import { getCustomRepository } from '@dbTools/typeorm' +import { TransactionLink } from '@model/TransactionLink' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import TransactionLinkArgs from '@arg/TransactionLinkArgs' +import { UserRepository } from '@repository/User' +import { calculateBalance } from '@/util/validate' +import { RIGHTS } from '@/auth/RIGHTS' +import { randomBytes } from 'crypto' +import { User } from '@model/User' + +// TODO: do not export, test it inside the resolver +export const transactionLinkCode = (date: Date): string => { + const time = date.getTime().toString(16) + return ( + randomBytes(48) + .toString('hex') + .substring(0, 96 - time.length) + time + ) +} + +const transactionLinkExpireDate = (date: Date): Date => { + // valid for 14 days + return new Date(date.setDate(date.getDate() + 14)) +} + +@Resolver() +export class TransactionLinkResolver { + @Authorized([RIGHTS.CREATE_TRANSACTION_LINK]) + @Mutation(() => TransactionLink) + async createTransactionLink( + @Args() { amount, memo, showEmail = false }: TransactionLinkArgs, + @Ctx() context: any, + ): Promise { + const userRepository = getCustomRepository(UserRepository) + const user = await userRepository.findByPubkeyHex(context.pubKey) + + // validate amount + // TODO taken from transaction resolver, duplicate code + const createdDate = new Date() + const sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate) + if (!sendBalance) { + throw new Error("user hasn't enough GDD or amount is < 0") + } + + // TODO!!!! Test balance for pending transaction links + + const transactionLink = dbTransactionLink.create() + transactionLink.userId = user.id + transactionLink.amount = amount + transactionLink.memo = memo + transactionLink.code = transactionLinkCode(createdDate) + transactionLink.createdAt = createdDate + transactionLink.validUntil = transactionLinkExpireDate(createdDate) + transactionLink.showEmail = showEmail + await dbTransactionLink.save(transactionLink).catch((error) => { + throw error + }) + + return new TransactionLink(transactionLink, new User(user)) + } +}