From e81d91e548511ab3980224b0b10d29ee8e1fe421 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 9 Oct 2024 10:56:38 +0330 Subject: [PATCH 1/5] Change update of create query of projectUserRecord --- .../projectUserRecordRepository.test.ts | 44 +++++++++++++++++++ .../projectUserRecordRepository.ts | 31 +++++++------ 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/repositories/projectUserRecordRepository.test.ts b/src/repositories/projectUserRecordRepository.test.ts index 2ddad08ab..d7730b91f 100644 --- a/src/repositories/projectUserRecordRepository.test.ts +++ b/src/repositories/projectUserRecordRepository.test.ts @@ -204,4 +204,48 @@ describe('projectUserRecordRepository', () => { ea1DonationAmount + ea2DonationAmount + qfDonationAmount, ); }); + + it('should update record if it already exists', async () => { + const donationAmount1 = 100; + const donationAmount2 = 200; + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + amount: donationAmount1, + status: DONATION_STATUS.VERIFIED, + }, + user.id, + project.id, + ); + + let projectUserRecord = await updateOrCreateProjectUserRecord({ + projectId: project.id, + userId: user.id, + }); + + assert.isOk(projectUserRecord); + assert.equal(projectUserRecord.totalDonationAmount, donationAmount1); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + amount: donationAmount2, + status: DONATION_STATUS.VERIFIED, + }, + user.id, + project.id, + ); + + projectUserRecord = await updateOrCreateProjectUserRecord({ + projectId: project.id, + userId: user.id, + }); + + assert.isOk(projectUserRecord); + assert.equal( + projectUserRecord.totalDonationAmount, + donationAmount1 + donationAmount2, + ); + }); }); diff --git a/src/repositories/projectUserRecordRepository.ts b/src/repositories/projectUserRecordRepository.ts index 98fdd6cb7..0655066bd 100644 --- a/src/repositories/projectUserRecordRepository.ts +++ b/src/repositories/projectUserRecordRepository.ts @@ -11,7 +11,7 @@ export async function updateOrCreateProjectUserRecord({ const { eaTotalDonationAmount, qfTotalDonationAmount, totalDonationAmount } = await Donation.createQueryBuilder('donation') .select('SUM(donation.amount)', 'totalDonationAmount') - // sum eaTotalDonationAmount if earlyAccessRoundId is not null + // Sum eaTotalDonationAmount if earlyAccessRoundId is not null .addSelect( 'SUM(CASE WHEN donation.earlyAccessRoundId IS NOT NULL THEN donation.amount ELSE 0 END)', 'eaTotalDonationAmount', @@ -27,23 +27,22 @@ export async function updateOrCreateProjectUserRecord({ .andWhere('donation.userId = :userId', { userId }) .getRawOne(); - let projectUserRecord = await ProjectUserRecord.findOneBy({ - projectId, - userId, - }); - - if (!projectUserRecord) { - projectUserRecord = ProjectUserRecord.create({ + // Create or update ProjectUserRecord using onConflict + const result = await ProjectUserRecord.createQueryBuilder() + .insert() + .values({ projectId, userId, - }); - } - - projectUserRecord.eaTotalDonationAmount = eaTotalDonationAmount || 0; - projectUserRecord.qfTotalDonationAmount = qfTotalDonationAmount || 0; - projectUserRecord.totalDonationAmount = totalDonationAmount || 0; - - return projectUserRecord.save(); + eaTotalDonationAmount: eaTotalDonationAmount || 0, + qfTotalDonationAmount: qfTotalDonationAmount || 0, + totalDonationAmount: totalDonationAmount || 0, + }) + .orUpdate( + ['eaTotalDonationAmount', 'qfTotalDonationAmount', 'totalDonationAmount'], + ['projectId', 'userId'], + ) + .execute(); + return result.raw[0]; } export type ProjectUserRecordAmounts = Pick< From b710c3b3dc29d8e4011ac7139a2646644ef1b98e Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 9 Oct 2024 11:06:14 +0330 Subject: [PATCH 2/5] Updated updateOrCreateProjectUserRecord to single one --- .../projectUserRecordRepository.ts | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/repositories/projectUserRecordRepository.ts b/src/repositories/projectUserRecordRepository.ts index 0655066bd..260c777a2 100644 --- a/src/repositories/projectUserRecordRepository.ts +++ b/src/repositories/projectUserRecordRepository.ts @@ -1,4 +1,4 @@ -import { Donation, DONATION_STATUS } from '../entities/donation'; +import { DONATION_STATUS } from '../entities/donation'; import { ProjectUserRecord } from '../entities/projectUserRecord'; export async function updateOrCreateProjectUserRecord({ @@ -8,40 +8,34 @@ export async function updateOrCreateProjectUserRecord({ projectId: number; userId: number; }): Promise { - const { eaTotalDonationAmount, qfTotalDonationAmount, totalDonationAmount } = - await Donation.createQueryBuilder('donation') - .select('SUM(donation.amount)', 'totalDonationAmount') - // Sum eaTotalDonationAmount if earlyAccessRoundId is not null - .addSelect( - 'SUM(CASE WHEN donation.earlyAccessRoundId IS NOT NULL THEN donation.amount ELSE 0 END)', - 'eaTotalDonationAmount', - ) - .addSelect( - 'SUM(CASE WHEN donation.qfRoundId IS NOT NULL THEN donation.amount ELSE 0 END)', - 'qfTotalDonationAmount', - ) - .where('donation.projectId = :projectId', { projectId }) - .andWhere('donation.status = :status', { - status: DONATION_STATUS.VERIFIED, - }) - .andWhere('donation.userId = :userId', { userId }) - .getRawOne(); - - // Create or update ProjectUserRecord using onConflict const result = await ProjectUserRecord.createQueryBuilder() .insert() .values({ projectId, userId, - eaTotalDonationAmount: eaTotalDonationAmount || 0, - qfTotalDonationAmount: qfTotalDonationAmount || 0, - totalDonationAmount: totalDonationAmount || 0, + eaTotalDonationAmount: () => ` + (SELECT COALESCE(SUM(CASE WHEN donation."earlyAccessRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) + FROM donation + WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) + `, + qfTotalDonationAmount: () => ` + (SELECT COALESCE(SUM(CASE WHEN donation."qfRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) + FROM donation + WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) + `, + totalDonationAmount: () => ` + (SELECT COALESCE(SUM(donation.amount), 0) + FROM donation + WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) + `, }) .orUpdate( ['eaTotalDonationAmount', 'qfTotalDonationAmount', 'totalDonationAmount'], ['projectId', 'userId'], ) + .setParameters({ projectId, status: DONATION_STATUS.VERIFIED, userId }) .execute(); + return result.raw[0]; } From 5a992513f360a793e426bbcd51cf73ba35e0b62c Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 9 Oct 2024 11:25:16 +0330 Subject: [PATCH 3/5] Change query to single raw query --- .../projectUserRecordRepository.ts | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/repositories/projectUserRecordRepository.ts b/src/repositories/projectUserRecordRepository.ts index 260c777a2..a6bbbf6fa 100644 --- a/src/repositories/projectUserRecordRepository.ts +++ b/src/repositories/projectUserRecordRepository.ts @@ -8,35 +8,33 @@ export async function updateOrCreateProjectUserRecord({ projectId: number; userId: number; }): Promise { - const result = await ProjectUserRecord.createQueryBuilder() - .insert() - .values({ - projectId, - userId, - eaTotalDonationAmount: () => ` - (SELECT COALESCE(SUM(CASE WHEN donation."earlyAccessRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) - FROM donation - WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) - `, - qfTotalDonationAmount: () => ` - (SELECT COALESCE(SUM(CASE WHEN donation."qfRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) - FROM donation - WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) - `, - totalDonationAmount: () => ` - (SELECT COALESCE(SUM(donation.amount), 0) - FROM donation - WHERE donation."projectId" = :projectId AND donation.status = :status AND donation."userId" = :userId) - `, - }) - .orUpdate( - ['eaTotalDonationAmount', 'qfTotalDonationAmount', 'totalDonationAmount'], - ['projectId', 'userId'], - ) - .setParameters({ projectId, status: DONATION_STATUS.VERIFIED, userId }) - .execute(); + const query = ` + INSERT INTO project_user_record ("projectId", "userId", "eaTotalDonationAmount", "qfTotalDonationAmount", "totalDonationAmount") + SELECT + $1 AS projectId, + $2 AS userId, + COALESCE(SUM(CASE WHEN donation."earlyAccessRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) AS eaTotalDonationAmount, + COALESCE(SUM(CASE WHEN donation."qfRoundId" IS NOT NULL THEN donation.amount ELSE 0 END), 0) AS qfTotalDonationAmount, + COALESCE(SUM(donation.amount), 0) AS totalDonationAmount + FROM donation + WHERE donation."projectId" = $1 + AND donation."userId" = $2 + AND donation.status = $3 + ON CONFLICT ("projectId", "userId") DO UPDATE + SET + "eaTotalDonationAmount" = EXCLUDED."eaTotalDonationAmount", + "qfTotalDonationAmount" = EXCLUDED."qfTotalDonationAmount", + "totalDonationAmount" = EXCLUDED."totalDonationAmount" + RETURNING *; +`; - return result.raw[0]; + const result = await ProjectUserRecord.query(query, [ + projectId, + userId, + DONATION_STATUS.VERIFIED, + ]); + + return result[0]; } export type ProjectUserRecordAmounts = Pick< From fa0f53f6f73884c7331cb2f2b9aade2e4d0d960b Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 9 Oct 2024 12:01:40 +0330 Subject: [PATCH 4/5] Updated updateOrCreateProjectRoundRecord --- .../projectRoundRecordRepository.test.ts | 22 +++++++++ .../projectRoundRecordRepository.ts | 47 +++++++++++-------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/repositories/projectRoundRecordRepository.test.ts b/src/repositories/projectRoundRecordRepository.test.ts index 834858de6..b07c0e5f8 100644 --- a/src/repositories/projectRoundRecordRepository.test.ts +++ b/src/repositories/projectRoundRecordRepository.test.ts @@ -210,6 +210,28 @@ describe('ProjectRoundRecord test cases', () => { expect(roundRecord2?.totalDonationAmount).to.equal(donationAmount2); expect(roundRecord2?.totalDonationUsdAmount).to.equal(donationUsdAmount2); }); + + it('should not cause issue in case of multiple call of updateOrCreateProjectRoundRecord', async () => { + const donationAmount1 = 100; + const donationUsdAmount1 = 150; + + await insertDonation({ + amount: donationAmount1, + valueUsd: donationUsdAmount1, + qfRoundId: qfRound1.id, + }); + + const record1 = await updateOrCreateProjectRoundRecord( + projectId, + qfRound1.id, + ); + const record2 = await updateOrCreateProjectRoundRecord( + projectId, + qfRound1.id, + ); + + expect(record1).to.deep.equal(record2); + }); }); describe('getProjectRoundRecord test cases', () => { diff --git a/src/repositories/projectRoundRecordRepository.ts b/src/repositories/projectRoundRecordRepository.ts index aa167e5b4..7e09befd9 100644 --- a/src/repositories/projectRoundRecordRepository.ts +++ b/src/repositories/projectRoundRecordRepository.ts @@ -42,33 +42,40 @@ export async function updateOrCreateProjectRoundRecord( const { totalDonationAmount, totalDonationUsdAmount } = await query.getRawOne(); - let record = await ProjectRoundRecord.findOneBy({ - projectId, - qfRoundId: qfRoundId ?? undefined, - earlyAccessRoundId: earlyAccessRoundId ?? undefined, - }); + // If a new record was created, result will have one entry; if not, result will be empty - if (!record) { - record = ProjectRoundRecord.create({ - projectId, - qfRoundId, - earlyAccessRoundId, - createdAt: new Date(), - updatedAt: new Date(), - }); - } - - record.totalDonationAmount = totalDonationAmount || 0; - record.totalDonationUsdAmount = totalDonationUsdAmount || 0; - record.updatedAt = new Date(); - record.cumulativePastRoundsDonationAmounts = + const cumulativePastRoundsDonationAmounts = await getCumulativePastRoundsDonationAmounts({ projectId, qfRoundId: qfRoundId || undefined, earlyAccessRoundId: earlyAccessRoundId || undefined, }); - const prr = await ProjectRoundRecord.save(record); + const result = await ProjectRoundRecord.createQueryBuilder( + 'projectRoundRecord', + ) + .insert() + .values({ + projectId, + qfRoundId, + earlyAccessRoundId, + totalDonationAmount, + totalDonationUsdAmount, + cumulativePastRoundsDonationAmounts, + createdAt: new Date(), + updatedAt: new Date(), + }) + .orUpdate( + [ + 'totalDonationAmount', + 'totalDonationUsdAmount', + 'cumulativePastRoundsDonationAmounts', + 'updatedAt', + ], + ['projectId', qfRoundId ? 'qfRoundId' : 'earlyAccessRoundId'], + ) + .execute(); + const prr = result.raw[0]; logger.info(`ProjectRoundRecord updated for project ${projectId}`); From 24b26e1d1bd3532d24cc6932319d5ef9f000ae66 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Wed, 9 Oct 2024 12:10:38 +0330 Subject: [PATCH 5/5] Fixed default value initialization --- src/repositories/projectRoundRecordRepository.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repositories/projectRoundRecordRepository.ts b/src/repositories/projectRoundRecordRepository.ts index 7e09befd9..cb9133ec8 100644 --- a/src/repositories/projectRoundRecordRepository.ts +++ b/src/repositories/projectRoundRecordRepository.ts @@ -59,8 +59,8 @@ export async function updateOrCreateProjectRoundRecord( projectId, qfRoundId, earlyAccessRoundId, - totalDonationAmount, - totalDonationUsdAmount, + totalDonationAmount: totalDonationAmount || 0, + totalDonationUsdAmount: totalDonationUsdAmount || 0, cumulativePastRoundsDonationAmounts, createdAt: new Date(), updatedAt: new Date(),