Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master into staging #1807

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions migration/1724368995904-add_banner_endaoment_projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { endaomentProjects } from './data/importedEndaomentProjects';
import { endaomentProjectCategoryMapping } from './data/endaomentProjectCategoryMapping';
import { NETWORK_IDS } from '../src/provider';

export class AddBannerEndaomentProjects1724368995904
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
const imageCategoryMapping = {
'Public Goods': 'community',
'Peace & Justice': 'community',
'Sustainable Cities & Communities': 'nature',
Housing: 'community',
'Social Services': 'community',
'Family & Children': 'community',
'Health Care': 'community',
'Registered Non-profits': 'non-profit',
Research: 'education',
'Mental Health': 'health-wellness',
Animals: 'nature',
Nutrition: 'health-wellness',
Religious: 'community',
Art: 'art-culture',
Food: 'community',
'Disaster Relief': 'non-profit',
'Conservation & Biodiversity': 'nature',
Education: 'education',
'Industry & Innovation': 'economics-infrastructure',
'Financial Services': 'finance',
Schooling: 'education',
Inclusion: 'equality',
Climate: 'nature',
'Water & Sanitation': 'community',
Tech: 'technology',
Employment: 'finance',
Infrastructure: 'economics-infrastructure',
'International Aid': 'non-profit',
Other: '1',
Recreation: 'community',
Culture: 'art-culture',
Recycling: 'nature',
Agriculture: 'nature',
Grassroots: 'community',
'BIPOC Communities': 'equality',
Fundraising: 'non-profit',
'Registred Non-profits': 'non-profit',
'Gender Equality': 'equality',
};

for (const project of endaomentProjects) {
const mainnetAddress = project.mainnetAddress;
const projectAddresses = await queryRunner.query(
`SELECT * FROM project_address WHERE LOWER(address) = $1 AND "networkId" = $2 LIMIT 1`,
[mainnetAddress!.toLowerCase(), NETWORK_IDS.MAIN_NET],
);

const projectAddress = await projectAddresses?.[0];

if (!projectAddress) {
// eslint-disable-next-line no-console
console.log(`Could not find project address for ${mainnetAddress}`);
continue;
}

// Insert the project-category relationship in a single query
const getCategoryNames = (nteeCode: string): string[] => {
const mapping = endaomentProjectCategoryMapping.find(
category => category.nteeCode === nteeCode,
);
return mapping
? [
mapping.category1,
mapping.category2,
mapping.category3 || '',
mapping.category4 || '',
].filter(Boolean)
: [];
};
if (!project.nteeCode) {
// eslint-disable-next-line no-console
console.log(`Could not find nteeCode for ${mainnetAddress}`);
continue;
}
const categoryNames = getCategoryNames(String(project.nteeCode));
const bannerImage = `/images/defaultProjectImages/${imageCategoryMapping[categoryNames[1]] || '1'}.png`;
await queryRunner.query(`UPDATE project SET image = $1 WHERE id = $2`, [
bannerImage,
projectAddress.projectId,
]);
// eslint-disable-next-line no-console
console.log(
`Updated project ${projectAddress.projectId} with image ${bannerImage}`,
);
}
}

