From 1c38188661ac20e481e191a35e937cdc09071daf Mon Sep 17 00:00:00 2001 From: ali Date: Tue, 26 Nov 2024 04:50:49 +0330 Subject: [PATCH] apply review changes --- config/example.env | 4 +- ... 1732582914845-addScoreTimestampToUser.ts} | 6 +- src/constants/qacc.ts | 7 + src/entities/user.ts | 16 +- src/resolvers/donationResolver.test.ts | 188 ------------------ src/resolvers/donationResolver.ts | 67 ------- src/resolvers/userResolver.ts | 2 +- src/services/qAccService.ts | 16 +- src/services/userService.ts | 2 +- test/graphqlQueries.ts | 16 -- 10 files changed, 37 insertions(+), 287 deletions(-) rename migration/{1732495872789-addScoreTimestampToUser.ts => 1732582914845-addScoreTimestampToUser.ts} (66%) diff --git a/config/example.env b/config/example.env index 282b65dc4..d80772651 100644 --- a/config/example.env +++ b/config/example.env @@ -309,6 +309,6 @@ MONGO_DB_REPORT_DB_NAME= # Gitcoin score GITCOIN_PASSPORT_MIN_VALID_SCORE= # 1 day -GITCOINT_PASSPORT_EXPIRATION_PERIOD_MS=86400000 -MAX_CONTRIBUTION_WITH_GITCOING_PASSPORT_ONLY= +GITCOIN_PASSPORT_EXPIRATION_PERIOD_MS=86400000 +MAX_CONTRIBUTION_WITH_GITCOIN_PASSPORT_ONLY= ACTIVATE_GITCOIN_SCORE_CHECK= diff --git a/migration/1732495872789-addScoreTimestampToUser.ts b/migration/1732582914845-addScoreTimestampToUser.ts similarity index 66% rename from migration/1732495872789-addScoreTimestampToUser.ts rename to migration/1732582914845-addScoreTimestampToUser.ts index 4046ad976..f57b7d109 100644 --- a/migration/1732495872789-addScoreTimestampToUser.ts +++ b/migration/1732582914845-addScoreTimestampToUser.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddScoreTimestampToUser1732495872789 +export class AddScoreTimestampToUser1732582914845 implements MigrationInterface { - name = 'AddScoreTimestampToUser1732495872789'; + name = 'AddScoreTimestampToUser1732582914845'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `ALTER TABLE "user" ADD "passportScoreUpdateTimestamp" character varying`, + `ALTER TABLE "user" ADD "passportScoreUpdateTimestamp" TIMESTAMP WITH TIME ZONE`, ); } diff --git a/src/constants/qacc.ts b/src/constants/qacc.ts index c1a9a3139..1d2bed444 100644 --- a/src/constants/qacc.ts +++ b/src/constants/qacc.ts @@ -15,3 +15,10 @@ export const QACC_DONATION_TOKEN_COINGECKO_ID = (config.get('QACC_DONATION_TOKEN_COINGECKO_ID') as string) || 'matic-network'; export const QACC_PRICE_FETCH_LEAD_TIME_IN_SECONDS = (+config.get('QACC_PRICE_FETCH_LEAD_TIME_IN_SECONDS') as number) || 300; // 5 minutes +export const GITCOIN_PASSPORT_EXPIRATION_PERIOD_MS = + (+config.get('GITCOIN_PASSPORT_EXPIRATION_PERIOD_MS') as number) || 86400000; // 1 day +export const GITCOIN_PASSPORT_MIN_VALID_SCORE = + (+config.get('GITCOIN_PASSPORT_MIN_VALID_SCORE') as number) || 50; +export const MAX_CONTRIBUTION_WITH_GITCOIN_PASSPORT_ONLY = + (+config.get('MAX_CONTRIBUTION_WITH_GITCOIN_PASSPORT_ONLY') as number) || + 1000; diff --git a/src/entities/user.ts b/src/entities/user.ts index 60a9183be..02a5f76b0 100644 --- a/src/entities/user.ts +++ b/src/entities/user.ts @@ -19,6 +19,7 @@ import { ProjectVerificationForm } from './projectVerificationForm'; import { ReferredEvent } from './referredEvent'; import { NOTIFICATIONS_EVENT_NAMES } from '../analytics/analytics'; import { PrivadoAdapter } from '../adapters/privado/privadoAdapter'; +import config from '../config'; export const publicSelectionFields = [ 'user.id', @@ -117,9 +118,9 @@ export class User extends BaseEntity { @Column({ type: 'real', nullable: true, default: null }) passportScore?: number; - @Field(_type => String, { nullable: true }) - @Column({ nullable: true }) - passportScoreUpdateTimestamp?: string; + @Field(_type => Date, { nullable: true }) + @Column({ type: 'timestamptz', nullable: true }) + passportScoreUpdateTimestamp?: Date; @Field(_type => Number, { nullable: true }) @Column({ nullable: true, default: null }) @@ -232,6 +233,15 @@ export class User extends BaseEntity { ); } + @Field(_type => Boolean, { nullable: true }) + get hasEnoughPassportScore(): boolean { + return !!( + this.passportScore && + this.passportScore >= + Number(config.get('GITCOIN_PASSPORT_MIN_VALID_SCORE')) + ); + } + @Field(_type => Int, { nullable: true }) async donationsCount() { return await Donation.createQueryBuilder('donation') diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 5ab35323a..4eb65a784 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -42,7 +42,6 @@ import { fetchNewDonorsCount, fetchNewDonorsDonationTotalUsd, fetchDonationMetricsQuery, - validateDonationQuery, } from '../../test/graphqlQueries'; import { NETWORK_IDS, QACC_NETWORK_ID } from '../provider'; import { User } from '../entities/user'; @@ -77,7 +76,6 @@ describe('donationsByProjectId() test cases', donationsByProjectIdTestCases); describe('donationByUserId() test cases', donationsByUserIdTestCases); describe('donationsByDonor() test cases', donationsByDonorTestCases); describe('createDonation() test cases', createDonationTestCases); -describe('validateDonation() test cases', validateDonationTestCases); // describe('updateDonationStatus() test cases', updateDonationStatusTestCases); describe('donationsToWallets() test cases', donationsToWalletsTestCases); describe('donationsFromWallets() test cases', donationsFromWalletsTestCases); @@ -2723,192 +2721,6 @@ function createDonationTestCases() { }); } -function validateDonationTestCases() { - let project; - let user; - - before(async () => { - // Set up a project and user before each test case - project = await saveProjectDirectlyToDb(createProjectData()); - user = await User.create({ - walletAddress: generateRandomEtheriumAddress(), - loginType: 'wallet', - firstName: 'Test User', - }).save(); - }); - - beforeEach(async () => { - sinon.stub(qAccService, 'getQAccDonationCap').resolves(10000); - }); - - afterEach(async () => { - sinon.restore(); - }); - - it('should return true if donation is valid', async () => { - // Mocking valid conditions for donation - const amount = 100; - const token = QACC_DONATION_TOKEN_SYMBOL; - const transactionNetworkId = QACC_NETWORK_ID; - const projectId = project.id; - - const accessToken = await generateTestAccessToken(user.id); - - const validateDonationResponse = await axios.post( - graphqlUrl, - { - query: validateDonationQuery, - variables: { - amount, - token, - transactionNetworkId, - projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isTrue(validateDonationResponse.data.data.validateDonation); - }); - - it('should return false if donation amount exceeds project cap', async () => { - // Test case for exceeding donation cap - const amount = 1000000; // Large donation amount - const token = QACC_DONATION_TOKEN_SYMBOL; - const transactionNetworkId = QACC_NETWORK_ID; - const projectId = project.id; - - const accessToken = await generateTestAccessToken(user.id); - - const validateDonationResponse = await axios.post( - graphqlUrl, - { - query: validateDonationQuery, - variables: { - amount, - token, - transactionNetworkId, - projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isFalse(validateDonationResponse.data.data.validateDonation); - }); - - it('should return false if user is unauthorized', async () => { - // Test case for unauthorized user - const amount = 100; - const token = QACC_DONATION_TOKEN_SYMBOL; - const transactionNetworkId = QACC_NETWORK_ID; - const projectId = project.id; - - const accessToken = 'InvalidAccessToken'; // Invalid token - - try { - await axios.post( - graphqlUrl, - { - query: validateDonationQuery, - variables: { - amount, - token, - transactionNetworkId, - projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - } catch (e) { - assert.include(e.response.data.errors[0].message, 'Unauthorized'); - } - }); - - it('should throw error if donation is invalid due to network mismatch', async () => { - // Test case for invalid network ID - const amount = 100; - const token = QACC_DONATION_TOKEN_SYMBOL; - const transactionNetworkId = 999; // Invalid network ID - const projectId = project.id; - - const accessToken = await generateTestAccessToken(user.id); - - const validateDonationResponse = await axios.post( - graphqlUrl, - { - query: validateDonationQuery, - variables: { - amount, - token, - transactionNetworkId, - projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isTrue( - (validateDonationResponse.data.errors[0].message as string).startsWith( - 'Invalid tokenAddress', - ), - ); - }); - - it('should throw error if project is not active', async () => { - // Test case for project not being active - const amount = 100; - const token = QACC_DONATION_TOKEN_SYMBOL; - const transactionNetworkId = QACC_NETWORK_ID; - const projectId = project.id; - - project.status.id = ProjStatus.deactive; // Setting project status to deactive - await project.save(); - - const accessToken = await generateTestAccessToken(user.id); - - const validateDonationResponse = await axios.post( - graphqlUrl, - { - query: validateDonationQuery, - variables: { - amount, - token, - transactionNetworkId, - projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isTrue( - (validateDonationResponse.data.errors[0].message as string).startsWith( - 'Just active projects accept donation', - ), - ); - }); -} - function donationsFromWalletsTestCases() { it('should find donations with special source successfully', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 1069f835c..c3e5ed05a 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -678,73 +678,6 @@ export class DonationResolver { }; } - @Query(_returns => Boolean) - async validateDonation( - @Arg('amount') amount: number, - @Arg('token') token: string, - @Arg('transactionNetworkId') transactionNetworkId: number, - @Arg('projectId') projectId: number, - @Ctx() ctx: ApolloContext, - ): Promise { - const logData = { - amount, - transactionNetworkId, - token, - projectId, - userId: ctx?.req?.user?.userId, - }; - logger.debug( - 'validateDonation() resolver has been called with this data', - logData, - ); - try { - const userId = ctx?.req?.user?.userId; - if (!userId) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - // Fetch user data - const donorUser = await findUserById(userId); - if (!donorUser) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const chainType = detectAddressChainType(donorUser.walletAddress!); - const networkId = getAppropriateNetworkId({ - networkId: transactionNetworkId, - chainType, - }); - const project = await findProjectById(projectId); - - if (!project) - throw new Error( - i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND), - ); - if (project.status.id !== ProjStatus.active) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.JUST_ACTIVE_PROJECTS_ACCEPT_DONATION, - ), - ); - } - const donateTime = new Date(); - - return await qacc.validateDonation({ - projectId, - networkId, - tokenSymbol: token, - userAddress: donorUser.walletAddress!, - amount, - donateTime, - }); - } catch (e) { - SentryLogger.captureException(e); - logger.error('validateDonation() error', { - error: e, - inputData: logData, - }); - throw e; - } - } - @Mutation(_returns => Number) async createDonation( @Arg('amount') amount: number, diff --git a/src/resolvers/userResolver.ts b/src/resolvers/userResolver.ts index 5ea6b8ddb..57876889d 100644 --- a/src/resolvers/userResolver.ts +++ b/src/resolvers/userResolver.ts @@ -126,7 +126,7 @@ export class UserResolver { if (passportScore && passportScore?.score) { const score = Number(passportScore.score); foundUser.passportScore = score; - foundUser.passportScoreUpdateTimestamp = Date.now().toString(); + foundUser.passportScoreUpdateTimestamp = new Date(); } if (passportStamps) foundUser.passportStamps = passportStamps.items.length; diff --git a/src/services/qAccService.ts b/src/services/qAccService.ts index e129288fb..9f209a386 100644 --- a/src/services/qAccService.ts +++ b/src/services/qAccService.ts @@ -8,8 +8,12 @@ import { findActiveEarlyAccessRound } from '../repositories/earlyAccessRoundRepo import { updateOrCreateProjectRoundRecord } from '../repositories/projectRoundRecordRepository'; import { updateOrCreateProjectUserRecord } from '../repositories/projectUserRecordRepository'; import { findActiveQfRound } from '../repositories/qfRoundRepository'; -import config from '../config'; import { updateUserGitcoinScore } from './userService'; +import { + GITCOIN_PASSPORT_EXPIRATION_PERIOD_MS, + GITCOIN_PASSPORT_MIN_VALID_SCORE, + MAX_CONTRIBUTION_WITH_GITCOIN_PASSPORT_ONLY, +} from '../constants/qacc'; const getEaProjectRoundRecord = async ({ projectId, @@ -213,17 +217,17 @@ const validDonationAmountBasedOnKYCAndScore = async ({ if ( !user.passportScore || !user.passportScoreUpdateTimestamp || - +user.passportScoreUpdateTimestamp > - Date.now() - (Number(config.get('VALID_SCORE_TIMESTAMP')) || 86400000) // default value is 1 day + user.passportScoreUpdateTimestamp.getTime() > + Date.now() - GITCOIN_PASSPORT_EXPIRATION_PERIOD_MS ) { await updateUserGitcoinScore(user); } if ( !user.passportScore || - user.passportScore < Number(config.get('GITCOIN_MIN_SCORE')) + user.passportScore < GITCOIN_PASSPORT_MIN_VALID_SCORE ) { throw new Error( - `passport score is less than ${config.get('GITCOIN_MIN_SCORE')}`, + `passport score is less than ${GITCOIN_PASSPORT_MIN_VALID_SCORE}`, ); } const userRecord = await getUserProjectRecord({ @@ -232,7 +236,7 @@ const validDonationAmountBasedOnKYCAndScore = async ({ }); const qfTotalDonationAmount = userRecord.qfTotalDonationAmount; const remainedCap = - Number(config.get('MAX_AMOUNT_NO_KYC')) - qfTotalDonationAmount; + MAX_CONTRIBUTION_WITH_GITCOIN_PASSPORT_ONLY - qfTotalDonationAmount; if (amount > remainedCap) { throw new Error('amount is more than allowed cap with gitcoin score'); } diff --git a/src/services/userService.ts b/src/services/userService.ts index 6582311f8..347f4278f 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -93,7 +93,7 @@ export const updateUserGitcoinScore = async (user: User) => { if (passportScore && passportScore?.score) { const score = Number(passportScore.score); user.passportScore = score; - user.passportScoreUpdateTimestamp = Date.now().toString(); + user.passportScoreUpdateTimestamp = new Date(); await user.save(); } }; diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index 858683512..65f17efc0 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -28,22 +28,6 @@ export const createDonationMutation = ` } `; -export const validateDonationQuery = ` - query ( - $amount: Float! - $token: String! - $transactionNetworkId: Float! - $projectId: Float! - ) { - validateDonation( - amount: $amount - token: $token - transactionNetworkId: $transactionNetworkId - projectId: $projectId - ) - } -`; - export const scoreUserAddressMutation = ` query ( $address: String!