From ead0a3186c6bfa0c2154d0b8d469c4edb578535a Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 12:41:23 +0330 Subject: [PATCH 01/23] add isGivbackEligible field --- src/entities/project.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/entities/project.ts b/src/entities/project.ts index c0b02bcf6..4b76d803c 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -408,6 +408,10 @@ export class Project extends BaseEntity { // @Column({ type: 'boolean', default: false }) // tunnableQf?: boolean; + @Field(_type => Boolean) + @Column({ type: 'boolean', default: false }) + isGivbackEligible: boolean; + @Field(_type => String) @Column({ type: 'enum', From 3c3697c8f2a47b2669bd8cbc690ce590954ca4a4 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 13:21:24 +0330 Subject: [PATCH 02/23] add AddIsGivbackEligibleColumnToProject1637168932304 --- ...213-AddIsGivbackEligibleColumnToProject.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 migration/1724060343213-AddIsGivbackEligibleColumnToProject.ts diff --git a/migration/1724060343213-AddIsGivbackEligibleColumnToProject.ts b/migration/1724060343213-AddIsGivbackEligibleColumnToProject.ts new file mode 100644 index 000000000..c0071c00f --- /dev/null +++ b/migration/1724060343213-AddIsGivbackEligibleColumnToProject.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class AddIsGivbackEligibleColumnToProject1637168932304 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + // Add the new column + await queryRunner.addColumn( + 'project', + new TableColumn({ + name: 'isGivbackEligible', + type: 'boolean', + isNullable: false, + default: false, + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + // Drop the isGivbackEligible column + await queryRunner.dropColumn('project', 'isGivbackEligible'); + } +} From a79066ea2c5d8920f9aafc4f90bb86e376106961 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 13:21:38 +0330 Subject: [PATCH 03/23] add UpdateIsGivbackEligibleForVerifiedProjects1637168932305 migration --- ...ateIsGivbackEligibleForVerifiedProjects.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 migration/1724060408379-UpdateIsGivbackEligibleForVerifiedProjects.ts diff --git a/migration/1724060408379-UpdateIsGivbackEligibleForVerifiedProjects.ts b/migration/1724060408379-UpdateIsGivbackEligibleForVerifiedProjects.ts new file mode 100644 index 000000000..284b84319 --- /dev/null +++ b/migration/1724060408379-UpdateIsGivbackEligibleForVerifiedProjects.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateIsGivbackEligibleForVerifiedProjects1637168932305 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + // Update isGivbackEligible to true for verified projects + await queryRunner.query(` + UPDATE project + SET "isGivbackEligible" = true + WHERE "verified" = true; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + // Revert the update (optional) + await queryRunner.query(` + UPDATE project + SET "isGivbackEligible" = false + WHERE "verified" = true; + `); + } +} From f407bb1fc29d2945c17c48b5bdb25c9a79dc774d Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 15:25:46 +0330 Subject: [PATCH 04/23] add migration to rename isProjectVerified to isProjectGivbackEligible --- ...ctVerifiedToIsGivbackEligibleInDonation.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 migrations/1724061402220-RenameIsProjectVerifiedToIsGivbackEligibleInDonation.ts diff --git a/migrations/1724061402220-RenameIsProjectVerifiedToIsGivbackEligibleInDonation.ts b/migrations/1724061402220-RenameIsProjectVerifiedToIsGivbackEligibleInDonation.ts new file mode 100644 index 000000000..94abf6ad5 --- /dev/null +++ b/migrations/1724061402220-RenameIsProjectVerifiedToIsGivbackEligibleInDonation.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameIsProjectVerifiedToIsGivbackEligibleInDonation1637168932306 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE donation + RENAME COLUMN "isProjectVerified" TO "isProjectGivbackEligible"; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE donation + RENAME COLUMN "isProjectGivbackEligible" TO "isProjectVerified"; + `); + } +} From 36fde9ae328774638bd470573e656a7348f0d7a6 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 15:28:15 +0330 Subject: [PATCH 05/23] change isProjectVerified tp isProjectGivbackEligible --- src/entities/donation.ts | 2 +- src/repositories/donationRepository.test.ts | 2 +- src/repositories/donationRepository.ts | 6 ++--- src/resolvers/donationResolver.test.ts | 8 +++---- src/resolvers/donationResolver.ts | 2 +- src/routers/apiGivRoutes.ts | 2 +- src/server/adminJs/adminJs-types.ts | 4 ++-- src/server/adminJs/adminJs.ts | 2 +- src/server/adminJs/tabs/donationTab.test.ts | 4 ++-- src/server/adminJs/tabs/donationTab.ts | 22 +++++++++++-------- src/services/Idriss/contractDonations.ts | 2 +- .../cronJobs/checkQRTransactionJob.ts | 2 +- .../cronJobs/importLostDonationsJob.ts | 2 +- src/services/googleSheets.ts | 2 +- src/services/onramper/donationService.ts | 2 +- src/services/recurringDonationService.ts | 2 +- 16 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/entities/donation.ts b/src/entities/donation.ts index b8fe75a25..ca04587e9 100644 --- a/src/entities/donation.ts +++ b/src/entities/donation.ts @@ -77,7 +77,7 @@ export class Donation extends BaseEntity { @Field() @Column('boolean', { default: false }) // https://github.com/Giveth/impact-graph/issues/407#issuecomment-1066892258 - isProjectVerified: boolean; + isProjectGivbackEligible: boolean; @Field() @Column('text', { default: DONATION_STATUS.PENDING }) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index bdbe07598..2230de000 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -382,7 +382,7 @@ function createDonationTestCases() { const newDonation = await createDonation({ donationAnonymous: false, donorUser: user, - isProjectVerified: false, + isProjectGivbackEligible: false, isTokenEligibleForGivback: false, project, segmentNotified: false, diff --git a/src/repositories/donationRepository.ts b/src/repositories/donationRepository.ts index 7ea52c932..152223b29 100644 --- a/src/repositories/donationRepository.ts +++ b/src/repositories/donationRepository.ts @@ -69,7 +69,7 @@ export const createDonation = async (data: { fromWalletAddress: string; transactionId: string; tokenAddress: string; - isProjectVerified: boolean; + isProjectGivbackEligible: boolean; donorUser: any; isTokenEligibleForGivback: boolean; segmentNotified: boolean; @@ -93,7 +93,7 @@ export const createDonation = async (data: { tokenAddress, project, isTokenEligibleForGivback, - isProjectVerified, + isProjectGivbackEligible, donationAnonymous, toWalletAddress, fromWalletAddress, @@ -118,7 +118,7 @@ export const createDonation = async (data: { tokenAddress, project, isTokenEligibleForGivback, - isProjectVerified, + isProjectGivbackEligible, createdAt: new Date(), segmentNotified: true, toWalletAddress, diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index c9fb7228f..83d12f528 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -2383,7 +2383,7 @@ function createDonationTestCases() { errorMessages.PROJECT_NOT_FOUND, ); }); - it('should isProjectVerified be true after create donation for verified projects', async () => { + it('should isProjectGivbackEligible be true after create donation for verified projects', async () => { const project = await saveProjectDirectlyToDb({ ...createProjectData(), verified: true, @@ -2420,9 +2420,9 @@ function createDonationTestCases() { }, }); assert.isOk(donation); - assert.isTrue(donation?.isProjectVerified); + assert.isTrue(donation?.isProjectGivbackEligible); }); - it('should isProjectVerified be true after create donation for unVerified projects', async () => { + it('should isProjectGivbackEligible be true after create donation for unVerified projects', async () => { const project = await saveProjectDirectlyToDb({ ...createProjectData(), verified: false, @@ -2459,7 +2459,7 @@ function createDonationTestCases() { }, }); assert.isOk(donation); - assert.isFalse(donation?.isProjectVerified); + assert.isFalse(donation?.isProjectGivbackEligible); }); it('should throw exception when donating to draft projects', async () => { const project = await saveProjectDirectlyToDb({ diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 594a63826..bfa261953 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -855,7 +855,7 @@ export class DonationResolver { project, isTokenEligibleForGivback, isCustomToken, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, createdAt: new Date(), segmentNotified: false, toWalletAddress: toAddress, diff --git a/src/routers/apiGivRoutes.ts b/src/routers/apiGivRoutes.ts index b05cbdc16..e7bf250cd 100644 --- a/src/routers/apiGivRoutes.ts +++ b/src/routers/apiGivRoutes.ts @@ -86,7 +86,7 @@ apiGivRouter.post( toWalletAddress, user: donor, anonymous, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, project, amount, valueUsd, diff --git a/src/server/adminJs/adminJs-types.ts b/src/server/adminJs/adminJs-types.ts index 4a13ece68..952f60738 100644 --- a/src/server/adminJs/adminJs-types.ts +++ b/src/server/adminJs/adminJs-types.ts @@ -43,7 +43,7 @@ export interface AdminJsDonationsQuery { createdAt?: string; currency?: string; transactionNetworkId?: string; - isProjectVerified?: string; + isProjectGivbackEligible?: string; qfRoundId?: string; } @@ -76,7 +76,7 @@ export const donationHeaders = [ 'id', 'transactionId', 'transactionNetworkId', - 'isProjectVerified', + 'isProjectGivbackEligible', 'status', 'toWalletAddress', 'fromWalletAddress', diff --git a/src/server/adminJs/adminJs.ts b/src/server/adminJs/adminJs.ts index 17e45c5f0..56a21ce4f 100644 --- a/src/server/adminJs/adminJs.ts +++ b/src/server/adminJs/adminJs.ts @@ -215,7 +215,7 @@ const getadminJsInstance = async () => { properties: { transactionNetworkId: 'Network', transactionId: 'txHash', - isProjectVerified: 'Givback Eligible', + isProjectGivbackEligible: 'Givback Eligible', disperseTxHash: 'disperseTxHash, this is optional, just for disperse transactions', }, diff --git a/src/server/adminJs/tabs/donationTab.test.ts b/src/server/adminJs/tabs/donationTab.test.ts index 7d1980822..1f546bc10 100644 --- a/src/server/adminJs/tabs/donationTab.test.ts +++ b/src/server/adminJs/tabs/donationTab.test.ts @@ -218,7 +218,7 @@ function createDonationTestCases() { priceUsd: tokenPrice, txType: 'gnosisSafe', segmentNotified: true, - isProjectVerified: true, + isProjectGivbackEligible: true, }, }, { @@ -248,7 +248,7 @@ function createDonationTestCases() { assert.equal(donation.status, DONATION_STATUS.VERIFIED); assert.equal(donation.priceUsd, tokenPrice); assert.equal(donation.segmentNotified, true); - assert.equal(donation.isProjectVerified, true); + assert.equal(donation.isProjectGivbackEligible, true); assert.equal(donation.amount, 5); assert.equal( donation.fromWalletAddress.toLowerCase(), diff --git a/src/server/adminJs/tabs/donationTab.ts b/src/server/adminJs/tabs/donationTab.ts index a2eb3779d..42d75f2f6 100644 --- a/src/server/adminJs/tabs/donationTab.ts +++ b/src/server/adminJs/tabs/donationTab.ts @@ -66,7 +66,7 @@ export const createDonation = async ( currency, priceUsd, txType, - isProjectVerified, + isProjectGivbackEligible, segmentNotified, } = request.payload; if (!priceUsd) { @@ -144,7 +144,7 @@ export const createDonation = async ( amount: transactionInfo?.amount, valueUsd: (transactionInfo?.amount as number) * priceUsd, status: DONATION_STATUS.VERIFIED, - isProjectVerified, + isProjectGivbackEligible, donationType, createdAt: new Date(transactionInfo?.timestamp * 1000), anonymous: true, @@ -251,10 +251,14 @@ export const buildDonationsQuery = ( referrerWallet: `%${queryStrings.referrerWallet}%`, }); - if (queryStrings.isProjectVerified) - query.andWhere('donation.isProjectVerified = :isProjectVerified', { - isProjectVerified: queryStrings.isProjectVerified === 'true', - }); + if (queryStrings.isProjectGivbackEligible) + query.andWhere( + 'donation.isProjectGivbackEligible = :isProjectGivbackEligible', + { + isProjectGivbackEligible: + queryStrings.isProjectGivbackEligible === 'true', + }, + ); if (queryStrings['createdAt~~from']) query.andWhere('donation."createdAt" >= :createdFrom', { @@ -402,7 +406,7 @@ const sendDonationsToGoogleSheet = async ( id: donation.id, transactionId: donation.transactionId, transactionNetworkId: donation.transactionNetworkId, - isProjectVerified: Boolean(donation.isProjectVerified), + isProjectGivbackEligible: Boolean(donation.isProjectGivbackEligible), status: donation.status, toWalletAddress: donation.toWalletAddress, fromWalletAddress: donation.fromWalletAddress, @@ -619,7 +623,7 @@ export const donationTab = { new: false, }, }, - isProjectVerified: { + isProjectGivbackEligible: { isVisible: { list: false, filter: false, @@ -765,7 +769,7 @@ export const donationTab = { isVisible: true, before: async (request: AdminJsRequestInterface) => { const availableFieldsForEdit = [ - 'isProjectVerified', + 'isProjectGivbackEligible', 'status', 'valueUsd', 'priceUsd', diff --git a/src/services/Idriss/contractDonations.ts b/src/services/Idriss/contractDonations.ts index feb540782..e14a89857 100644 --- a/src/services/Idriss/contractDonations.ts +++ b/src/services/Idriss/contractDonations.ts @@ -215,7 +215,7 @@ export const createIdrissTwitterDonation = async ( origin: DONATION_ORIGINS.IDRISS_TWITTER, isTokenEligibleForGivback, isCustomToken: false, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, createdAt: moment(idrissDonation.blockTimestamp).toDate(), segmentNotified: false, isExternal: true, diff --git a/src/services/cronJobs/checkQRTransactionJob.ts b/src/services/cronJobs/checkQRTransactionJob.ts index cfe23edba..5e4ce3c4e 100644 --- a/src/services/cronJobs/checkQRTransactionJob.ts +++ b/src/services/cronJobs/checkQRTransactionJob.ts @@ -116,7 +116,7 @@ async function checkTransactions(donation: DraftDonation): Promise { fromWalletAddress: transaction.source_account, transactionId: transaction.transaction_hash, tokenAddress: donation.tokenAddress, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, donorUser: donor, isTokenEligibleForGivback: token.isGivbackEligible, segmentNotified: false, diff --git a/src/services/cronJobs/importLostDonationsJob.ts b/src/services/cronJobs/importLostDonationsJob.ts index 5438cc6c5..250229558 100644 --- a/src/services/cronJobs/importLostDonationsJob.ts +++ b/src/services/cronJobs/importLostDonationsJob.ts @@ -233,7 +233,7 @@ export const importLostDonations = async () => { anonymous: false, segmentNotified: true, isTokenEligibleForGivback: tokenInDB?.isGivbackEligible, - isProjectVerified: project?.verified, + isProjectGivbackEligible: project?.isGivbackEligible, qfRoundId: qfRound?.id, }); diff --git a/src/services/googleSheets.ts b/src/services/googleSheets.ts index a888a32b6..549323cf1 100644 --- a/src/services/googleSheets.ts +++ b/src/services/googleSheets.ts @@ -55,7 +55,7 @@ interface DonationExport { id: number; transactionId: string; transactionNetworkId: number; - isProjectVerified: boolean; + isProjectGivbackEligible: boolean; status: string; toWalletAddress: string; fromWalletAddress: string; diff --git a/src/services/onramper/donationService.ts b/src/services/onramper/donationService.ts index bc51cd8d8..0008077fd 100644 --- a/src/services/onramper/donationService.ts +++ b/src/services/onramper/donationService.ts @@ -108,7 +108,7 @@ export const createFiatDonationFromOnramper = async ( project, isTokenEligibleForGivback, isCustomToken, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, createdAt: new Date(fiatTransaction.payload.timestamp), segmentNotified: false, toWalletAddress: toAddress.toString().toLowerCase(), diff --git a/src/services/recurringDonationService.ts b/src/services/recurringDonationService.ts index 3e2d52d41..4124d2c01 100644 --- a/src/services/recurringDonationService.ts +++ b/src/services/recurringDonationService.ts @@ -196,7 +196,7 @@ export const createRelatedDonationsToStream = async ( status: DONATION_STATUS.VERIFIED, isTokenEligibleForGivback, isCustomToken, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, createdAt: new Date(), segmentNotified: false, toWalletAddress: toAddress, From 58cd9429b43a0a31c509a4b92c973741a6e9160c Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 19:16:22 +0330 Subject: [PATCH 06/23] update octant donation --- migration/1696918830123-add_octant_donations_to_db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration/1696918830123-add_octant_donations_to_db.ts b/migration/1696918830123-add_octant_donations_to_db.ts index 6a14d6eb0..7d606c8a6 100644 --- a/migration/1696918830123-add_octant_donations_to_db.ts +++ b/migration/1696918830123-add_octant_donations_to_db.ts @@ -68,7 +68,7 @@ const transactions: Partial[] = [ transactionId: '0x30954cb441cb7b2184e6cd1afc6acbd1318f86a68b669f6bfb2786dd459e2d6c', currency: 'ETH', - isProjectVerified: true, + isProjectGivbackEligible: true, isTokenEligibleForGivback: true, amount: 5, valueUsd: 9_458.4, From 770a02a0bf42636b15d2b16fd92eaec952c2764d Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 19:50:18 +0330 Subject: [PATCH 07/23] add approve project --- .../projectVerificationRepository.ts | 39 +++++++++++++++++ src/server/adminJs/adminJsPermissions.test.ts | 8 ++-- src/server/adminJs/adminJsPermissions.ts | 10 +++-- .../adminJs/tabs/projectVerificationTab.ts | 42 +++++++++---------- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/repositories/projectVerificationRepository.ts b/src/repositories/projectVerificationRepository.ts index df600f776..7324b22e8 100644 --- a/src/repositories/projectVerificationRepository.ts +++ b/src/repositories/projectVerificationRepository.ts @@ -9,6 +9,7 @@ import { ProjectRegistry, ProjectVerificationForm, } from '../entities/projectVerificationForm'; +import { Project } from '../entities/project'; import { findProjectById } from './projectRepository'; import { findUserById } from './userRepository'; import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; @@ -376,3 +377,41 @@ export const getVerificationFormByProjectId = async ( .leftJoinAndSelect('project_verification_form.user', 'user') .getOne(); }; + +export const approveProject = async (params: { + approved: boolean; + projectId: number; +}): Promise => { + const project = await Project.findOne({ where: { id: params.projectId } }); + + if (!project) + throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); + + project.isGivbackEligible = params.approved; + if (params.approved) project.verificationStatus = null; // reset this field + + return project.save(); +}; + +export const approveMultipleProjects = async (params: { + approved: boolean; + projectsIds: string[] | number[]; +}): Promise => { + if (params.approved) { + await Project.query(` + UPDATE project + SET "verificationStatus" = NULL + WHERE id IN (${params.projectsIds?.join(',')}) + `); + } + + return Project.createQueryBuilder('project') + .update(Project, { + isGivbackEligible: params.approved, + }) + .where('project.id IN (:...ids)') + .setParameter('ids', params.projectsIds) + .returning('*') + .updateEntity(true) + .execute(); +}; diff --git a/src/server/adminJs/adminJsPermissions.test.ts b/src/server/adminJs/adminJsPermissions.test.ts index 6481f3076..b742275bd 100644 --- a/src/server/adminJs/adminJsPermissions.test.ts +++ b/src/server/adminJs/adminJsPermissions.test.ts @@ -88,10 +88,10 @@ const actionsPerRole = Object.freeze({ 'delete', 'edit', 'show', - 'verifyProject', + 'approveProject', 'makeEditableByUser', 'rejectProject', - 'verifyProjects', + 'approveProjects', 'rejectProjects', ], mainCategory: ['list', 'show', 'new', 'edit'], @@ -156,10 +156,10 @@ const actionsPerRole = Object.freeze({ 'delete', 'edit', 'show', - 'verifyProject', + 'approveProject', 'makeEditableByUser', 'rejectProject', - 'verifyProjects', + 'approveProjects', 'rejectProjects', ], mainCategory: ['list', 'show'], diff --git a/src/server/adminJs/adminJsPermissions.ts b/src/server/adminJs/adminJsPermissions.ts index 1fc29d0f1..3fbbe0510 100644 --- a/src/server/adminJs/adminJsPermissions.ts +++ b/src/server/adminJs/adminJsPermissions.ts @@ -12,6 +12,7 @@ export enum ResourceActions { LIST_PROJECT = 'listProject', UNLIST_PROJECT = 'unlistProject', VERIFY_PROJECT = 'verifyProject', + APPROVE_PROJECT = 'approveProject', REJECT_PROJECT = 'rejectProject', REVOKE_BADGE = 'revokeBadge', ACTIVATE_PROJECT = 'activateProject', @@ -20,6 +21,7 @@ export enum ResourceActions { ADD_FEATURED_PROJECT_UPDATE = 'addFeaturedProjectUpdate', MAKE_EDITABLE_BY_USER = 'makeEditableByUser', VERIFY_PROJECTS = 'verifyProjects', + APPROVE_PROJECTS = 'approveProjects', REJECT_PROJECTS = 'rejectProjects', ADD_PROJECT_TO_QF_ROUND = 'addToQfRound', REMOVE_PROJECT_FROM_QF_ROUND = 'removeFromQfRound', @@ -407,10 +409,10 @@ const projectVerificationFormPermissions = { delete: true, edit: true, show: true, - verifyProject: true, + approveProject: true, makeEditableByUser: true, rejectProject: true, - verifyProjects: true, + approveProjects: true, rejectProjects: true, }, [UserRole.OPERATOR]: { @@ -422,10 +424,10 @@ const projectVerificationFormPermissions = { delete: true, edit: true, show: true, - verifyProject: true, + approveProject: true, makeEditableByUser: true, rejectProject: true, - verifyProjects: true, + approveProjects: true, rejectProjects: true, }, [UserRole.CAMPAIGN_MANAGER]: { diff --git a/src/server/adminJs/tabs/projectVerificationTab.ts b/src/server/adminJs/tabs/projectVerificationTab.ts index 169cf453e..41abe11a5 100644 --- a/src/server/adminJs/tabs/projectVerificationTab.ts +++ b/src/server/adminJs/tabs/projectVerificationTab.ts @@ -18,6 +18,8 @@ import { AdminJsRequestInterface, } from '../adminJs-types'; import { + approveMultipleProjects, + approveProject, findProjectVerificationFormById, makeFormDraft, verifyForm, @@ -30,8 +32,6 @@ import { import { findProjectById, updateProjectWithVerificationForm, - verifyMultipleProjects, - verifyProject, } from '../../../repositories/projectRepository'; import { getNotificationAdapter } from '../../../adapters/adaptersFactory'; import { logger } from '../../../utils/logger'; @@ -82,12 +82,12 @@ export const setCommentEmailAndTimeStamps: After = async ( export const verifySingleVerificationForm = async ( context: AdminJsContextInterface, request: AdminJsRequestInterface, - verified: boolean, + approved: boolean, ) => { const { currentAdmin } = context; let responseMessage = ''; let responseType = 'success'; - const verificationStatus = verified + const verificationStatus = approved ? PROJECT_VERIFICATION_STATUSES.VERIFIED : PROJECT_VERIFICATION_STATUSES.REJECTED; const formId = Number(request?.params?.recordId); @@ -95,7 +95,7 @@ export const verifySingleVerificationForm = async ( try { if ( - verified && + approved && ![ PROJECT_VERIFICATION_STATUSES.REJECTED, PROJECT_VERIFICATION_STATUSES.SUBMITTED, @@ -108,7 +108,7 @@ export const verifySingleVerificationForm = async ( ); } if ( - !verified && + !approved && PROJECT_VERIFICATION_STATUSES.SUBMITTED !== verificationFormInDb?.status ) { throw new Error( @@ -124,9 +124,9 @@ export const verifySingleVerificationForm = async ( adminId: currentAdmin.id, }); const projectId = verificationForm.projectId; - const project = await verifyProject({ verified, projectId }); + const project = await approveProject({ approved, projectId }); - if (verified) { + if (approved) { await updateProjectWithVerificationForm(verificationForm, project); await getNotificationAdapter().projectVerified({ project, @@ -140,7 +140,7 @@ export const verifySingleVerificationForm = async ( } responseMessage = `Project(s) successfully ${ - verified ? 'verified' : 'rejected' + approved ? 'approved' : 'rejected' }`; } catch (error) { logger.error('verifyVerificationForm() error', error); @@ -226,16 +226,16 @@ export const makeEditableByUser = async ( }; }; -export const verifyVerificationForms = async ( +export const approveVerificationForms = async ( context: AdminJsContextInterface, request: AdminJsRequestInterface, - verified: boolean, + approved: boolean, ) => { const { records, currentAdmin } = context; let responseMessage = ''; let responseType = 'success'; try { - const verificationStatus = verified + const verificationStatus = approved ? PROJECT_VERIFICATION_STATUSES.VERIFIED : PROJECT_VERIFICATION_STATUSES.REJECTED; const formIds = request?.query?.recordIds?.split(','); @@ -248,7 +248,7 @@ export const verifyVerificationForms = async ( const projectsIds = projectsForms.raw.map(projectForm => { return projectForm.projectId; }); - const projects = await verifyMultipleProjects({ verified, projectsIds }); + const projects = await approveMultipleProjects({ approved, projectsIds }); const projectIds = projects.raw.map(project => { return project.id; @@ -270,7 +270,7 @@ export const verifyVerificationForms = async ( verificationForm.project, ); const { project } = verificationForm; - if (verified) { + if (approved) { await getNotificationAdapter().projectVerified({ project, }); @@ -283,7 +283,7 @@ export const verifyVerificationForms = async ( } } responseMessage = `Project(s) successfully ${ - verified ? 'verified' : 'rejected' + approved ? 'approved' : 'rejected' }`; } catch (error) { logger.error('verifyVerificationForm() error', error); @@ -613,13 +613,13 @@ export const projectVerificationTab = { ResourceActions.NEW, ), }, - verifyProject: { + approveProject: { actionType: 'record', isVisible: true, isAccessible: ({ currentAdmin }) => canAccessProjectVerificationFormAction( { currentAdmin }, - ResourceActions.VERIFY_PROJECT, + ResourceActions.APPROVE_PROJECT, ), handler: async (request, response, context) => { return verifySingleVerificationForm(context, request, true); @@ -652,16 +652,16 @@ export const projectVerificationTab = { }, component: false, }, - verifyProjects: { + approveProjects: { actionType: 'bulk', isVisible: true, isAccessible: ({ currentAdmin }) => canAccessProjectVerificationFormAction( { currentAdmin }, - ResourceActions.VERIFY_PROJECTS, + ResourceActions.APPROVE_PROJECTS, ), handler: async (request, response, context) => { - return verifyVerificationForms(context, request, true); + return approveVerificationForms(context, request, true); }, component: false, }, @@ -674,7 +674,7 @@ export const projectVerificationTab = { ResourceActions.REJECT_PROJECTS, ), handler: async (request, response, context) => { - return verifyVerificationForms(context, request, false); + return approveVerificationForms(context, request, false); }, component: false, }, From 2bb1eda6ad47061b6fd3e57255a6b3f2fe08887e Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 20:18:53 +0330 Subject: [PATCH 08/23] treat project.verified and project.isGivbackEligible equally on sorting --- src/repositories/projectRepository.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index b160c5d81..2b0c298a5 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -198,7 +198,10 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.GIVPower: query - .orderBy(`project.verified`, OrderDirection.DESC) + .orderBy( + `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + OrderDirection.DESC, + ) .addOrderBy( 'projectPower.totalPower', OrderDirection.DESC, @@ -207,7 +210,10 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.InstantBoosting: // This is our default sorting query - .orderBy(`project.verified`, OrderDirection.DESC) + .orderBy( + `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + OrderDirection.DESC, + ) .addOrderBy( 'projectInstantPower.totalPower', OrderDirection.DESC, @@ -232,7 +238,10 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { OrderDirection.DESC, 'NULLS LAST', ) - .addOrderBy(`project.verified`, OrderDirection.DESC); + .addOrderBy( + `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + OrderDirection.DESC, + ); } break; case SortingField.EstimatedMatching: @@ -244,13 +253,20 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { OrderDirection.DESC, 'NULLS LAST', ) - .addOrderBy(`project.verified`, OrderDirection.DESC); + .addOrderBy( + `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + OrderDirection.DESC, + ); } break; + default: query .orderBy('projectInstantPower.totalPower', OrderDirection.DESC) - .addOrderBy(`project.verified`, OrderDirection.DESC); + .addOrderBy( + `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + OrderDirection.DESC, + ); break; } From 11b163c9916fc8e1dca8aea52fc30632a15b8e94 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 19 Aug 2024 20:23:09 +0330 Subject: [PATCH 09/23] remove reset verification status on verify --- src/repositories/projectRepository.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 2b0c298a5..f41ee181c 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -340,14 +340,6 @@ export const verifyMultipleProjects = async (params: { verified: boolean; projectsIds: string[] | number[]; }): Promise => { - if (params.verified) { - await Project.query(` - UPDATE project - SET "verificationStatus" = NULL - WHERE id IN (${params.projectsIds?.join(',')}) - `); - } - return Project.createQueryBuilder('project') .update(Project, { verified: params.verified, @@ -397,7 +389,6 @@ export const verifyProject = async (params: { throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); project.verified = params.verified; - if (params.verified) project.verificationStatus = null; // reset this field return project.save(); }; From 48ffb0a0ae235c0a61d545312f08ccff7e4184ea Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 20 Aug 2024 12:42:02 +0330 Subject: [PATCH 10/23] check isGivbackEligible on create ProjectVerificationForm --- src/resolvers/projectVerificationFormResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolvers/projectVerificationFormResolver.ts b/src/resolvers/projectVerificationFormResolver.ts index b5a6e170d..2217bc99c 100644 --- a/src/resolvers/projectVerificationFormResolver.ts +++ b/src/resolvers/projectVerificationFormResolver.ts @@ -224,7 +224,7 @@ export class ProjectVerificationFormResolver { ), ); } - if (project.verified) { + if (project.isGivbackEligible) { throw new Error( i18n.__(translationErrorMessagesKeys.PROJECT_IS_ALREADY_VERIFIED), ); From 631a67d3f2ff7766b76dbe6ff58fe7c9d900b69a Mon Sep 17 00:00:00 2001 From: Cherik Date: Wed, 21 Aug 2024 10:35:05 +0330 Subject: [PATCH 11/23] add ProjectInstantPowerViewV3 migration --- ...1724223781248-ProjectInstantPowerViewV3.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 migration/1724223781248-ProjectInstantPowerViewV3.ts diff --git a/migration/1724223781248-ProjectInstantPowerViewV3.ts b/migration/1724223781248-ProjectInstantPowerViewV3.ts new file mode 100644 index 000000000..2b61012e7 --- /dev/null +++ b/migration/1724223781248-ProjectInstantPowerViewV3.ts @@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProjectInstantPowerViewV31724223781248 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP MATERIALIZED VIEW IF EXISTS public.project_instant_power_view; + CREATE MATERIALIZED VIEW IF NOT EXISTS public.project_instant_power_view AS + SELECT + innerview."projectId", + ROUND(CAST(innerview."totalPower" as NUMERIC), 2) as "totalPower", + rank() OVER ( + ORDER BY + innerview."totalPower" DESC + ) AS "powerRank" + FROM + ( + SELECT + project.id AS "projectId", + CASE + WHEN (project.verified = true OR project."isGivbackEligible" = true) AND project."statusId" = 5 THEN COALESCE(sum(pp."boostedPower"), 0 :: double precision) + ELSE 0 :: double precision + END AS "totalPower" + FROM + project + LEFT JOIN ( + SELECT + "powerBoosting"."projectId", + sum("instantPowerBalance".balance * "powerBoosting".percentage :: double precision / 100 :: double precision) AS "boostedPower", + now() AS "updateTime" + FROM + instant_power_balance "instantPowerBalance" + JOIN power_boosting "powerBoosting" ON "powerBoosting"."userId" = "instantPowerBalance"."userId" + GROUP BY + "powerBoosting"."projectId" + ) pp ON pp."projectId" = project.id + GROUP BY + project.id + ) innerview + ORDER BY + innerview."totalPower" DESC WITH DATA; + `); + + await queryRunner.query(` + CREATE UNIQUE INDEX idx_project_instant_power_view_unique ON public.project_instant_power_view ("projectId"); + `); + + await queryRunner.query(` + CREATE INDEX project_instant_power_view_project_id ON public.project_instant_power_view USING hash ("projectId") TABLESPACE pg_default; + `); + + await queryRunner.query(` + CREATE INDEX project_instant_power_view_total_power ON public.project_instant_power_view USING btree ("totalPower" DESC) TABLESPACE pg_default; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP MATERIALIZED VIEW IF EXISTS public.project_instant_power_view; + DROP INDEX IF EXISTS public.idx_project_instant_power_view_unique; + DROP INDEX IF EXISTS public.project_instant_power_view_project_id; + DROP INDEX IF EXISTS public.project_instant_power_view_total_power; + `); + } +} From ddf24c7d9edd459d8b26875f04336286f4caf6f0 Mon Sep 17 00:00:00 2001 From: Cherik Date: Wed, 21 Aug 2024 10:52:52 +0330 Subject: [PATCH 12/23] use verifiedOrIsGivbackEligibleCondition --- src/repositories/projectRepository.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index f41ee181c..bbd5e7cd2 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -23,6 +23,8 @@ import { ProjectStatusHistory } from '../entities/projectStatusHistory'; import { Reaction } from '../entities/reaction'; import { SocialProfile } from '../entities/socialProfile'; +const verifiedOrIsGivbackEligibleCondition = `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`; + export const findProjectById = (projectId: number): Promise => { // return Project.findOne({ id: projectId }); @@ -198,10 +200,7 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.GIVPower: query - .orderBy( - `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, - OrderDirection.DESC, - ) + .orderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC) .addOrderBy( 'projectPower.totalPower', OrderDirection.DESC, @@ -210,10 +209,7 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.InstantBoosting: // This is our default sorting query - .orderBy( - `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, - OrderDirection.DESC, - ) + .orderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC) .addOrderBy( 'projectInstantPower.totalPower', OrderDirection.DESC, @@ -239,7 +235,7 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { 'NULLS LAST', ) .addOrderBy( - `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC, ); } @@ -254,7 +250,7 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { 'NULLS LAST', ) .addOrderBy( - `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, + verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC, ); } @@ -263,10 +259,7 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { default: query .orderBy('projectInstantPower.totalPower', OrderDirection.DESC) - .addOrderBy( - `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`, - OrderDirection.DESC, - ); + .addOrderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC); break; } From a1d0374ae1e6102736bc4c6d154503f49be661cb Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 3 Sep 2024 17:03:35 +0330 Subject: [PATCH 13/23] Use different materialized view for givback factor related to #1770 --- .../1725260193333-projectGivbackRankView.ts | 66 ++++++ package.json | 1 + src/entities/ProjectGivbackRankView.ts | 53 +++++ src/entities/entities.ts | 3 + .../projectGivbackViewRepository.test.ts | 209 ++++++++++++++++++ .../projectGivbackViewRepository.ts | 34 +++ src/services/cronJobs/updatePowerRoundJob.ts | 2 + src/services/givbackService.ts | 20 +- test/pre-test-scripts.ts | 2 + test/testUtils.ts | 2 + 10 files changed, 382 insertions(+), 10 deletions(-) create mode 100644 migration/1725260193333-projectGivbackRankView.ts create mode 100644 src/entities/ProjectGivbackRankView.ts create mode 100644 src/repositories/projectGivbackViewRepository.test.ts create mode 100644 src/repositories/projectGivbackViewRepository.ts diff --git a/migration/1725260193333-projectGivbackRankView.ts b/migration/1725260193333-projectGivbackRankView.ts new file mode 100644 index 000000000..abd20eeea --- /dev/null +++ b/migration/1725260193333-projectGivbackRankView.ts @@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProjectGivbackRankViewV31725260193333 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + ` + DROP + MATERIALIZED VIEW IF EXISTS public.project_givback_rank_view; + CREATE MATERIALIZED VIEW IF NOT EXISTS public.project_givback_rank_view AS + SELECT + innerview."projectId", + ROUND(CAST(innerview."totalPower" as NUMERIC), 2) as "totalPower", + rank() OVER ( + ORDER BY + innerview."totalPower" DESC + ) AS "powerRank", + "powerRound".round + FROM + ( + SELECT + project.id AS "projectId", + CASE project."isGivbackEligible" and project."statusId" = 5 WHEN false THEN 0 :: double precision ELSE COALESCE( + sum(pp."boostedPower"), + 0 :: double precision + ) END AS "totalPower" + FROM + project project + LEFT JOIN ( + SELECT + "powerRound".round, + "powerBoostingSnapshot"."projectId", + "powerBoostingSnapshot"."userId", + avg( + "powerBalanceSnapshot".balance * "powerBoostingSnapshot".percentage :: double precision / 100 :: double precision + ) AS "boostedPower", + now() AS "updateTime" + FROM + power_round "powerRound" + JOIN power_snapshot "powerSnapshot" ON "powerSnapshot"."roundNumber" = "powerRound".round + JOIN power_balance_snapshot "powerBalanceSnapshot" ON "powerBalanceSnapshot"."powerSnapshotId" = "powerSnapshot".id + JOIN power_boosting_snapshot "powerBoostingSnapshot" ON "powerBoostingSnapshot"."powerSnapshotId" = "powerSnapshot".id + AND "powerBoostingSnapshot"."userId" = "powerBalanceSnapshot"."userId" + GROUP BY + "powerRound".round, + "powerBoostingSnapshot"."projectId", + "powerBoostingSnapshot"."userId" + ) pp ON pp."projectId" = project.id + GROUP BY + project.id + ) innerview, + power_round "powerRound" + ORDER BY + innerview."totalPower" DESC WITH DATA; + CREATE UNIQUE INDEX project_givback_rank_view_project_id_round_unique ON public.project_givback_rank_view ("projectId", "round"); + CREATE INDEX project_givback_rank_view_project_id ON public.project_givback_rank_view USING hash ("projectId") TABLESPACE pg_default; + CREATE INDEX project_givback_rank_view_total_power ON public.project_givback_rank_view USING btree ("totalPower" DESC) TABLESPACE pg_default; + `, + ); + } + + public async down(_queryRunner: QueryRunner): Promise { + // + } +} diff --git a/package.json b/package.json index edcb23176..9f770613e 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,7 @@ "test:anchorContractAddressRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/anchorContractAddressRepository.test.ts", "test:recurringDonationRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/recurringDonationRepository.test.ts", "test:userPassportScoreRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/userPassportScoreRepository.test.ts", + "test:projectGivbackRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/projectGivbackViewRepository.test.ts", "test:recurringDonationService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/recurringDonationService.test.ts", "test:dbCronRepository": "NODE_ENV=test mocha -t 90000 ./test/pre-test-scripts.ts ./src/repositories/dbCronRepository.test.ts", "test:powerBoostingResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/powerBoostingResolver.test.ts", diff --git a/src/entities/ProjectGivbackRankView.ts b/src/entities/ProjectGivbackRankView.ts new file mode 100644 index 000000000..5bff5b691 --- /dev/null +++ b/src/entities/ProjectGivbackRankView.ts @@ -0,0 +1,53 @@ +import { + OneToOne, + ViewColumn, + ViewEntity, + JoinColumn, + RelationId, + BaseEntity, + PrimaryColumn, + Column, + Index, +} from 'typeorm'; +import { Field, Float, Int, ObjectType } from 'type-graphql'; +import { Project } from '../entities/project'; +import { ColumnNumericTransformer } from '../utils/entities'; + +@ViewEntity('project_givback_rank_view', { synchronize: false }) +@Index('project_givback_rank_view_project_id_unique', ['projectId', 'round'], { + unique: true, +}) +// It's similar to ProjectPowerView, but with a small difference that it uses a different view +// That just includes project with isGivbackEligible = true +@ObjectType() +export class ProjectGivbackRankView extends BaseEntity { + @Field() + @ViewColumn() + @PrimaryColumn() + @RelationId( + (projectGivbackRankView: ProjectGivbackRankView) => + projectGivbackRankView.project, + ) + projectId: number; + + @ViewColumn() + @Field(_type => Float) + @Column('numeric', { + scale: 2, + transformer: new ColumnNumericTransformer(), + }) + totalPower: number; + + @Field(_type => Project) + @OneToOne(_type => Project, project => project.projectPower) + @JoinColumn({ referencedColumnName: 'id' }) + project: Project; + + @ViewColumn() + @Field(_type => Int) + powerRank: number; + + @ViewColumn() + @Field(_type => Int) + round: number; +} diff --git a/src/entities/entities.ts b/src/entities/entities.ts index feeb0d0f6..0e5e204a5 100644 --- a/src/entities/entities.ts +++ b/src/entities/entities.ts @@ -51,6 +51,7 @@ import { ProjectActualMatchingView } from './ProjectActualMatchingView'; import { ProjectSocialMedia } from './projectSocialMedia'; import { DraftRecurringDonation } from './draftRecurringDonation'; import { UserQfRoundModelScore } from './userQfRoundModelScore'; +import { ProjectGivbackRankView } from './ProjectGivbackRankView'; export const getEntities = (): DataSourceOptions['entities'] => { return [ @@ -118,5 +119,7 @@ export const getEntities = (): DataSourceOptions['entities'] => { AnchorContractAddress, RecurringDonation, DraftRecurringDonation, + + ProjectGivbackRankView, ]; }; diff --git a/src/repositories/projectGivbackViewRepository.test.ts b/src/repositories/projectGivbackViewRepository.test.ts new file mode 100644 index 000000000..736d3fe6b --- /dev/null +++ b/src/repositories/projectGivbackViewRepository.test.ts @@ -0,0 +1,209 @@ +import { assert } from 'chai'; +import { AppDataSource } from '../orm'; +import { PowerBalanceSnapshot } from '../entities/powerBalanceSnapshot'; +import { PowerBoostingSnapshot } from '../entities/powerBoostingSnapshot'; +import { + createProjectData, + generateRandomEtheriumAddress, + saveProjectDirectlyToDb, + saveUserDirectlyToDb, +} from '../../test/testUtils'; +import { + insertSinglePowerBoosting, + takePowerBoostingSnapshot, +} from './powerBoostingRepository'; +import { findPowerSnapshots } from './powerSnapshotRepository'; +import { addOrUpdatePowerSnapshotBalances } from './powerBalanceSnapshotRepository'; +import { setPowerRound } from './powerRoundRepository'; +import { + findProjectGivbackRankViewByProjectId, + getBottomGivbackRank, + refreshProjectGivbackRankView, +} from './projectGivbackViewRepository'; + +describe( + 'findProjectGivbackRankViewByProjectId test', + findProjectGivbackRankViewByProjectIdTestCases, +); + +describe('getBottomGivbackRank test cases', getBottomGivbackRankTestCases); + +function getBottomGivbackRankTestCases() { + beforeEach(async () => { + await AppDataSource.getDataSource().query( + 'truncate power_snapshot cascade', + ); + await PowerBalanceSnapshot.clear(); + await PowerBoostingSnapshot.clear(); + }); + + it('should return bottomPowerRank correctly', async () => { + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const project1 = await saveProjectDirectlyToDb(createProjectData()); + const project2 = await saveProjectDirectlyToDb(createProjectData()); + await saveProjectDirectlyToDb(createProjectData()); + await saveProjectDirectlyToDb(createProjectData()); + + const roundNumber = project1.id * 10; + + await insertSinglePowerBoosting({ + user, + project: project1, + percentage: 10, + }); + await insertSinglePowerBoosting({ + user, + project: project2, + percentage: 20, + }); + + await takePowerBoostingSnapshot(); + const [powerSnapshots] = await findPowerSnapshots(); + const snapshot = powerSnapshots[0]; + + snapshot.blockNumber = 1; + snapshot.roundNumber = roundNumber; + await snapshot.save(); + + await addOrUpdatePowerSnapshotBalances({ + userId: user.id, + powerSnapshotId: snapshot.id, + balance: 100, + }); + + await setPowerRound(roundNumber); + await refreshProjectGivbackRankView(); + + const bottomPowerRank = await getBottomGivbackRank(); + assert.equal(bottomPowerRank, 3); + }); + it('should return bottomPowerRank correctly and not consider project that are not isGivbackEligible but are verified', async () => { + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const project1 = await saveProjectDirectlyToDb({ + ...createProjectData(), + isGivbackEligible: false, + verified: true, + }); + const project2 = await saveProjectDirectlyToDb(createProjectData()); + await saveProjectDirectlyToDb(createProjectData()); + await saveProjectDirectlyToDb(createProjectData()); + + const roundNumber = project1.id * 10; + + await insertSinglePowerBoosting({ + user, + project: project1, + percentage: 10, + }); + await insertSinglePowerBoosting({ + user, + project: project2, + percentage: 20, + }); + + await takePowerBoostingSnapshot(); + const [powerSnapshots] = await findPowerSnapshots(); + const snapshot = powerSnapshots[0]; + + snapshot.blockNumber = 1; + snapshot.roundNumber = roundNumber; + await snapshot.save(); + + await addOrUpdatePowerSnapshotBalances({ + userId: user.id, + powerSnapshotId: snapshot.id, + balance: 100, + }); + + await setPowerRound(roundNumber); + await refreshProjectGivbackRankView(); + + const bottomPowerRank = await getBottomGivbackRank(); + assert.equal(bottomPowerRank, 2); + }); +} + +function findProjectGivbackRankViewByProjectIdTestCases() { + beforeEach(async () => { + await AppDataSource.getDataSource().query( + 'truncate power_snapshot cascade', + ); + await PowerBalanceSnapshot.clear(); + await PowerBoostingSnapshot.clear(); + }); + + it('Return project rank correctly', async () => { + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const project1 = await saveProjectDirectlyToDb(createProjectData()); + + const roundNumber = project1.id * 10; + + await insertSinglePowerBoosting({ + user, + project: project1, + percentage: 10, + }); + + await takePowerBoostingSnapshot(); + const [powerSnapshots] = await findPowerSnapshots(); + const snapshot = powerSnapshots[0]; + + snapshot.blockNumber = 1; + snapshot.roundNumber = roundNumber; + await snapshot.save(); + + await addOrUpdatePowerSnapshotBalances({ + userId: user.id, + powerSnapshotId: snapshot.id, + balance: 100, + }); + + await setPowerRound(roundNumber); + await refreshProjectGivbackRankView(); + const projectPower = await findProjectGivbackRankViewByProjectId( + project1.id, + ); + assert.isOk(projectPower); + assert.equal(projectPower?.powerRank, 1); + assert.equal(projectPower?.totalPower, 10); + }); + it('Return project rank correctly and not consider project that are not isGivbackEligible but are verified', async () => { + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const project1 = await saveProjectDirectlyToDb({ + ...createProjectData(), + isGivbackEligible: false, + verified: true, + }); + + const roundNumber = project1.id * 10; + + await insertSinglePowerBoosting({ + user, + project: project1, + percentage: 10, + }); + + await takePowerBoostingSnapshot(); + const [powerSnapshots] = await findPowerSnapshots(); + const snapshot = powerSnapshots[0]; + + snapshot.blockNumber = 1; + snapshot.roundNumber = roundNumber; + await snapshot.save(); + + await addOrUpdatePowerSnapshotBalances({ + userId: user.id, + powerSnapshotId: snapshot.id, + balance: 100, + }); + + await setPowerRound(roundNumber); + await refreshProjectGivbackRankView(); + const projectPower = await findProjectGivbackRankViewByProjectId( + project1.id, + ); + assert.isOk(projectPower); + assert.equal(projectPower?.powerRank, 1); + assert.equal(projectPower?.totalPower, 0); + }); +} diff --git a/src/repositories/projectGivbackViewRepository.ts b/src/repositories/projectGivbackViewRepository.ts new file mode 100644 index 000000000..ed20e75be --- /dev/null +++ b/src/repositories/projectGivbackViewRepository.ts @@ -0,0 +1,34 @@ +import { logger } from '../utils/logger'; +import { AppDataSource } from '../orm'; +import { ProjectGivbackRankView } from '../entities/ProjectGivbackRankView'; + +export const refreshProjectGivbackRankView = async (): Promise => { + logger.debug('Refresh project_givback_rank_view materialized view'); + try { + return AppDataSource.getDataSource().query( + ` + REFRESH MATERIALIZED VIEW CONCURRENTLY project_givback_rank_view + `, + ); + } catch (e) { + logger.error('refreshProjectGivbackRankView() error', e); + } +}; + +export const getBottomGivbackRank = async (): Promise => { + try { + const powerRank = await AppDataSource.getDataSource().query(` + SELECT MAX("powerRank") FROM project_givback_rank_view + `); + return Number(powerRank[0].max); + } catch (e) { + logger.error('getBottomGivbackRank error', e); + throw new Error('Error in getting last power rank'); + } +}; + +export const findProjectGivbackRankViewByProjectId = async ( + projectId: number, +): Promise => { + return ProjectGivbackRankView.findOne({ where: { projectId } }); +}; diff --git a/src/services/cronJobs/updatePowerRoundJob.ts b/src/services/cronJobs/updatePowerRoundJob.ts index d36f94c5d..6e6e26096 100644 --- a/src/services/cronJobs/updatePowerRoundJob.ts +++ b/src/services/cronJobs/updatePowerRoundJob.ts @@ -19,6 +19,7 @@ import { import { getNotificationAdapter } from '../../adapters/adaptersFactory'; import { sleep } from '../../utils/utils'; import { fillIncompletePowerSnapshots } from '../powerSnapshotServices'; +import { refreshProjectGivbackRankView } from '../../repositories/projectGivbackViewRepository'; const cronJobTime = (config.get('UPDATE_POWER_ROUND_CRONJOB_EXPRESSION') as string) || @@ -55,6 +56,7 @@ export const runUpdatePowerRoundCronJob = () => { refreshProjectPowerView(), refreshProjectFuturePowerView(), refreshUserProjectPowerView(), + refreshProjectGivbackRankView(), ]); if (powerRound !== currentRound?.round) { // Refreshing views need time to refresh tables, so I wait for 1 minute and after that check project rank changes diff --git a/src/services/givbackService.ts b/src/services/givbackService.ts index f94db6f1a..5871535cc 100644 --- a/src/services/givbackService.ts +++ b/src/services/givbackService.ts @@ -1,8 +1,8 @@ -import { - findProjectPowerViewByProjectId, - getBottomRank, -} from '../repositories/projectPowerViewRepository'; import { getPowerRound } from '../repositories/powerRoundRepository'; +import { + findProjectGivbackRankViewByProjectId, + getBottomGivbackRank, +} from '../repositories/projectGivbackViewRepository'; export const calculateGivbackFactor = async ( projectId: number, @@ -14,21 +14,21 @@ export const calculateGivbackFactor = async ( }> => { const minGivFactor = Number(process.env.GIVBACK_MIN_FACTOR); const maxGivFactor = Number(process.env.GIVBACK_MAX_FACTOR); - const [projectPowerView, bottomRank, powerRound] = await Promise.all([ - findProjectPowerViewByProjectId(projectId), - getBottomRank(), + const [projectGivbackRankView, bottomRank, powerRound] = await Promise.all([ + findProjectGivbackRankViewByProjectId(projectId), + getBottomGivbackRank(), getPowerRound(), ]); const eachRoundImpact = (maxGivFactor - minGivFactor) / (bottomRank - 1); - const givbackFactor = projectPowerView?.powerRank + const givbackFactor = projectGivbackRankView?.powerRank ? minGivFactor + - eachRoundImpact * (bottomRank - projectPowerView?.powerRank) + eachRoundImpact * (bottomRank - projectGivbackRankView?.powerRank) : minGivFactor; return { givbackFactor: givbackFactor || 0, - projectRank: projectPowerView?.powerRank, + projectRank: projectGivbackRankView?.powerRank, bottomRankInRound: bottomRank, powerRound: powerRound?.round as number, }; diff --git a/test/pre-test-scripts.ts b/test/pre-test-scripts.ts index e5316236c..edbde2405 100644 --- a/test/pre-test-scripts.ts +++ b/test/pre-test-scripts.ts @@ -42,6 +42,7 @@ import { ProjectFuturePowerViewV21717643016553 } from '../migration/171764301655 import { ProjectUserInstantPowerViewV21717644442966 } from '../migration/1717644442966-ProjectUserInstantPowerView_V2'; import { ProjectInstantPowerViewV21717648653115 } from '../migration/1717648653115-ProjectInstantPowerView_V2'; import { UserProjectPowerViewV21717645768886 } from '../migration/1717645768886-UserProjectPowerView_V2'; +import { ProjectGivbackRankViewV31725260193333 } from '../migration/1725260193333-projectGivbackRankView'; async function seedDb() { await seedUsers(); @@ -551,6 +552,7 @@ async function runMigrations() { await new ProjectActualMatchingViewV161717646612482().up(queryRunner); await new EnablePgTrgmExtension1713859866338().up(queryRunner); await new AddPgTrgmIndexes1715086559930().up(queryRunner); + await new ProjectGivbackRankViewV31725260193333().up(queryRunner); } finally { await queryRunner.release(); } diff --git a/test/testUtils.ts b/test/testUtils.ts index 3c3f923d5..f3c0caf6c 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -151,6 +151,7 @@ export interface CreateProjectData { image?: string; networkId?: number; chainType?: ChainType; + isGivbackEligible: boolean; } export const saveUserDirectlyToDb = async ( @@ -312,6 +313,7 @@ export const createProjectData = (name?: string): CreateProjectData => { walletAddress, categories: ['food1'], verified: true, + isGivbackEligible: true, listed: true, reviewStatus: ReviewStatus.Listed, giveBacks: false, From ec348628081e7b660161e656531ccc4012f53f76 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 3 Sep 2024 17:38:51 +0330 Subject: [PATCH 14/23] Fix build error --- src/resolvers/donationResolver.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 83d12f528..da109a11a 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -3794,6 +3794,7 @@ function donationsByUserIdTestCases() { walletAddress: generateRandomEtheriumAddress(), categories: ['food1'], verified: true, + isGivbackEligible: true, listed: true, reviewStatus: ReviewStatus.Listed, giveBacks: false, @@ -3878,6 +3879,7 @@ function donationsByUserIdTestCases() { listed: true, reviewStatus: ReviewStatus.Listed, giveBacks: false, + isGivbackEligible: false, creationDate: new Date(), updatedAt: new Date(), latestUpdateCreationDate: new Date(), From 59ac27e47948b8817bd733defd8a641490631634 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Wed, 4 Sep 2024 13:52:30 +0330 Subject: [PATCH 15/23] Fix build error --- src/services/cronJobs/checkQRTransactionJob.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/cronJobs/checkQRTransactionJob.ts b/src/services/cronJobs/checkQRTransactionJob.ts index a8402b787..2747c3930 100644 --- a/src/services/cronJobs/checkQRTransactionJob.ts +++ b/src/services/cronJobs/checkQRTransactionJob.ts @@ -108,7 +108,7 @@ const registerSecondaryDonation = async ( fromWalletAddress: transaction.source_account, transactionId: transaction.transaction_hash, tokenAddress: donation.tokenAddress, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, donorUser: donor, isTokenEligibleForGivback: token.isGivbackEligible, segmentNotified: false, From 13695cd35d673fd6a2ba2fae0cf8d932e71b5c93 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 14:07:12 +0330 Subject: [PATCH 16/23] Fix project query for isGivbackEligible and verified --- src/entities/project.ts | 2 +- src/repositories/projectRepository.ts | 25 ++++----- .../projectVerificationFormResolver.test.ts | 5 +- src/services/campaignService.ts | 53 ++++++++++--------- test/graphqlQueries.ts | 1 + 5 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/entities/project.ts b/src/entities/project.ts index 4b76d803c..f10daa91b 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -408,7 +408,7 @@ export class Project extends BaseEntity { // @Column({ type: 'boolean', default: false }) // tunnableQf?: boolean; - @Field(_type => Boolean) + @Field(_type => Boolean, { nullable: true }) @Column({ type: 'boolean', default: false }) isGivbackEligible: boolean; diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 1fcfc29ff..114f14686 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -23,8 +23,6 @@ import { ProjectStatusHistory } from '../entities/projectStatusHistory'; import { Reaction } from '../entities/reaction'; import { SocialProfile } from '../entities/socialProfile'; -const verifiedOrIsGivbackEligibleCondition = `CASE WHEN project.verified = true OR project.isGivbackEligible = true THEN 1 ELSE 0 END`; - export const findProjectById = (projectId: number): Promise => { // return Project.findOne({ id: projectId }); @@ -200,7 +198,8 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.GIVPower: query - .orderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC) + .addOrderBy('project.isGivbackEligible', 'DESC') // Primary sorting condition + .addOrderBy('project.verified', 'DESC') // Secondary sorting condition .addOrderBy( 'projectPower.totalPower', OrderDirection.DESC, @@ -209,7 +208,8 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { break; case SortingField.InstantBoosting: // This is our default sorting query - .orderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC) + .addOrderBy('project.isGivbackEligible', 'DESC') // Primary sorting condition + .addOrderBy('project.verified', 'DESC') // Secondary sorting condition .addOrderBy( 'projectInstantPower.totalPower', OrderDirection.DESC, @@ -234,10 +234,8 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { OrderDirection.DESC, 'NULLS LAST', ) - .addOrderBy( - verifiedOrIsGivbackEligibleCondition, - OrderDirection.DESC, - ); + .addOrderBy('project.isGivbackEligible', 'DESC') // Primary sorting condition + .addOrderBy('project.verified', 'DESC'); // Secondary sorting condition } break; case SortingField.EstimatedMatching: @@ -249,17 +247,16 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { OrderDirection.DESC, 'NULLS LAST', ) - .addOrderBy( - verifiedOrIsGivbackEligibleCondition, - OrderDirection.DESC, - ); + .addOrderBy('project.isGivbackEligible', 'DESC') // Primary sorting condition + .addOrderBy('project.verified', 'DESC'); // Secondary sorting condition } break; default: query - .orderBy('projectInstantPower.totalPower', OrderDirection.DESC) - .addOrderBy(verifiedOrIsGivbackEligibleCondition, OrderDirection.DESC); + .addOrderBy('projectInstantPower.totalPower', OrderDirection.DESC) + .addOrderBy('project.isGivbackEligible', 'DESC') // Primary sorting condition + .addOrderBy('project.verified', 'DESC'); // Secondary sorting condition break; } diff --git a/src/resolvers/projectVerificationFormResolver.test.ts b/src/resolvers/projectVerificationFormResolver.test.ts index f13c94e67..c36c6ba75 100644 --- a/src/resolvers/projectVerificationFormResolver.test.ts +++ b/src/resolvers/projectVerificationFormResolver.test.ts @@ -75,6 +75,7 @@ function createProjectVerificationFormMutationTestCases() { statusId: ProjStatus.deactive, adminUserId: user.id, verified: false, + isGivbackEligible: false, listed: false, reviewStatus: ReviewStatus.NotListed, }); @@ -93,7 +94,6 @@ function createProjectVerificationFormMutationTestCases() { }, }, ); - assert.equal( result.data.data.createProjectVerificationForm.status, PROJECT_VERIFICATION_STATUSES.DRAFT, @@ -210,7 +210,8 @@ function createProjectVerificationFormMutationTestCases() { ...createProjectData(), statusId: ProjStatus.deactive, adminUserId: user.id, - verified: false, + verified: true, + isGivbackEligible: false, listed: false, reviewStatus: ReviewStatus.NotListed, }); diff --git a/src/services/campaignService.ts b/src/services/campaignService.ts index fd22e2947..03735f39d 100644 --- a/src/services/campaignService.ts +++ b/src/services/campaignService.ts @@ -55,32 +55,37 @@ export const getAllProjectsRelatedToActiveCampaigns = async (): Promise<{ }; export const cacheProjectCampaigns = async (): Promise => { - logger.debug('cacheProjectCampaigns() has been called'); - const newProjectCampaignCache = {}; - const activeCampaigns = await findAllActiveCampaigns(); - for (const campaign of activeCampaigns) { - const projectsQueryParams = createFetchCampaignProjectsQuery(campaign); - if (!projectsQueryParams) { - continue; - } - const projectsQuery = filterProjectsQuery(projectsQueryParams); - const projects = await projectsQuery.getMany(); - for (const project of projects) { - newProjectCampaignCache[project.id] - ? newProjectCampaignCache[project.id].push(campaign.slug) - : (newProjectCampaignCache[project.id] = [campaign.slug]); + try { + logger.debug('cacheProjectCampaigns() has been called'); + const newProjectCampaignCache = {}; + const activeCampaigns = await findAllActiveCampaigns(); + for (const campaign of activeCampaigns) { + const projectsQueryParams = createFetchCampaignProjectsQuery(campaign); + if (!projectsQueryParams) { + continue; + } + const projectsQuery = filterProjectsQuery(projectsQueryParams); + const projects = await projectsQuery.getMany(); + for (const project of projects) { + newProjectCampaignCache[project.id] + ? newProjectCampaignCache[project.id].push(campaign.slug) + : (newProjectCampaignCache[project.id] = [campaign.slug]); + } } + await setObjectInRedis({ + key: PROJECT_CAMPAIGN_CACHE_REDIS_KEY, + value: newProjectCampaignCache, + // cronjob would fill it every 10 minutes so the expiration doesnt matter + expirationInSeconds: 60 * 60 * 24 * 1, // 1 day + }); + logger.debug( + 'cacheProjectCampaigns() ended successfully, projectCampaignCache size ', + Object.keys(newProjectCampaignCache).length, + ); + } catch (e) { + logger.error('cacheProjectCampaigns() failed with error: ', e); + throw e; } - await setObjectInRedis({ - key: PROJECT_CAMPAIGN_CACHE_REDIS_KEY, - value: newProjectCampaignCache, - // cronjob would fill it every 10 minutes so the expiration doesnt matter - expirationInSeconds: 60 * 60 * 24 * 1, // 1 day - }); - logger.debug( - 'cacheProjectCampaigns() ended successfully, projectCampaignCache size ', - Object.keys(newProjectCampaignCache).length, - ); }; export const fillCampaignProjects = async (params: { diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index c05b84047..5ebf6e023 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -927,6 +927,7 @@ export const fetchMultiFilterAllProjectsQuery = ` impactLocation qualityScore verified + isGivbackEligible traceCampaignId listed reviewStatus From 948205a9e646c87813346efe6e57908de7cda3e6 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 14:18:19 +0330 Subject: [PATCH 17/23] Fix add base token migration --- .../1716367359560-add_base_chain_tokens.ts | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts index 4e5f0eb00..c0268dbd0 100644 --- a/migration/1716367359560-add_base_chain_tokens.ts +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -6,46 +6,51 @@ import { NETWORK_IDS } from '../src/provider'; export class AddBaseChainTokens1716367359560 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - const environment = config.get('ENVIRONMENT') as string; + try { + const environment = config.get('ENVIRONMENT') as string; - const networkId = - environment === 'production' - ? NETWORK_IDS.BASE_MAINNET - : NETWORK_IDS.BASE_SEPOLIA; + const networkId = + environment === 'production' + ? NETWORK_IDS.BASE_MAINNET + : NETWORK_IDS.BASE_SEPOLIA; - await queryRunner.manager.save( - Token, - seedTokens - .filter(token => token.networkId === networkId) - .map(token => { - const t = { - ...token, - }; - t.address = t.address?.toLowerCase(); - delete t.chainType; - return t; - }), - ); - const tokens = await queryRunner.query(` - SELECT * FROM token - WHERE "networkId" = ${networkId} - `); - const givethOrganization = ( - await queryRunner.query(`SELECT * FROM organization - WHERE label='giveth'`) - )[0]; + await queryRunner.manager.save( + Token, + seedTokens + .filter(token => token.networkId === networkId) + .map(token => { + const t = { + ...token, + }; + t.address = t.address?.toLowerCase(); + delete t.chainType; + return t; + }), + ); + const tokens = await queryRunner.query(` + SELECT * FROM token + WHERE "networkId" = ${networkId} + `); + const givethOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='giveth'`) + )[0]; - const traceOrganization = ( - await queryRunner.query(`SELECT * FROM organization - WHERE label='trace'`) - )[0]; + const traceOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='trace'`) + )[0]; - for (const token of tokens) { - // Add all Base tokens to Giveth organization - await queryRunner.query(`INSERT INTO organization_tokens_token ("tokenId","organizationId") VALUES - (${token.id}, ${givethOrganization.id}), - (${token.id}, ${traceOrganization.id}) - ;`); + for (const token of tokens) { + // Add all Base tokens to Giveth organization + await queryRunner.query(`INSERT INTO organization_tokens_token ("tokenId","organizationId") VALUES + (${token.id}, ${givethOrganization.id}), + (${token.id}, ${traceOrganization.id}) + ;`); + } + } catch (e) { + // tslint:disable-next-line: no-console + console.log('Error in migration AddBaseChainTokens1716367359560', e); } } From 230b3e94bd08ef8969a76069fa348c7b42813bbd Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 14:25:37 +0330 Subject: [PATCH 18/23] Fix eslint errors --- migration/1716367359560-add_base_chain_tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts index c0268dbd0..a8cf101c3 100644 --- a/migration/1716367359560-add_base_chain_tokens.ts +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -49,7 +49,7 @@ export class AddBaseChainTokens1716367359560 implements MigrationInterface { ;`); } } catch (e) { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.log('Error in migration AddBaseChainTokens1716367359560', e); } } From 7229b677aabd2ab03e4c8f423382f6d0ecd3bc59 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 14:42:24 +0330 Subject: [PATCH 19/23] Fix add base token migration --- .../1716367359560-add_base_chain_tokens.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts index a8cf101c3..81025c2f3 100644 --- a/migration/1716367359560-add_base_chain_tokens.ts +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -14,19 +14,27 @@ export class AddBaseChainTokens1716367359560 implements MigrationInterface { ? NETWORK_IDS.BASE_MAINNET : NETWORK_IDS.BASE_SEPOLIA; - await queryRunner.manager.save( - Token, - seedTokens - .filter(token => token.networkId === networkId) - .map(token => { - const t = { - ...token, - }; - t.address = t.address?.toLowerCase(); - delete t.chainType; - return t; - }), - ); + try { + await queryRunner.manager.save( + Token, + seedTokens + .filter(token => token.networkId === networkId) + .map(token => { + const t = { + ...token, + }; + t.address = t.address?.toLowerCase(); + delete t.chainType; + return t; + }), + ); + } catch (e) { + // eslint-disable-next-line no-console + console.log( + 'Error in migration AddBaseChainTokens1716367359560, saving tokens', + e, + ); + } const tokens = await queryRunner.query(` SELECT * FROM token WHERE "networkId" = ${networkId} From 291c317e187b6d2bd2680d7ff145d520d601b532 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 15:52:52 +0330 Subject: [PATCH 20/23] Fix add base token migration --- migration/1646295724658-createTokensTable.ts | 1 + .../1716367359560-add_base_chain_tokens.ts | 85 ++++++++----------- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/migration/1646295724658-createTokensTable.ts b/migration/1646295724658-createTokensTable.ts index e107727f4..d832a1c7f 100644 --- a/migration/1646295724658-createTokensTable.ts +++ b/migration/1646295724658-createTokensTable.ts @@ -10,6 +10,7 @@ export class createTokensTable1646295724658 implements MigrationInterface { name text COLLATE pg_catalog."default" NOT NULL, symbol text COLLATE pg_catalog."default" NOT NULL, address text COLLATE pg_catalog."default" NOT NULL, + "isQR" BOOLEAN DEFAULT FALSE NOT NUL, "networkId" integer NOT NULL, decimals integer NOT NULL, "order" integer, diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts index 81025c2f3..4e5f0eb00 100644 --- a/migration/1716367359560-add_base_chain_tokens.ts +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -6,59 +6,46 @@ import { NETWORK_IDS } from '../src/provider'; export class AddBaseChainTokens1716367359560 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - try { - const environment = config.get('ENVIRONMENT') as string; + const environment = config.get('ENVIRONMENT') as string; - const networkId = - environment === 'production' - ? NETWORK_IDS.BASE_MAINNET - : NETWORK_IDS.BASE_SEPOLIA; + const networkId = + environment === 'production' + ? NETWORK_IDS.BASE_MAINNET + : NETWORK_IDS.BASE_SEPOLIA; - try { - await queryRunner.manager.save( - Token, - seedTokens - .filter(token => token.networkId === networkId) - .map(token => { - const t = { - ...token, - }; - t.address = t.address?.toLowerCase(); - delete t.chainType; - return t; - }), - ); - } catch (e) { - // eslint-disable-next-line no-console - console.log( - 'Error in migration AddBaseChainTokens1716367359560, saving tokens', - e, - ); - } - const tokens = await queryRunner.query(` - SELECT * FROM token - WHERE "networkId" = ${networkId} - `); - const givethOrganization = ( - await queryRunner.query(`SELECT * FROM organization - WHERE label='giveth'`) - )[0]; + await queryRunner.manager.save( + Token, + seedTokens + .filter(token => token.networkId === networkId) + .map(token => { + const t = { + ...token, + }; + t.address = t.address?.toLowerCase(); + delete t.chainType; + return t; + }), + ); + const tokens = await queryRunner.query(` + SELECT * FROM token + WHERE "networkId" = ${networkId} + `); + const givethOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='giveth'`) + )[0]; - const traceOrganization = ( - await queryRunner.query(`SELECT * FROM organization - WHERE label='trace'`) - )[0]; + const traceOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='trace'`) + )[0]; - for (const token of tokens) { - // Add all Base tokens to Giveth organization - await queryRunner.query(`INSERT INTO organization_tokens_token ("tokenId","organizationId") VALUES - (${token.id}, ${givethOrganization.id}), - (${token.id}, ${traceOrganization.id}) - ;`); - } - } catch (e) { - // eslint-disable-next-line no-console - console.log('Error in migration AddBaseChainTokens1716367359560', e); + for (const token of tokens) { + // Add all Base tokens to Giveth organization + await queryRunner.query(`INSERT INTO organization_tokens_token ("tokenId","organizationId") VALUES + (${token.id}, ${givethOrganization.id}), + (${token.id}, ${traceOrganization.id}) + ;`); } } From 89e2b2016595a237b33fef01699d58d02e5ccb7e Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 16:02:04 +0330 Subject: [PATCH 21/23] Fix add base token migration --- migration/1716367359560-add_base_chain_tokens.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts index 4e5f0eb00..7622bb86f 100644 --- a/migration/1716367359560-add_base_chain_tokens.ts +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -6,6 +6,10 @@ import { NETWORK_IDS } from '../src/provider'; export class AddBaseChainTokens1716367359560 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE token ADD COLUMN IF NOT EXISTS "isQR" BOOLEAN DEFAULT FALSE NOT NULL`, + ); + const environment = config.get('ENVIRONMENT') as string; const networkId = From c73dc416577733e7b7836e9f91f2d8b8343b0600 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 5 Sep 2024 16:52:41 +0330 Subject: [PATCH 22/23] Fix donation test cases related to isGivbackEligible --- src/resolvers/donationResolver.test.ts | 9 +++++---- src/server/adminJs/tabs/projectsTab.test.ts | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 60ab35c07..572100e7a 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -48,7 +48,6 @@ import { takePowerBoostingSnapshot, } from '../repositories/powerBoostingRepository'; import { setPowerRound } from '../repositories/powerRoundRepository'; -import { refreshProjectPowerView } from '../repositories/projectPowerViewRepository'; import { PowerBalanceSnapshot } from '../entities/powerBalanceSnapshot'; import { PowerBoostingSnapshot } from '../entities/powerBoostingSnapshot'; import { AppDataSource } from '../orm'; @@ -67,6 +66,7 @@ import { import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; import { createNewRecurringDonation } from '../repositories/recurringDonationRepository'; import { RECURRING_DONATION_STATUS } from '../entities/recurringDonation'; +import { refreshProjectGivbackRankView } from '../repositories/projectGivbackViewRepository'; // eslint-disable-next-line @typescript-eslint/no-var-requires const moment = require('moment'); @@ -1567,7 +1567,7 @@ function createDonationTestCases() { assert.isTrue(donation?.isTokenEligibleForGivback); assert.equal(donation?.amount, amount); }); - it('should create GIV donation and fill averageGivbackFactor', async () => { + it(' should create GIV donation and fill averageGivbackFactor', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); const project2 = await saveProjectDirectlyToDb(createProjectData()); const user = await User.create({ @@ -1609,7 +1609,7 @@ function createDonationTestCases() { balance: 100, }); await setPowerRound(roundNumber); - await refreshProjectPowerView(); + await refreshProjectGivbackRankView(); const accessToken = await generateTestAccessToken(user.id); const saveDonationResponse = await axios.post( @@ -2426,6 +2426,7 @@ function createDonationTestCases() { const project = await saveProjectDirectlyToDb({ ...createProjectData(), verified: false, + isGivbackEligible: true, }); const user = await User.create({ walletAddress: generateRandomEtheriumAddress(), @@ -2459,7 +2460,7 @@ function createDonationTestCases() { }, }); assert.isOk(donation); - assert.isFalse(donation?.isProjectGivbackEligible); + assert.isTrue(donation?.isProjectGivbackEligible); }); it('should throw exception when donating to draft projects', async () => { const project = await saveProjectDirectlyToDb({ diff --git a/src/server/adminJs/tabs/projectsTab.test.ts b/src/server/adminJs/tabs/projectsTab.test.ts index 832c5d930..8b53726e3 100644 --- a/src/server/adminJs/tabs/projectsTab.test.ts +++ b/src/server/adminJs/tabs/projectsTab.test.ts @@ -921,13 +921,13 @@ function verifyMultipleProjectsTestCases() { where: { id: project2.id }, }); - assert.notEqual(project1Updated?.verificationStatus, 'revoked'); - assert.equal(project1Updated?.verificationStatus, null); + assert.equal(project1Updated?.verificationStatus, 'revoked'); + assert.notEqual(project1Updated?.verified, false); assert.equal(project1Updated?.verified, true); - assert.notEqual(project2Updated?.verificationStatus, 'reminder'); - assert.equal(project2Updated?.verificationStatus, null); + assert.equal(project2Updated?.verificationStatus, 'reminder'); + assert.notEqual(project2Updated?.verified, false); assert.equal(project2Updated?.verified, true); }); From ed909163f7e84cbabb08ae30ceb5dc81caa28ae0 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Wed, 11 Sep 2024 10:25:17 +0330 Subject: [PATCH 23/23] Fix build error --- src/services/cronJobs/checkQRTransactionJob.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/cronJobs/checkQRTransactionJob.ts b/src/services/cronJobs/checkQRTransactionJob.ts index a7a17a725..05b1e7abf 100644 --- a/src/services/cronJobs/checkQRTransactionJob.ts +++ b/src/services/cronJobs/checkQRTransactionJob.ts @@ -148,7 +148,7 @@ export async function checkTransactions( fromWalletAddress: transaction.source_account, transactionId: transaction.transaction_hash, tokenAddress: donation.tokenAddress, - isProjectVerified: project.verified, + isProjectGivbackEligible: project.isGivbackEligible, donorUser: donor, isTokenEligibleForGivback: token.isGivbackEligible, segmentNotified: false,