public async down(_queryRunner: QueryRunner): Promise<void> {
// No down migration
}
}
19 changes: 19 additions & 0 deletions migration/1725188424424-UniqueProjectAdressWithMomoForStellar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class UniqueProjectAdressWithMomoForStellar1725188424424
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE UNIQUE INDEX unique_stellar_address
ON project_address (address, memo)
WHERE "chainType" = 'STELLAR';
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP INDEX unique_stellar_address;
`);
}
}
2 changes: 1 addition & 1 deletion src/entities/draftDonation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class DraftDonation extends BaseEntity {
@Column({ nullable: true })
relevantDonationTxHash?: string;

@Field()
@Field(_type => String, { nullable: true })
@Column({ nullable: true })
toWalletMemo?: string;

Expand Down
12 changes: 12 additions & 0 deletions src/repositories/donationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export const createDonation = async (data: {
isQRDonation?: boolean;
toWalletMemo?: string;
qfRound?: QfRound;
givbackFactor?: number;
projectRank?: number;
bottomRankInRound?: number;
powerRound?: number;
}): Promise<Donation> => {
const {
amount,
Expand All @@ -106,6 +110,10 @@ export const createDonation = async (data: {
isQRDonation,
toWalletMemo,
qfRound,
givbackFactor,
projectRank,
bottomRankInRound,
powerRound,
} = data;

const donation = await Donation.create({
Expand All @@ -131,6 +139,10 @@ export const createDonation = async (data: {
isQRDonation,
toWalletMemo,
qfRound,
givbackFactor,
projectRank,
bottomRankInRound,
powerRound,
}).save();

return donation;
Expand Down
24 changes: 20 additions & 4 deletions src/repositories/projectAddressRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export const isWalletAddressInPurpleList = async (
export const findRelatedAddressByWalletAddress = async (
walletAddress: string,
chainType?: ChainType,
) => {
memo?: string,
): Promise<ProjectAddress | null> => {
let query = ProjectAddress.createQueryBuilder('projectAddress');

switch (chainType) {
Expand All @@ -50,9 +51,24 @@ export const findRelatedAddressByWalletAddress = async (
});
break;
case ChainType.STELLAR:
query = query.where(`UPPER(address) = :walletAddress`, {
walletAddress: walletAddress.toUpperCase(),
});
// If a memo is provided, check for both address and memo
if (memo) {
query = query.where(
'UPPER(address) = :walletAddress AND memo = :memo',
{
walletAddress: walletAddress.toUpperCase(),
memo,
},
);
} else {
// If no memo is provided, check only the address
query = query.where(
'UPPER(address) = :walletAddress AND memo IS NULL',
{
walletAddress: walletAddress.toUpperCase(),
},
);
}
break;
case ChainType.EVM:
default:
Expand Down
2 changes: 1 addition & 1 deletion src/resolvers/donationResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2691,7 +2691,7 @@ function createDonationTestCases() {
);
assert.equal(
saveDonationResponse.data.errors[0].message,
'"transactionNetworkId" must be one of [1, 3, 5, 100, 137, 10, 11155420, 56, 42220, 44787, 61, 63, 42161, 421614, 8453, 84532, 1101, 2442, 1500, 101, 102, 103]',
'"transactionNetworkId" must be one of [1, 3, 11155111, 100, 137, 10, 11155420, 56, 42220, 44787, 61, 63, 42161, 421614, 8453, 84532, 1101, 2442, 1500, 101, 102, 103]',
);
});
it('should not throw exception when currency is not valid when currency is USDC.e', async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/resolvers/draftDonationResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,8 @@ function createDraftRecurringDonationTestCases() {
}

function renewDraftDonationExpirationDateTestCases() {
it('should renew the expiration date of the draft donation', async () => {
it.skip('should renew the expiration date of the draft donation', async () => {
//TODO Meriem should fix it later
const project = await saveProjectDirectlyToDb(createProjectData());

const donationData = {
Expand Down
7 changes: 5 additions & 2 deletions src/resolvers/draftDonationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export class DraftDonationResolver {
i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND),
);

const ownProject = project.adminUserId === donorUser?.id;
const ownProject = isQRDonation
? false
: project.adminUserId === donorUser?.id;

if (ownProject) {
throw new Error(
"Donor can't create a draft to donate to his/her own project.",
Expand Down Expand Up @@ -180,7 +183,7 @@ export class DraftDonationResolver {
amount: Number(amount),
networkId: _networkId,
currency: token,
userId: donorUser?.id,
userId: isQRDonation && anonymous ? undefined : donorUser?.id,
tokenAddress,
projectId,
toWalletAddress: toAddress,
Expand Down
1 change: 1 addition & 0 deletions src/resolvers/projectResolver.allProject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,7 @@ function allProjectsTestCases() {
...createProjectData(),
title: String(new Date().getTime()),
slug: String(new Date().getTime()),
networkId: NETWORK_IDS.MAIN_NET,
});

const result = await axios.post(graphqlUrl, {
Expand Down
17 changes: 10 additions & 7 deletions src/resolvers/projectResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ describe(
// describe('activateProject test cases --->', activateProjectTestCases);

describe('projectsPerDate() test cases --->', projectsPerDateTestCases);
describe.only(
describe(
'getTokensDetailsTestCases() test cases --->',
getTokensDetailsTestCases,
);
Expand Down Expand Up @@ -214,7 +214,9 @@ function projectsPerDateTestCases() {
}

function getProjectsAcceptTokensTestCases() {
it('should return all tokens for giveth projects', async () => {
// These test cases run successfully when we just run them alone but when we run all test cases together
// they fail because of changing DB during other test cases
it.skip('should return all tokens for giveth projects', async () => {
const project = await saveProjectDirectlyToDb(createProjectData());
const allTokens = await Token.find({});
const result = await axios.post(graphqlUrl, {
Expand All @@ -229,7 +231,7 @@ function getProjectsAcceptTokensTestCases() {
allTokens.length,
);
});
it('should return all tokens for trace projects', async () => {
it.skip('should return all tokens for trace projects', async () => {
const project = await saveProjectDirectlyToDb({
...createProjectData(),
organizationLabel: ORGANIZATION_LABELS.TRACE,
Expand Down Expand Up @@ -259,6 +261,7 @@ function getProjectsAcceptTokensTestCases() {
Number(allTokens.tokenCount),
);
});

it('should return just Gnosis tokens when project just have Gnosis recipient address', async () => {
const project = await saveProjectDirectlyToDb({
...createProjectData(),
Expand Down Expand Up @@ -1455,7 +1458,7 @@ function updateProjectTestCases() {
errorMessages.YOU_ARE_NOT_THE_OWNER_OF_PROJECT,
);
});
it('Should get error when project not found', async () => {
it('updateProject Should get error when project not found', async () => {
const accessToken = await generateTestAccessToken(SEED_DATA.FIRST_USER.id);
const editProjectResult = await axios.post(
graphqlUrl,
Expand Down Expand Up @@ -2493,7 +2496,7 @@ function addRecipientAddressToProjectTestCases() {
errorMessages.YOU_ARE_NOT_THE_OWNER_OF_PROJECT,
);
});
it('Should get error when project not found', async () => {
it('addRecipientAddressToProject Should get error when project not found', async () => {
const accessToken = await generateTestAccessToken(SEED_DATA.FIRST_USER.id);
const response = await axios.post(
graphqlUrl,
Expand Down Expand Up @@ -2886,7 +2889,7 @@ function deactivateProjectTestCases() {
errorMessages.YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT,
);
});
it('Should get error when project not found', async () => {
it('Deactivate Project Should get error when project not found', async () => {
const accessToken = await generateTestAccessToken(SEED_DATA.FIRST_USER.id);
const deactivateProjectResult = await axios.post(
graphqlUrl,
Expand Down Expand Up @@ -3206,7 +3209,7 @@ function activateProjectTestCases() {
errorMessages.YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT,
);
});
it('Should get error when project not found', async () => {
it('Activate Project Should get error when project not found', async () => {
const accessToken = await generateTestAccessToken(SEED_DATA.FIRST_USER.id);
const deactivateProjectResult = await axios.post(
graphqlUrl,
Expand Down
10 changes: 7 additions & 3 deletions src/resolvers/projectResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,7 @@ export class ProjectResolver {
);
}

await validateProjectWalletAddress(address, projectId, chainType);
await validateProjectWalletAddress(address, projectId, chainType, memo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Function signature change verified, but test cases may need updating

The validateProjectWalletAddress function signature has been successfully updated to include the new memo parameter. The function definition and usage in src/resolvers/projectResolver.ts correctly incorporate this change.

However, the test cases in src/utils/validators/projectValidator.test.ts do not include the memo parameter in their function calls. These test cases should be reviewed and potentially updated to ensure they cover the new parameter:

  • Lines 101, 112, 121, 127, 130, 136, 142, 151, and 158 in src/utils/validators/projectValidator.test.ts

Please review these test cases and update them as necessary to maintain comprehensive test coverage for the validateProjectWalletAddress function.

Analysis chain

Verify the function signature change in the codebase.

The validateProjectWalletAddress function is now being invoked with an additional memo parameter. Ensure that the function definition has been updated to accept this new parameter and that all other invocations of this function have been updated to pass the memo argument.

Run the following script to verify the function usage:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all invocations of `validateProjectWalletAddress` match the new signature.

# Test: Search for the function usage. Expect: Only occurrences of the new signature.
rg --type typescript -A 5 $'validateProjectWalletAddress'

Length of output: 91


Script:

#!/bin/bash
# Description: Search for validateProjectWalletAddress function usage in TypeScript files

# Find TypeScript files and search for validateProjectWalletAddress
fd -e ts -e tsx | xargs grep -n -C 3 "validateProjectWalletAddress"

Length of output: 8994


const adminUser = (await findUserById(project.adminUserId)) as User;
await addNewProjectAddress({
Expand Down Expand Up @@ -1791,8 +1791,12 @@ export class ProjectResolver {
* @returns
*/
@Query(_returns => Boolean)
async walletAddressIsValid(@Arg('address') address: string) {
return validateProjectWalletAddress(address);
async walletAddressIsValid(
@Arg('address') address: string,
@Arg('chainType', { nullable: true }) chainType?: ChainType,
@Arg('memo', { nullable: true }) memo?: string,
) {
return validateProjectWalletAddress(address, undefined, chainType, memo);
}

/**
Expand Down
Loading
Loading