diff --git a/migration/1707045732631-modify_recurring_donation_table.ts b/migration/1707045732631-modify_recurring_donation_table.ts new file mode 100644 index 000000000..2b1134ced --- /dev/null +++ b/migration/1707045732631-modify_recurring_donation_table.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ModifyRecurringDonationTable1707045732631 + implements MigrationInterface +{ + async up(queryRunner: QueryRunner): Promise { + // Add amount, anonymous, interval, and currency columns with default values + await queryRunner.query(` + ALTER TABLE recurring_donation + ADD COLUMN IF NOT EXISTS amount INT NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS interval text NOT NULL DEFAULT 'monthly', + ADD COLUMN IF NOT EXISTS currency text NOT NULL DEFAULT 'USD' + ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'pending' + `); + } + + async down(queryRunner: QueryRunner): Promise { + // Remove the columns if the migration is rolled back + await queryRunner.query(` + ALTER TABLE recurring_donation + DROP COLUMN amount, + DROP COLUMN status, + DROP COLUMN interval, + DROP COLUMN currency + `); + } +} diff --git a/src/entities/recurringDonation.ts b/src/entities/recurringDonation.ts index 8215d5a0e..eaf2929fc 100644 --- a/src/entities/recurringDonation.ts +++ b/src/entities/recurringDonation.ts @@ -31,14 +31,33 @@ export class RecurringDonation extends BaseEntity { readonly id: number; @Field() - @Column() + @Column({ nullable: false }) networkId: number; + @Field() + @Column({ nullable: false }) + amount: number; + + // daily, weekly, monthly, yearly + @Field() + @Column({ nullable: false }) + interval: string; + @Index() @Field() - @Column() + @Column({ nullable: false }) txHash: string; + @Index() + @Field() + @Column({ nullable: false }) + currency: string; + + @Index() + @Field() + @Column({ nullable: false, default: 'pending' }) + status: string; + @Index() @Field(type => Project) @ManyToOne(type => Project) @@ -50,9 +69,14 @@ export class RecurringDonation extends BaseEntity { @Column({ nullable: true }) projectId: number; - @Column({ nullable: true }) + @Column({ nullable: true, default: false }) + @Field({ nullable: true }) finished: boolean; + @Column({ nullable: true, default: false }) + @Field({ nullable: true }) + anonymous: boolean; + @Index() @Field(type => AnchorContractAddress) @ManyToOne(type => AnchorContractAddress) @@ -74,17 +98,11 @@ export class RecurringDonation extends BaseEntity { @Column({ nullable: true }) donorId: number; - @Field({ nullable: true }) - @Column('text', { default: RECURRING_DONATION_STATUS.PENDING }) - status: string; - - @Field({ nullable: true }) - @Column({ nullable: true }) - anonymous: boolean; - @UpdateDateColumn() + @Field() updatedAt: Date; @CreateDateColumn() + @Field() createdAt: Date; } diff --git a/src/repositories/campaignRepository.ts b/src/repositories/campaignRepository.ts index cf7bebc40..e6fd0c5fe 100644 --- a/src/repositories/campaignRepository.ts +++ b/src/repositories/campaignRepository.ts @@ -1,7 +1,4 @@ import { Campaign } from '../entities/campaign'; -import { findProjectBySlug } from './projectRepository'; -import { errorMessages } from '../utils/errorMessages'; -import { Project } from '../entities/project'; export const findAllActiveCampaigns = async (): Promise => { return Campaign.createQueryBuilder('campaign') diff --git a/src/repositories/recurringDonationRepository.test.ts b/src/repositories/recurringDonationRepository.test.ts index fe4e33350..01300685e 100644 --- a/src/repositories/recurringDonationRepository.test.ts +++ b/src/repositories/recurringDonationRepository.test.ts @@ -41,6 +41,9 @@ function createNewRecurringDonationTestCases() { networkId: NETWORK_IDS.OPTIMISTIC, donor: creator, anchorContractAddress, + amount: 100, + currency: 'USD', + interval: 'monthly', project, }); diff --git a/src/repositories/recurringDonationRepository.ts b/src/repositories/recurringDonationRepository.ts index 92627b4b1..d6449ae64 100644 --- a/src/repositories/recurringDonationRepository.ts +++ b/src/repositories/recurringDonationRepository.ts @@ -9,6 +9,9 @@ export const createNewRecurringDonation = async (params: { anchorContractAddress: AnchorContractAddress; networkId: number; txHash: string; + interval: string; + amount: number; + currency: string; }): Promise => { const recurringDonation = await RecurringDonation.create({ project: params.project, @@ -16,6 +19,9 @@ export const createNewRecurringDonation = async (params: { anchorContractAddress: params.anchorContractAddress, networkId: params.networkId, txHash: params.txHash, + currency: params.currency, + interval: params.interval, + amount: params.amount, }); return recurringDonation.save(); }; diff --git a/src/resolvers/recurringDonationResolver.test.ts b/src/resolvers/recurringDonationResolver.test.ts index 34ab0437b..652de0672 100644 --- a/src/resolvers/recurringDonationResolver.test.ts +++ b/src/resolvers/recurringDonationResolver.test.ts @@ -1,23 +1,34 @@ import { NETWORK_IDS } from '../provider'; import { + createDonationData, createProjectData, + DONATION_SEED_DATA, generateRandomEtheriumAddress, generateRandomEvmTxHash, generateTestAccessToken, graphqlUrl, + saveDonationDirectlyToDb, saveProjectDirectlyToDb, + saveRecurringDonationDirectlyToDb, saveUserDirectlyToDb, + SEED_DATA, } from '../../test/testUtils'; import { assert } from 'chai'; import axios from 'axios'; -import { createRecurringDonationQuery } from '../../test/graphqlQueries'; +import { + createRecurringDonationQuery, + fetchDonationsByProjectIdQuery, + fetchRecurringDonationsByProjectIdQuery, +} from '../../test/graphqlQueries'; import { errorMessages } from '../utils/errorMessages'; import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; +import { Donation, DONATION_STATUS } from '../entities/donation'; describe( 'createRecurringDonation test cases', createRecurringDonationTestCases, ); +describe('donationsByProjectId test cases', donationsByProjectIdTestCases); function createRecurringDonationTestCases() { it('should create recurringDonation successfully', async () => { @@ -51,6 +62,9 @@ function createRecurringDonationTestCases() { projectId: project.id, networkId: NETWORK_IDS.OPTIMISTIC, txHash: generateRandomEvmTxHash(), + amount: 100, + currency: 'GIV', + interval: 'monthly', }, }, { @@ -91,6 +105,9 @@ function createRecurringDonationTestCases() { projectId: project.id, networkId: NETWORK_IDS.OPTIMISTIC, txHash: generateRandomEvmTxHash(), + amount: 100, + currency: 'GIV', + interval: 'monthly', }, }); @@ -113,6 +130,9 @@ function createRecurringDonationTestCases() { projectId: 99999, networkId: NETWORK_IDS.OPTIMISTIC, txHash: generateRandomEvmTxHash(), + amount: 100, + currency: 'GIV', + interval: 'monthly', }, }, { @@ -148,6 +168,9 @@ function createRecurringDonationTestCases() { projectId: project.id, networkId: NETWORK_IDS.OPTIMISTIC, txHash: generateRandomEvmTxHash(), + amount: 100, + currency: 'GIV', + interval: 'monthly', }, }, { @@ -164,3 +187,337 @@ function createRecurringDonationTestCases() { ); }); } + +function donationsByProjectIdTestCases() { + it('should sort by the createdAt DESC', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + orderBy: { + field: 'createdAt', + direction: 'DESC', + }, + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 2); + for (let i = 0; i < donations.length - 1; i++) { + assert.isTrue(donations[i].createdAt >= donations[i + 1].createdAt); + } + }); + it('should sort by the createdAt ASC', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + orderBy: { + field: 'createdAt', + direction: 'ASC', + }, + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 2); + for (let i = 0; i < donations.length - 1; i++) { + assert.isTrue(donations[i].createdAt <= donations[i + 1].createdAt); + } + }); + it('should sort by the amount ASC', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 100, + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 200, + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + orderBy: { + field: 'amount', + direction: 'ASC', + }, + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 2); + for (let i = 0; i < donations.length - 1; i++) { + assert.isTrue(donations[i].amount <= donations[i + 1].amount); + } + }); + it('should sort by the amount DESC', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 100, + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 200, + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + orderBy: { + field: 'amount', + direction: 'DESC', + }, + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 2); + for (let i = 0; i < donations.length - 1; i++) { + assert.isTrue(donations[i].amount >= donations[i + 1].amount); + } + }); + it('should search by the currency', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + currency: 'USDT', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + currency: 'GIV', + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + searchTerm: 'GIV', + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); + assert.equal(donations[0].currency, 'GIV'); + }); + it('should search by the amount', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 100, + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + amount: 200, + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + searchTerm: '100', + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); + assert.equal(donations[0].amount, '100'); + }); + it('should search by the failed status', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'failed', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'verified', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'pending', + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + status: 'failed', + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); + assert.equal(donations[0].status, 'failed'); + }); + it('should search by the pending status', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'failed', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'verified', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'pending', + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + status: 'pending', + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); + assert.equal(donations[0].status, 'pending'); + }); + it('should search by the verified status', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'failed', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'verified', + }, + }); + await saveRecurringDonationDirectlyToDb({ + donationData: { + projectId: project.id, + status: 'pending', + }, + }); + + const result = await axios.post( + graphqlUrl, + { + query: fetchRecurringDonationsByProjectIdQuery, + variables: { + projectId: project.id, + status: 'verified', + }, + }, + {}, + ); + + const donations = + result.data.data.recurringDonationsByProjectId.recurringDonations; + assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); + assert.equal(donations[0].status, 'verified'); + }); +} diff --git a/src/resolvers/recurringDonationResolver.ts b/src/resolvers/recurringDonationResolver.ts index dc44782ca..deaec72a7 100644 --- a/src/resolvers/recurringDonationResolver.ts +++ b/src/resolvers/recurringDonationResolver.ts @@ -1,4 +1,17 @@ -import { Arg, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'; +import { + Arg, + Args, + ArgsType, + Ctx, + Field, + InputType, + Int, + Mutation, + ObjectType, + Query, + registerEnumType, + Resolver, +} from 'type-graphql'; import { AnchorContractAddress } from '../entities/anchorContractAddress'; import { findProjectById } from '../repositories/projectRepository'; @@ -8,6 +21,89 @@ import { ApolloContext } from '../types/ApolloContext'; import { findUserById } from '../repositories/userRepository'; import { RecurringDonation } from '../entities/recurringDonation'; import { createNewRecurringDonation } from '../repositories/recurringDonationRepository'; +import { publicSelectionFields } from '../entities/user'; +import { Brackets } from 'typeorm'; +import { detectAddressChainType } from '../utils/networks'; +import { Service } from 'typedi'; +import { Max, Min } from 'class-validator'; + +@InputType() +class RecurringDonationSortBy { + @Field(type => RecurringDonationSortField) + field: RecurringDonationSortField; + + @Field(type => RecurringDonationSortDirection) + direction: RecurringDonationSortDirection; +} + +export enum RecurringDonationSortField { + createdAt = 'createdAt', + amount = 'amount', +} + +enum RecurringDonationSortDirection { + ASC = 'ASC', + DESC = 'DESC', +} + +const RecurringDonationNullDirection = { + ASC: 'NULLS FIRST', + DESC: 'NULLS LAST', +}; + +registerEnumType(RecurringDonationSortField, { + name: 'RecurringDonationSortField', + description: 'Sort by field', +}); + +registerEnumType(RecurringDonationSortDirection, { + name: 'RecurringDonationSortDirection', + description: 'Sort direction', +}); + +@ObjectType() +class PaginateRecurringDonations { + @Field(type => [RecurringDonation], { nullable: true }) + recurringDonations: RecurringDonation[]; + + @Field(type => Number, { nullable: true }) + totalCount: number; +} + +@Service() +@ArgsType() +class UserRecurringDonationsArgs { + @Field(type => Int, { defaultValue: 0 }) + @Min(0) + skip: number; + + @Field(type => Int, { defaultValue: 10 }) + @Min(0) + @Max(50) + take: number; + + @Field(type => RecurringDonationSortBy, { + defaultValue: { + field: RecurringDonationSortField.createdAt, + direction: RecurringDonationSortDirection.DESC, + }, + }) + orderBy: RecurringDonationSortBy; + + @Field(type => Int, { nullable: false }) + userId: number; + @Field(type => String, { nullable: true }) + status: string; +} + +@ObjectType() +class UserDonations { + @Field(type => [RecurringDonation]) + recurringDonations: RecurringDonation[]; + + @Field(type => Int) + totalCount: number; +} @Resolver(of => AnchorContractAddress) export class RecurringDonationResolver { @@ -17,6 +113,9 @@ export class RecurringDonationResolver { @Arg('projectId', () => Int) projectId: number, @Arg('networkId', () => Int) networkId: number, @Arg('txHash', () => String) txHash: string, + @Arg('currency', () => String) currency: string, + @Arg('interval', () => String) interval: string, + @Arg('amount', () => Int) amount: number, ): Promise { const userId = ctx?.req?.user?.userId; const donor = await findUserById(userId); @@ -46,6 +145,102 @@ export class RecurringDonationResolver { anchorContractAddress: currentAnchorProjectAddress, networkId, txHash, + amount, + interval, + currency, }); } + + @Query(returns => PaginateRecurringDonations, { nullable: true }) + async recurringDonationsByProjectId( + @Ctx() ctx: ApolloContext, + @Arg('take', type => Int, { defaultValue: 10 }) take: number, + @Arg('skip', type => Int, { defaultValue: 0 }) skip: number, + @Arg('projectId', type => Int, { nullable: false }) projectId: number, + @Arg('status', type => String, { nullable: true }) status: string, + @Arg('finished', type => Boolean, { nullable: true, defaultValue: false }) + finished: boolean, + @Arg('searchTerm', type => String, { nullable: true }) searchTerm: string, + @Arg('orderBy', type => RecurringDonationSortBy, { + defaultValue: { + field: RecurringDonationSortField.createdAt, + direction: RecurringDonationSortDirection.DESC, + }, + }) + orderBy: RecurringDonationSortBy, + ) { + const project = await findProjectById(projectId); + if (!project) { + throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); + } + + const query = RecurringDonation.createQueryBuilder('recurringDonation') + .leftJoin('recurringDonation.donor', 'donor') + .addSelect([ + 'donor.id', + 'donor.walletAddress', + 'donor.name', + 'donor.firstName', + 'donor.lastName', + 'donor.url', + 'donor.avatar', + 'donor.totalDonated', + 'donor.totalReceived', + 'donor.passportScore', + 'donor.passportStamps', + ]) + .where(`recurringDonation.projectId = ${projectId}`); + query + .andWhere(`recurringDonation.finished = ${finished}`) + .orderBy( + `recurringDonation.${orderBy.field}`, + orderBy.direction, + RecurringDonationNullDirection[orderBy.direction as string], + ); + + if (status) { + query.andWhere(`recurringDonation.status = :status`, { + status, + }); + } + + if (searchTerm) { + query.andWhere( + new Brackets(qb => { + qb.where( + '(donor.name ILIKE :searchTerm AND recurringDonation.anonymous = false)', + { + searchTerm: `%${searchTerm}%`, + }, + ) + .orWhere('recurringDonation.status ILIKE :searchTerm', { + searchTerm: `%${searchTerm}%`, + }) + .orWhere('recurringDonation.currency ILIKE :searchTerm', { + searchTerm: `%${searchTerm}%`, + }); + + if ( + detectAddressChainType(searchTerm) === undefined && + Number(searchTerm) + ) { + const amount = Number(searchTerm); + + qb.orWhere('recurringDonation.amount = :amount', { + amount, + }); + } + }), + ); + } + + const [recurringDonations, donationsCount] = await query + .take(take) + .skip(skip) + .getManyAndCount(); + return { + recurringDonations, + totalCount: donationsCount, + }; + } } diff --git a/src/services/projectViewsService.ts b/src/services/projectViewsService.ts index 328fcab5f..c1ccb7f8d 100644 --- a/src/services/projectViewsService.ts +++ b/src/services/projectViewsService.ts @@ -42,9 +42,9 @@ export const getQfRoundActualDonationDetails = async ( await refreshProjectActualMatchingView(); const rows = await QfRound.query(` - SELECT * - FROM project_actual_matching_view - WHERE "qfRoundId" = ${qfRoundId} + SELECT * + FROM project_actual_matching_view + WHERE "qfRoundId" = ${qfRoundId} `); let totalReward = qfRound!.allocatedFund; diff --git a/src/utils/locales/en.json b/src/utils/locales/en.json index d0a0e983d..ddd253bc4 100644 --- a/src/utils/locales/en.json +++ b/src/utils/locales/en.json @@ -103,7 +103,8 @@ "REGISTERED_NON_PROFITS_CATEGORY_DOESNT_EXIST": "There is not any category with name registered-non-profits, probably you forgot to run migrations", "PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED": "Content length exceeded", "INVALID_TOKEN_ADDRESS": "Invalid tokenAddress", - "There is already an anchor address for this project, only project owner can change it": "There is already an anchor address for this project, only project owner can change it", - "Project doesnt have recipient address on this network": "Project doesnt have recipient address on this network", - "There is already an anchor address for this project": "There is already an anchor address for this project" -} + "There is already an anchor address for this project, only project owner can change it": "There is already an anchor address for this project, only project owner can change it", + "Project doesnt have recipient address on this network": "Project doesnt have recipient address on this network", + "There is already an anchor address for this project": "There is already an anchor address for this project", + "There is not anchor address for this project": "There is not anchor address for this project" +} \ No newline at end of file diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index bd9635fb0..26124ac2c 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -284,6 +284,48 @@ export const fetchDonationsByProjectIdQuery = ` } } `; +export const fetchRecurringDonationsByProjectIdQuery = ` + query ( + $take: Int + $skip: Int + $projectId: Int! + $searchTerm: String + $status: String + $finished: Boolean + $orderBy: RecurringDonationSortBy + + + ) { + recurringDonationsByProjectId( + take: $take + skip: $skip + projectId: $projectId + searchTerm: $searchTerm + status: $status + finished: $finished + orderBy: $orderBy + + ) { + recurringDonations { + id + txHash + networkId + amount + currency + anonymous + status + donor { + id + walletAddress + firstName + email + } + createdAt + } + totalCount + } + } +`; export const donationsFromWallets = ` query ( $fromWalletAddresses: [String!]! @@ -2104,11 +2146,17 @@ export const createRecurringDonationQuery = ` mutation ($projectId: Int!, $networkId: Int!, $txHash: String! + $interval: String! + $amount: Int! + $currency: String! ) { createRecurringDonation( projectId: $projectId networkId: $networkId txHash:$txHash + amount:$amount + currency:$currency + interval:$interval ) { txHash networkId diff --git a/test/testUtils.ts b/test/testUtils.ts index c72f1b28b..5de1be497 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -30,6 +30,9 @@ import { Category, CATEGORY_NAMES } from '../src/entities/category'; import { FeaturedUpdate } from '../src/entities/featuredUpdate'; import { ChainType } from '../src/types/network'; import { Keypair } from '@solana/web3.js'; +import { RecurringDonation } from '../src/entities/recurringDonation'; +import { AnchorContractAddress } from '../src/entities/anchorContractAddress'; +import { findProjectById } from '../src/repositories/projectRepository'; // tslint:disable-next-line:no-var-requires const moment = require('moment'); @@ -158,6 +161,26 @@ export const saveUserDirectlyToDb = async ( }).save(); }; +export const saveAnchorContractDirectlyToDb = async (params: { + projectId: number; + creatorId: number; + contractAddress?: string; + txHash?: string; + networkId?: number; + chainType?: ChainType; +}): Promise => { + const projectOwnerId = (await findProjectById(params.projectId))?.adminUser + ?.id; + return AnchorContractAddress.create({ + projectId: params.projectId, + creatorId: params.creatorId, + address: params.contractAddress || generateRandomEtheriumAddress(), + networkId: params.networkId || NETWORK_IDS.OPTIMISM_GOERLI, + txHash: params.txHash || generateRandomEtheriumAddress(), + ownerId: projectOwnerId, + }).save(); +}; + export const saveFeaturedProjectDirectlyToDb = async ( projectId: number, projectUpdateId: number, @@ -1847,6 +1870,36 @@ export const saveDonationDirectlyToDb = async ( }).save(); }; +export const saveRecurringDonationDirectlyToDb = async (params?: { + donationData?: Partial; +}): Promise => { + const projectId = + params?.donationData?.projectId || + (await saveProjectDirectlyToDb(createProjectData())).id; + const donorId = + params?.donationData?.donorId || + (await saveUserDirectlyToDb(generateRandomEtheriumAddress())).id; + const anchorContractAddressId = + params?.donationData?.anchorContractAddressId || + ( + await saveAnchorContractDirectlyToDb({ + creatorId: donorId, + projectId, + }) + ).id; + return RecurringDonation.create({ + amount: params?.donationData?.amount || 10, + status: params?.donationData?.status || 'pending', + networkId: params?.donationData?.networkId || NETWORK_IDS.OPTIMISM_GOERLI, + currency: params?.donationData?.currency || 'USDT', + interval: params?.donationData?.interval || 'monthly', + txHash: params?.donationData?.txHash || generateRandomEtheriumAddress(), + donorId, + projectId, + anchorContractAddressId, + }).save(); +}; + export const saveCategoryDirectlyToDb = async (categoryData: CategoryData) => { return Category.create(categoryData as Category).save(); };