diff --git a/backend/src/graphql/model/Transaction.ts b/backend/src/graphql/model/Transaction.ts index 24c66ac67..a7329bcef 100644 --- a/backend/src/graphql/model/Transaction.ts +++ b/backend/src/graphql/model/Transaction.ts @@ -42,7 +42,9 @@ export class Transaction { this.creationDate = transaction.creationDate this.linkedUser = linkedUser this.linkedTransactionId = transaction.linkedTransactionId - this.transactionLinkId = transaction.transactionLinkId + this.linkId = transaction.contribution + ? transaction.contribution.contributionLinkId + : transaction.transactionLinkId } @Field(() => Number) @@ -81,7 +83,7 @@ export class Transaction { @Field(() => Number, { nullable: true }) linkedTransactionId?: number | null - // Links to the TransactionLink when transaction was created by a link + // Links to the TransactionLink/ContributionLink when transaction was created by a link @Field(() => Number, { nullable: true }) - transactionLinkId?: number | null + linkId?: number | null } diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 5c9e3250e..b83a9876f 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -368,6 +368,19 @@ describe('AdminResolver', () => { expect(new Date(result.data.deleteUser)).toEqual(expect.any(Date)) }) + it('has deleted_at set in users and user contacts', async () => { + await expect( + User.findOneOrFail({ + where: { id: user.id }, + withDeleted: true, + relations: ['emailContact'], + }), + ).resolves.toMatchObject({ + deletedAt: expect.any(Date), + emailContact: expect.objectContaining({ deletedAt: expect.any(Date) }), + }) + }) + describe('delete deleted user', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -491,6 +504,15 @@ describe('AdminResolver', () => { }), ) }) + + it('has deleted_at set to null in users and user contacts', async () => { + await expect( + User.findOneOrFail({ where: { id: user.id }, relations: ['emailContact'] }), + ).resolves.toMatchObject({ + deletedAt: null, + emailContact: expect.objectContaining({ deletedAt: null }), + }) + }) }) }) }) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 574377dce..d084799e7 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -203,7 +203,7 @@ export class AdminResolver { @Arg('userId', () => Int) userId: number, @Ctx() context: Context, ): Promise { - const user = await dbUser.findOne({ id: userId }) + const user = await dbUser.findOne({ where: { id: userId }, relations: ['emailContact'] }) // user exists ? if (!user) { logger.error(`Could not find user with userId: ${userId}`) @@ -217,6 +217,7 @@ export class AdminResolver { } // soft-delete user await user.softRemove() + await user.emailContact.softRemove() const newUser = await dbUser.findOne({ id: userId }, { withDeleted: true }) return newUser ? newUser.deletedAt : null } @@ -224,7 +225,10 @@ export class AdminResolver { @Authorized([RIGHTS.UNDELETE_USER]) @Mutation(() => Date, { nullable: true }) async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise { - const user = await dbUser.findOne({ id: userId }, { withDeleted: true }) + const user = await dbUser.findOne( + { id: userId }, + { withDeleted: true, relations: ['emailContact'] }, + ) if (!user) { logger.error(`Could not find user with userId: ${userId}`) throw new Error(`Could not find user with userId: ${userId}`) @@ -234,6 +238,7 @@ export class AdminResolver { throw new Error('User is not deleted') } await user.recover() + await user.emailContact.recover() return null } diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index be4c41242..eb8cda2c5 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -204,7 +204,7 @@ export class TransactionResolver { // find current balance const lastTransaction = await dbTransaction.findOne( { userId: user.id }, - { order: { balanceDate: 'DESC' } }, + { order: { balanceDate: 'DESC' }, relations: ['contribution'] }, ) logger.debug(`lastTransaction=${lastTransaction}`) @@ -326,7 +326,6 @@ export class TransactionResolver { // validate recipient user const recipientUser = await findUserByEmail(email) - if (recipientUser.deletedAt) { logger.error(`The recipient account was deleted: recipientUser=${recipientUser}`) throw new Error('The recipient account was deleted') diff --git a/backend/src/typeorm/repository/Transaction.ts b/backend/src/typeorm/repository/Transaction.ts index f84b57626..58e9ca30b 100644 --- a/backend/src/typeorm/repository/Transaction.ts +++ b/backend/src/typeorm/repository/Transaction.ts @@ -12,29 +12,24 @@ export class TransactionRepository extends Repository { order: Order, onlyCreation?: boolean, ): Promise<[Transaction[], number]> { - if (onlyCreation) { - return this.createQueryBuilder('userTransaction') - .where('userTransaction.userId = :userId', { userId }) - .andWhere('userTransaction.typeId = :typeId', { - typeId: TransactionTypeId.CREATION, - }) - .orderBy('userTransaction.balanceDate', order) - .limit(limit) - .offset(offset) - .getManyAndCount() - } - return this.createQueryBuilder('userTransaction') + const query = this.createQueryBuilder('userTransaction') + .leftJoinAndSelect( + 'userTransaction.contribution', + 'contribution', + 'userTransaction.id = contribution.transactionId', + ) .where('userTransaction.userId = :userId', { userId }) + + if (onlyCreation) { + query.andWhere('userTransaction.typeId = :typeId', { + typeId: TransactionTypeId.CREATION, + }) + } + + return query .orderBy('userTransaction.balanceDate', order) .limit(limit) .offset(offset) .getManyAndCount() } - - findLastForUser(userId: number): Promise { - return this.createQueryBuilder('userTransaction') - .where('userTransaction.userId = :userId', { userId }) - .orderBy('userTransaction.balanceDate', 'DESC') - .getOne() - } } diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts index 08d44b48d..b02f87ee5 100644 --- a/backend/src/util/virtualTransactions.ts +++ b/backend/src/util/virtualTransactions.ts @@ -49,6 +49,7 @@ const virtualLinkTransaction = ( decay: decay.toDecimalPlaces(2, Decimal.ROUND_FLOOR), memo: '', creationDate: null, + contribution: null, ...defaultModelFunctions, } return new Transaction(linkDbTransaction, user) @@ -78,6 +79,7 @@ const virtualDecayTransaction = ( decayStart: decay.start, memo: '', creationDate: null, + contribution: null, ...defaultModelFunctions, } return new Transaction(decayDbTransaction, user) diff --git a/database/entity/0036-unique_previous_in_transactions/Transaction.ts b/database/entity/0036-unique_previous_in_transactions/Transaction.ts index 99202eee4..ef8d0abdc 100644 --- a/database/entity/0036-unique_previous_in_transactions/Transaction.ts +++ b/database/entity/0036-unique_previous_in_transactions/Transaction.ts @@ -1,6 +1,7 @@ import Decimal from 'decimal.js-light' -import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { Contribution } from '../Contribution' @Entity('transactions') export class Transaction extends BaseEntity { @@ -91,4 +92,8 @@ export class Transaction extends BaseEntity { default: null, }) transactionLinkId?: number | null + + @OneToOne(() => Contribution, (contribution) => contribution.transaction) + @JoinColumn({ name: 'id', referencedColumnName: 'transactionId' }) + contribution?: Contribution | null } diff --git a/database/entity/0052-add_updated_at_to_contributions/Contribution.ts b/database/entity/0052-add_updated_at_to_contributions/Contribution.ts index 2242a753f..4676c14af 100644 --- a/database/entity/0052-add_updated_at_to_contributions/Contribution.ts +++ b/database/entity/0052-add_updated_at_to_contributions/Contribution.ts @@ -8,10 +8,12 @@ import { JoinColumn, ManyToOne, OneToMany, + OneToOne, } from 'typeorm' import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' import { User } from '../User' import { ContributionMessage } from '../ContributionMessage' +import { Transaction } from '../Transaction' @Entity('contributions') export class Contribution extends BaseEntity { @@ -92,4 +94,8 @@ export class Contribution extends BaseEntity { @OneToMany(() => ContributionMessage, (message) => message.contribution) @JoinColumn({ name: 'contribution_id' }) messages?: ContributionMessage[] + + @OneToOne(() => Transaction, (transaction) => transaction.contribution) + @JoinColumn({ name: 'transaction_id' }) + transaction?: Transaction | null } diff --git a/frontend/src/components/TransactionRows/AmountAndNameRow.vue b/frontend/src/components/TransactionRows/AmountAndNameRow.vue index 322ad7dfa..eb68d9f37 100644 --- a/frontend/src/components/TransactionRows/AmountAndNameRow.vue +++ b/frontend/src/components/TransactionRows/AmountAndNameRow.vue @@ -10,21 +10,21 @@
-
+ {{ itemText }} - - {{ $t('via_link') }} - - -
+ {{ itemText }} + + {{ $t('via_link') }} + +
@@ -46,7 +46,7 @@ export default { type: String, required: false, }, - transactionLinkId: { + linkId: { type: Number, required: false, default: null, diff --git a/frontend/src/components/Transactions/TransactionCreation.vue b/frontend/src/components/Transactions/TransactionCreation.vue index 694d907ed..eaf89a582 100644 --- a/frontend/src/components/Transactions/TransactionCreation.vue +++ b/frontend/src/components/Transactions/TransactionCreation.vue @@ -12,7 +12,12 @@ - + @@ -77,6 +82,10 @@ export default { type: String, required: true, }, + linkId: { + type: Number, + required: false, + }, previousBookedBalance: { type: String, required: true, diff --git a/frontend/src/components/Transactions/TransactionReceive.vue b/frontend/src/components/Transactions/TransactionReceive.vue index 8899b3807..389ac9d5d 100644 --- a/frontend/src/components/Transactions/TransactionReceive.vue +++ b/frontend/src/components/Transactions/TransactionReceive.vue @@ -17,7 +17,7 @@ v-on="$listeners" :amount="amount" :linkedUser="linkedUser" - :transactionLinkId="transactionLinkId" + :linkId="linkId" /> @@ -82,7 +82,7 @@ export default { typeId: { type: String, }, - transactionLinkId: { + linkId: { type: Number, required: false, }, diff --git a/frontend/src/components/Transactions/TransactionSend.vue b/frontend/src/components/Transactions/TransactionSend.vue index f9125b89c..c02f230e7 100644 --- a/frontend/src/components/Transactions/TransactionSend.vue +++ b/frontend/src/components/Transactions/TransactionSend.vue @@ -17,7 +17,7 @@ v-on="$listeners" :amount="amount" :linkedUser="linkedUser" - :transactionLinkId="transactionLinkId" + :linkId="linkId" /> @@ -83,7 +83,7 @@ export default { type: String, required: true, }, - transactionLinkId: { + linkId: { type: Number, required: false, }, diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 1c910a23e..d261797c2 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -45,7 +45,7 @@ export const transactionsQuery = gql` end duration } - transactionLinkId + linkId } } }