diff --git a/.github/workflows/staging-pipeline.yml b/.github/workflows/staging-pipeline.yml index b4d3111c0..72fd84aec 100644 --- a/.github/workflows/staging-pipeline.yml +++ b/.github/workflows/staging-pipeline.yml @@ -68,22 +68,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v1 - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-access-key-id: ${{ secrets.AWS_S3_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.AWS_S3_REGION }} - - - name: Download latest DB backup from S3 - run: | - FILENAME=$(aws s3 ls ${{ secrets.AWS_S3_BUCKET_PATH_STAGING }}/ | sort | tail -n 1 | awk '{print $4}') - aws s3 cp ${{ secrets.AWS_S3_BUCKET_PATH_STAGING }}/$FILENAME /tmp/db_backup.zip - - - name: Unzip DB backup - run: | - unzip /tmp/db_backup.zip -d /tmp - mv /tmp/backups/givethio-staging/*.sql /tmp/backups/givethio-staging/db_backup.sql - name: Wait for PostgreSQL to become ready run: | @@ -94,9 +78,6 @@ jobs: sleep 1 done - - name: Restore DB backup - run: PGPASSWORD=postgres psql -h localhost -p 5443 -U postgres -d givethio < /tmp/backups/givethio-staging/db_backup.sql - - name: Use Node.js uses: actions/setup-node@v1 with: @@ -111,8 +92,8 @@ jobs: - name: Run build run: npm run build - - name: Run migrations - run: npm run db:migrate:run:test + # - name: Run migrations + # run: npm run db:migrate:run:test - name: Run tests run: npm run test @@ -152,7 +133,7 @@ jobs: username: ${{ github.actor }} password: ${{ github.token }} registry: ghcr.io - repository: giveth/impact-graph + repository: generalmagicio/qacc-be # todo: check this to be correct, I just set that to be different from giveth/impact-graph add_git_labels: true # Add branch name to docker image tag @see{@link https://github.com/docker/build-push-action/tree/releases/v1#tag_with_ref} tag_with_ref: true diff --git a/config/example.env b/config/example.env index 15b219cf6..34b06bf8d 100644 --- a/config/example.env +++ b/config/example.env @@ -224,14 +224,6 @@ DONATION_SAVE_BACKUP_DATABASE= # Default value is saveBackup DONATION_SAVE_BACKUP_ADAPTER=saveBackup -ENABLE_UPDATE_RECURRING_DONATION_STREAM=true - -# Default value is 1 -NUMBER_OF_UPDATE_RECURRING_DONATION_CONCURRENT_JOB=1 - -# Default value is 0 0 * * * that means one day at 00:00 -UPDATE_RECURRING_DONATIONS_STREAM_CRONJOB=0 0 * * * - # Default value is 0.4 PROJECT_SEARCH_SIMILARITY_THRESHOLD=0.4 @@ -253,12 +245,6 @@ QfRound_PASSPORT_SCORE_CHECK_START_TIMESTAMP_IN_SECONDS= ORTTO_API_KEY=FAKE_API_KEY ORTTO_PERSON_API=https://api.ap3api.com/v1/person/merge - -RECURRING_DONATION_VERIFICATION_EXPIRATION_HOURS=24 -NUMBER_OF_VERIFY_RECURRING_DONATION_CONCURRENT_JOB=1 -ENABLE_DRAFT_RECURRING_DONATION=true -DRAFT_RECURRING_DONATION_MATCH_EXPIRATION_HOURS=24 - OPTIMISTIC_SEPOLIA_SCAN_API_KEY= BASE_SCAN_API_URL=https://api.basescan.org/api diff --git a/config/test.env b/config/test.env index 13956e192..0f23bc9af 100644 --- a/config/test.env +++ b/config/test.env @@ -129,7 +129,6 @@ PROJECT_REVOKE_SERVICE_ACTIVE=true GIVBACK_MIN_FACTOR=0.5 GIVBACK_MAX_FACTOR=0.8 -ONRAMPER_SECRET=secreto THIRD_PARTY_PROJECTS_ADMIN_USER_ID=4 PROJECT_FILTERS_THREADS_POOL_CONCURRENCY=1 @@ -144,7 +143,6 @@ CHAINVINE_ADAPTER=mock CHAINVINE_API_ENABLE_TEST_MODE=true # We should not try to verify donaitons after some hours, because checking old donations would make lots of requests to web3 providers DONATION_VERIFICAITON_EXPIRATION_HOURS=24 -RECURRING_DONATION_VERIFICAITON_EXPIRATION_HOURS=24 # We need it for monoswap POLYGON_MAINNET_NODE_HTTP_URL=https://polygon-rpc.com @@ -205,10 +203,6 @@ INSERT_USER_PASSPORT_SCORE_FOR_QF_ROUND_CRONJOB_TIME=0 0 * * * # Optional QfRound_PASSPORT_SCORE_CHECK_START_TIMESTAMP_IN_SECONDS= -ENABLE_DRAFT_RECURRING_DONATION=true -DRAFT_RECURRING_DONATION_MATCH_EXPIRATION_HOURS=24 - - OPTIMISTIC_SEPOLIA_SCAN_API_KEY= SUPER_FLUID_ADAPTER=superfluid diff --git a/docs/adminPermissions.md b/docs/adminPermissions.md index 9f605d262..e5f1a191d 100644 --- a/docs/adminPermissions.md +++ b/docs/adminPermissions.md @@ -30,5 +30,4 @@ Below table has been generated by https://www.tablesgenerator.com/markdown_table | Broadcast Notification | list, new, show | list, show | list, show | list, show | - | | Project Update | list, show, addFeaturedProjectUpdate | list, show | list, show | list, show, addFeaturedProjectUpdate | - | | Sybil | list, show, new, edit, delete, bulkDelete | list, show | list, show | list, show | list, show, new, edit, delete, bulkDelete | -| Project Fraud | list, show, new, edit, delete, bulkDelete | list, show | list, show | list, show | list, show, new, edit, delete, bulkDelete | -| Recurring donation | list, show, new, edit, delete, bulkDelete | list, show | list, show | list, show | - | | \ No newline at end of file +| Project Fraud | list, show, new, edit, delete, bulkDelete | list, show | list, show | list, show | list, show, new, edit, delete, bulkDelete | \ No newline at end of file diff --git a/package.json b/package.json index e2ca1d90c..95c2dafe7 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,6 @@ "test:backupDonationImport": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/backupDonationImport.test.ts", "test:projectEntity": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/entities/project.test.ts", "test:projectValidators": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/utils/validators/projectValidator.test.ts", - "test:onramperWebhook": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/onramper/webhookHandler.test.ts", "test:donationTracker": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/segment/DonationTracker.test.ts", "test:userRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/userRepository.test.ts", "test:statusReasonRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/statusReasonRepository.test.ts", @@ -153,15 +152,11 @@ "test:broadcastNotificationRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/broadcastNotificationRepository.test.ts", "test:projectAddressRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/projectAddressRepository.test.ts", "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:recurringDonationService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/recurringDonationService.test.ts", "test:anchorContractAddressResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/anchorContractAddressResolver.test.ts", - "test:recurringDonationResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/recurringDonationResolver.test.ts", "test:donationService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/qfRoundHistoryRepository.test.ts ./src/services/donationService.test.ts", "test:draftDonationService": "NODE_ENV=test mocha -t 99999 ./test/pre-test-scripts.ts src/services/chains/evm/draftDonationService.test.ts src/repositories/draftDonationRepository.test.ts src/workers/draftDonationMatchWorker.test.ts src/resolvers/draftDonationResolver.test.ts", "test:draftDonationWorker": "NODE_ENV=test mocha -t 99999 ./test/pre-test-scripts.ts src/workers/draftDonationMatchWorker.test.ts", - "test:draftRecurringDonationService": "NODE_ENV=test mocha -t 99999 ./test/pre-test-scripts.ts src/services/chains/evm/draftRecurringDonationService.test.ts", "test:userService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/userService.test.ts", "test:lostDonations": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/importLostDonationsJob.test.ts", "test:reactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/reactionsService.test.ts", diff --git a/src/adapters/notifications/MockNotificationAdapter.ts b/src/adapters/notifications/MockNotificationAdapter.ts index eff3c531b..870f37275 100644 --- a/src/adapters/notifications/MockNotificationAdapter.ts +++ b/src/adapters/notifications/MockNotificationAdapter.ts @@ -8,7 +8,6 @@ import { Donation } from '../../entities/donation'; import { Project } from '../../entities/project'; import { User } from '../../entities/user'; import { logger } from '../../utils/logger'; -import { RecurringDonation } from '../../entities/recurringDonation'; export class MockNotificationAdapter implements NotificationAdapterInterface { async subscribeOnboarding(params: { email: string }): Promise { @@ -40,17 +39,9 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { } donationReceived(params: { - donation: Donation | RecurringDonation; + donation: Donation; project: Project; }): Promise { - if (params.donation instanceof RecurringDonation) { - logger.debug('MockNotificationAdapter donationReceived', { - projectSlug: params.project.slug, - donationTxHash: params.donation.txHash, - donationNetworkId: params.donation.networkId, - }); - return Promise.resolve(undefined); - } logger.debug('MockNotificationAdapter donationReceived', { projectSlug: params.project.slug, donationTxHash: params.donation.transactionId, diff --git a/src/adapters/notifications/NotificationAdapterInterface.ts b/src/adapters/notifications/NotificationAdapterInterface.ts index 959a616ef..1e02155e9 100644 --- a/src/adapters/notifications/NotificationAdapterInterface.ts +++ b/src/adapters/notifications/NotificationAdapterInterface.ts @@ -1,7 +1,6 @@ import { Donation } from '../../entities/donation'; import { Project } from '../../entities/project'; import { UserStreamBalanceWarning, User } from '../../entities/user'; -import { RecurringDonation } from '../../entities/recurringDonation'; export interface BroadCastNotificationInputParams { broadCastNotificationId: number; @@ -41,7 +40,7 @@ export interface NotificationAdapterInterface { updateOrttoPeople(params: OrttoPerson[]): Promise; donationReceived(params: { - donation: Donation | RecurringDonation; + donation: Donation; project: Project; user: User | null; }): Promise; diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index 5968d2935..039f25525 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -22,10 +22,6 @@ import { } from '../../repositories/userRepository'; import { buildProjectLink } from './NotificationCenterUtils'; import { buildTxLink } from '../../utils/networks'; -import { RecurringDonation } from '../../entities/recurringDonation'; -import { getTokenPrice } from '../../services/priceService'; -import { Token } from '../../entities/token'; -import { toFixNumber } from '../../services/donationService'; import { findOrganizationById } from '../../repositories/organizationRepository'; const notificationCenterUsername = process.env.NOTIFICATION_CENTER_USERNAME; @@ -123,7 +119,6 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { metadata: { ...payload, networkName, - recurringDonationTab: `${process.env.WEBSITE_URL}/account?tab=recurring-donations`, }, segment: { payload, @@ -213,44 +208,21 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } async donationReceived(params: { - donation: Donation | RecurringDonation; + donation: Donation; project: Project; user: User | null; }): Promise { const { project, donation, user } = params; - const isRecurringDonation = donation instanceof RecurringDonation; - let transactionId: string, transactionNetworkId: number; - if (isRecurringDonation) { - transactionId = donation.txHash; - transactionNetworkId = donation.networkId; - const token = await Token.findOneBy({ - symbol: donation.currency, - networkId: transactionNetworkId, - }); - const amount = - (Number(donation.flowRate) / 10 ** (token?.decimals || 18)) * 2628000; // convert flowRate in wei from per second to per month - const price = await getTokenPrice(transactionNetworkId, token!); - const donationValueUsd = toFixNumber(amount * price, 4); - logger.debug('donationReceived (recurring) has been called', { - params, - amount, - price, - donationValueUsd, - token, - }); - if (donationValueUsd <= 5) return; - } else { - transactionId = donation.transactionId; - transactionNetworkId = donation.transactionNetworkId; - const donationValueUsd = donation.valueUsd; - logger.debug('donationReceived has been called', { - params, - transactionId, - transactionNetworkId, - donationValueUsd, - }); - if (donationValueUsd <= 1) return; - } + const transactionId = donation.transactionId; + const transactionNetworkId = donation.transactionNetworkId; + const donationValueUsd = donation.valueUsd; + logger.debug('donationReceived has been called', { + params, + transactionId, + transactionNetworkId, + donationValueUsd, + }); + if (donationValueUsd <= 1) return; await sendProjectRelatedNotificationsQueue.add({ project, @@ -268,12 +240,7 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { }), }, trackId: - 'donation-received-' + - transactionNetworkId + - '-' + - transactionId + - '-' + - isRecurringDonation, + 'donation-received-' + transactionNetworkId + '-' + transactionId, }); } @@ -957,35 +924,16 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { const getEmailDataDonationAttributes = async (params: { user: User; project: Project; - donation: Donation | RecurringDonation; + donation: Donation; }) => { const { user, project, donation } = params; - const isRecurringDonation = donation instanceof RecurringDonation; - let amount: number, - transactionId: string, - transactionNetworkId: number, - toWalletAddress: string | undefined, - donationValueUsd: number | undefined, - donationValueEth: number | undefined, - transakStatus: string | undefined; - if (isRecurringDonation) { - transactionId = donation.txHash; - transactionNetworkId = donation.networkId; - const token = await Token.findOneBy({ - symbol: donation.currency, - networkId: transactionNetworkId, - }); - amount = - (Number(donation.flowRate) / 10 ** (token?.decimals || 18)) * 2628000; // convert flowRate in wei from per second to per month - } else { - amount = Number(donation.amount); - transactionId = donation.transactionId; - transactionNetworkId = donation.transactionNetworkId; - toWalletAddress = donation.toWalletAddress.toLowerCase(); - donationValueUsd = donation.valueUsd; - donationValueEth = donation.valueEth; - transakStatus = donation.transakStatus; - } + const amount = Number(donation.amount); + const transactionId = donation.transactionId; + const transactionNetworkId = donation.transactionNetworkId; + const toWalletAddress = donation.toWalletAddress.toLowerCase(); + const donationValueUsd = donation.valueUsd; + const donationValueEth = donation.valueEth; + const transakStatus = donation.transakStatus; return { email: user.email, title: project.title, @@ -994,7 +942,6 @@ const getEmailDataDonationAttributes = async (params: { slug: project.slug, projectLink: `${process.env.WEBSITE_URL}/project/${project.slug}`, amount, - isRecurringDonation, token: donation.currency, transactionId: transactionId.toLowerCase(), transactionNetworkId: Number(transactionNetworkId), diff --git a/src/adapters/superFluid/superFluidAdapter.ts b/src/adapters/superFluid/superFluidAdapter.ts index 39a684a7f..8e973299a 100644 --- a/src/adapters/superFluid/superFluidAdapter.ts +++ b/src/adapters/superFluid/superFluidAdapter.ts @@ -96,53 +96,6 @@ const getFlowsQuery = ` }, */ export class SuperFluidAdapter implements SuperFluidAdapterInterface { - async streamPeriods(params: { - address: string; - chain: number; - start: number; - end: number; - priceGranularity: string; - virtualization: string; - currency: string; - recurringDonationTxHash: string; - }) { - const { - address, - chain, - start, - end, - priceGranularity, - virtualization, - currency, - recurringDonationTxHash, - } = params; - try { - const response = await axios.get( - 'https://accounting.superfluid.dev/v1/stream-periods', - { - params: { - addresses: address, - chains: chain, - start, - end, - priceGranularity, - virtualization, - currency, - }, - }, - ); - // Fetch the stream table with the recurringDonation TxHash - const filteredData = response.data.filter(streamTable => - streamTable.startedAtEvent - .toLowerCase() - .includes(recurringDonationTxHash.toLowerCase()), - ); - return filteredData[0]; - } catch (e) { - logger.error('superFluidAdaptor.streamPeriods error', e); - } - } - /* RESPONSE { "data": { diff --git a/src/adapters/superFluid/superFluidAdapterInterface.ts b/src/adapters/superFluid/superFluidAdapterInterface.ts index 569059bcd..48056827d 100644 --- a/src/adapters/superFluid/superFluidAdapterInterface.ts +++ b/src/adapters/superFluid/superFluidAdapterInterface.ts @@ -10,16 +10,6 @@ export interface FlowUpdatedEvent { } export interface SuperFluidAdapterInterface { - streamPeriods(params: { - address: string; - chain: number; - start: number; - end: number; - priceGranularity: string; - virtualization: string; - currency: string; - recurringDonationTxHash: string; - }): Promise; accountBalance(accountId: string): Promise; getFlowByTxHash(params: { receiver: string; diff --git a/src/adapters/superFluid/superFluidMockAdapter.ts b/src/adapters/superFluid/superFluidMockAdapter.ts index bd99190f8..bf4906147 100644 --- a/src/adapters/superFluid/superFluidMockAdapter.ts +++ b/src/adapters/superFluid/superFluidMockAdapter.ts @@ -4,57 +4,6 @@ import { } from './superFluidAdapterInterface'; export class SuperFluidMockAdapter implements SuperFluidAdapterInterface { - async streamPeriods() { - return { - id: '0x8c3bf3eb2639b2326ff937d041292da2e79adbbf-0xd964ab7e202bab8fbaa28d5ca2b2269a5497cf68-0x1305f6b6df9dc47159d12eb7ac2804d4a33173c2-0.0-0.0', - flowRate: '462962962962962', - startedAtTimestamp: '1617118948', - startedAtBlockNumber: '12658248', - stoppedAtTimestamp: '1626702963', - stoppedAtBlockNumber: '17035432', - totalAmountStreamed: '4437043981481472252430', - chainId: 137, - token: { - id: '0x1305f6b6df9dc47159d12eb7ac2804d4a33173c2', - symbol: 'DAIx', - name: 'Super DAI (PoS)', - underlyingAddress: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - }, - sender: '0x8c3bf3eb2639b2326ff937d041292da2e79adbbf', - receiver: '0xd964ab7e202bab8fbaa28d5ca2b2269a5497cf68', - startedAtEvent: - '0x241d2db890d58d2d9980ad214580c4f3ea22021e2b8dd89387a6257fceebef9d', - stoppedAtEvent: - '0x4d8e9edec495fdcdeece9061267f7eef1a96923378c8940ca5ab09439d08d2fd', - virtualPeriods: [ - { - startTime: 1617249600, - endTime: 1619827199, - amount: '-1193332870370367888200', - amountFiat: '-1193.9934002402638654', - }, - { - startTime: 1619827200, - endTime: 1622505599, - amount: '-1239999537037034457800', - amountFiat: '-1241.1103421307001618', - }, - { - startTime: 1622505600, - endTime: 1625097599, - amount: '-1199999537037034541000', - amountFiat: '-1202.7360631768932664', - }, - { - startTime: 1625097600, - endTime: 1626702963, - amount: '-743223611111109565210', - amountFiat: '-745.57117099624968902', - }, - ], - }; - } - async accountBalance() { return { id: '0x0000000000000000000000000000000000000000', diff --git a/src/entities/donation.ts b/src/entities/donation.ts index 5cf38db86..f43f4a152 100644 --- a/src/entities/donation.ts +++ b/src/entities/donation.ts @@ -12,7 +12,6 @@ import { Project } from './project'; import { User } from './user'; import { QfRound } from './qfRound'; import { ChainType } from '../types/network'; -import { RecurringDonation } from './recurringDonation'; export const DONATION_STATUS = { PENDING: 'pending', @@ -187,15 +186,6 @@ export class Donation extends BaseEntity { @Column({ nullable: true }) userId: number; - @Index() - @Field(_type => RecurringDonation, { nullable: true }) - @ManyToOne(_type => RecurringDonation, { eager: true, nullable: true }) - recurringDonation?: RecurringDonation; - - @RelationId((donation: Donation) => donation.recurringDonation) - @Column({ nullable: true }) - recurringDonationId: number; - @Field(_type => String, { nullable: true }) @Column('text', { nullable: true }) contactEmail?: string | null; @@ -217,14 +207,6 @@ export class Donation extends BaseEntity { @Column({ nullable: true }) donationType?: string; - @Field(_type => String, { nullable: true }) - @Column({ nullable: true }) - onramperTransactionStatus?: string; - - @Field(_type => String, { nullable: true }) - @Column({ nullable: true }) - onramperId?: string; - @Field(_type => String, { nullable: true }) @Column({ nullable: true }) referrerWallet?: string; diff --git a/src/entities/draftRecurringDonation.ts b/src/entities/draftRecurringDonation.ts deleted file mode 100644 index f96140198..000000000 --- a/src/entities/draftRecurringDonation.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Field, ID, ObjectType } from 'type-graphql'; -import { - PrimaryGeneratedColumn, - Column, - Entity, - BaseEntity, - Index, - CreateDateColumn, -} from 'typeorm'; -import { ChainType } from '../types/network'; - -export const DRAFT_RECURRING_DONATION_STATUS = { - PENDING: 'pending', - MATCHED: 'matched', - FAILED: 'failed', -}; - -export const RECURRING_DONATION_ORIGINS = { - DRAFT_RECURRING_DONATION_MATCHING: 'DraftRecurringDonationMatching', -}; - -@Entity() -@ObjectType() -// To mark the draft recurring donation as matched, when the recurringDonation is created in RecurringDonationResolver -export class DraftRecurringDonation extends BaseEntity { - @Field(_type => ID) - @PrimaryGeneratedColumn() - id: number; - - @Field() - @Column({ nullable: false }) - networkId: number; - - @Field() - @Column({ nullable: false }) - flowRate: string; - - @Field(_type => String) - @Column({ - type: 'enum', - enum: ChainType, - default: ChainType.EVM, - }) - chainType: ChainType; - - @Index() - @Field() - @Column({ nullable: false }) - currency: string; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - isBatch: boolean; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - anonymous: boolean; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - // When creating a draft recurring donation, the user can choose to update an existing recurring donation - // This flag is used to determine if the draft recurring donation is for update - isForUpdate: boolean; - - @Field() - @Column({ nullable: true }) - projectId: number; - - @Field() - @Column({ nullable: true }) - @Index({ where: `status = '${DRAFT_RECURRING_DONATION_STATUS.PENDING}'` }) - donorId: number; - - @Field() - @Column({ - type: 'enum', - enum: DRAFT_RECURRING_DONATION_STATUS, - default: DRAFT_RECURRING_DONATION_STATUS.PENDING, - }) - @Index({ where: `status = '${DRAFT_RECURRING_DONATION_STATUS.PENDING}'` }) - status: string; - @Field() - @Column({ nullable: true }) - matchedRecurringDonationId?: number; - - @Field({ nullable: true }) - @Column('text', { nullable: true }) - origin: string; - - @Field({ nullable: true }) - @Column({ nullable: true }) - errorMessage?: string; - - @Index() - @Field(_type => Date) - @CreateDateColumn() - createdAt: Date; -} diff --git a/src/entities/entities.ts b/src/entities/entities.ts index 31cf406ca..153abcd0c 100644 --- a/src/entities/entities.ts +++ b/src/entities/entities.ts @@ -26,13 +26,11 @@ import { ReferredEvent } from './referredEvent'; import { QfRoundHistory } from './qfRoundHistory'; import { ProjectEstimatedMatchingView } from './ProjectEstimatedMatchingView'; import { AnchorContractAddress } from './anchorContractAddress'; -import { RecurringDonation } from './recurringDonation'; import { Sybil } from './sybil'; import { DraftDonation } from './draftDonation'; import { ProjectFraud } from './projectFraud'; import { ProjectActualMatchingView } from './ProjectActualMatchingView'; import { ProjectSocialMedia } from './projectSocialMedia'; -import { DraftRecurringDonation } from './draftRecurringDonation'; import { UserQfRoundModelScore } from './userQfRoundModelScore'; export const getEntities = (): DataSourceOptions['entities'] => { @@ -79,7 +77,5 @@ export const getEntities = (): DataSourceOptions['entities'] => { UserQfRoundModelScore, AnchorContractAddress, - RecurringDonation, - DraftRecurringDonation, ]; }; diff --git a/src/entities/organization.ts b/src/entities/organization.ts index dbd7acfc7..1e47eeebb 100644 --- a/src/entities/organization.ts +++ b/src/entities/organization.ts @@ -26,10 +26,6 @@ export class Organization extends BaseEntity { @Column('boolean', { default: false }) disableNotifications: boolean; - @Field() - @Column('boolean', { default: false }) - disableRecurringDonations: boolean; - @Field() @Column('boolean', { default: false }) disableUpdateEnforcement: boolean; diff --git a/src/entities/recurringDonation.ts b/src/entities/recurringDonation.ts deleted file mode 100644 index af58e3c1e..000000000 --- a/src/entities/recurringDonation.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - BaseEntity, - Column, - CreateDateColumn, - Entity, - Index, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - RelationId, - Unique, - UpdateDateColumn, -} from 'typeorm'; -import { Field, ID, ObjectType } from 'type-graphql'; -import { Project } from './project'; -import { User } from './user'; -import { AnchorContractAddress } from './anchorContractAddress'; -import { Donation } from './donation'; - -export const RECURRING_DONATION_STATUS = { - PENDING: 'pending', - VERIFIED: 'verified', - ENDED: 'ended', - FAILED: 'failed', - ACTIVE: 'active', -}; - -@Entity() -@ObjectType() -@Unique(['txHash', 'networkId', 'project']) -// TODO entity is not completed -export class RecurringDonation extends BaseEntity { - @Field(_type => ID) - @PrimaryGeneratedColumn() - readonly id: number; - - @Field() - @Column({ nullable: false }) - networkId: number; - - @Field() - @Column({ nullable: true, default: 0, type: 'real' }) - amountStreamed?: number; - - @Field() - @Column({ nullable: true, default: 0, type: 'real' }) - totalUsdStreamed?: number; - - // per second - @Field() - @Column({ nullable: false }) - flowRate: string; - - @Index() - @Field() - @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) - project: Project; - - @RelationId( - (recurringDonation: RecurringDonation) => recurringDonation.project, - ) - @Column({ nullable: true }) - projectId: number; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - finished: boolean; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - isArchived: boolean; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - isBatch: boolean; - - @Column({ nullable: true, default: false }) - @Field({ nullable: true }) - anonymous: boolean; - - @Field({ nullable: true }) - @Column('text', { nullable: true }) - origin: string; - - @Index() - @Field(_type => AnchorContractAddress) - @ManyToOne(_type => AnchorContractAddress, { eager: true }) - anchorContractAddress: AnchorContractAddress; - - @RelationId( - (recurringDonation: RecurringDonation) => - recurringDonation.anchorContractAddress, - ) - @Column({ nullable: true }) - anchorContractAddressId: number; - - @Index() - @Field(_type => User, { nullable: true }) - @ManyToOne(_type => User, { eager: true, nullable: true }) - donor: User; - - @RelationId((recurringDonation: RecurringDonation) => recurringDonation.donor) - @Column({ nullable: true }) - donorId: number; - - @Field(_type => [Donation], { nullable: true }) - @OneToMany(_type => Donation, donation => donation.recurringDonation) - donations?: Donation[]; - - @UpdateDateColumn() - @Field() - updatedAt: Date; - - @CreateDateColumn() - @Field() - createdAt: Date; -} diff --git a/src/entities/user.ts b/src/entities/user.ts index 548e12e36..08e45daa0 100644 --- a/src/entities/user.ts +++ b/src/entities/user.ts @@ -17,7 +17,6 @@ import { AccountVerification } from './accountVerification'; import { ProjectStatusHistory } from './projectStatusHistory'; import { ProjectVerificationForm } from './projectVerificationForm'; import { ReferredEvent } from './referredEvent'; -import { RecurringDonation } from './recurringDonation'; import { NOTIFICATIONS_EVENT_NAMES } from '../analytics/analytics'; export const publicSelectionFields = [ @@ -191,35 +190,16 @@ export class User extends BaseEntity { @Field(_type => Int, { nullable: true }) async donationsCount() { - // Count for non-recurring donations - const nonRecurringDonationsCount = await Donation.createQueryBuilder( - 'donation', - ) + return await Donation.createQueryBuilder('donation') .where(`donation."userId" = :userId`, { userId: this.id }) .andWhere(`donation.status = :status`, { status: DONATION_STATUS.VERIFIED, }) - .andWhere(`donation."recurringDonationId" IS NULL`) .cache( `user-donationsCount-normal-${this.id}`, Number(process.env.USER_STATS_CACHE_TIME || 60000), ) .getCount(); - - // Count for recurring donations - const recurringDonationsCount = await RecurringDonation.createQueryBuilder( - 'recurring_donation', - ) - .where(`recurring_donation."donorId" = :donorId`, { donorId: this.id }) - .andWhere('recurring_donation.totalUsdStreamed > 0') - .cache( - `user-donationsCount-recurring-${this.id}`, - Number(process.env.USER_STATS_CACHE_TIME || 60000), - ) - .getCount(); - - // Sum of both counts - return nonRecurringDonationsCount + recurringDonationsCount; } @Field(_type => Int, { nullable: true }) @@ -232,7 +212,7 @@ export class User extends BaseEntity { { reviewStatus: ReviewStatus.Listed }, ) .cache( - `user-likedProjectsCount-recurring-${this.id}`, + `user-likedProjectsCount-${this.id}`, Number(process.env.USER_STATS_CACHE_TIME || 60000), ) .getCount(); diff --git a/src/ormconfig.ts b/src/ormconfig.ts index bfb4c35d1..242d471d1 100644 --- a/src/ormconfig.ts +++ b/src/ormconfig.ts @@ -30,6 +30,7 @@ const ormConfig: DataSourceOptions = { database: process.env.TYPEORM_DATABASE_NAME, entities: getEntities(), migrations: ['migration/*.ts'], + synchronize: process.env.NODE_ENV !== 'production', // Enable sync for test environments // cli: { // migrationsDir: 'migration', // }, diff --git a/src/repositories/draftRecurringDonationRepository.ts b/src/repositories/draftRecurringDonationRepository.ts deleted file mode 100644 index 41f009b85..000000000 --- a/src/repositories/draftRecurringDonationRepository.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { logger } from '../utils/logger'; -import { - DRAFT_RECURRING_DONATION_STATUS, - DraftRecurringDonation, -} from '../entities/draftRecurringDonation'; - -// mark donation status matched based on fromWalletAddress, toWalletAddress, networkId, tokenAddress and amount -export async function markDraftRecurringDonationStatusMatched(params: { - matchedRecurringDonationId: number; - flowRate: string; - projectId: number; - networkId: number; - currency: string; -}): Promise { - try { - const { - networkId, - currency, - matchedRecurringDonationId, - projectId, - flowRate, - } = params; - await DraftRecurringDonation.update( - { - projectId, - flowRate, - networkId, - currency, - status: DRAFT_RECURRING_DONATION_STATUS.PENDING, - }, - { - status: DRAFT_RECURRING_DONATION_STATUS.MATCHED, - matchedRecurringDonationId, - }, - ); - } catch (e) { - logger.error( - `Error in markDraftRecurringDonationStatusMatched - params: ${params} - error: ${e.message}`, - ); - } -} - -export async function deleteExpiredDraftRecurringDonations(hours: number) { - try { - const expiredTime = new Date(Date.now() - hours * 60 * 60 * 1000); - - // donation is expired if it'screated before expiredTime - const result = await DraftRecurringDonation.createQueryBuilder() - .delete() - .where('createdAt < :expiredTime', { expiredTime }) - .execute(); - - logger.debug(`Expired draft donations removed: ${result.affected}`); - } catch (e) { - logger.error(`Error in removing expired draft donations, ${e.message}`); - } -} diff --git a/src/repositories/recurringDonationRepository.test.ts b/src/repositories/recurringDonationRepository.test.ts deleted file mode 100644 index 60c29f122..000000000 --- a/src/repositories/recurringDonationRepository.test.ts +++ /dev/null @@ -1,498 +0,0 @@ -import moment from 'moment'; -import { assert } from 'chai'; -import { - createDonationData, - createProjectData, - generateRandomEtheriumAddress, - generateRandomEvmTxHash, - saveDonationDirectlyToDb, - saveProjectDirectlyToDb, - saveUserDirectlyToDb, -} from '../../test/testUtils'; -import { NETWORK_IDS } from '../provider'; -import { addNewAnchorAddress } from './anchorContractAddressRepository'; -import { - nonZeroRecurringDonationsByProjectId, - createNewRecurringDonation, - findRecurringDonationById, - findRecurringDonationByProjectIdAndUserIdAndCurrency, - updateRecurringDonationFromTheStreamDonations, -} from './recurringDonationRepository'; -import { getPendingRecurringDonationsIds } from './recurringDonationRepository'; -import { DONATION_STATUS } from '../entities/donation'; -import { RECURRING_DONATION_STATUS } from '../entities/recurringDonation'; - -describe( - 'createNewRecurringDonationTestCases', - createNewRecurringDonationTestCases, -); - -describe( - 'findRecurringDonationByProjectIdAndUserIdTestCases', - findRecurringDonationByProjectIdAndUserIdTestCases, -); -describe( - 'countOfActiveRecurringDonationsByProjectIdTestCases', - countOfActiveRecurringDonationsByProjectIdTestCases, -); - -describe( - 'getPendingRecurringDonationsIds() test cases', - getPendingRecurringDonationsIdsTestCases, -); -describe( - 'updateRecurringDonationFromTheStreamDonations() test cases', - updateRecurringDonationFromTheStreamDonationsTestCases, -); - -function getPendingRecurringDonationsIdsTestCases() { - it('should return pending donations in last 48 hours', async () => { - const pendingRecurringDonations = await getPendingRecurringDonationsIds(); - - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - // recurringDonation.status = RECURRING_DONATION_STATUS.PENDING - // await recurringDonation.save() - - const oldDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - oldDonation.createdAt = moment() - .subtract({ - hours: - Number(process.env.RECURRING_DONATION_VERIFICAITON_EXPIRATION_HOURS) + - 2, - }) - .toDate(); - await oldDonation.save(); - - const oldDonation2 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - oldDonation2.createdAt = moment() - .subtract({ - hours: - Number(process.env.RECURRING_DONATION_VERIFICAITON_EXPIRATION_HOURS) + - 2, - }) - .toDate(); - await oldDonation2.save(); - - const newPendingDonations = await getPendingRecurringDonationsIds(); - - assert.equal( - newPendingDonations.length, - pendingRecurringDonations.length + 1, - ); - assert.isOk( - newPendingDonations.find( - donation => donation.id === recurringDonation.id, - ), - ); - assert.notOk( - newPendingDonations.find(donation => donation.id === oldDonation.id), - ); - assert.notOk( - newPendingDonations.find(donation => donation.id === oldDonation2.id), - ); - }); -} - -function createNewRecurringDonationTestCases() { - it('should create recurring donation successfully', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency: 'USD', - project, - anonymous: false, - isBatch: false, - }); - - assert.isNotNull(recurringDonation); - assert.equal( - recurringDonation.anchorContractAddress.id, - anchorContractAddress.id, - ); - assert.equal(recurringDonation.donor.id, creator.id); - }); -} - -function findRecurringDonationByProjectIdAndUserIdTestCases() { - it('should find recurring donation successfully', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - const foundRecurringDonation = - await findRecurringDonationByProjectIdAndUserIdAndCurrency({ - projectId: project.id, - userId: creator.id, - currency, - }); - assert.equal(foundRecurringDonation?.id, recurringDonation.id); - }); -} - -function countOfActiveRecurringDonationsByProjectIdTestCases() { - it('should return count correctly', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - totalUsdStreamed: 1, - currency, - project, - anonymous: false, - isBatch: false, - }); - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - const count = await nonZeroRecurringDonationsByProjectId(project.id); - assert.equal(count, 1); - }); - it('should return count correctly, when there is more than 1 active recurring donation', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - - const recurringDonation2 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation2.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation2.save(); - - const count = await nonZeroRecurringDonationsByProjectId(project.id); - assert.equal(count, 2); - }); - it('should return count correctly, when there is active and non active donations', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const creator = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - - const recurringDonation0 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 0, - }); - recurringDonation0.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation0.save(); - - const recurringDonation1 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation1.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation1.save(); - - const recurringDonation2 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - recurringDonation2.status = RECURRING_DONATION_STATUS.PENDING; - await recurringDonation2.save(); - - const recurringDonation3 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 2, - }); - recurringDonation3.status = RECURRING_DONATION_STATUS.ENDED; - await recurringDonation3.save(); - - const recurringDonation4 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: creator, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - recurringDonation4.status = RECURRING_DONATION_STATUS.FAILED; - await recurringDonation4.save(); - - const count = await nonZeroRecurringDonationsByProjectId(project.id); - assert.equal(count, 2); - }); -} - -function updateRecurringDonationFromTheStreamDonationsTestCases() { - it('should fill amountStreamed, totalUsdStreamed correctly', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: donor, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USDT'; - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: donor, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - }); - const d1 = await saveDonationDirectlyToDb( - { - ...createDonationData(), - status: DONATION_STATUS.VERIFIED, - amount: 13, - valueUsd: 15, - }, - donor.id, - project.id, - ); - d1.recurringDonation = recurringDonation; - await d1.save(); - - const d2 = await saveDonationDirectlyToDb( - { - ...createDonationData(), - status: DONATION_STATUS.VERIFIED, - amount: 12, - valueUsd: 14, - }, - donor.id, - project.id, - ); - d2.recurringDonation = recurringDonation; - await d2.save(); - - await updateRecurringDonationFromTheStreamDonations(recurringDonation.id); - const updatedRecurringDonation = await findRecurringDonationById( - recurringDonation.id, - ); - - assert.equal( - updatedRecurringDonation?.totalUsdStreamed, - d1.valueUsd + d2.valueUsd, - ); - assert.equal( - updatedRecurringDonation?.amountStreamed, - d1.amount + d2.amount, - ); - }); -} diff --git a/src/repositories/recurringDonationRepository.ts b/src/repositories/recurringDonationRepository.ts deleted file mode 100644 index 7dec0dea1..000000000 --- a/src/repositories/recurringDonationRepository.ts +++ /dev/null @@ -1,182 +0,0 @@ -import moment from 'moment'; -import { MoreThan } from 'typeorm'; -import { Project } from '../entities/project'; -import { User } from '../entities/user'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../entities/recurringDonation'; -import { AnchorContractAddress } from '../entities/anchorContractAddress'; -import { logger } from '../utils/logger'; - -export const createNewRecurringDonation = async (params: { - project: Project; - donor: User; - anchorContractAddress: AnchorContractAddress; - networkId: number; - txHash: string; - flowRate: string; - currency: string; - anonymous: boolean; - isBatch: boolean; - totalUsdStreamed?: number; -}): Promise => { - const recurringDonation = RecurringDonation.create({ - project: params.project, - donor: params.donor, - anchorContractAddress: params.anchorContractAddress, - networkId: params.networkId, - txHash: params.txHash, - currency: params.currency, - flowRate: params.flowRate, - anonymous: params.anonymous, - isBatch: params.isBatch, - totalUsdStreamed: params.totalUsdStreamed, - }); - return recurringDonation.save(); -}; -export const updateRecurringDonation = async (params: { - recurringDonation: RecurringDonation; - txHash?: string; - flowRate?: string; - anonymous?: boolean; - isArchived?: boolean; - status?: string; -}): Promise => { - const { recurringDonation, txHash, anonymous, flowRate, status, isArchived } = - params; - if (txHash && flowRate) { - recurringDonation.txHash = txHash; - recurringDonation.flowRate = flowRate; - recurringDonation.finished = false; - recurringDonation.isArchived = false; - recurringDonation.status = RECURRING_DONATION_STATUS.PENDING; - } - - if (anonymous) { - recurringDonation.anonymous = anonymous; - } - - if ( - recurringDonation.status === RECURRING_DONATION_STATUS.ACTIVE && - status === RECURRING_DONATION_STATUS.ENDED - ) { - recurringDonation.status = status; - recurringDonation.finished = true; - } - - if ( - recurringDonation.status === RECURRING_DONATION_STATUS.ENDED && - isArchived - ) { - recurringDonation.isArchived = true; - } else if (isArchived === false) { - // isArchived can be undefined, so we need to check if it's false - recurringDonation.isArchived = false; - } - - return recurringDonation.save(); -}; - -// TODO Need to write test cases for this function -export const findActiveRecurringDonations = async (): Promise< - RecurringDonation[] -> => { - // Return not finished recurring donations - return RecurringDonation.find({ - where: { - finished: false, - }, - }); -}; - -export const updateRecurringDonationFromTheStreamDonations = async ( - recurringDonationId: number, -) => { - try { - await RecurringDonation.query( - ` - UPDATE "recurring_donation" - SET "totalUsdStreamed" = ( - SELECT COALESCE(SUM(d."valueUsd"), 0) - FROM donation as d - WHERE d."recurringDonationId" = $1 - ), - "amountStreamed" = ( - SELECT COALESCE(SUM(d."amount"), 0) - FROM donation as d - WHERE d."recurringDonationId" = $1 - ) - WHERE "id" = $1 - `, - [recurringDonationId], - ); - } catch (e) { - logger.error('updateRecurringDonationFromTheStreamDonations() error', e); - } -}; - -export const findRecurringDonationById = async ( - id: number, -): Promise => { - return await RecurringDonation.createQueryBuilder('recurringDonation') - .innerJoinAndSelect( - `recurringDonation.anchorContractAddress`, - 'anchorContractAddress', - ) - .leftJoinAndSelect(`recurringDonation.donations`, 'donations') - .leftJoinAndSelect('recurringDonation.project', 'project') - .leftJoinAndSelect(`recurringDonation.donor`, 'donor') - .where(`recurringDonation.id = :id`, { id }) - .getOne(); -}; - -export const nonZeroRecurringDonationsByProjectId = async ( - projectId: number, -): Promise => { - return await RecurringDonation.createQueryBuilder('recurringDonation') - .where(`recurringDonation.projectId = :projectId`, { projectId }) - .andWhere('recurringDonation.totalUsdStreamed > 0') - .getCount(); -}; - -export const findRecurringDonationByProjectIdAndUserIdAndCurrency = - async (params: { - projectId: number; - userId: number; - currency: string; - }): Promise => { - return RecurringDonation.createQueryBuilder('recurringDonation') - .where(`recurringDonation.projectId = :projectId`, { - projectId: params.projectId, - }) - .andWhere(`recurringDonation.donorId = :userId`, { - userId: params.userId, - }) - .andWhere(`recurringDonation.currency = :currency`, { - currency: params.currency, - }) - .leftJoinAndSelect('recurringDonation.project', 'project') - .leftJoinAndSelect('recurringDonation.donor', 'donor') - .getOne(); - }; - -export const getPendingRecurringDonationsIds = (): Promise< - { id: number }[] -> => { - const date = moment() - .subtract({ - hours: - Number(process.env.RECURRING_DONATION_VERIFICAITON_EXPIRATION_HOURS) || - 72, - }) - .toDate(); - logger.debug('getPendingRecurringDonationsIds -> expirationDate', date); - return RecurringDonation.find({ - where: { - status: RECURRING_DONATION_STATUS.PENDING, - createdAt: MoreThan(date), - }, - select: ['id'], - }); -}; diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 23304b9c3..a80e4c2b7 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -1,5 +1,6 @@ import { assert } from 'chai'; import axios from 'axios'; +import { In, Not } from 'typeorm'; import { generateTestAccessToken, graphqlUrl, @@ -52,9 +53,6 @@ import { DRAFT_DONATION_STATUS, DraftDonation, } from '../entities/draftDonation'; -import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; -import { createNewRecurringDonation } from '../repositories/recurringDonationRepository'; -import { RECURRING_DONATION_STATUS } from '../entities/recurringDonation'; // eslint-disable-next-line @typescript-eslint/no-var-requires const moment = require('moment'); @@ -649,74 +647,6 @@ function donationsTestCases() { allDonationsCount, ); }); - it.skip('should get result with recurring donations joined (for streamed mini donations)', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: user, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - const donation = await saveDonationDirectlyToDb( - { - ...createDonationData(), - }, - user.id, - project.id, - ); - donation.recurringDonation = recurringDonation; - await donation.save(); - - // Use moment to parse the createdAt string - const momentDate = moment(donation.createdAt, 'YYYYMMDD HH:mm:ss'); - - // Create fromDate as one second before - const fromDate = momentDate - .clone() - .subtract(1, 'seconds') - .format('YYYYMMDD HH:mm:ss'); - - // Create toDate as one second after - const toDate = momentDate - .clone() - .add(1, 'seconds') - .format('YYYYMMDD HH:mm:ss'); - const donationsResponse = await axios.post(graphqlUrl, { - query: fetchAllDonationsQuery, - variables: { - fromDate, - toDate, - }, - }); - assert.isOk(donationsResponse.data.data.donations); - assert.equal(donationsResponse.data.data.donations.length, 1); - assert.equal( - Number(donationsResponse.data.data.donations[0].recurringDonation.id), - recurringDonation.id, - ); - }); it('should get result when sending fromDate', async () => { const oldDonation = await saveDonationDirectlyToDb( createDonationData(), @@ -2836,6 +2766,11 @@ function donationsFromWalletsTestCases() { } function donationsByProjectIdTestCases() { + beforeEach(async () => { + await Donation.delete({ + id: Not(In(Object.values(DONATION_SEED_DATA).map(d => d.id))), + }); + }); it('should return filtered by qfRound donations when specified', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); const qfRound = await QfRound.create({ @@ -3386,75 +3321,6 @@ function donationsByProjectIdTestCases() { donations.find(donation => Number(donation.id) === pendingDonation.id), ); }); - it('should return recurringDonationsCount and totalCount correctly', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await saveDonationDirectlyToDb( - { ...createDonationData(), status: DONATION_STATUS.VERIFIED }, - user.id, - project.id, - ); - - const anchorAddress = generateRandomEtheriumAddress(); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: anchorAddress, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const currency = 'USD'; - - const recurringDonation = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: user, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - - const recurringDonation2 = await createNewRecurringDonation({ - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.OPTIMISTIC, - donor: user, - anchorContractAddress, - flowRate: '100', - currency, - project, - anonymous: false, - isBatch: false, - totalUsdStreamed: 1, - }); - recurringDonation2.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation2.save(); - - const result = await axios.post( - graphqlUrl, - { - query: fetchDonationsByProjectIdQuery, - variables: { - projectId: project.id, - }, - }, - {}, - ); - - assert.equal(result.data.data.donationsByProjectId.totalCount, 1); - assert.equal( - result.data.data.donationsByProjectId.recurringDonationsCount, - 2, - ); - }); } function donationsByUserIdTestCases() { diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 5ccc9c12b..6ce5bd9e8 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -70,7 +70,6 @@ import { DRAFT_DONATION_STATUS, DraftDonation, } from '../entities/draftDonation'; -import { nonZeroRecurringDonationsByProjectId } from '../repositories/recurringDonationRepository'; const draftDonationEnabled = process.env.ENABLE_DRAFT_DONATION === 'true'; @@ -82,9 +81,6 @@ class PaginateDonations { @Field(_type => Number, { nullable: true }) totalCount: number; - @Field(_type => Number, { nullable: true }) - recurringDonationsCount: number; - @Field(_type => Number, { nullable: true }) totalUsdBalance: number; @@ -268,7 +264,6 @@ export class DonationResolver { .leftJoin('donation.user', 'user') .addSelect(publicSelectionFields) .leftJoinAndSelect('donation.project', 'project') - .leftJoinAndSelect('donation.recurringDonation', 'recurringDonation') .leftJoinAndSelect('project.categories', 'categories'); if (fromDate) { @@ -563,9 +558,6 @@ export class DonationResolver { .leftJoin('donation.user', 'user') .leftJoinAndSelect('donation.qfRound', 'qfRound') .addSelect(publicSelectionFields) - .where( - `donation.projectId = ${projectId} AND donation.recurringDonationId IS NULL`, - ) .orderBy( `donation.${orderBy.field}`, orderBy.direction, @@ -611,9 +603,6 @@ export class DonationResolver { ); } - const recurringDonationsCount = - await nonZeroRecurringDonationsByProjectId(projectId); - const [donations, donationsCount] = await query .take(take) .skip(skip) @@ -622,7 +611,6 @@ export class DonationResolver { donations, totalCount: donationsCount, totalUsdBalance: project.totalDonations, - recurringDonationsCount, }; } @@ -636,7 +624,6 @@ export class DonationResolver { return this.donationRepository .createQueryBuilder('donation') .where({ userId: ctx.req.user.userId }) - .andWhere(`donation.recurringDonationId IS NULL`) .leftJoin('donation.user', 'user') .addSelect(publicSelectionFields) .leftJoinAndSelect('donation.project', 'project') @@ -655,7 +642,6 @@ export class DonationResolver { .leftJoinAndSelect('donation.user', 'user') .leftJoinAndSelect('donation.qfRound', 'qfRound') .where(`donation.userId = ${userId}`) - .andWhere(`donation.recurringDonationId IS NULL`) .orderBy( `donation.${orderBy.field}`, orderBy.direction, diff --git a/src/resolvers/draftDonationResolver.test.ts b/src/resolvers/draftDonationResolver.test.ts index 81abe98b1..21f986ec7 100644 --- a/src/resolvers/draftDonationResolver.test.ts +++ b/src/resolvers/draftDonationResolver.test.ts @@ -7,12 +7,8 @@ import { createProjectData, generateRandomEvmTxHash, generateRandomEtheriumAddress, - saveRecurringDonationDirectlyToDb, } from '../../test/testUtils'; -import { - createDraftDonationMutation, - createDraftRecurringDonationMutation, -} from '../../test/graphqlQueries'; +import { createDraftDonationMutation } from '../../test/graphqlQueries'; import { NETWORK_IDS } from '../provider'; import { User } from '../entities/user'; import { generateRandomString } from '../utils/utils'; @@ -21,16 +17,8 @@ import { DRAFT_DONATION_STATUS, DraftDonation, } from '../entities/draftDonation'; -import { - DRAFT_RECURRING_DONATION_STATUS, - DraftRecurringDonation, -} from '../entities/draftRecurringDonation'; describe('createDraftDonation() test cases', createDraftDonationTestCases); -describe( - 'createDraftRecurringDonation() test cases', - createDraftRecurringDonationTestCases, -); function createDraftDonationTestCases() { let project; @@ -176,181 +164,3 @@ function createDraftDonationTestCases() { ); }); } - -function createDraftRecurringDonationTestCases() { - let project; - let user; - let accessToken; - let donationData; - - beforeEach(async () => { - project = await saveProjectDirectlyToDb(createProjectData()); - - user = await User.create({ - walletAddress: generateRandomEtheriumAddress(), - loginType: 'wallet', - firstName: 'first name', - }).save(); - - accessToken = await generateTestAccessToken(user.id); - donationData = { - projectId: project.id, - networkId: NETWORK_IDS.XDAI, - flowRate: '100', - currency: 'GIV', - toAddress: project.walletAddress, - }; - }); - it('create simple draft recurring donation', async () => { - const saveDonationResponse = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: donationData, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse.data.data.createDraftRecurringDonation); - const draftRecurringDonation = await DraftRecurringDonation.findOne({ - where: { - id: saveDonationResponse.data.data.createDraftRecurringDonation, - }, - }); - - expect(draftRecurringDonation).deep.contain({ - networkId: donationData.networkId, - chainType: ChainType.EVM, - status: DRAFT_RECURRING_DONATION_STATUS.PENDING, - currency: 'GIV', - anonymous: false, - isBatch: false, - flowRate: donationData.flowRate, - projectId: project.id, - donorId: user.id, - }); - }); - it('create simple draft donation when isForUpdate:true but recurringDonation doesnt exist', async () => { - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - networkId: NETWORK_IDS.XDAI, - currency: 'GIV', - }, - }); - const saveDonationResponse = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: { - ...donationData, - isForUpdate: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse.data.data.createDraftRecurringDonation); - const draftRecurringDonation = await DraftRecurringDonation.findOne({ - where: { - id: saveDonationResponse.data.data.createDraftRecurringDonation, - }, - }); - - expect(draftRecurringDonation).deep.contain({ - networkId: donationData.networkId, - chainType: ChainType.EVM, - status: DRAFT_RECURRING_DONATION_STATUS.PENDING, - currency: 'GIV', - anonymous: false, - isBatch: false, - flowRate: donationData.flowRate, - projectId: project.id, - donorId: user.id, - }); - }); - - it.skip('should return the same draft recurring donation id if the same donation is created twice', async () => { - const saveDonationResponse = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: donationData, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse.data.data.createDraftRecurringDonation); - - const saveDonationResponse2 = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: donationData, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse2.data.data.createDraftRecurringDonation); - expect( - saveDonationResponse2.data.data.createDraftRecurringDonation, - ).to.be.equal(saveDonationResponse.data.data.createDraftRecurringDonation); - }); - - it('should create a new draft recurring donation if the first one is matched', async () => { - const saveDonationResponse = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: donationData, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse.data.data.createDraftRecurringDonation); - - const draftDonation = await DraftRecurringDonation.findOne({ - where: { - id: saveDonationResponse.data.data.createDraftRecurringDonation, - }, - }); - - draftDonation!.status = DRAFT_RECURRING_DONATION_STATUS.MATCHED; - await draftDonation!.save(); - - const saveDonationResponse2 = await axios.post( - graphqlUrl, - { - query: createDraftRecurringDonationMutation, - variables: donationData, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isOk(saveDonationResponse2.data.data.createDraftRecurringDonation); - expect( - saveDonationResponse2.data.data.createDraftRecurringDonation, - ).to.be.not.equal( - saveDonationResponse.data.data.createDraftRecurringDonation, - ); - }); -} diff --git a/src/resolvers/draftDonationResolver.ts b/src/resolvers/draftDonationResolver.ts index 658432cfa..2ee6110f0 100644 --- a/src/resolvers/draftDonationResolver.ts +++ b/src/resolvers/draftDonationResolver.ts @@ -6,7 +6,6 @@ import SentryLogger from '../sentryLogger'; import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; import { createDraftDonationQueryValidator, - createDraftRecurringDonationQueryValidator, validateWithJoiSchema, } from '../utils/validators/graphqlQueryValidators'; import { logger } from '../utils/logger'; @@ -19,16 +18,8 @@ import { DRAFT_DONATION_STATUS, DraftDonation, } from '../entities/draftDonation'; -import { DraftRecurringDonation } from '../entities/draftRecurringDonation'; -import { - findRecurringDonationById, - findRecurringDonationByProjectIdAndUserIdAndCurrency, -} from '../repositories/recurringDonationRepository'; -import { RecurringDonation } from '../entities/recurringDonation'; const draftDonationEnabled = process.env.ENABLE_DRAFT_DONATION === 'true'; -const draftRecurringDonationEnabled = - process.env.ENABLE_DRAFT_RECURRING_DONATION === 'true'; // eslint-disable-next-line unused-imports/no-unused-imports @Resolver(_of => User) @@ -171,146 +162,4 @@ export class DraftDonationResolver { throw e; } } - - @Mutation(_returns => Number) - async createDraftRecurringDonation( - @Arg('networkId') networkId: number, - @Arg('flowRate') flowRate: string, - @Arg('currency') currency: string, - @Arg('isBatch', { nullable: true, defaultValue: false }) isBatch: boolean, - @Arg('anonymous', { nullable: true, defaultValue: false }) - anonymous: boolean, - @Arg('projectId') projectId: number, - @Ctx() ctx: ApolloContext, - @Arg('recurringDonationId', { nullable: true }) - recurringDonationId?: number, - @Arg('isForUpdate', { nullable: true, defaultValue: false }) - isForUpdate?: boolean, - ): Promise { - const logData = { - flowRate, - networkId, - currency, - anonymous, - isBatch, - isForUpdate, - recurringDonationId, - projectId, - userId: ctx?.req?.user?.userId, - }; - logger.debug( - 'createDraftRecurringDonation() resolver has been called with this data', - logData, - ); - if (!draftRecurringDonationEnabled) { - throw new Error( - i18n.__(translationErrorMessagesKeys.DRAFT_RECURRING_DONATION_DISABLED), - ); - } - try { - const userId = ctx?.req?.user?.userId; - const donorUser = await findUserById(userId); - if (!donorUser) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const chainType = detectAddressChainType(donorUser.walletAddress!); - const _networkId = getAppropriateNetworkId({ - networkId, - chainType, - }); - - const validaDataInput = { - flowRate, - networkId: _networkId, - anonymous, - currency, - isBatch, - projectId, - chainType, - isForUpdate, - recurringDonationId, - }; - try { - validateWithJoiSchema( - validaDataInput, - createDraftRecurringDonationQueryValidator, - ); - } catch (e) { - logger.error('Error on validating createDraftRecurringDonation input', { - validaDataInput, - error: e, - }); - throw e; // Rethrow the original error - } - let recurringDonation: RecurringDonation | null; - if (recurringDonationId && isForUpdate) { - recurringDonation = - await findRecurringDonationById(recurringDonationId); - if (!recurringDonation || recurringDonation.donorId !== donorUser.id) { - throw new Error( - i18n.__(translationErrorMessagesKeys.RECURRING_DONATION_NOT_FOUND), - ); - } - } else if (isForUpdate) { - recurringDonation = - await findRecurringDonationByProjectIdAndUserIdAndCurrency({ - projectId, - userId: donorUser.id, - currency, - }); - if (!recurringDonation || recurringDonation.donorId !== donorUser.id) { - throw new Error( - i18n.__(translationErrorMessagesKeys.RECURRING_DONATION_NOT_FOUND), - ); - } - } - if (chainType !== ChainType.EVM) { - throw new Error(i18n.__(translationErrorMessagesKeys.EVM_SUPPORT_ONLY)); - } - - const draftRecurringDonationId = - await DraftRecurringDonation.createQueryBuilder( - 'draftRecurringDonation', - ) - .insert() - .values({ - networkId: _networkId, - currency, - flowRate, - donorId: donorUser.id, - isBatch, - projectId, - isForUpdate, - anonymous: Boolean(anonymous), - chainType: chainType as ChainType, - matchedRecurringDonationId: recurringDonationId, - }) - .orIgnore() - .returning('id') - .execute(); - - if (draftRecurringDonationId.raw.length === 0) { - // TODO unreached code, because we dont have any unique index on this table, so we need to think about it - const existingDraftDonation = await DraftRecurringDonation.findOne({ - where: { - networkId: _networkId, - currency, - projectId, - donorId: donorUser.id, - flowRate, - }, - select: ['id'], - }); - return existingDraftDonation!.id; - } - return draftRecurringDonationId.raw[0].id; - } catch (e) { - SentryLogger.captureException(e); - logger.error('createDraftRecurringDonation() error', { - error: e, - inputData: logData, - }); - throw e; - } - } } diff --git a/src/resolvers/recurringDonationResolver.test.ts b/src/resolvers/recurringDonationResolver.test.ts deleted file mode 100644 index 48021ebc4..000000000 --- a/src/resolvers/recurringDonationResolver.test.ts +++ /dev/null @@ -1,2939 +0,0 @@ -import moment from 'moment'; -import { assert } from 'chai'; -import axios from 'axios'; -import { NETWORK_IDS } from '../provider'; -import { - createProjectData, - generateRandomEtheriumAddress, - generateRandomEvmTxHash, - generateTestAccessToken, - graphqlUrl, - saveProjectDirectlyToDb, - saveRecurringDonationDirectlyToDb, - saveUserDirectlyToDb, -} from '../../test/testUtils'; -import { - createRecurringDonationQuery, - fetchRecurringDonationsByProjectIdQuery, - fetchRecurringDonationsByUserIdQuery, - updateRecurringDonationQuery, - updateRecurringDonationQueryById, - updateRecurringDonationStatusMutation, -} from '../../test/graphqlQueries'; - -describe( - 'createRecurringDonation test cases', - createRecurringDonationTestCases, -); -import { errorMessages } from '../utils/errorMessages'; -import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; -import { RECURRING_DONATION_STATUS } from '../entities/recurringDonation'; -import { QfRound } from '../entities/qfRound'; -import { generateRandomString } from '../utils/utils'; -import { ORGANIZATION_LABELS } from '../entities/organization'; - -describe( - 'updateRecurringDonation test cases', - updateRecurringDonationTestCases, -); -describe( - 'recurringDonationsByProjectId test cases', - recurringDonationsByProjectIdTestCases, -); - -describe( - 'recurringDonationsByUserId test cases', - recurringDonationsByUserIdTestCases, -); - -describe( - 'updateRecurringDonationStatus test cases', - updateRecurringDonationStatusTestCases, -); - -describe( - 'updateRecurringDonationById test cases', - updateRecurringDonationByIdTestCases, -); - -function createRecurringDonationTestCases() { - it('should create recurringDonation successfully', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNotNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.data.createRecurringDonation.networkId, - NETWORK_IDS.OPTIMISTIC, - ); - assert.equal(result.data.data.createRecurringDonation.anonymous, false); - assert.equal(result.data.data.createRecurringDonation.isBatch, false); - }); - it('should create recurringDonation successfully with anonymous true', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNotNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.data.createRecurringDonation.networkId, - NETWORK_IDS.OPTIMISTIC, - ); - assert.equal(result.data.data.createRecurringDonation.anonymous, true); - }); - it('should create recurringDonation successfully with isBatch true', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - isBatch: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNotNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.data.createRecurringDonation.networkId, - NETWORK_IDS.OPTIMISTIC, - ); - assert.equal(result.data.data.createRecurringDonation.isBatch, true); - }); - - it('should return unAuthorized error when not sending JWT', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const result = await axios.post(graphqlUrl, { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - }, - }); - - assert.isNull(result.data.data.createRecurringDonation); - assert.equal(result.data.errors[0].message, errorMessages.UN_AUTHORIZED); - }); - - it('should return unAuthorized error when project not found', async () => { - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const accessToken = await generateTestAccessToken(contractCreator.id); - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: 99999, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.errors[0].message, - errorMessages.PROJECT_NOT_FOUND, - ); - }); - - it('should return error when project doesnt have anchorAddress on that network', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - { ...createProjectData(), networkId: NETWORK_IDS.MAIN_NET }, - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const accessToken = await generateTestAccessToken(donor.id); - - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.errors[0].message, - errorMessages.THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT, - ); - }); - - it('should return error when project belongs to endaoment and doesnt accepp recurring donation', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - { - ...createProjectData(), - networkId: NETWORK_IDS.MAIN_NET, - organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, - }, - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const accessToken = await generateTestAccessToken(donor.id); - - const result = await axios.post( - graphqlUrl, - { - query: createRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.isNull(result.data.data.createRecurringDonation); - assert.equal( - result.data.errors[0].message, - errorMessages.PROJECT_DOESNT_ACCEPT_RECURRING_DONATION, - ); - }); -} - -function updateRecurringDonationTestCases() { - it('should allow to end recurring donation when its active, and archive when its ended', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.status, RECURRING_DONATION_STATUS.ACTIVE); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - status: RECURRING_DONATION_STATUS.ENDED, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.status, - RECURRING_DONATION_STATUS.ENDED, - ); - - const archivingResult = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - isArchived: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.equal( - archivingResult.data.data.updateRecurringDonationParams.isArchived, - true, - ); - }); - it('should not allow to archive recurring donation when its not ended', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.status, RECURRING_DONATION_STATUS.ACTIVE); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - isArchived: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.isArchived, - false, - ); - }); - it('should not change isArchived when its already true and we dont send it', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ENDED, - isArchived: true, - }, - }); - assert.equal(donation.isArchived, true); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.isArchived, - true, - ); - }); - it('should not change isArchived when its already false and we dont send it', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ENDED, - isArchived: false, - }, - }); - assert.equal(donation.isArchived, false); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.isArchived, - false, - ); - }); - - it('should update recurring donation successfully', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.flowRate, - flowRate, - ); - assert.isTrue(result.data.data.updateRecurringDonationParams.anonymous); - }); - it('should update recurring donation successfully for project that is related to a QF', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - await QfRound.update({}, { isActive: false }); - const qfRound = QfRound.create({ - isActive: true, - name: 'test', - slug: generateRandomString(10), - allocatedFund: 100, - minimumPassportScore: 8, - beginDate: new Date(), - endDate: moment().add(10, 'days').toDate(), - }); - await qfRound.save(); - project.qfRounds = [qfRound]; - await project.save(); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.flowRate, - flowRate, - ); - assert.isTrue(result.data.data.updateRecurringDonationParams.anonymous); - qfRound.isActive = false; - await qfRound.save(); - }); - it('should change status and isFinished when updating flowRate and txHash ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const txHash = generateRandomEvmTxHash(); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.flowRate, - flowRate, - ); - assert.isFalse(result.data.data.updateRecurringDonationParams.finished); - assert.equal( - result.data.data.updateRecurringDonationParams.status, - RECURRING_DONATION_STATUS.PENDING, - ); - assert.equal(result.data.data.updateRecurringDonationParams.txHash, txHash); - assert.equal( - result.data.data.updateRecurringDonationParams.flowRate, - flowRate, - ); - }); - it('should not change txHash when flowRate has not sent ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const txHash = generateRandomEvmTxHash(); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.equal( - result.data.data.updateRecurringDonationParams.status, - RECURRING_DONATION_STATUS.ACTIVE, - ); - assert.notEqual( - result.data.data.updateRecurringDonationParams.txHash, - txHash, - ); - }); - it('should not change flowRate when txHash has not sent ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.notEqual( - result.data.data.updateRecurringDonationParams.flowRate, - flowRate, - ); - assert.equal( - result.data.data.updateRecurringDonationParams.status, - RECURRING_DONATION_STATUS.ACTIVE, - ); - }); - - it('should get error when someone wants to update someone else recurring donation', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - assert.equal(donation.status, RECURRING_DONATION_STATUS.PENDING); - const anotherUser = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const accessToken = await generateTestAccessToken(anotherUser.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - flowRate: '10', - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - anonymous: false, - currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.errors[0].message, - errorMessages.RECURRING_DONATION_NOT_FOUND, - ); - }); - - it('should return unAuthorized error when not sending JWT', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const result = await axios.post(graphqlUrl, { - query: updateRecurringDonationQuery, - variables: { - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - anonymous: true, - currency: 'GIV', - }, - }); - - assert.isNull(result.data.data.updateRecurringDonationParams); - assert.equal(result.data.errors[0].message, errorMessages.UN_AUTHORIZED); - }); - - it('should return unAuthorized error when project not found', async () => { - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const accessToken = await generateTestAccessToken(contractCreator.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQuery, - variables: { - projectId: 99999, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - anonymous: true, - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNull(result.data.data.updateRecurringDonationParams); - assert.equal( - result.data.errors[0].message, - errorMessages.PROJECT_NOT_FOUND, - ); - }); -} - -function updateRecurringDonationByIdTestCases() { - it('should allow to end recurring donation when its active, and archive when its ended', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.status, RECURRING_DONATION_STATUS.ACTIVE); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - status: RECURRING_DONATION_STATUS.ENDED, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.status, - RECURRING_DONATION_STATUS.ENDED, - ); - - const archivingResult = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - isArchived: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.equal( - archivingResult.data.data.updateRecurringDonationParamsById.isArchived, - true, - ); - }); - it('should not allow to archive recurring donation when its not ended', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.status, RECURRING_DONATION_STATUS.ACTIVE); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - isArchived: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.isArchived, - false, - ); - }); - it('should not change isArchived when its already true and we dont send it', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ENDED, - isArchived: true, - }, - }); - assert.equal(donation.isArchived, true); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.isArchived, - true, - ); - }); - it('should not change isArchived when its already false and we dont send it', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const user = await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: user, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - currency: 'ETH', - networkId: NETWORK_IDS.OPTIMISTIC, - donorId: donor.id, - anchorContractAddressId: anchorContractAddress.id, - status: RECURRING_DONATION_STATUS.ENDED, - isArchived: false, - }, - }); - assert.equal(donation.isArchived, false); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - networkId: donation.networkId, - currency: donation.currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.isArchived, - false, - ); - }); - - it('should update recurring donation successfully', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.flowRate, - flowRate, - ); - assert.isTrue(result.data.data.updateRecurringDonationParamsById.anonymous); - }); - it('should change status and isFinished when updating flowRate and txHash ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const txHash = generateRandomEvmTxHash(); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.flowRate, - flowRate, - ); - assert.isFalse(result.data.data.updateRecurringDonationParamsById.finished); - assert.equal( - result.data.data.updateRecurringDonationParamsById.status, - RECURRING_DONATION_STATUS.PENDING, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.txHash, - txHash, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.flowRate, - flowRate, - ); - }); - it('should not change txHash when flowRate has not sent ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const txHash = generateRandomEvmTxHash(); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - assert.equal( - result.data.data.updateRecurringDonationParamsById.status, - RECURRING_DONATION_STATUS.ACTIVE, - ); - assert.notEqual( - result.data.data.updateRecurringDonationParamsById.txHash, - txHash, - ); - }); - it('should not change flowRate when txHash has not sent ', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency, - finished: true, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - assert.equal(donation.flowRate, '300'); - - const accessToken = await generateTestAccessToken(donor.id); - const flowRate = '201'; - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: donation.id, - projectId: project.id, - flowRate, - currency, - networkId: NETWORK_IDS.OPTIMISTIC, - anonymous: true, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.notEqual( - result.data.data.updateRecurringDonationParamsById.flowRate, - flowRate, - ); - assert.equal( - result.data.data.updateRecurringDonationParamsById.status, - RECURRING_DONATION_STATUS.ACTIVE, - ); - }); - - it('should get error when someone wants to update someone else recurring donation', async () => { - const currency = 'GIV'; - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - amount: 1, - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency, - timestamp: 1647069070 * 1000, - }; - const project = await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - }, - }); - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - assert.equal(donation.status, RECURRING_DONATION_STATUS.PENDING); - const anotherUser = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const accessToken = await generateTestAccessToken(anotherUser.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: recurringDonation.id, - projectId: project.id, - flowRate: '10', - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - anonymous: false, - currency, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.errors[0].message, - errorMessages.RECURRING_DONATION_NOT_FOUND, - ); - }); - - it('should return unAuthorized error when not sending JWT', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: donor, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const result = await axios.post(graphqlUrl, { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: 1, - projectId: project.id, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - anonymous: true, - currency: 'GIV', - }, - }); - - assert.isNull(result.data.data.updateRecurringDonationParamsById); - assert.equal(result.data.errors[0].message, errorMessages.UN_AUTHORIZED); - }); - - it('should return unAuthorized error when project not found', async () => { - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const accessToken = await generateTestAccessToken(contractCreator.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationQueryById, - variables: { - recurringDonationId: 1, - projectId: 99999, - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - flowRate: '100', - anonymous: true, - currency: 'GIV', - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.isNull(result.data.data.updateRecurringDonationParamsById); - assert.equal( - result.data.errors[0].message, - errorMessages.PROJECT_NOT_FOUND, - ); - }); -} - -function recurringDonationsByProjectIdTestCases() { - 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 flowRate ASC', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '1000', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '2000', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByProjectIdQuery, - variables: { - projectId: project.id, - orderBy: { - field: 'flowRate', - 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++) { - // assertion flor big int of flowRates - assert.isTrue( - BigInt(donations[i].flowRate) <= BigInt(donations[i + 1].flowRate), - ); - } - }); - it('should sort by the flowRate DESC', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '100', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '2000', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByProjectIdQuery, - variables: { - projectId: project.id, - orderBy: { - field: 'flowRate', - 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( - BigInt(donations[i].flowRate) >= BigInt(donations[i + 1].flowRate), - ); - } - }); - 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 flowRate', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '100', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - flowRate: '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].flowRate, '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'); - }); - it('should filter include archived ones when passing includeArchived:true', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - }, - }); - - const archivedRecurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - isArchived: true, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByProjectIdQuery, - variables: { - projectId: project.id, - includeArchived: true, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByProjectId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 2); - assert.isOk( - donations.find(d => Number(d.id) === archivedRecurringDonation.id), - ); - assert.isOk(donations.find(d => Number(d.id) === recurringDonation.id)); - }); - it('should not include archived ones when passing includeArchived:false', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - }, - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - isArchived: true, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByProjectIdQuery, - variables: { - projectId: project.id, - includeArchived: false, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByProjectId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByProjectId.totalCount, 1); - assert.equal(donations[0].isArchived, false); - }); - it('should return non archived if we dont send includeArchived field', async () => { - const project = await saveProjectDirectlyToDb(createProjectData()); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - isArchived: false, - }, - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - status: 'verified', - isArchived: true, - }, - }); - - 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, 1); - assert.isOk(donations.find(d => Number(d.id) === recurringDonation.id)); - }); -} - -function recurringDonationsByUserIdTestCases() { - it('should sort by the createdAt DESC', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - orderBy: { - field: 'createdAt', - direction: 'DESC', - }, - }, - }, - {}, - ); - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.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 donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - orderBy: { - field: 'createdAt', - direction: 'ASC', - }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.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 flowRate ASC', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - flowRate: '100', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - flowRate: '200', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - orderBy: { - field: 'flowRate', - direction: 'ASC', - }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 2); - for (let i = 0; i < donations.length - 1; i++) { - assert.isTrue( - BigInt(donations[i].flowRate) <= BigInt(donations[i + 1].flowRate), - ); - } - }); - it('should sort by the flowRate DESC', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - flowRate: '100', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - flowRate: '200', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - orderBy: { - field: 'flowRate', - direction: 'DESC', - }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 2); - for (let i = 0; i < donations.length - 1; i++) { - assert.isTrue( - BigInt(donations[i].flowRate) >= BigInt(donations[i + 1].flowRate), - ); - } - }); - it('should filter by two tokens', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const d1 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'DAI', - }, - }); - const d2 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDC', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDT', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - filteredTokens: ['DAI', 'USDC'], - }, - }, - {}, - ); - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 2); - assert.isOk(donations.find(d => Number(d.id) === d1.id)); - assert.isOk(donations.find(d => Number(d.id) === d2.id)); - }); - it('should filter by one token', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const d1 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'DAI', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDT', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDT', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - filteredTokens: ['DAI'], - }, - }, - {}, - ); - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isOk(donations.find(d => Number(d.id) === d1.id)); - }); - it('should join with anchor contracts', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const project = await saveProjectDirectlyToDb(createProjectData()); - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: projectOwner, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - projectId: project.id, - currency: 'DAI', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - }, - }, - {}, - ); - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.equal( - donations[0].project.anchorContracts[0].id, - anchorContractAddress.id, - ); - }); - it('should not filter if filteredTokens is not passing', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const d1 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'DAI', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDC', - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - currency: 'USDT', - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - }, - }, - {}, - ); - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 3); - assert.isOk(donations.find(d => Number(d.id) === d1.id)); - }); - it('should filter by finishStatus filter both active and ended', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const d1 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: true, - }, - }); - const d2 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: false, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - finishStatus: { active: true, ended: true }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 2); - assert.isOk(donations.find(d => Number(d.id) === d1.id)); - assert.isOk(donations.find(d => Number(d.id) === d2.id)); - }); - it('should return just not finished recurring donations when not passing finishStatus', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const d1 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: true, - }, - }); - const d2 = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: false, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isNotOk(donations.find(d => Number(d.id) === d1.id)); - assert.isOk(donations.find(d => Number(d.id) === d2.id)); - }); - it('should filter by finishStatus filter just active', async () => { - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const endedOne = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: true, - }, - }); - const activeOne = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: false, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - finishStatus: { active: true, ended: false }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isOk(donations.find(d => Number(d.id) === activeOne.id)); - assert.isNotOk(donations.find(d => Number(d.id) === endedOne.id)); - }); - it('should filter by finishStatus filter just ended', async () => { - await saveProjectDirectlyToDb(createProjectData()); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const endedOne = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: true, - }, - }); - const activeOne = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - finished: false, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - finishStatus: { active: false, ended: true }, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isNotOk(donations.find(d => Number(d.id) === activeOne.id)); - assert.isOk(donations.find(d => Number(d.id) === endedOne.id)); - }); - it('should filter by status and return active recurring donations', async () => { - await saveProjectDirectlyToDb(createProjectData()); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const activeDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - const pendingDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.PENDING, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isOk(donations.find(d => Number(d.id) === activeDonation.id)); - assert.isNotOk(donations.find(d => Number(d.id) === pendingDonation.id)); - }); - it('should filter include archived ones when passing includeArchived:true', async () => { - await saveProjectDirectlyToDb(createProjectData()); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - isArchived: false, - }, - }); - const archivedRecurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.PENDING, - isArchived: true, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - includeArchived: true, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 2); - assert.isOk( - donations.find(d => Number(d.id) === archivedRecurringDonation.id), - ); - assert.isOk(donations.find(d => Number(d.id) === recurringDonation.id)); - }); - it('should not include archived ones when passing includeArchived:false', async () => { - await saveProjectDirectlyToDb(createProjectData()); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - isArchived: false, - }, - }); - const archivedRecurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.PENDING, - isArchived: true, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - includeArchived: false, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isNotOk( - donations.find(d => Number(d.id) === archivedRecurringDonation.id), - ); - assert.isOk(donations.find(d => Number(d.id) === recurringDonation.id)); - }); - it('should return non archived if we dont send includeArchived field', async () => { - await saveProjectDirectlyToDb(createProjectData()); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.ACTIVE, - isArchived: false, - }, - }); - const archivedRecurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - status: RECURRING_DONATION_STATUS.PENDING, - isArchived: true, - }, - }); - - const result = await axios.post( - graphqlUrl, - { - query: fetchRecurringDonationsByUserIdQuery, - variables: { - userId: donor.id, - }, - }, - {}, - ); - - const donations = - result.data.data.recurringDonationsByUserId.recurringDonations; - assert.equal(result.data.data.recurringDonationsByUserId.totalCount, 1); - assert.isNotOk( - donations.find(d => Number(d.id) === archivedRecurringDonation.id), - ); - assert.isOk(donations.find(d => Number(d.id) === recurringDonation.id)); - }); -} - -function updateRecurringDonationStatusTestCases() { - it('should donation status remain pending after calling without sending status (we assume its not mined so far)', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '1000', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - assert.equal(donation.status, RECURRING_DONATION_STATUS.PENDING); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationStatusMutation, - variables: { - donationId: donation.id, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationStatus.status, - RECURRING_DONATION_STATUS.PENDING, - ); - }); - - it('should update donation status to failed, tx is not mined and donor says it failed', async () => { - const transactionInfo = { - txHash: generateRandomEvmTxHash(), - networkId: NETWORK_IDS.XDAI, - flowRate: '200', - fromAddress: generateRandomEtheriumAddress(), - toAddress: generateRandomEtheriumAddress(), - currency: 'GIV', - timestamp: 1647069070 * 1000, - }; - await saveProjectDirectlyToDb({ - ...createProjectData(), - walletAddress: transactionInfo.toAddress, - }); - await saveUserDirectlyToDb(transactionInfo.fromAddress); - const donor = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); - const donation = await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: donor.id, - }, - }); - assert.equal(donation.status, RECURRING_DONATION_STATUS.PENDING); - - const accessToken = await generateTestAccessToken(donor.id); - const result = await axios.post( - graphqlUrl, - { - query: updateRecurringDonationStatusMutation, - variables: { - donationId: donation.id, - status: RECURRING_DONATION_STATUS.FAILED, - }, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.updateRecurringDonationStatus.status, - RECURRING_DONATION_STATUS.FAILED, - ); - }); -} diff --git a/src/resolvers/recurringDonationResolver.ts b/src/resolvers/recurringDonationResolver.ts deleted file mode 100644 index d7f18749b..000000000 --- a/src/resolvers/recurringDonationResolver.ts +++ /dev/null @@ -1,753 +0,0 @@ -import { - Arg, - Args, - ArgsType, - Ctx, - Field, - InputType, - Int, - Mutation, - ObjectType, - Query, - registerEnumType, - Resolver, -} from 'type-graphql'; - -import { Brackets } from 'typeorm'; -import { Service } from 'typedi'; -import { Max, Min } from 'class-validator'; -import { AnchorContractAddress } from '../entities/anchorContractAddress'; -import { findProjectById } from '../repositories/projectRepository'; -import { - errorMessages, - i18n, - translationErrorMessagesKeys, -} from '../utils/errorMessages'; -import { findActiveAnchorAddress } from '../repositories/anchorContractAddressRepository'; -import { ApolloContext } from '../types/ApolloContext'; -import { findUserById } from '../repositories/userRepository'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../entities/recurringDonation'; -import { - createNewRecurringDonation, - findRecurringDonationById, - findRecurringDonationByProjectIdAndUserIdAndCurrency, - updateRecurringDonation, -} from '../repositories/recurringDonationRepository'; -import { detectAddressChainType } from '../utils/networks'; -import { logger } from '../utils/logger'; -import { - resourcePerDateReportValidator, - updateDonationQueryValidator, - validateWithJoiSchema, -} from '../utils/validators/graphqlQueryValidators'; -import { sleep } from '../utils/utils'; -import SentryLogger from '../sentryLogger'; -import { - updateRecurringDonationStatusWithNetwork, - recurringDonationsCountPerDateRange, - recurringDonationsCountPerDateRangePerMonth, - recurringDonationsStreamedCUsdTotal, - recurringDonationsStreamedCUsdTotalPerMonth, - recurringDonationsTotalPerToken, - recurringDonationsCountPerToken, -} from '../services/recurringDonationService'; -import { markDraftRecurringDonationStatusMatched } from '../repositories/draftRecurringDonationRepository'; -import { ResourcePerDateRange } from './donationResolver'; -@InputType() -class RecurringDonationSortBy { - @Field(_type => RecurringDonationSortField) - field: RecurringDonationSortField; - - @Field(_type => RecurringDonationSortDirection) - direction: RecurringDonationSortDirection; -} - -@InputType() -class FinishStatus { - @Field(_type => Boolean) - active: boolean; - - @Field(_type => Boolean) - ended: boolean; -} - -@ObjectType() -class RDTataPerToken { - @Field(_type => String) - token: string; - - @Field(_type => Number) - total: number; -} - -@ObjectType() -class RDRessourcePerDateRange extends ResourcePerDateRange { - @Field(_type => [RDTataPerToken], { nullable: true }) - totalPerToken: RDTataPerToken[]; -} - -export enum RecurringDonationSortField { - createdAt = 'createdAt', - flowRate = 'flowRate', -} - -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', -}); - -registerEnumType(FinishStatus, { - name: 'FinishStatus', - description: 'Filter active status', -}); - -@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; - - @Field(_type => Boolean, { nullable: true, defaultValue: false }) - includeArchived: boolean; - - @Field(_type => FinishStatus, { - nullable: true, - defaultValue: { - active: true, - ended: false, - }, - }) - finishStatus: FinishStatus; - - @Field(_type => [String], { nullable: true, defaultValue: [] }) - filteredTokens: string[]; -} - -@ObjectType() -class UserRecurringDonations { - @Field(_type => [RecurringDonation]) - recurringDonations: RecurringDonation[]; - - @Field(_type => Int) - totalCount: number; -} - -// eslint-disable-next-line unused-imports/no-unused-imports -@Resolver(_of => AnchorContractAddress) -export class RecurringDonationResolver { - @Mutation(_returns => RecurringDonation, { nullable: true }) - async createRecurringDonation( - @Ctx() ctx: ApolloContext, - @Arg('projectId', () => Int) projectId: number, - @Arg('networkId', () => Int) networkId: number, - @Arg('txHash', () => String) txHash: string, - @Arg('currency', () => String) currency: string, - @Arg('flowRate', () => String) flowRate: string, - @Arg('anonymous', () => Boolean, { defaultValue: false }) - anonymous: boolean, - @Arg('isBatch', () => Boolean, { defaultValue: false }) - isBatch: boolean, - ): Promise { - const userId = ctx?.req?.user?.userId; - const donor = await findUserById(userId); - if (!donor) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const project = await findProjectById(projectId); - if (!project) { - throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); - } - if (project.organization.disableRecurringDonations) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.PROJECT_DOESNT_ACCEPT_RECURRING_DONATION, - ), - ); - } - const currentAnchorProjectAddress = await findActiveAnchorAddress({ - projectId, - networkId, - }); - - if (!currentAnchorProjectAddress) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT, - ), - ); - } - const recurringDonation = await createNewRecurringDonation({ - donor, - project, - anchorContractAddress: currentAnchorProjectAddress, - networkId, - txHash, - flowRate, - currency, - anonymous, - isBatch, - }); - - await markDraftRecurringDonationStatusMatched({ - matchedRecurringDonationId: recurringDonation.id, - networkId, - currency, - projectId, - flowRate, - }); - - return recurringDonation; - } - - @Mutation(_returns => RecurringDonation, { nullable: true }) - async updateRecurringDonationParamsById( - @Ctx() ctx: ApolloContext, - @Arg('recurringDonationId', () => Int) recurringDonationId: number, - @Arg('projectId', () => Int) projectId: number, - @Arg('networkId', () => Int) networkId: number, - @Arg('currency', () => String) currency: string, - @Arg('txHash', () => String, { nullable: true }) txHash?: string, - @Arg('flowRate', () => String, { nullable: true }) flowRate?: string, - @Arg('anonymous', () => Boolean, { nullable: true }) anonymous?: boolean, - @Arg('isArchived', () => Boolean, { nullable: true }) isArchived?: boolean, - @Arg('status', () => String, { nullable: true }) status?: string, - ): Promise { - const userId = ctx?.req?.user?.userId; - const donor = await findUserById(userId); - - if (!donor) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const project = await findProjectById(projectId); - if (!project) { - throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); - } - const recurringDonation = - await findRecurringDonationById(recurringDonationId); - - if (!recurringDonation) { - // TODO set proper error message - throw new Error(errorMessages.RECURRING_DONATION_NOT_FOUND); - } - - if (recurringDonation.donor.id !== donor.id) { - throw new Error(errorMessages.RECURRING_DONATION_NOT_FOUND); - } - - const currentAnchorProjectAddress = await findActiveAnchorAddress({ - projectId, - networkId, - }); - - if (!currentAnchorProjectAddress) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT, - ), - ); - } - - const updatedRecurringDonation = await updateRecurringDonation({ - recurringDonation, - txHash, - flowRate, - anonymous, - status, - isArchived, - }); - - await markDraftRecurringDonationStatusMatched({ - matchedRecurringDonationId: recurringDonation.id, - networkId, - currency, - projectId, - flowRate: updatedRecurringDonation.flowRate, - }); - - return updatedRecurringDonation; - } - - @Mutation(_returns => RecurringDonation, { nullable: true }) - async updateRecurringDonationParams( - @Ctx() ctx: ApolloContext, - @Arg('projectId', () => Int) projectId: number, - @Arg('networkId', () => Int) networkId: number, - @Arg('currency', () => String) currency: string, - @Arg('txHash', () => String, { nullable: true }) txHash?: string, - @Arg('flowRate', () => String, { nullable: true }) flowRate?: string, - @Arg('anonymous', () => Boolean, { nullable: true }) anonymous?: boolean, - @Arg('isArchived', () => Boolean, { nullable: true }) isArchived?: boolean, - @Arg('status', () => String, { nullable: true }) status?: string, - ): Promise { - const userId = ctx?.req?.user?.userId; - const donor = await findUserById(userId); - - if (!donor) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const project = await findProjectById(projectId); - if (!project) { - throw new Error(i18n.__(translationErrorMessagesKeys.PROJECT_NOT_FOUND)); - } - const recurringDonation = - await findRecurringDonationByProjectIdAndUserIdAndCurrency({ - projectId, - userId: donor.id, - currency, - }); - - if (!recurringDonation) { - // TODO set proper error message - throw new Error(errorMessages.RECURRING_DONATION_NOT_FOUND); - } - - if (recurringDonation.donor.id !== donor.id) { - throw new Error(errorMessages.RECURRING_DONATION_NOT_FOUND); - } - - const currentAnchorProjectAddress = await findActiveAnchorAddress({ - projectId, - networkId, - }); - - if (!currentAnchorProjectAddress) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT, - ), - ); - } - - const updatedRecurringDonation = await updateRecurringDonation({ - recurringDonation, - txHash, - flowRate, - anonymous, - status, - isArchived, - }); - - await markDraftRecurringDonationStatusMatched({ - matchedRecurringDonationId: recurringDonation.id, - networkId, - currency, - projectId, - flowRate: updatedRecurringDonation.flowRate, - }); - - return updatedRecurringDonation; - } - - @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('includeArchived', _type => Boolean, { - nullable: true, - defaultValue: false, - }) - includeArchived: boolean, - @Arg('finishStatus', _type => FinishStatus, { - nullable: true, - defaultValue: { - active: true, - ended: false, - }, - }) - finishStatus: FinishStatus, - @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.orderBy( - `recurringDonation.${orderBy.field}`, - orderBy.direction, - RecurringDonationNullDirection[orderBy.direction as string], - ); - const finishStatusArray: boolean[] = []; - if (finishStatus.active) { - finishStatusArray.push(false); - } - if (finishStatus.ended) { - finishStatusArray.push(true); - } - if (finishStatusArray.length > 0) { - query.andWhere(`recurringDonation.finished in (:...finishStatusArray)`, { - finishStatusArray, - }); - } - - if (status) { - query.andWhere(`recurringDonation.status = :status`, { - status, - }); - } - - if (!includeArchived) { - // Return only non-archived recurring donations - query.andWhere(`recurringDonation.isArchived = :isArchived`, { - isArchived: false, - }); - } - - 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) - ) { - qb.orWhere('recurringDonation.flowRate = :searchTerm', { - searchTerm, - }); - } - }), - ); - } - const [recurringDonations, donationsCount] = await query - .take(take) - .skip(skip) - .getManyAndCount(); - return { - recurringDonations, - totalCount: donationsCount, - }; - } - - @Query(_returns => UserRecurringDonations, { nullable: true }) - async recurringDonationsByUserId( - @Args() - { - take, - skip, - orderBy, - userId, - status, - includeArchived, - finishStatus, - filteredTokens, - }: UserRecurringDonationsArgs, - @Ctx() ctx: ApolloContext, - ) { - const loggedInUserId = ctx?.req?.user?.userId; - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .leftJoinAndSelect('recurringDonation.project', 'project') - .leftJoinAndSelect('project.anchorContracts', 'anchor_contract_address') - .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.donorId = ${userId}`) - .orderBy( - `recurringDonation.${orderBy.field}`, - orderBy.direction, - RecurringDonationNullDirection[orderBy.direction as string], - ); - if (!loggedInUserId || loggedInUserId !== userId) { - query.andWhere(`recurringDonation.anonymous = ${false}`); - } - - if (status) { - query.andWhere(`recurringDonation.status = :status`, { - status, - }); - } - - if (!includeArchived) { - // Return only non-archived recurring donations - query.andWhere(`recurringDonation.isArchived = :isArchived`, { - isArchived: false, - }); - } - - const finishStatusArray: boolean[] = []; - if (finishStatus.active) { - finishStatusArray.push(false); - } - if (finishStatus.ended) { - finishStatusArray.push(true); - } - if (finishStatusArray.length > 0) { - query.andWhere(`recurringDonation.finished in (:...finishStatusArray)`, { - finishStatusArray, - }); - } - - if (filteredTokens && filteredTokens.length > 0) { - query.andWhere(`recurringDonation.currency IN (:...filteredTokens)`, { - filteredTokens, - }); - } - - const [recurringDonations, totalCount] = await query - .take(take) - .skip(skip) - .getManyAndCount(); - return { - recurringDonations, - totalCount, - }; - } - - @Mutation(_returns => RecurringDonation) - async updateRecurringDonationStatus( - @Arg('donationId') donationId: number, - @Arg('status', { nullable: true }) status: string, - @Ctx() ctx: ApolloContext, - ): Promise { - // TODO We should write more test cases once we implement updateRecurringDonationStatusWithNetwork - - try { - const userId = ctx?.req?.user?.userId; - if (!userId) { - throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); - } - const recurringDonation = await findRecurringDonationById(donationId); - if (!recurringDonation) { - throw new Error( - i18n.__(translationErrorMessagesKeys.DONATION_NOT_FOUND), - ); - } - if (recurringDonation.donorId !== userId) { - logger.error( - 'updateRecurringDonationStatus error because requester is not owner of recurringDonation', - { - user: ctx?.req?.user, - donationInfo: { - id: donationId, - userId: recurringDonation.donorId, - status: recurringDonation.status, - txHash: recurringDonation.txHash, - }, - }, - ); - throw new Error( - i18n.__( - translationErrorMessagesKeys.YOU_ARE_NOT_OWNER_OF_THIS_DONATION, - ), - ); - } - validateWithJoiSchema( - { - status, - donationId, - }, - updateDonationQueryValidator, - ); - if (recurringDonation.status === RECURRING_DONATION_STATUS.VERIFIED) { - return recurringDonation; - } - - // Sometimes web3 provider doesnt return transactions right after it get mined - // so I put a delay , it might solve our problem - await sleep(10_000); - - const updatedRecurringDonation = - await updateRecurringDonationStatusWithNetwork({ - donationId, - }); - - if ( - updatedRecurringDonation.status === RECURRING_DONATION_STATUS.PENDING && - status === RECURRING_DONATION_STATUS.FAILED - ) { - // We just update status of donation with tx status in blockchain network - // but if user send failed status, and there were nothing in network we change it to failed - updatedRecurringDonation.status = RECURRING_DONATION_STATUS.FAILED; - updatedRecurringDonation.finished = true; - await updatedRecurringDonation.save(); - } - return updatedRecurringDonation; - } catch (e) { - SentryLogger.captureException(e); - logger.error('updateRecurringDonationStatus() error ', e); - throw e; - } - } - - @Query(_returns => RDRessourcePerDateRange, { nullable: true }) - async recurringDonationsCountPerDate( - // fromDate and toDate should be in this format YYYYMMDD HH:mm:ss - @Arg('fromDate', { nullable: true }) fromDate?: string, - @Arg('toDate', { nullable: true }) toDate?: string, - @Arg('networkId', { nullable: true }) networkId?: number, - @Arg('onlyVerified', { nullable: true }) onlyVerified?: boolean, - ): Promise { - try { - validateWithJoiSchema( - { fromDate, toDate }, - resourcePerDateReportValidator, - ); - const total = await recurringDonationsCountPerDateRange( - fromDate, - toDate, - networkId, - onlyVerified, - ); - const totalPerMonthAndYear = - await recurringDonationsCountPerDateRangePerMonth( - fromDate, - toDate, - networkId, - onlyVerified, - ); - const totalPerToken = await recurringDonationsCountPerToken({ - fromDate, - toDate, - networkId, - onlyVerified, - }); - - return { - total, - totalPerMonthAndYear, - totalPerToken, - }; - } catch (e) { - logger.error('recurringDonationsCountPerDate query error', e); - throw e; - } - } - - @Query(_returns => RDRessourcePerDateRange, { nullable: true }) - async recurringDonationsTotalStreamedUsdPerDate( - // fromDate and toDate should be in this format YYYYMMDD HH:mm:ss - @Arg('fromDate', { nullable: true }) fromDate?: string, - @Arg('toDate', { nullable: true }) toDate?: string, - @Arg('networkId', { nullable: true }) networkId?: number, - @Arg('onlyVerified', { nullable: true }) onlyVerified?: boolean, - ): Promise { - try { - validateWithJoiSchema( - { fromDate, toDate }, - resourcePerDateReportValidator, - ); - const total = await recurringDonationsStreamedCUsdTotal( - fromDate, - toDate, - networkId, - onlyVerified, - ); - const totalPerMonthAndYear = - await recurringDonationsStreamedCUsdTotalPerMonth( - fromDate, - toDate, - networkId, - onlyVerified, - ); - const totalPerToken = await recurringDonationsTotalPerToken({ - fromDate, - toDate, - networkId, - onlyVerified, - }); - - return { - total, - totalPerMonthAndYear, - totalPerToken, - }; - } catch (e) { - logger.error('recurringDonationsTotalStreamedUsdPerDate query error', e); - throw e; - } - } -} diff --git a/src/resolvers/resolvers.ts b/src/resolvers/resolvers.ts index 4f35de03a..383b069dc 100644 --- a/src/resolvers/resolvers.ts +++ b/src/resolvers/resolvers.ts @@ -13,7 +13,6 @@ import { ChainvineResolver } from './chainvineResolver'; import { QfRoundResolver } from './qfRoundResolver'; import { QfRoundHistoryResolver } from './qfRoundHistoryResolver'; import { AnchorContractAddressResolver } from './anchorContractAddressResolver'; -import { RecurringDonationResolver } from './recurringDonationResolver'; import { DraftDonationResolver } from './draftDonationResolver'; import { OnboardingFormResolver } from './onboardingFormResolver'; @@ -39,7 +38,6 @@ export const getResolvers = (): Function[] => { QfRoundHistoryResolver, AnchorContractAddressResolver, - RecurringDonationResolver, OnboardingFormResolver, ]; }; diff --git a/src/resolvers/userResolver.test.ts b/src/resolvers/userResolver.test.ts index fff358e1e..10ca74ea6 100644 --- a/src/resolvers/userResolver.test.ts +++ b/src/resolvers/userResolver.test.ts @@ -7,12 +7,10 @@ import { createDonationData, createProjectData, generateRandomEtheriumAddress, - generateRandomEvmTxHash, generateTestAccessToken, graphqlUrl, saveDonationDirectlyToDb, saveProjectDirectlyToDb, - saveRecurringDonationDirectlyToDb, saveUserDirectlyToDb, SEED_DATA, } from '../../test/testUtils'; @@ -25,9 +23,6 @@ import { errorMessages } from '../utils/errorMessages'; import { DONATION_STATUS } from '../entities/donation'; import { getGitcoinAdapter } from '../adapters/adaptersFactory'; import { updateUserTotalDonated } from '../services/userService'; -import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; -import { NETWORK_IDS } from '../provider'; -import { RECURRING_DONATION_STATUS } from '../entities/recurringDonation'; describe('updateUser() test cases', updateUserTestCases); describe('userByAddress() test cases', userByAddressTestCases); @@ -226,229 +221,6 @@ function userByAddressTestCases() { assert.equal(result.data.data.userByAddress.donationsCount, 2); assert.equal(result.data.data.userByAddress.totalDonated, 150); }); - it('Get donationsCount and totalDonated correctly, when there is just recurringDonations, one active', async () => { - const userData = { - firstName: 'firstName', - lastName: 'lastName', - email: 'giveth@gievth.com', - avatar: 'pinata address', - url: 'website url', - loginType: 'wallet', - walletAddress: generateRandomEtheriumAddress(), - }; - const user = await User.create(userData).save(); - const project = await saveProjectDirectlyToDb(createProjectData()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - totalUsdStreamed: 200, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - await updateUserTotalDonated(user.id); - - const accessToken = await generateTestAccessToken(user.id); - const result = await axios.post( - graphqlUrl, - { - query: userByAddress, - variables: { - address: userData.walletAddress, - }, - }, - { - headers: { - authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.userByAddress.walletAddress, - userData.walletAddress, - ); - assert.equal(result.data.data.userByAddress.totalDonated, 200); - assert.equal(result.data.data.userByAddress.donationsCount, 1); - }); - it('Get donationsCount and totalDonated correctly, when there is just recurringDonations, active, pending, failed, ended', async () => { - const userData = { - firstName: 'firstName', - lastName: 'lastName', - email: 'giveth@gievth.com', - avatar: 'pinata address', - url: 'website url', - loginType: 'wallet', - walletAddress: generateRandomEtheriumAddress(), - }; - const user = await User.create(userData).save(); - const project = await saveProjectDirectlyToDb(createProjectData()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - totalUsdStreamed: 200, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - status: RECURRING_DONATION_STATUS.PENDING, - }, - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - totalUsdStreamed: 200, - status: RECURRING_DONATION_STATUS.ENDED, - }, - }); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - status: RECURRING_DONATION_STATUS.FAILED, - }, - }); - - await updateUserTotalDonated(user.id); - - const accessToken = await generateTestAccessToken(user.id); - const result = await axios.post( - graphqlUrl, - { - query: userByAddress, - variables: { - address: userData.walletAddress, - }, - }, - { - headers: { - authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.userByAddress.walletAddress, - userData.walletAddress, - ); - // for totalDonated we consider all recurring donations but for donationsCount we consider only active recurring donations - assert.equal(result.data.data.userByAddress.totalDonated, 400); - assert.equal(result.data.data.userByAddress.donationsCount, 2); - }); - it('Get donationsCount and totalDonated correctly, when there is both recurringDonations and one time donation', async () => { - const userData = { - firstName: 'firstName', - lastName: 'lastName', - email: 'giveth@gievth.com', - avatar: 'pinata address', - url: 'website url', - loginType: 'wallet', - walletAddress: generateRandomEtheriumAddress(), - }; - const user = await User.create(userData).save(); - const project = await saveProjectDirectlyToDb(createProjectData()); - - await addNewAnchorAddress({ - project, - owner: project.adminUser, - creator: user, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - await saveDonationDirectlyToDb( - { - ...createDonationData(), - amount: 50, - valueUsd: 50, - currency: 'USDT', - status: DONATION_STATUS.VERIFIED, - }, - user.id, - project.id, - ); - await updateUserTotalDonated(user.id); - - await saveRecurringDonationDirectlyToDb({ - donationData: { - donorId: user.id, - projectId: project.id, - flowRate: '300', - anonymous: false, - currency: 'USDT', - totalUsdStreamed: 200, - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - await updateUserTotalDonated(user.id); - - const accessToken = await generateTestAccessToken(user.id); - const result = await axios.post( - graphqlUrl, - { - query: userByAddress, - variables: { - address: userData.walletAddress, - }, - }, - { - headers: { - authorization: `Bearer ${accessToken}`, - }, - }, - ); - assert.equal( - result.data.data.userByAddress.walletAddress, - userData.walletAddress, - ); - - assert.equal(result.data.data.userByAddress.totalDonated, 250); - assert.equal(result.data.data.userByAddress.donationsCount, 2); - }); it('Returns null when no user is found', async () => { const result = await axios.post(graphqlUrl, { diff --git a/src/server/adminJs/adminJs.ts b/src/server/adminJs/adminJs.ts index 17e45c5f0..7e2a1b74b 100644 --- a/src/server/adminJs/adminJs.ts +++ b/src/server/adminJs/adminJs.ts @@ -29,7 +29,6 @@ import { qfRoundTab } from './tabs/qfRoundTab'; import { qfRoundHistoryTab } from './tabs/qfRoundHistoryTab'; import { SybilTab } from './tabs/sybilTab'; import { ProjectFraudTab } from './tabs/projectFraudTab'; -import { RecurringDonationTab } from './tabs/recurringDonationTab'; import { AnchorContractAddressTab } from './tabs/anchorContractAddressTab'; // use redis for session data instead of in-memory storage // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -149,7 +148,6 @@ const getResources = async (): Promise => { qfRoundHistoryTab, SybilTab, ProjectFraudTab, - RecurringDonationTab, AnchorContractAddressTab, ]; diff --git a/src/server/adminJs/adminJsPermissions.test.ts b/src/server/adminJs/adminJsPermissions.test.ts index 6481f3076..f5ff2cd14 100644 --- a/src/server/adminJs/adminJsPermissions.test.ts +++ b/src/server/adminJs/adminJsPermissions.test.ts @@ -99,7 +99,6 @@ const actionsPerRole = Object.freeze({ broadcastNotification: ['list', 'show', 'new'], sybil: ['list', 'show', 'new', 'edit', 'delete', 'bulkDelete'], projectFraud: ['list', 'show', 'new', 'edit', 'delete', 'bulkDelete'], - // recurringDonation: ['list', 'show', 'new', 'edit', 'delete', 'bulkDelete'], }, campaignManager: { users: ['list', 'show'], @@ -122,7 +121,6 @@ const actionsPerRole = Object.freeze({ broadcastNotification: ['list', 'show'], sybil: ['list', 'show'], projectFraud: ['list', 'show'], - // recurringDonation: ['list', 'show'], }, reviewer: { users: ['list', 'show'], @@ -167,7 +165,6 @@ const actionsPerRole = Object.freeze({ broadcastNotification: ['list', 'show'], sybil: ['list', 'show'], projectFraud: ['list', 'show'], - // recurringDonation: ['list', 'show'], }, operator: { users: ['list', 'show'], @@ -202,7 +199,6 @@ const actionsPerRole = Object.freeze({ broadcastNotification: ['list', 'show'], sybil: ['list', 'show'], projectFraud: ['list', 'show'], - // recurringDonation: ['list', 'show'], }, qfManager: { qfRound: ['list', 'show', 'edit', 'new', 'returnAllDonationData'], diff --git a/src/server/adminJs/adminJsPermissions.ts b/src/server/adminJs/adminJsPermissions.ts index 1fc29d0f1..e1ed53931 100644 --- a/src/server/adminJs/adminJsPermissions.ts +++ b/src/server/adminJs/adminJsPermissions.ts @@ -564,31 +564,6 @@ const sybilPermissions = { // Add more roles here as needed }; -// will be modified later on -const recurringDonationPermissions = { - [UserRole.ADMIN]: { - list: true, - show: true, - new: true, - edit: true, - delete: true, - bulkDelete: true, - }, - [UserRole.OPERATOR]: { - list: true, - show: true, - }, - [UserRole.VERIFICATION_FORM_REVIEWER]: { - list: true, - show: true, - }, - [UserRole.CAMPAIGN_MANAGER]: { - list: true, - show: true, - }, - // Add more roles here as needed -}; - const hasAccessToResource = (params: { currentAdmin: any; action: string; @@ -812,14 +787,3 @@ export const canAccessSybilAction = ({ currentAdmin }, action: string) => { resourcePermissions: sybilPermissions, }); }; - -export const canAccessRecurringDonationAction = ( - { currentAdmin }, - action: string, -) => { - return hasAccessToResource({ - currentAdmin, - action, - resourcePermissions: recurringDonationPermissions, - }); -}; diff --git a/src/server/adminJs/tabs/donationTab.ts b/src/server/adminJs/tabs/donationTab.ts index b4a3364a9..907c4f8be 100644 --- a/src/server/adminJs/tabs/donationTab.ts +++ b/src/server/adminJs/tabs/donationTab.ts @@ -467,25 +467,6 @@ export const donationTab = { }, }, - onramperTransactionStatus: { - isVisible: { - list: false, - filter: false, - show: true, - edit: false, - new: false, - }, - }, - - onramperId: { - isVisible: { - list: false, - filter: false, - show: true, - edit: false, - new: false, - }, - }, qfRoundUserScore: { isVisible: { list: false, diff --git a/src/server/adminJs/tabs/recurringDonationTab.ts b/src/server/adminJs/tabs/recurringDonationTab.ts deleted file mode 100644 index 8816895c9..000000000 --- a/src/server/adminJs/tabs/recurringDonationTab.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { RecurringDonation } from '../../../entities/recurringDonation'; - -export const RecurringDonationTab = { - resource: RecurringDonation, - - options: { - actions: { - list: { - isAccessible: ({ currentAdmin }) => - currentAdmin && currentAdmin.role !== 'qfManager', - }, - show: { - isAccessible: ({ currentAdmin }) => - currentAdmin && currentAdmin.role !== 'qfManager', - }, - new: { - isVisible: false, - }, - edit: { - isVisible: false, - }, - delete: { - isVisible: false, - }, - bulkDelete: { - isVisible: false, - }, - }, - }, -}; diff --git a/src/server/bootstrap.ts b/src/server/bootstrap.ts index 10e20495b..eda71855a 100644 --- a/src/server/bootstrap.ts +++ b/src/server/bootstrap.ts @@ -44,7 +44,6 @@ import { SOCIAL_PROFILES_PREFIX, } from '../routers/oauth2Callbacks'; import { dropDbCronExtension } from '../repositories/dbCronRepository'; -import { onramperWebhookHandler } from '../services/onramper/webhookHandler'; import { AppDataSource, CronDataSource } from '../orm'; import { ApolloContext } from '../types/ApolloContext'; import { ProjectResolverWorker } from '../workers/projectsResolverWorker'; @@ -56,10 +55,7 @@ import { runUpdateProjectCampaignsCacheJob } from '../services/cronJobs/updatePr import { corsOptions } from './cors'; import { runSyncLostDonations } from '../services/cronJobs/importLostDonationsJob'; import { runSyncBackupServiceDonations } from '../services/cronJobs/backupDonationImportJob'; -import { runUpdateRecurringDonationStream } from '../services/cronJobs/updateStreamOldRecurringDonationsJob'; import { runDraftDonationMatchWorkerJob } from '../services/cronJobs/draftDonationMatchingJob'; -import { runCheckUserSuperTokenBalancesJob } from '../services/cronJobs/checkUserSuperTokenBalancesJob'; -import { runCheckPendingRecurringDonationsCronJob } from '../services/cronJobs/syncRecurringDonationsWithNetwork'; Resource.validate = validate; @@ -278,7 +274,6 @@ export async function bootstrap() { app.get('/health', (_req, res) => { res.send('Hi every thing seems ok'); }); - app.post('/fiat_webhook', onramperWebhookHandler); app.post('/transak_webhook', webhookHandler); const httpServer = http.createServer(app); @@ -325,10 +320,8 @@ export async function bootstrap() { async function initializeCronJobs() { logger.debug('initializeCronJobs() has been called', new Date()); runCheckPendingDonationsCronJob(); - runCheckPendingRecurringDonationsCronJob(); runNotifyMissingDonationsCronJob(); runCheckPendingProjectListingCronJob(); - if (process.env.PROJECT_REVOKE_SERVICE_ACTIVE === 'true') { runCheckProjectVerificationStatus(); } @@ -350,15 +343,6 @@ export async function bootstrap() { runDraftDonationMatchWorkerJob(); } - if (process.env.ENABLE_DRAFT_RECURRING_DONATION === 'true') { - // TODO now disabling this field would break the recurring donation feature so I commented because otherwise draftDonation worker pool woud not work - // runDraftRecurringDonationMatchWorkerJob(); - } - - if (process.env.ENABLE_UPDATE_RECURRING_DONATION_STREAM === 'true') { - runUpdateRecurringDonationStream(); - runCheckUserSuperTokenBalancesJob(); - } logger.debug( 'initializeCronJobs() before runCheckActiveStatusOfQfRounds() ', new Date(), diff --git a/src/services/chains/evm/draftRecurringDonationService.test.ts b/src/services/chains/evm/draftRecurringDonationService.test.ts deleted file mode 100644 index 48ca6e082..000000000 --- a/src/services/chains/evm/draftRecurringDonationService.test.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { expect } from 'chai'; -import { - saveProjectDirectlyToDb, - createProjectData, - saveUserDirectlyToDb, - generateRandomEtheriumAddress, - generateRandomEvmTxHash, - saveRecurringDonationDirectlyToDb, -} from '../../../../test/testUtils'; -import { NETWORK_IDS } from '../../../provider'; -import { Project } from '../../../entities/project'; -import { User } from '../../../entities/user'; -import { - DraftRecurringDonation, - RECURRING_DONATION_ORIGINS, -} from '../../../entities/draftRecurringDonation'; -import { AnchorContractAddress } from '../../../entities/anchorContractAddress'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../../../entities/recurringDonation'; -import { addNewAnchorAddress } from '../../../repositories/anchorContractAddressRepository'; -import { matchDraftRecurringDonations } from './draftRecurringDonationService'; - -describe('matchDraftRecurringDonations', matchDraftRecurringDonationsTests); - -function matchDraftRecurringDonationsTests() { - let project1: Project; - let anchorContractAddress1: AnchorContractAddress; - let user1: User; - - beforeEach(async () => { - project1 = await saveProjectDirectlyToDb(createProjectData()); - - user1 = await User.create({ - walletAddress: generateRandomEtheriumAddress(), - loginType: 'wallet', - firstName: 'first name', - }).save(); - - anchorContractAddress1 = await addNewAnchorAddress({ - project: project1, - owner: user1, - creator: user1, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - }); - - afterEach(async () => { - await DraftRecurringDonation.delete({}); - await RecurringDonation.delete({}); - await AnchorContractAddress.delete({}); - }); - - it('should create a recurring donation based on the draft donation OP Sepholia #1', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10 - const user = await saveUserDirectlyToDb( - '0x871Cd6353B803CECeB090Bb827Ecb2F361Db81AB', - ); - const txHash = - '0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10'; - anchorContractAddress1.address = - '0x1190f5ac0f509d8f3f4b662bf17437d37d64527c'; - anchorContractAddress1.isActive = true; - await anchorContractAddress1.save(); - - const draftRecurringDonation = await DraftRecurringDonation.create({ - projectId: project1!.id, - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - currency: 'ETH', - donorId: user!.id, - flowRate: '285225986', - }).save(); - const oneSecEarlierThanTx = new Date(1711283035000); - draftRecurringDonation.createdAt = oneSecEarlierThanTx; - await draftRecurringDonation.save(); - - expect(draftRecurringDonation).to.be.ok; - - await matchDraftRecurringDonations([draftRecurringDonation!]); - - const recurringDonation = await RecurringDonation.findOne({ - where: { - txHash, - }, - }); - - const updatedDraftDonation = await DraftRecurringDonation.findOne({ - where: { - id: draftRecurringDonation.id, - }, - }); - - expect(recurringDonation).to.be.ok; - - expect(recurringDonation?.txHash).to.be.equal(txHash); - expect(recurringDonation?.status).to.equal( - RECURRING_DONATION_STATUS.PENDING, - ); - expect(recurringDonation?.origin).to.equal( - RECURRING_DONATION_ORIGINS.DRAFT_RECURRING_DONATION_MATCHING, - ); - expect(updatedDraftDonation?.matchedRecurringDonationId).to.equal( - recurringDonation?.id, - ); - }); - - it('should create a recurring donation based on the draft donation OP Sepholia #1, update existing', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10 - const user = await saveUserDirectlyToDb( - '0x871Cd6353B803CECeB090Bb827Ecb2F361Db81AB', - ); - const txHash = - '0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10'; - anchorContractAddress1.address = - '0x1190f5ac0f509d8f3f4b662bf17437d37d64527c'; - anchorContractAddress1.isActive = true; - await anchorContractAddress1.save(); - - const existingRecurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - txHash: generateRandomEvmTxHash(), - projectId: project1!.id, - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - currency: 'ETH', - donorId: user!.id, - flowRate: '11111', - status: RECURRING_DONATION_STATUS.ACTIVE, - }, - }); - - const draftRecurringDonation = await DraftRecurringDonation.create({ - projectId: project1!.id, - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - matchedRecurringDonationId: existingRecurringDonation.id, - isForUpdate: true, - currency: 'ETH', - donorId: user!.id, - flowRate: '285225986', - }).save(); - - const oneSecEarlierThanTx = new Date(1711283035000); - draftRecurringDonation.createdAt = oneSecEarlierThanTx; - await draftRecurringDonation.save(); - - expect(draftRecurringDonation).to.be.ok; - - await matchDraftRecurringDonations([draftRecurringDonation!]); - - const recurringDonation = await RecurringDonation.findOne({ - where: { - txHash, - }, - }); - - const updatedDraftDonation = await DraftRecurringDonation.findOne({ - where: { - id: draftRecurringDonation.id, - }, - }); - - expect(recurringDonation).to.be.ok; - - expect(recurringDonation?.txHash).to.be.equal(txHash); - expect(recurringDonation?.flowRate).to.be.equal( - draftRecurringDonation.flowRate, - ); - expect(recurringDonation?.status).to.equal( - RECURRING_DONATION_STATUS.PENDING, - ); - expect(recurringDonation?.origin).to.equal( - RECURRING_DONATION_ORIGINS.DRAFT_RECURRING_DONATION_MATCHING, - ); - expect(updatedDraftDonation?.matchedRecurringDonationId).to.equal( - recurringDonation?.id, - ); - }); - - it('should create a recurring donation based on the draft donation OP Sepholia #2 batch', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x1833603bc894448b54cf9c03483fa361508fa101abcfa6c3b6ef51425cab533f - const user = await saveUserDirectlyToDb( - '0xa1179f64638adb613ddaac32d918eb6beb824104', - ); - const txHash = - '0x1833603bc894448b54cf9c03483fa361508fa101abcfa6c3b6ef51425cab533f'; - anchorContractAddress1.address = - '0xe6375bc298aEB29D173B2AB359D492439A43b268'; - anchorContractAddress1.isActive = true; - await anchorContractAddress1.save(); - - const draftRecurringDonation = await DraftRecurringDonation.create({ - projectId: project1!.id, - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - currency: 'ETH', - donorId: user!.id, - flowRate: '152207001', - }).save(); - const oneSecEarlierThanTx = new Date(1711264598000); - draftRecurringDonation.createdAt = oneSecEarlierThanTx; - draftRecurringDonation.isBatch = true; - await draftRecurringDonation.save(); - - expect(draftRecurringDonation).to.be.ok; - - await matchDraftRecurringDonations([draftRecurringDonation!]); - - const recurringDonation = await RecurringDonation.findOne({ - where: { - txHash, - }, - }); - - const updatedDraftDonation = await DraftRecurringDonation.findOne({ - where: { - id: draftRecurringDonation.id, - }, - }); - - expect(recurringDonation).to.be.ok; - - expect(recurringDonation?.txHash).to.be.equal(txHash); - expect(recurringDonation?.status).to.equal( - RECURRING_DONATION_STATUS.PENDING, - ); - expect(recurringDonation?.origin).to.equal( - RECURRING_DONATION_ORIGINS.DRAFT_RECURRING_DONATION_MATCHING, - ); - expect(updatedDraftDonation?.matchedRecurringDonationId).to.equal( - recurringDonation?.id, - ); - }); -} diff --git a/src/services/chains/evm/draftRecurringDonationService.ts b/src/services/chains/evm/draftRecurringDonationService.ts deleted file mode 100644 index cd79dc283..000000000 --- a/src/services/chains/evm/draftRecurringDonationService.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { ModuleThread, Pool, spawn, Worker } from 'threads'; -import { WorkerModule } from 'threads/dist/types/worker'; -import { DRAFT_DONATION_STATUS } from '../../../entities/draftDonation'; -import { ApolloContext } from '../../../types/ApolloContext'; -import { logger } from '../../../utils/logger'; -import { - DRAFT_RECURRING_DONATION_STATUS, - DraftRecurringDonation, - RECURRING_DONATION_ORIGINS, -} from '../../../entities/draftRecurringDonation'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../../../entities/recurringDonation'; -import { RecurringDonationResolver } from '../../../resolvers/recurringDonationResolver'; -import { findUserById } from '../../../repositories/userRepository'; -import { findActiveAnchorAddress } from '../../../repositories/anchorContractAddressRepository'; -import { findRecurringDonationById } from '../../../repositories/recurringDonationRepository'; -import { getSuperFluidAdapter } from '../../../adapters/adaptersFactory'; -import { FlowUpdatedEvent } from '../../../adapters/superFluid/superFluidAdapterInterface'; -import { convertTimeStampToSeconds } from '../../../utils/utils'; - -type DraftRecurringDonationWorkerFunctions = 'matchDraftRecurringDonations'; -export type DraftRecurringDonationWorker = - WorkerModule; - -export async function matchDraftRecurringDonations( - draftRecurringDonations: DraftRecurringDonation[], -) { - logger.debug( - 'matchDraftRecurringDonations() has been called draftDonation.length', - draftRecurringDonations.length, - ); - for (const draftRecurringDonation of draftRecurringDonations) { - try { - const anchorContractAddress = await findActiveAnchorAddress({ - networkId: draftRecurringDonation.networkId, - projectId: draftRecurringDonation.projectId, - }); - const donor = await findUserById(draftRecurringDonation.donorId); - const superFluidAdapter = getSuperFluidAdapter(); - - const getFlowParams = { - flowRate: draftRecurringDonation.flowRate, - receiver: anchorContractAddress?.address?.toLowerCase() as string, - sender: donor?.walletAddress?.toLowerCase() as string, - timestamp_gt: convertTimeStampToSeconds( - draftRecurringDonation.createdAt.getTime(), - ), - }; - const flow = - await superFluidAdapter.getFlowByReceiverSenderFlowRate(getFlowParams); - if (flow) { - logger.debug('matchDraftRecurringDonations flow: ', flow); - await submitMatchedDraftRecurringDonation(draftRecurringDonation, flow); - } else { - logger.error('matchDraftRecurringDonations flow is undefined', flow); - } - } catch (e) { - logger.error('error validating draftRecurringDonation', { - draftRecurringDonationId: - draftRecurringDonation.matchedRecurringDonationId, - flowRate: draftRecurringDonation?.flowRate, - error: e, - }); - } - } -} - -async function submitMatchedDraftRecurringDonation( - draftRecurringDonation: DraftRecurringDonation, - tx: FlowUpdatedEvent, -) { - logger.debug( - 'submitMatchedDraftRecurringDonation() has been called', - draftRecurringDonation, - tx, - ); - // Check whether a donation with same networkId and txHash already exists - const existingRecurringDonation = await RecurringDonation.findOne({ - where: { - networkId: draftRecurringDonation.networkId, - txHash: tx.transactionHash, - projectId: draftRecurringDonation.projectId, - }, - }); - - if (existingRecurringDonation) { - // Check whether the donation has not been saved during matching procedure - await draftRecurringDonation.reload(); - if (draftRecurringDonation.status === DRAFT_DONATION_STATUS.PENDING) { - draftRecurringDonation.status = DRAFT_DONATION_STATUS.FAILED; - draftRecurringDonation.errorMessage = `Recurring donation with same networkId and txHash with ID ${existingRecurringDonation.id} already exists`; - await draftRecurringDonation.save(); - } - return; - } - - const recurringDonationResolver = new RecurringDonationResolver(); - - const { - flowRate, - networkId, - anonymous, - currency, - projectId, - isBatch, - matchedRecurringDonationId, - isForUpdate, - } = draftRecurringDonation; - const txHash = tx.transactionHash; - try { - logger.debug( - `Creating donation for draftDonation with ID ${draftRecurringDonation.id}`, - ); - let recurringDonation; - if (isForUpdate) { - const oldRecurringDonation = await findRecurringDonationById( - matchedRecurringDonationId!, - ); - recurringDonation = - await recurringDonationResolver.updateRecurringDonationParams( - { - req: { user: { userId: draftRecurringDonation.donorId }, auth: {} }, - } as ApolloContext, - projectId, - networkId, - currency, - - txHash, - flowRate, - anonymous, - oldRecurringDonation?.isArchived, - ); - } else { - recurringDonation = - await recurringDonationResolver.createRecurringDonation( - { - req: { user: { userId: draftRecurringDonation.donorId }, auth: {} }, - } as ApolloContext, - projectId, - networkId, - txHash, - currency, - flowRate, - anonymous, - isBatch, - ); - } - - await RecurringDonation.update(Number(recurringDonation.id), { - origin: RECURRING_DONATION_ORIGINS.DRAFT_RECURRING_DONATION_MATCHING, - status: RECURRING_DONATION_STATUS.PENDING, - }); - - await DraftRecurringDonation.update(draftRecurringDonation.id, { - matchedRecurringDonationId: recurringDonation.id, - }); - - logger.debug( - `Recurring donation with ID ${recurringDonation.id} has been created for draftRecurringDonation with ID ${draftRecurringDonation.id}`, - ); - // donation resolver does it - // draftDonation.status = DRAFT_DONATION_STATUS.MATCHED; - // draftDonation.matchedDonationId = Number(donationId); - } catch (e) { - logger.fatal( - `Error on creating donation for draftDonation with ID ${draftRecurringDonation.id}`, - e, - ); - draftRecurringDonation.status = DRAFT_RECURRING_DONATION_STATUS.FAILED; - draftRecurringDonation.errorMessage = e.message; - await draftRecurringDonation.save(); - } -} - -let workerIsIdle = true; -let pool: Pool>; - -export async function runDraftRecurringDonationMatchWorker() { - if (!workerIsIdle) { - logger.debug('Draft recurring donation matching worker is already running'); - return; - } - workerIsIdle = false; - - if (!pool) { - pool = Pool( - () => - spawn( - new Worker('./../../../workers/draftRecurringDonationMatchWorker'), - ), - { - name: 'draftRecurringDonationMatchWorker', - concurrency: 4, - size: 2, - }, - ); - } - try { - await pool.queue(draftRecurringDonationWorker => - draftRecurringDonationWorker.matchDraftRecurringDonations(), - ); - await pool.settled(true); - } catch (e) { - logger.error( - `error in calling draft recurring donation match worker: ${e.message}`, - ); - } finally { - workerIsIdle = true; - } -} diff --git a/src/services/cronJobs/checkUserSuperTokenBalancesJob.ts b/src/services/cronJobs/checkUserSuperTokenBalancesJob.ts deleted file mode 100644 index ae7a3bba2..000000000 --- a/src/services/cronJobs/checkUserSuperTokenBalancesJob.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { schedule } from 'node-cron'; -import config from '../../config'; -import { logger } from '../../utils/logger'; -import { - processRecurringDonationBalancesJobs, - runCheckUserSuperTokenBalances, -} from './checkUserSuperTokenBalancesQueue'; - -const cronJobTime = - (config.get('CHECK_USERS_SUPER_TOKEN_BALANCES_CRONJOB_TIME') as string) || - '0 0 * * *'; // one day at 00:00 - -export const runCheckUserSuperTokenBalancesJob = () => { - logger.debug( - 'runCheckUserSuperTokenBalancesJob() has been called, cronJobTime', - cronJobTime, - ); - processRecurringDonationBalancesJobs(); - - schedule(cronJobTime, async () => { - logger.debug('runCheckUserSuperTokenBalancesJob() has been started'); - try { - await runCheckUserSuperTokenBalances(); - } catch (error) { - logger.error('runCheckUserSuperTokenBalancesJob() error', error); - } - logger.debug('runCheckUserSuperTokenBalancesJob() has been finished'); - }); -}; diff --git a/src/services/cronJobs/checkUserSuperTokenBalancesQueue.ts b/src/services/cronJobs/checkUserSuperTokenBalancesQueue.ts deleted file mode 100644 index 567ad9671..000000000 --- a/src/services/cronJobs/checkUserSuperTokenBalancesQueue.ts +++ /dev/null @@ -1,167 +0,0 @@ -import Bull from 'bull'; -import { - getNotificationAdapter, - getSuperFluidAdapter, -} from '../../adapters/adaptersFactory'; -import config from '../../config'; -import { RecurringDonation } from '../../entities/recurringDonation'; -import { redisConfig } from '../../redis'; -import { findUserById } from '../../repositories/userRepository'; -import { logger } from '../../utils/logger'; -import { - findActiveRecurringDonations, - findRecurringDonationById, -} from '../../repositories/recurringDonationRepository'; -import { getCurrentDateFormatted } from '../../utils/utils'; -import { getNetworkNameById, superTokens } from '../../provider'; -import { NOTIFICATIONS_EVENT_NAMES } from '../../analytics/analytics'; - -const runCheckUserSuperTokenBalancesQueue = new Bull( - 'user-token-balances-stream-queue', - { - redis: redisConfig, - }, -); - -const numberOfUpdateRecurringDonationsStreamConcurrentJob = - Number(config.get('NUMBER_OF_CHECK_USER_SUPER_TOKEN_BALANCES_JOB')) || 1; - -const TWO_MINUTES = 1000 * 60 * 2; -setInterval(async () => { - const superTokenBalancesQueueCount = - await runCheckUserSuperTokenBalancesQueue.count(); - logger.debug(`Check User token Balances queues count:`, { - superTokenBalancesQueueCount, - }); -}, TWO_MINUTES); - -export const runCheckUserSuperTokenBalances = async () => { - logger.debug('runCheckUserSuperTokenBalances() has been called'); - - const recurringDonations = await findActiveRecurringDonations(); - logger.debug('Active recurring donations length', recurringDonations.length); - recurringDonations.forEach(recurringDonation => { - logger.debug('Add pending recurringDonation to queue', { - recurringDonationId: recurringDonation.id, - }); - runCheckUserSuperTokenBalancesQueue.add( - { - recurringDonationId: recurringDonation.id, - }, - { - // Because we want to run this job once per day so we need to add the date to the job id - jobId: `update-recurring-donations-stream-queue-${getCurrentDateFormatted()}-${ - recurringDonation.id - }`, - removeOnComplete: true, - removeOnFail: true, - }, - ); - }); -}; - -export function processRecurringDonationBalancesJobs() { - logger.debug('processRecurringDonationBalancesJobs() has been called'); - runCheckUserSuperTokenBalancesQueue.process( - numberOfUpdateRecurringDonationsStreamConcurrentJob, - async (job, done) => { - const { recurringDonationId } = job.data; - logger.debug('job processing', { jobData: job.data }); - try { - await checkRecurringDonationBalances({ recurringDonationId }); - done(); - } catch (e) { - logger.error('processRecurringDonationBalancesJobs error', e); - done(); - } - }, - ); -} - -export const checkRecurringDonationBalances = async (params: { - recurringDonationId: number; -}) => { - const recurringDonation = await findRecurringDonationById( - params.recurringDonationId, - ); - logger.debug( - `checkRecurringDonationBalances() has been called for id ${params.recurringDonationId}`, - ); - if (!recurringDonation) return; - await validateDonorSuperTokenBalance(recurringDonation); -}; - -const weekInSec = 60 * 60 * 24 * 7; -const monthInSec = 60 * 60 * 24 * 30; -export const validateDonorSuperTokenBalance = async ( - recurringDonation: RecurringDonation, -) => { - const superFluidAdapter = getSuperFluidAdapter(); - const user = await findUserById(recurringDonation.donorId); - - if (!user) return; - - const accountBalances = await superFluidAdapter.accountBalance( - user.walletAddress!, - ); - - logger.debug( - `validateDonorSuperTokenBalance for recurringDonation id ${recurringDonation.id}`, - { accountBalances, userId: user.id }, - ); - - if (!accountBalances || accountBalances.length === 0) return; - - for (const tokenBalance of accountBalances) { - const { maybeCriticalAtTimestamp, token } = tokenBalance; - if (!user!.email) continue; - const tokenSymbol = superTokens.find(t => t.id === token.id) - ?.underlyingToken.symbol; - // We shouldn't notify the user if the token is not the same as the recurring donation - if (tokenSymbol !== recurringDonation.currency) continue; - const nowInSec = Number((Date.now() / 1000).toFixed()); - const balanceLongerThanMonth = - Math.abs(nowInSec - maybeCriticalAtTimestamp) > monthInSec; - if (balanceLongerThanMonth) { - if (user.streamBalanceWarning) { - user.streamBalanceWarning[tokenSymbol] = null; - await user.save(); - } - continue; - } - const balanceLongerThanWeek = - Math.abs(nowInSec - maybeCriticalAtTimestamp) > weekInSec; - - const depletedBalance = - maybeCriticalAtTimestamp === 0 || !maybeCriticalAtTimestamp; - const eventName = depletedBalance - ? NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_DEPLETED - : balanceLongerThanWeek - ? NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_MONTH - : NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_WEEK; - - // If the balance warning is the same, we've already sent the notification - if ( - user.streamBalanceWarning && - user.streamBalanceWarning[tokenSymbol] === eventName - ) - continue; - if (user.streamBalanceWarning) { - user.streamBalanceWarning[tokenSymbol] = eventName; - } else { - user.streamBalanceWarning = { - [tokenSymbol]: eventName, - }; - } - await user.save(); - // Notify user their super token is running out - await getNotificationAdapter().userSuperTokensCritical({ - user, - eventName, - tokenSymbol: tokenSymbol!, - isEnded: recurringDonation.finished, - project: recurringDonation.project, - networkName: getNetworkNameById(recurringDonation.networkId), - }); - } -}; diff --git a/src/services/cronJobs/draftRecurringDonationMatchingJob.ts b/src/services/cronJobs/draftRecurringDonationMatchingJob.ts deleted file mode 100644 index c99e96acc..000000000 --- a/src/services/cronJobs/draftRecurringDonationMatchingJob.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { schedule } from 'node-cron'; -import config from '../../config'; -import { logger } from '../../utils/logger'; -import { - DRAFT_RECURRING_DONATION_STATUS, - DraftRecurringDonation, -} from '../../entities/draftRecurringDonation'; -import { runDraftRecurringDonationMatchWorker } from '../chains/evm/draftRecurringDonationService'; -import { deleteExpiredDraftRecurringDonations } from '../../repositories/draftRecurringDonationRepository'; - -const cronJobTime = - (config.get('MATCH_DRAFT_DONATION_CRONJOB_EXPRESSION') as string) || - '0 */5 * * *'; - -const TWO_MINUTES = 1000 * 60 * 2; - -// Queue for filling snapshot balances - -// Periodically log the queue count - -export const runDraftRecurringDonationMatchWorkerJob = () => { - logger.debug('runDraftRecurringDonationMatchWorkerJob', cronJobTime); - - schedule(cronJobTime, async () => { - const hours = Number( - process.env.DRAFT_RECURRING_DONATION_MATCH_EXPIRATION_HOURS || 48, - ); - await deleteExpiredDraftRecurringDonations(hours); - await runDraftRecurringDonationMatchWorker(); - }); - - setInterval(async () => { - const count = await DraftRecurringDonation.countBy({ - status: DRAFT_RECURRING_DONATION_STATUS.PENDING, - }); - logger.debug('Pending Draft Recurring Donations count:', { count }); - }, TWO_MINUTES); -}; diff --git a/src/services/cronJobs/syncRecurringDonationsWithNetwork.ts b/src/services/cronJobs/syncRecurringDonationsWithNetwork.ts deleted file mode 100644 index 44d0d6cac..000000000 --- a/src/services/cronJobs/syncRecurringDonationsWithNetwork.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { schedule } from 'node-cron'; -import Bull from 'bull'; -import config from '../../config'; -import { redisConfig } from '../../redis'; -import { logger } from '../../utils/logger'; -import { getPendingRecurringDonationsIds } from '../../repositories/recurringDonationRepository'; -import { updateRecurringDonationStatusWithNetwork } from '../recurringDonationService'; - -const verifyRecurringDonationsQueue = new Bull( - 'verify-recurring-donations-queue', - { - redis: redisConfig, - }, -); -const TWO_MINUTES = 1000 * 60 * 2; -setInterval(async () => { - const verifyDonationsQueueCount = await verifyRecurringDonationsQueue.count(); - logger.debug(`Verify recurring donations job queues count:`, { - verifyDonationsQueueCount, - }); -}, TWO_MINUTES); - -const numberOfVerifyDonationConcurrentJob = - Number(config.get('NUMBER_OF_VERIFY_RECURRING_CONCURRENT_JOB')) || 1; - -const cronJobTime = - (config.get('VERIFY_RECURRING_CRONJOB_EXPRESSION') as string) || - '0 0 * * * *'; - -export const runCheckPendingRecurringDonationsCronJob = () => { - logger.debug( - 'runCheckPendingRecurringDonationsCronJob() has been called, cronJobTime', - cronJobTime, - ); - processVerifyRecurringDonationsJobs(); - - // https://github.com/node-cron/node-cron#cron-syntax - schedule(cronJobTime, async () => { - await addJobToCheckPendingRecurringDonationsWithNetwork(); - }); - addJobToCheckPendingRecurringDonationsWithNetwork(); -}; - -const addJobToCheckPendingRecurringDonationsWithNetwork = async () => { - logger.debug( - 'addJobToCheckPendingRecurringDonationsWithNetwork() has been called', - ); - - const recurringDonations = await getPendingRecurringDonationsIds(); - logger.debug( - 'Pending recurringDonations to be check', - recurringDonations.length, - ); - recurringDonations.forEach(donation => { - logger.debug('Add pending recurring donation to queue', { - donationId: donation.id, - }); - verifyRecurringDonationsQueue.add( - { - donationId: donation.id, - }, - { - jobId: `verify-recurring-donation-id-${donation.id}`, - removeOnComplete: true, - removeOnFail: true, - }, - ); - }); -}; - -function processVerifyRecurringDonationsJobs() { - logger.debug('processVerifyRecurringDonationsJobs() has been called'); - verifyRecurringDonationsQueue.process( - numberOfVerifyDonationConcurrentJob, - async (job, done) => { - const { donationId } = job.data; - logger.debug('job processing', { jobData: job.data }); - try { - await updateRecurringDonationStatusWithNetwork({ donationId }); - done(); - } catch (e) { - logger.error( - 'processVerifyRecurringDonationsJobs >> updateRecurringDonationStatusWithNetwork error', - e, - ); - done(); - } - }, - ); -} diff --git a/src/services/cronJobs/updateStreamOldRecurringDonationsJob.ts b/src/services/cronJobs/updateStreamOldRecurringDonationsJob.ts deleted file mode 100644 index e102afa0b..000000000 --- a/src/services/cronJobs/updateStreamOldRecurringDonationsJob.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { schedule } from 'node-cron'; -import config from '../../config'; -import { logger } from '../../utils/logger'; -import { - processRecurringDonationStreamJobs, - updateRecurringDonationsStream, -} from '../recurringDonationStreamQueue'; - -const cronJobTime = - (config.get('UPDATE_RECURRING_DONATIONS_STREAM_CRONJOB') as string) || - '0 0 * * *'; // every day at 00:00 - -export const runUpdateRecurringDonationStream = () => { - logger.debug( - 'runUpdateRecurringDonationStream() has been called, cronJobTime', - cronJobTime, - ); - processRecurringDonationStreamJobs(); - schedule(cronJobTime, async () => { - logger.debug('runUpdateRecurringDonationStream() has been started'); - try { - await updateRecurringDonationsStream(); - } catch (error) { - logger.error('runUpdateRecurringDonationStream() error', error); - } - logger.debug('runUpdateRecurringDonationStream() has been finished'); - }); -}; diff --git a/src/services/onramper/donationService.ts b/src/services/onramper/donationService.ts deleted file mode 100644 index bc51cd8d8..000000000 --- a/src/services/onramper/donationService.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Donation, DONATION_STATUS } from '../../entities/donation'; -import { ProjStatus } from '../../entities/project'; -import { Token } from '../../entities/token'; -import { NETWORK_IDS } from '../../provider'; -import { findProjectRecipientAddressByNetworkId } from '../../repositories/projectAddressRepository'; -import { findProjectById } from '../../repositories/projectRepository'; -import { findUserById } from '../../repositories/userRepository'; -import { i18n, translationErrorMessagesKeys } from '../../utils/errorMessages'; -import { logger } from '../../utils/logger'; -import { isTokenAcceptableForProject } from '../donationService'; -import { OnRamperFiatTransaction, OnRamperMetadata } from './fiatTransaction'; -import SentryLogger from '../../sentryLogger'; -import { - updateUserTotalDonated, - updateUserTotalReceived, -} from '../userService'; -import { updateProjectStatistics } from '../projectService'; - -export const createFiatDonationFromOnramper = async ( - fiatTransaction: OnRamperFiatTransaction, -): Promise => { - try { - let donorUser; - let donation = await Donation.findOne({ - where: { - transactionId: fiatTransaction.payload.txHash!.toLowerCase(), - }, - }); - - if (donation) { - throw new Error( - i18n.__(translationErrorMessagesKeys.FIAT_DONATION_ALREADY_EXISTS), - ); - } - - // Custom Metadata from the frontend at the time of donation - let metadata: OnRamperMetadata; - if (typeof fiatTransaction.payload.partnerContext === 'string') { - metadata = JSON.parse(fiatTransaction.payload.partnerContext); - } else { - metadata = fiatTransaction.payload.partnerContext; - } - - if (metadata.userId) { - donorUser = await findUserById(Number(metadata.userId)); - } - const project = await findProjectById(Number(metadata.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, - ), - ); - } - // mainnet ETH is the out currency - const priceChainId = NETWORK_IDS.MAIN_NET; - const isCustomToken = false; - - // Ethereum mainnet always exists - const tokenInDb = await Token.findOne({ - where: { - networkId: priceChainId, - symbol: fiatTransaction.payload.outCurrency, - }, - }); - const isTokenEligibleForGivback = tokenInDb!.isGivbackEligible; - - const acceptsToken = await isTokenAcceptableForProject({ - projectId: project.id, - tokenId: tokenInDb!.id, - }); - - if (!acceptsToken) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.PROJECT_DOES_NOT_SUPPORT_THIS_TOKEN, - ), - ); - } - - const projectRelatedAddress = await findProjectRecipientAddressByNetworkId({ - projectId: project.id, - networkId: priceChainId, - }); - - if (!projectRelatedAddress) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.THERE_IS_NO_RECIPIENT_ADDRESS_FOR_THIS_NETWORK_ID_AND_PROJECT, - ), - ); - } - - const toAddress = projectRelatedAddress?.address.toLowerCase() as string; - const ethMainnetAddress = '0x0000000000000000000000000000000000000000'; - - // FromWalletAddress is not the donor wallet, but the Onramper Address - donation = Donation.create({ - amount: Number(fiatTransaction.payload.outAmount), - transactionId: fiatTransaction.payload.txHash!.toLowerCase(), - isFiat: true, - transactionNetworkId: Number(priceChainId), - currency: fiatTransaction.payload.outCurrency, - tokenAddress: ethMainnetAddress, - project, - isTokenEligibleForGivback, - isCustomToken, - isProjectVerified: project.verified, - createdAt: new Date(fiatTransaction.payload.timestamp), - segmentNotified: false, - toWalletAddress: toAddress.toString().toLowerCase(), - fromWalletAddress: fiatTransaction.payload.wallet!.toLowerCase(), - anonymous: metadata.anonymous === 'true', - onramperId: fiatTransaction.payload.txId, - onramperTransactionStatus: fiatTransaction.type, - status: DONATION_STATUS.VERIFIED, - }); - - if (donorUser) { - donation.user = donorUser; - } - - if (metadata.email) { - donation.contactEmail = metadata.email.toLowerCase(); - } - - await donation.save(); - - // await updateDonationPricesAndValues( - // donation, - // project, - // null, - // fiatTransaction.payload.outCurrency, - // priceChainId, - // fiatTransaction.payload.outAmount, - // ); - - // After updating, recalculate user total donated and owner total received - if (donorUser) { - await updateUserTotalDonated(donorUser.id); - } - - // After updating price we update totalDonations - await updateProjectStatistics(project.id); - await updateUserTotalReceived(project.adminUserId); - } catch (e) { - SentryLogger.captureException(e); - logger.error('createFiatDonationFromOnramper() error', e); - throw e; - } -}; diff --git a/src/services/onramper/fiatTransaction.ts b/src/services/onramper/fiatTransaction.ts deleted file mode 100644 index 99a3a9985..000000000 --- a/src/services/onramper/fiatTransaction.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* EXAMPLE PAYLOAD -{ - "type": "transaction_completed" or "transaction_pending", - "summary": "", - "payload": { - "onramperTxId": "xxxxxxxxxxxxx", - "txId": "xxxxxxxxxxx", (usable in onramper UI) - "gatewayIdentifier": "Transak", (many different gateways, not important) - "timestamp": 1669671063842, - "inCurrency": "CHF", (not important to save) - "inAmount": 29, - "outCurrency": "ETH", (save this) - "outAmount": 0.02347075, - "medium": "creditCard", - - // could be a json string or object, consider both - "partnerContext": "{\"userId\":\"66\",\"userWallet\":\"0x00d18ca9782be1caef611017c2fbc1a39779a57c\",\"projectWallet\":\"0x76a1F47f8d998D07a15189a07d9aADA180E09aC6\",\"projectId\":\"71\"}", - - "wallet": "0x76a1F47f8d998D07a15189a07d9aADA180E09aC6", (not important) - - // transaction Hash only when completed it arrives - "txHash": "0x914557b20101eea80f6260511ad2698061a940a1e9ef0c2d35aac85ef62a8aac" - } -} -*/ - -// set as optional ?, non required values or values that arrive later -export interface OnRamperFiatTransaction { - type: 'transaction_completed' | 'transaction_pending'; - summary?: string; - payload: { - onramperTxId?: string; - txId: string; - gatewayIdentifier?: string; - timestamp: Date; - inCurrency?: string; - inAmount?: string; - outCurrency: string; - outAmount: string; - purchaseAmount?: string; - partnerContext: string | OnRamperMetadata; - wallet?: string; - txHash?: string; - }; -} - -export interface OnRamperMetadata { - userId: string; - userWallet: string; - projectId: string; - projectWallet: string; - anonymous: string; - email: string; -} diff --git a/src/services/onramper/webhookHandler.test.ts b/src/services/onramper/webhookHandler.test.ts deleted file mode 100644 index fa59f144d..000000000 --- a/src/services/onramper/webhookHandler.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import axios from 'axios'; -import { assert } from 'chai'; -import { serverBaseAddress } from '../../../test/testUtils'; -import { Donation } from '../../entities/donation'; - -describe('onramperWebhookHandlerTestCases', onramperWebhookHandlerTestCases); - -const payload = { - type: 'transaction_completed', - summary: '', - payload: { - onramperTxId: 'O4x_YoHNLTEcgHDYnTHTbA--', - txId: '6c4d4395-97dc-46f6-86c2-ff80278d6068', - gatewayIdentifier: 'Transak', - timestamp: 1669671063842, - inCurrency: 'CHF', - inAmount: 29, - outCurrency: 'ETH', - outAmount: 0.02347075, - medium: 'creditCard', - partnerContext: - '{"userId":"1","userWallet":"0x00d18ca9782be1caef611017c2fbc1a39779a57c","projectWallet":"0x76a1F47f8d998D07a15189a07d9aADA180E09aC6","projectId":"1"}', - wallet: '0x76a1F47f8d998D07a15189a07d9aADA180E09aC6', - txHash: - '0x914557b20101eea80f6260511ad2698061a940a1e9ef0c2d35aac85ef62a8aac', - }, -}; - -const transactionId = - '0x914557b20101eea80f6260511ad2698061a940a1e9ef0c2d35aac85ef62a8aac'; - -function onramperWebhookHandlerTestCases() { - it('should return error 403 if the hmac-sha256 signature is invalid', async () => { - try { - await axios.post(`${serverBaseAddress}/fiat_webhook`, payload, { - headers: { - 'x-onramper-webhook-signature': 'xxxxxx', - }, - }); - } catch (e) { - const status = e.response.status; - assert.equal(status, 403); - } - }); - it('should return error if the hmac-sha256 signature header is missing', async () => { - try { - await axios.post(`${serverBaseAddress}/fiat_webhook`, payload); - } catch (e) { - const status = e.response.status; - assert.equal(status, 403); - } - }); - it('should create donation succesfully with onramper data with valid hmac', async () => { - const result = await axios.post( - `${serverBaseAddress}/fiat_webhook`, - payload, - { - headers: { - 'x-onramper-webhook-signature': - 'cc60c9e8a16cc80ad1805983939a729eac4a3777d88671489933a58e0c8c3084', - }, - }, - ); - assert.isOk(result); - assert.equal(result.status, 200); - const ethMainnetAddress = '0x0000000000000000000000000000000000000000'; - - const createdDonation = await Donation.createQueryBuilder('donation') - .where('donation.transactionId = :transactionId', { transactionId }) - .getOne(); - - assert.isOk(createdDonation); - assert.equal(createdDonation!.userId, 1); - assert.equal(createdDonation!.projectId, 1); - assert.equal(createdDonation!.amount, payload.payload.outAmount); - assert.equal(createdDonation!.currency, payload.payload.outCurrency); - assert.equal(createdDonation!.tokenAddress, ethMainnetAddress); - }); -} diff --git a/src/services/onramper/webhookHandler.ts b/src/services/onramper/webhookHandler.ts deleted file mode 100644 index b7b85a197..000000000 --- a/src/services/onramper/webhookHandler.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { createFiatDonationFromOnramper } from './donationService'; -// import { TransakOrder } from './order'; -import { logger } from '../../utils/logger'; -import { OnRamperFiatTransaction } from './fiatTransaction'; -import { i18n } from '../../utils/errorMessages'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const sha256 = require('js-sha256'); -const onramperSecret = process.env.ONRAMPER_SECRET as string; - -/** - * @see { https://docs.onramper.com/API-Reference/#webhooks} - */ -export async function onramperWebhookHandler(request, response) { - try { - const payloadSignature = - request.headers['X-Onramper-Webhook-Signature'] || - request.headers['x-onramper-webhook-signature']; - if (!onramperSecret || !payloadSignature) - throw new Error(i18n.__('ONRAMPER_SIGNATURE_MISSING')); - - const fiatTransaction = request.body as OnRamperFiatTransaction; - const fiatTransactionStringified = JSON.stringify(fiatTransaction); - - const digestedHmac = sha256.hmac( - onramperSecret, - fiatTransactionStringified, - ); - if (digestedHmac !== payloadSignature) - throw new Error(i18n.__('ONRAMPER_SIGNATURE_INVALID')); - - // No point saving pending or failed transactions without txHash - if (fiatTransaction.type === 'transaction_completed') { - await createFiatDonationFromOnramper(fiatTransaction); - } - - logger.debug( - 'User Onramper Transaction Arrived', - JSON.stringify({ - type: fiatTransaction.type, - partnerContext: fiatTransaction.payload.partnerContext, - txId: fiatTransaction.payload.txId, - }), - ); - - response.status(200).send(); - } catch (error) { - logger.error('onramperWebhookHandler() error ', error); - response.status(403).send(); - } -} diff --git a/src/services/recurringDonationService.test.ts b/src/services/recurringDonationService.test.ts deleted file mode 100644 index c6bd6e943..000000000 --- a/src/services/recurringDonationService.test.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { assert } from 'chai'; -import { - createProjectData, - generateRandomEtheriumAddress, - generateRandomEvmTxHash, - saveProjectDirectlyToDb, - saveRecurringDonationDirectlyToDb, - saveUserDirectlyToDb, -} from '../../test/testUtils'; -import { - createRelatedDonationsToStream, - updateRecurringDonationStatusWithNetwork, -} from './recurringDonationService'; -import { Donation } from '../entities/donation'; -import { addNewAnchorAddress } from '../repositories/anchorContractAddressRepository'; -import { NETWORK_IDS } from '../provider'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../entities/recurringDonation'; -import { AnchorContractAddress } from '../entities/anchorContractAddress'; -import { findRecurringDonationById } from '../repositories/recurringDonationRepository'; - -describe( - 'createRelatedDonationsToStream test cases', - createRelatedDonationsToStreamTestCases, -); - -describe( - 'updateRecurringDonationStatusWithNetwork test cases', - updateRecurringDonationStatusWithNetworkTestCases, -); - -function updateRecurringDonationStatusWithNetworkTestCases() { - it('should verify transaction from OP Sepolia #1 createFlow', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10 - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const donor = await saveUserDirectlyToDb( - '0x871cd6353b803ceceb090bb827ecb2f361db81ab', - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: '0x1190f5ac0f509d8f3f4b662bf17437d37d64527c', - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'ETH', - status: RECURRING_DONATION_STATUS.PENDING, - txHash: - '0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10', - donorId: donor.id, - flowRate: '285225986', - }, - }); - const updatedDonation = await updateRecurringDonationStatusWithNetwork({ - donationId: recurringDonation.id, - }); - assert.equal(updatedDonation.status, RECURRING_DONATION_STATUS.ACTIVE); - await RecurringDonation.delete({ id: recurringDonation.id }); - await AnchorContractAddress.delete({ id: anchorContractAddress.id }); - }); - - it('should verify transaction from OP Sepolia #2 batchCall', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x1833603bc894448b54cf9c03483fa361508fa101abcfa6c3b6ef51425cab533f - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const donor = await saveUserDirectlyToDb( - '0xa1179f64638adb613ddaac32d918eb6beb824104', - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: '0xe6375bc298aEB29D173B2AB359D492439A43b268', - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'ETH', - status: RECURRING_DONATION_STATUS.PENDING, - txHash: - '0x1833603bc894448b54cf9c03483fa361508fa101abcfa6c3b6ef51425cab533f', - donorId: donor.id, - flowRate: '152207001', - isBatch: true, - }, - }); - const updatedDonation = await updateRecurringDonationStatusWithNetwork({ - donationId: recurringDonation.id, - }); - assert.equal(updatedDonation.status, RECURRING_DONATION_STATUS.ACTIVE); - await RecurringDonation.delete({ id: recurringDonation.id }); - await AnchorContractAddress.delete({ id: anchorContractAddress.id }); - }); - - it('should verify transaction from OP Sepolia when updateFlow function of smart contract has been called', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x74d98ba95c7969746afc38e46748aa64f239e816785be74b03372397cf844986 - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const donor = await saveUserDirectlyToDb( - '0xf577ae8b97d839b9c0522a620299dc08792c738c', - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: '0x0015cE4FeA643B64000400B0e61F4C03E020b75f', - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'ETH', - status: RECURRING_DONATION_STATUS.PENDING, - txHash: - '0x74d98ba95c7969746afc38e46748aa64f239e816785be74b03372397cf844986', - donorId: donor.id, - flowRate: '23194526400669', - }, - }); - const updatedDonation = await updateRecurringDonationStatusWithNetwork({ - donationId: recurringDonation.id, - }); - assert.equal(updatedDonation.status, RECURRING_DONATION_STATUS.ACTIVE); - await RecurringDonation.delete({ id: recurringDonation.id }); - await AnchorContractAddress.delete({ id: anchorContractAddress.id }); - }); - it('should remain pending, different toAddress from OP Sepolia', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10 - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const donor = await saveUserDirectlyToDb( - '0x871cd6353b803ceceb090bb827ecb2f361db81ab', - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'ETH', - status: RECURRING_DONATION_STATUS.PENDING, - txHash: - '0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10', - donorId: donor.id, - flowRate: '285225986', - }, - }); - const updatedDonation = await updateRecurringDonationStatusWithNetwork({ - donationId: recurringDonation.id, - }); - assert.equal(updatedDonation.status, RECURRING_DONATION_STATUS.PENDING); - - await RecurringDonation.delete({ id: recurringDonation.id }); - await AnchorContractAddress.delete({ id: anchorContractAddress.id }); - }); - it('should donation remain pending, different amount from OP Sepolia', async () => { - // https://sepolia-optimism.etherscan.io/tx/0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10 - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const donor = await saveUserDirectlyToDb( - '0x871cd6353b803ceceb090bb827ecb2f361db81ab', - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISM_SEPOLIA, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'ETH', - status: RECURRING_DONATION_STATUS.PENDING, - txHash: - '0x516567c51c3506afe1291f7055fa0e858cc2ca9ed4079625c747fe92bd125a10', - donorId: donor.id, - flowRate: '10000000', - }, - }); - const updatedDonation = await updateRecurringDonationStatusWithNetwork({ - donationId: recurringDonation.id, - }); - assert.equal(updatedDonation.status, RECURRING_DONATION_STATUS.PENDING); - - await RecurringDonation.delete({ id: recurringDonation.id }); - await AnchorContractAddress.delete({ id: anchorContractAddress.id }); - }); -} - -function createRelatedDonationsToStreamTestCases() { - // TODO As I changed superFluid adapter to user staging address - // And return not mockAdapter in test more this test is not valid anymore - // I will skip it for now but we will keep it here for future reference - it.skip('should search by the currency', async () => { - const projectOwner = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - const project = await saveProjectDirectlyToDb( - createProjectData(), - projectOwner, - ); - const contractCreator = await saveUserDirectlyToDb( - generateRandomEtheriumAddress(), - ); - - const anchorContractAddress = await addNewAnchorAddress({ - project, - owner: projectOwner, - creator: contractCreator, - address: generateRandomEtheriumAddress(), - networkId: NETWORK_IDS.OPTIMISTIC, - txHash: generateRandomEvmTxHash(), - }); - - const recurringDonation = await saveRecurringDonationDirectlyToDb({ - donationData: { - projectId: project.id, - anchorContractAddressId: anchorContractAddress.id, - currency: 'Daix', - status: 'pending', - }, - }); - - const recurringDonationWithAnchorContract = await findRecurringDonationById( - recurringDonation.id, - ); - - await createRelatedDonationsToStream(recurringDonationWithAnchorContract!); - - const recurringDonationUpdated = await findRecurringDonationById( - recurringDonationWithAnchorContract!.id, - ); - - const donations = await Donation.createQueryBuilder('donation') - .where(`donation."recurringDonationId" = :recurringDonationId`, { - recurringDonationId: recurringDonationWithAnchorContract!.id, - }) - .getMany(); - - // STREAM TEST DATA HAS ENDED STATUS - assert.equal( - recurringDonationUpdated?.status, - RECURRING_DONATION_STATUS.ENDED, - ); - assert.equal(donations.length, 4); - assert.equal(true, true); // its not saving the recurring donation Id, saving as null - // add more tests, define criteria for verified - }); -} diff --git a/src/services/recurringDonationService.ts b/src/services/recurringDonationService.ts deleted file mode 100644 index 4d3bba1b0..000000000 --- a/src/services/recurringDonationService.ts +++ /dev/null @@ -1,707 +0,0 @@ -import path from 'path'; -import { promises as fs } from 'fs'; -import { ethers } from 'ethers'; -import { - getNotificationAdapter, - getSuperFluidAdapter, -} from '../adapters/adaptersFactory'; -import { Donation, DONATION_STATUS } from '../entities/donation'; -import { - RECURRING_DONATION_STATUS, - RecurringDonation, -} from '../entities/recurringDonation'; -import { Token } from '../entities/token'; -import { - getNetworkNameById, - getProvider, - NETWORK_IDS, - superTokensToToken, -} from '../provider'; -import { findProjectRecipientAddressByNetworkId } from '../repositories/projectAddressRepository'; -import { findProjectById } from '../repositories/projectRepository'; -import { - findRecurringDonationById, - updateRecurringDonationFromTheStreamDonations, -} from '../repositories/recurringDonationRepository'; -import { findUserById } from '../repositories/userRepository'; -import { ChainType } from '../types/network'; -import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; -import { logger } from '../utils/logger'; -import { - isTokenAcceptableForProject, - updateDonationPricesAndValues, -} from './donationService'; -import { updateUserTotalDonated, updateUserTotalReceived } from './userService'; -import config from '../config'; -import { User } from '../entities/user'; -import { NOTIFICATIONS_EVENT_NAMES } from '../analytics/analytics'; -import { relatedActiveQfRoundForProject } from './qfRoundService'; -import { updateProjectStatistics } from './projectService'; -import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; - -// Initially it will only be monthly data -export const priceDisplay = 'month'; - -export const fetchStreamTableStartDate = ( - recurringDonation: RecurringDonation, -): number => { - if (recurringDonation.donations && recurringDonation.donations.length > 0) { - const latestDonation = recurringDonation?.donations?.reduce( - (prev, current) => { - return prev.createdAt > current.createdAt ? prev : current; - }, - ); - - return Math.floor(latestDonation?.createdAt?.getTime() / 1000); - } - - return Math.floor(recurringDonation.createdAt.getTime() / 1000); -}; - -export const createRelatedDonationsToStream = async ( - recurringDonation: RecurringDonation, -) => { - const superFluidAdapter = getSuperFluidAdapter(); - const streamData = await superFluidAdapter.streamPeriods({ - address: recurringDonation.anchorContractAddress.address, - chain: recurringDonation.networkId, - start: fetchStreamTableStartDate(recurringDonation), - end: Math.floor(new Date().getTime() / 1000), - priceGranularity: priceDisplay, - virtualization: priceDisplay, - currency: 'USD', - recurringDonationTxHash: recurringDonation.txHash, - }); - - if ( - streamData && - recurringDonation.status === RECURRING_DONATION_STATUS.PENDING - ) { - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - } - - if (streamData.stoppedAtTimestamp) { - recurringDonation.finished = true; - recurringDonation.status = RECURRING_DONATION_STATUS.ENDED; - await recurringDonation.save(); - await getNotificationAdapter().userSuperTokensCritical({ - user: recurringDonation.donor, - eventName: NOTIFICATIONS_EVENT_NAMES.SUPER_TOKENS_BALANCE_DEPLETED, - tokenSymbol: recurringDonation.currency, - isEnded: recurringDonation.finished, - project: recurringDonation.project, - networkName: getNetworkNameById(recurringDonation.networkId), - }); - } - - const project = await findProjectById(recurringDonation.projectId); - const donorUser = await findUserById(recurringDonation.donorId); - - if (!project) return; - if (!donorUser) return; - - const uniquePeriods: any[] = []; - - for (const period of streamData.virtualPeriods) { - const existingPeriod = await Donation.findOne({ - where: { - recurringDonationId: recurringDonation.id, - virtualPeriodStart: period.startTime, - virtualPeriodEnd: period.endTime, - }, - }); - - if (!existingPeriod) { - uniquePeriods.push({ - startTime: period.startTime, - endTime: period.endTime, - amount: period.amount, - amountFiat: period.amountFiat, - }); - } - } - // create donation if any virtual period is missing - if (uniquePeriods.length === 0) return; - - for (const streamPeriod of uniquePeriods) { - try { - const environment = config.get('ENVIRONMENT') as string; - - const networkId: number = - environment !== 'production' - ? NETWORK_IDS.OPTIMISM_SEPOLIA - : NETWORK_IDS.OPTIMISTIC; - - const symbolCurrency = recurringDonation.currency.includes('x') - ? superTokensToToken[recurringDonation.currency] - : recurringDonation.currency; - const tokenInDb = await Token.findOne({ - where: { - networkId, - symbol: symbolCurrency, - }, - }); - const isCustomToken = !tokenInDb; - let isTokenEligibleForGivback = false; - if (isCustomToken && !project!.organization.supportCustomTokens) { - throw new Error(i18n.__(translationErrorMessagesKeys.TOKEN_NOT_FOUND)); - } else if (tokenInDb) { - const acceptsToken = await isTokenAcceptableForProject({ - projectId: project!.id, - tokenId: tokenInDb.id, - }); - if (!acceptsToken && !project!.organization.supportCustomTokens) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.PROJECT_DOES_NOT_SUPPORT_THIS_TOKEN, - ), - ); - } - isTokenEligibleForGivback = tokenInDb.isGivbackEligible; - } - const projectRelatedAddress = - await findProjectRecipientAddressByNetworkId({ - projectId: project.id, - networkId, - }); - if (!projectRelatedAddress) { - throw new Error( - i18n.__( - translationErrorMessagesKeys.THERE_IS_NO_RECIPIENT_ADDRESS_FOR_THIS_NETWORK_ID_AND_PROJECT, - ), - ); - } - - const toAddress = projectRelatedAddress?.address?.toLowerCase(); - const fromAddress = donorUser.walletAddress?.toLowerCase(); - const transactionTx = `${streamData.id?.toLowerCase()}-${streamPeriod.endTime}`; - const donation = Donation.create({ - amount: normalizeNegativeAmount( - streamPeriod.amount, - // All Super fluid tokens have 18 decimals even USDSx https://optimistic.etherscan.io/token/0x8430f084b939208e2eded1584889c9a66b90562f - 18, - ), - - // prevent setting NaN value for valueUsd - valueUsd: Math.abs(Number(streamPeriod.amountFiat)) || 0, - transactionId: transactionTx, - isFiat: false, - transactionNetworkId: networkId, - currency: tokenInDb?.symbol, - user: donorUser, - tokenAddress: tokenInDb?.address, - project, - status: DONATION_STATUS.VERIFIED, - isTokenEligibleForGivback, - isCustomToken, - isProjectVerified: project.verified, - createdAt: new Date(), - segmentNotified: false, - toWalletAddress: toAddress, - fromWalletAddress: fromAddress, - recurringDonation, - anonymous: Boolean(recurringDonation.anonymous), - chainType: ChainType.EVM, - virtualPeriodStart: streamPeriod.startTime, - virtualPeriodEnd: streamPeriod.endTime, - }); - - await donation.save(); - logger.debug(`Streamed donation has been created successfully`, { - donationId: donation.id, - recurringDonationId: recurringDonation.id, - amount: donation.amount, - }); - - const activeQfRoundForProject = await relatedActiveQfRoundForProject( - project.id, - ); - - if ( - activeQfRoundForProject && - activeQfRoundForProject.isEligibleNetwork(networkId) - ) { - const projectOwner = await User.findOneBy({ id: project.adminUserId }); - donation.qfRound = activeQfRoundForProject; - donation.qfRoundUserScore = projectOwner?.passportScore; - } - - await donation.save(); - - if (!donation.valueUsd || donation.valueUsd === 0) { - await updateDonationPricesAndValues( - donation, - project, - tokenInDb!, - donation.transactionNetworkId, - ); - } - - logger.debug(`Streamed donation After filling valueUsd`, { - donationId: donation.id, - recurringDonationId: recurringDonation.id, - amount: donation.amount, - valueUsd: donation.valueUsd, - }); - await updateRecurringDonationFromTheStreamDonations(recurringDonation.id); - - await updateUserTotalDonated(donation.userId); - - // After updating price we update totalDonations - await updateProjectStatistics(donation.projectId); - await updateUserTotalReceived(project!.adminUser.id); - } catch (e) { - logger.error( - 'createRelatedDonationsToStream() error', - { - recurringDonationId: recurringDonation.id, - }, - e, - ); - } - } -}; - -export function normalizeNegativeAmount( - amount: string, - decimals: number, -): number { - return Math.abs(Number(amount)) / 10 ** decimals; -} - -export const getRecurringDonationTxInfo = async (params: { - txHash: string; - networkId: number; - isBatch: boolean; -}): Promise< - { - receiver: string; - flowRate: string; - tokenAddress: string; - }[] -> => { - const { txHash, networkId, isBatch } = params; - const output: { - receiver: string; - flowRate: string; - tokenAddress: string; - }[] = []; - - logger.debug('getRecurringDonationTxInfo() has been called', params); - - try { - const web3Provider = getProvider(networkId); - const networkData = await web3Provider.getTransaction(txHash); - if (!networkData) { - logger.error( - 'Transaction not found in the network. maybe its not mined yet', - { - networkId, - txHash, - }, - ); - throw new Error( - i18n.__(translationErrorMessagesKeys.TRANSACTION_NOT_FOUND), - ); - } - - let receiverLowercase = ''; - let flowRateBigNumber = ''; - let tokenAddress = ''; - - if (!isBatch) { - const abiPath = path.join(__dirname, '../abi/superFluidAbi.json'); - const abi = JSON.parse(await fs.readFile(abiPath, 'utf-8')); - const iface = new ethers.utils.Interface(abi); - const decodedData = iface.parseTransaction({ data: networkData.data }); - tokenAddress = decodedData.args[0].toLowerCase(); - receiverLowercase = decodedData.args[2].toLowerCase(); - flowRateBigNumber = decodedData.args[3]; - output.push({ - tokenAddress, - receiver: receiverLowercase, - flowRate: ethers.BigNumber.from(flowRateBigNumber).toString(), - }); - } else { - // ABI comes from https://sepolia-optimism.etherscan.io/address/0x78743a68d52c9d6ccf3ff4558f3af510592e3c2d#code - const abiPath = path.join(__dirname, '../abi/superFluidAbiBatch.json'); - const abi = JSON.parse(await fs.readFile(abiPath, 'utf-8')); - const iface = new ethers.utils.Interface(abi); - const decodedData = iface.parseTransaction({ data: networkData.data }); - - for (const bachItem of decodedData.args[0]) { - // console.log('opData', decodedData.args) - const operationData = bachItem[2]; - const decodedOperationData = ethers.utils.defaultAbiCoder.decode( - ['bytes', 'bytes'], - operationData, - ); - const abiPath2 = path.join( - __dirname, - '../abi/superFluidAbi_batch_decoded.json', - ); - const decodedDataAbi = JSON.parse(await fs.readFile(abiPath2, 'utf-8')); - const decodedDataIface = new ethers.utils.Interface(decodedDataAbi); - const finalDecodedData = decodedDataIface.parseTransaction({ - data: decodedOperationData[0], - }); - receiverLowercase = finalDecodedData.args[1].toLowerCase(); - flowRateBigNumber = finalDecodedData.args[2]; - tokenAddress = decodedData.args[0].toLowerCase(); - output.push({ - tokenAddress, - receiver: receiverLowercase, - flowRate: ethers.BigNumber.from(flowRateBigNumber).toString(), - }); - } - } - - return output; - } catch (e) { - logger.error('getRecurringDonationTxInfo() error', { - error: e, - params, - }); - throw e; - } -}; - -export const updateRecurringDonationStatusWithNetwork = async (params: { - donationId: number; -}): Promise => { - logger.debug( - 'updateRecurringDonationStatusWithNetwork() has been called', - params, - ); - const recurringDonation = await findRecurringDonationById(params.donationId); - if (!recurringDonation) { - throw new Error('Recurring donation not found'); - } - - try { - const superFluidAdapter = getSuperFluidAdapter(); - const txData = await superFluidAdapter.getFlowByTxHash({ - receiver: - recurringDonation?.anchorContractAddress?.address?.toLowerCase() as string, - flowRate: recurringDonation.flowRate, - sender: recurringDonation?.donor?.walletAddress?.toLowerCase() as string, - transactionHash: recurringDonation.txHash, - }); - if (!txData) { - throw new Error( - `SuperFluid tx not found in the subgraph txHash:${recurringDonation.txHash}`, - ); - } - recurringDonation.status = RECURRING_DONATION_STATUS.ACTIVE; - await recurringDonation.save(); - const project = recurringDonation.project; - const projectOwner = await User.findOneBy({ id: project.adminUserId }); - await getNotificationAdapter().donationReceived({ - project, - user: projectOwner, - donation: recurringDonation, - }); - return recurringDonation; - } catch (e) { - logger.error('updateRecurringDonationStatusWithNetwork() error', { - error: e, - params, - }); - return recurringDonation; - } -}; - -export const recurringDonationsCountPerDateRange = async ( - fromDate?: string, - toDate?: string, - networkId?: number, - onlyVerified?: boolean, -): Promise => { - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .select('COALESCE(COUNT(recurringDonation.id), 0)', 'count') - .where('recurringDonation.status = :status', { - status: RECURRING_DONATION_STATUS.ACTIVE, - }); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - const recurringDonationsCount = await query - .cache( - `recurringDonationsCountPerDateRange-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ) - .getRawOne(); - - return recurringDonationsCount.count; -}; - -export const recurringDonationsCountPerDateRangePerMonth = async ( - fromDate?: string, - toDate?: string, - networkId?: number, - onlyVerified?: boolean, -): Promise => { - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .select('COUNT(recurringDonation.id)', 'total') - .addSelect("TO_CHAR(recurringDonation.createdAt, 'YYYY/MM')", 'date') - .where('recurringDonation.status = :status', { - status: RECURRING_DONATION_STATUS.ACTIVE, - }); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - query.groupBy('date'); - query.orderBy('date', 'ASC'); - - query.cache( - `recurringDonationsCountPerDateRangePerMonthAndYear-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ); - - return query.getRawMany(); -}; - -export const recurringDonationsStreamedCUsdTotal = async ( - fromDate?: string, - toDate?: string, - networkId?: number, - onlyVerified?: boolean, -): Promise => { - const query = RecurringDonation.createQueryBuilder( - 'recurringDonation', - ).select('COALESCE(SUM(recurringDonation.totalUsdStreamed), 0)', 'total'); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - const recurringDonationsTotal = await query - .cache( - `recurringDonationsStreamedCUsdTotal-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ) - .getRawOne(); - - return recurringDonationsTotal.total; -}; - -export const recurringDonationsStreamedCUsdTotalPerMonth = async ( - fromDate?: string, - toDate?: string, - networkId?: number, - onlyVerified?: boolean, -): Promise => { - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .select('SUM(recurringDonation.totalUsdStreamed)', 'total') - .addSelect("TO_CHAR(recurringDonation.createdAt, 'YYYY/MM')", 'date'); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - query.groupBy('date'); - query.orderBy('date', 'ASC'); - - const recurringDonationsTotal = await query - .cache( - `recurringDonationsStreamedCUsdTotalPerMonth-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ) - .getRawMany(); - - return recurringDonationsTotal; -}; - -export const recurringDonationsTotalPerToken = async (params: { - fromDate?: string; - toDate?: string; - networkId?: number; - onlyVerified?: boolean; -}): Promise<{ token: string; total: number }[]> => { - const { fromDate, toDate, networkId, onlyVerified } = params; - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .select('recurringDonation.currency', 'token') - .addSelect('COALESCE(SUM(recurringDonation.totalUsdStreamed), 0)', 'total') - .groupBy('recurringDonation.currency') - .having('SUM(recurringDonation.totalUsdStreamed) > 0'); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - const recurringDonationsTotal = await query - .cache( - `recurringDonationsTotalPerToken-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ) - .getRawMany(); - - return recurringDonationsTotal; -}; - -export const recurringDonationsCountPerToken = async (params: { - fromDate?: string; - toDate?: string; - networkId?: number; - onlyVerified?: boolean; -}): Promise<{ token: string; total: number }[]> => { - const { fromDate, toDate, networkId, onlyVerified } = params; - const query = RecurringDonation.createQueryBuilder('recurringDonation') - .select('recurringDonation.currency', 'token') - .addSelect('COALESCE(COUNT(recurringDonation.id), 0)', 'total') - .where('recurringDonation.status = :status', { - status: RECURRING_DONATION_STATUS.ACTIVE, - }) - .groupBy('recurringDonation.currency') - .having('COUNT(recurringDonation.id) > 0'); - - if (fromDate) { - query.andWhere('recurringDonation.createdAt >= :fromDate', { - fromDate: new Date(fromDate), - }); - } - - if (toDate) { - query.andWhere('recurringDonation.createdAt <= :toDate', { - toDate: new Date(toDate), - }); - } - - if (networkId) { - query.andWhere('recurringDonation.networkId = :networkId', { - networkId, - }); - } - - if (onlyVerified) { - query - .leftJoin('recurringDonation.project', 'project') - .andWhere('project.verified = :verified', { - verified: true, - }); - } - - const recurringDonationsTotal = await query - .cache( - `recurringDonationsCountPerToken-${fromDate || ''}-${toDate || ''}-${networkId || 'all'}-${onlyVerified || 'all'}`, - 300000, - ) - .getRawMany(); - - return recurringDonationsTotal; -}; diff --git a/src/services/recurringDonationStreamQueue.ts b/src/services/recurringDonationStreamQueue.ts deleted file mode 100644 index 4df0e13f9..000000000 --- a/src/services/recurringDonationStreamQueue.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Bull from 'bull'; -import { logger } from '../utils/logger'; -import { - findActiveRecurringDonations, - findRecurringDonationById, -} from '../repositories/recurringDonationRepository'; -import { redisConfig } from '../redis'; -import config from '../config'; -import { getCurrentDateFormatted } from '../utils/utils'; -import { createRelatedDonationsToStream } from './recurringDonationService'; - -const updateRecurringDonationsStreamQueue = new Bull( - 'update-recurring-donations-stream-queue', - { - redis: redisConfig, - }, -); -const TWO_MINUTES = 1000 * 60 * 2; -setInterval(async () => { - const updateRecurringDonationsStreamQueueCount = - await updateRecurringDonationsStreamQueue.count(); - logger.debug(`Update recurring donations stream job queues count:`, { - updateRecurringDonationsStreamQueueCount, - }); -}, TWO_MINUTES); - -export const updateRecurringDonationsStream = async () => { - logger.debug('updateRecurringDonationsStream Job Queue has been called'); - - const recurringDonations = await findActiveRecurringDonations(); - logger.debug( - 'updateRecurringDonationsStream-Active recurring donations length', - recurringDonations.length, - ); - recurringDonations.forEach(recurringDonation => { - logger.debug( - 'updateRecurringDonationsStream-Add pending recurringDonation to queue', - { - recurringDonationId: recurringDonation.id, - }, - ); - updateRecurringDonationsStreamQueue.add( - { - recurringDonationId: recurringDonation.id, - }, - { - // Because we want to run this job once per day so we need to add the date to the job id - jobId: `update-recurring-donations-stream-queue-${getCurrentDateFormatted()}-${ - recurringDonation.id - }`, - removeOnComplete: true, - removeOnFail: true, - }, - ); - }); -}; - -export function processRecurringDonationStreamJobs() { - logger.debug('processRecurringDonationStreamJobs() has been called'); - updateRecurringDonationsStreamQueue.process( - numberOfUpdateRecurringDonationsStreamConcurrentJob, - async (job, done) => { - const { recurringDonationId } = job.data; - logger.debug('job processing', { jobData: job.data }); - try { - await updateRecurringDonationStream({ recurringDonationId }); - done(); - } catch (e) { - logger.error( - 'processRecurringDonationStreamJobs >> updateRecurringDonationStream error', - e, - ); - done(); - } - }, - ); -} - -const numberOfUpdateRecurringDonationsStreamConcurrentJob = - Number(config.get('NUMBER_OF_UPDATE_RECURRING_DONATION_CONCURRENT_JOB')) || 1; - -export const updateRecurringDonationStream = async (params: { - recurringDonationId: number; -}) => { - logger.debug( - 'updateRecurringDonationStream() has been called for id', - params.recurringDonationId, - ); - const recurringDonation = await findRecurringDonationById( - params.recurringDonationId, - ); - - if (!recurringDonation) return; - await createRelatedDonationsToStream(recurringDonation); -}; diff --git a/src/services/userService.ts b/src/services/userService.ts index b8915aef1..993a432e6 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -13,11 +13,7 @@ export const updateUserTotalDonated = async (userId: number) => { SET "totalDonated" = ( SELECT COALESCE(SUM(d."valueUsd"),0) FROM donation as d - WHERE d."userId" = $1 AND d."status" = 'verified' AND d."recurringDonationId" IS NULL - ) + ( - SELECT COALESCE(SUM(rd."totalUsdStreamed"), 0) - FROM recurring_donation as rd - WHERE rd."donorId" = $1 + WHERE d."userId" = $1 AND d."status" = 'verified' ) WHERE "id" = $1 `, diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 253bcdac6..d312b2397 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -19,7 +19,6 @@ export const setI18nLocaleForRequest = async (req, _res, next) => { }; export const errorMessages = { - FIAT_DONATION_ALREADY_EXISTS: 'Onramper donation already exists', CAMPAIGN_NOT_FOUND: 'Campaign not found', QF_ROUND_NOT_FOUND: 'qf round not found', NONE_OF_WALLET_ADDRESSES_FOUND_IN_DB: @@ -27,8 +26,6 @@ export const errorMessages = { NO_VALID_PROJECTS_FOUND: 'No valid project slug found in the CSV', THERE_IS_NOT_ANY_FEATURED_CAMPAIGN: 'There is not any featured campaign', CHAINVINE_REFERRER_NOT_FOUND: 'Chainvine referrer not found', - ONRAMPER_SIGNATURE_INVALID: 'Onramper signature invalid', - ONRAMPER_SIGNATURE_MISSING: 'Onramper signature missing', UPLOAD_FAILED: 'Upload file failed', CHANGE_API_INVALID_TITLE_OR_EIN: 'ChangeAPI title or EIN not found or invalid', @@ -103,12 +100,9 @@ export const errorMessages = { 'There is already an anchor address for this project', THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT: 'There is not anchor address for this project', - PROJECT_DOESNT_HAVE_ANY_ADDRESS_ON_THIS_NETWORK_FOR_RECURRING_DONATION: - 'Project doesnt have any address on this network for recurring donation', PROJECT_DOESNT_HAVE_RECIPIENT_ADDRESS_ON_THIS_NETWORK: 'Project doesnt have recipient address on this network', PROJECT_IS_NOT_ACTIVE: 'Project is not active.', - RECURRING_DONATION_NOT_FOUND: 'Recurring donation not found.', INVALID_FUNCTION: 'Invalid function name of transaction', PROJECT_UPDATE_NOT_FOUND: 'Project update not found.', DONATION_NOT_FOUND: 'donation not found', @@ -185,8 +179,6 @@ export const errorMessages = { TX_NOT_FOUND: 'Transaction not found', INVALID_PROJECT_ID: 'Invalid project id', INVALID_PROJECT_OWNER: 'Project owner is invalid', - PROJECT_DOESNT_ACCEPT_RECURRING_DONATION: - 'Project does not accept recurring donation', }; export const translationErrorMessagesKeys = { @@ -197,8 +189,6 @@ export const translationErrorMessagesKeys = { CHAINVINE_CLICK_EVENT_ERROR: 'CHAINVINE_CLICK_EVENT_ERROR', CHAINVINE_REGISTRATION_ERROR: 'CHAINVINE_REGISTRATION_ERROR', FIAT_DONATION_ALREADY_EXISTS: 'FIAT_DONATION_ALREADY_EXISTS', - ONRAMPER_SIGNATURE_INVALID: 'ONRAMPER_SIGNATURE_INVALID', - ONRAMPER_SIGNATURE_MISSING: 'ONRAMPER_SIGNATURE_MISSING', UPLOAD_FAILED: 'UPLOAD_FAILED', CHANGE_API_INVALID_TITLE_OR_EIN: 'CHANGE_API_INVALID_TITLE_OR_EIN', INVALID_SOCIAL_NETWORK: 'INVALID_SOCIAL_NETWORK', @@ -265,17 +255,12 @@ export const translationErrorMessagesKeys = { YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT: 'YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT', PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND', - PROJECT_DOESNT_ACCEPT_RECURRING_DONATION: - 'Project does not accept recurring donation', - RECURRING_DONATION_NOT_FOUND: 'Recurring donation not found.', PROJECT_DOESNT_HAVE_RECIPIENT_ADDRESS_ON_THIS_NETWORK: 'Project doesnt have recipient address on this network', THERE_IS_AN_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT: 'There is already an anchor address for this project', THERE_IS_NOT_ACTIVE_ANCHOR_ADDRESS_FOR_THIS_PROJECT: 'There is not anchor address for this project', - PROJECT_DOESNT_HAVE_ANY_ADDRESS_ON_THIS_NETWORK_FOR_RECURRING_DONATION: - 'Project doesnt have any address on this network for recurring donation', PROJECT_IS_NOT_ACTIVE: 'PROJECT_IS_NOT_ACTIVE', INVALID_FUNCTION: 'INVALID_FUNCTION', PROJECT_UPDATE_NOT_FOUND: 'PROJECT_UPDATE_NOT_FOUND', @@ -340,6 +325,5 @@ export const translationErrorMessagesKeys = { PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED: 'PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED', DRAFT_DONATION_DISABLED: 'DRAFT_DONATION_DISABLED', - DRAFT_RECURRING_DONATION_DISABLED: 'DRAFT_RECURRING_DONATION_DISABLED', EVM_SUPPORT_ONLY: 'EVM_SUPPORT_ONLY', }; diff --git a/src/utils/locales/en.json b/src/utils/locales/en.json index eaa654299..c98912f12 100644 --- a/src/utils/locales/en.json +++ b/src/utils/locales/en.json @@ -3,8 +3,6 @@ "CHAINVINE_CLICK_EVENT_ERROR": "Unable to register click event or link donor", "CHAINVINE_REGISTRATION_ERROR": "Chainvine ID failed to be generated for the user", "FIAT_DONATION_ALREADY_EXISTS": "Fiat donation already exists", - "ONRAMPER_SIGNATURE_INVALID": "Request payload or signature is invalid", - "ONRAMPER_SIGNATURE_MISSING": "Request headers does not contain signature", "UPLOAD_FAILED": "Upload file failed", "CHANGE_API_INVALID_TITLE_OR_EIN": "ChangeAPI title or EIN not found or invalid", "INVALID_SOCIAL_NETWORK": "Invalid social network", @@ -104,10 +102,6 @@ "There is not anchor address for this project": "There is not anchor address for this project", "DRAFT_DONATION_DISABLED": "Draft donation is disabled", "EVM_SUPPORT_ONLY": "Only EVM support", - "Recurring donation not found": "Recurring donation not found", - "Recurring donation not found.": "Recurring donation not found.", "INVALID_PROJECT_ID": "INVALID_PROJECT_ID", - "TX_NOT_FOUND": "TX_NOT_FOUND", - "PROJECT_DOESNT_ACCEPT_RECURRING_DONATION": "PROJECT_DOESNT_ACCEPT_RECURRING_DONATION", - "Project does not accept recurring donation": "Project does not accept recurring donation" + "TX_NOT_FOUND": "TX_NOT_FOUND" } \ No newline at end of file diff --git a/src/utils/locales/es.json b/src/utils/locales/es.json index cda161a0f..d7d54ef92 100644 --- a/src/utils/locales/es.json +++ b/src/utils/locales/es.json @@ -3,8 +3,6 @@ "CHAINVINE_CLICK_EVENT_ERROR": "No fue posible registrar el click-event", "CHAINVINE_REGISTRATION_ERROR": "Chainvine ID no pudo ser generado para el usuario", "FIAT_DONATION_ALREADY_EXISTS": "La donación Fiat ya existe", - "ONRAMPER_SIGNATURE_INVALID": "El cuerpo o firma son invalidos", - "ONRAMPER_SIGNATURE_MISSING": "El encabezado no continue la firma", "UPLOAD_FAILED": "No fue posible subir el archivo", "CHANGE_API_INVALID_TITLE_OR_EIN": "ChangeAPI título de API o EIN no encontrado o no válido", "INVALID_SOCIAL_NETWORK": "Red social inválida", diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index 0d0f6f0a0..5747900d4 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -151,20 +151,6 @@ export const createDraftDonationQueryValidator = Joi.object({ relevantDonationTxHash: Joi.string().allow(null, ''), }); -export const createDraftRecurringDonationQueryValidator = Joi.object({ - networkId: Joi.number() - .required() - .valid(...Object.values(NETWORK_IDS)), - currency: Joi.string().required(), - flowRate: Joi.string().required(), - projectId: Joi.number().integer().min(0).required(), - recurringDonationId: Joi.number().integer(), - anonymous: Joi.boolean(), - isBatch: Joi.boolean(), - isForUpdate: Joi.boolean(), - chainType: Joi.string().required(), -}); - export const updateDonationQueryValidator = Joi.object({ donationId: Joi.number().integer().min(0).required(), status: Joi.string().valid(DONATION_STATUS.VERIFIED, DONATION_STATUS.FAILED), diff --git a/src/workers/draftRecurringDonationMatchWorker.ts b/src/workers/draftRecurringDonationMatchWorker.ts deleted file mode 100644 index 1fe617137..000000000 --- a/src/workers/draftRecurringDonationMatchWorker.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { expose } from 'threads/worker'; -import { WorkerModule } from 'threads/dist/types/worker'; -import { DRAFT_DONATION_STATUS } from '../entities/draftDonation'; -import { matchDraftRecurringDonations } from '../services/chains/evm/draftRecurringDonationService'; -import { logger } from '../utils/logger'; -import { AppDataSource } from '../orm'; -import { DraftRecurringDonation } from '../entities/draftRecurringDonation'; - -type DraftRecurringDonationWorkerFunctions = 'matchDraftRecurringDonations'; - -export type DrafRecurringtDonationWorker = - WorkerModule; - -const TAKE_DRAFT_RECURRING_DONATION = 1000; - -const worker: DrafRecurringtDonationWorker = { - async matchDraftRecurringDonations() { - await AppDataSource.initialize(false); - // const dataSource = await AppDataSource.getDataSource(); - try { - let draftDonationSkip = 0; - // eslint-disable-next-line no-constant-condition - while (true) { - const draftRecurringDonations = await DraftRecurringDonation.find({ - where: { - status: DRAFT_DONATION_STATUS.PENDING, - }, - order: { networkId: 'ASC' }, - take: TAKE_DRAFT_RECURRING_DONATION, - skip: draftDonationSkip, - }); - - if (draftRecurringDonations.length === 0) break; - - await matchDraftRecurringDonations(draftRecurringDonations); - if (draftRecurringDonations.length < TAKE_DRAFT_RECURRING_DONATION) { - break; - } else { - draftDonationSkip += draftRecurringDonations.length; - } - } - } catch (e) { - logger.error('Error in matchDraftRecurringDonations worker', e); - } - }, -}; - -expose(worker); diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index f2894c2e3..252db5d89 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -67,30 +67,6 @@ export const createDraftDonationMutation = ` } `; -export const createDraftRecurringDonationMutation = ` - mutation ( - $networkId: Float! - $currency: String! - $projectId: Float! - $recurringDonationId: Float - $anonymous: Boolean - $isBatch: Boolean - $isForUpdate: Boolean - $flowRate: String! - ) { - createDraftRecurringDonation( - networkId: $networkId - currency: $currency - recurringDonationId: $recurringDonationId - projectId: $projectId - anonymous: $anonymous - isBatch: $isBatch - isForUpdate: $isForUpdate - flowRate: $flowRate - ) - } -`; - export const updateDonationStatusMutation = ` mutation ( $status: String @@ -106,20 +82,6 @@ export const updateDonationStatusMutation = ` } } `; -export const updateRecurringDonationStatusMutation = ` - mutation ( - $status: String - $donationId: Float! - ) { - updateRecurringDonationStatus( - status: $status - donationId: $donationId - ){ - id - status - } - } -`; export const createProjectQuery = ` mutation ($project: CreateProjectInput!) { @@ -358,103 +320,6 @@ export const fetchDonationsByProjectIdQuery = ` } totalCount totalUsdBalance - recurringDonationsCount - } - } -`; -export const fetchRecurringDonationsByProjectIdQuery = ` - query ( - $take: Int - $skip: Int - $projectId: Int! - $searchTerm: String - $status: String - $includeArchived: Boolean - $finishStatus: FinishStatus - $orderBy: RecurringDonationSortBy - ) { - recurringDonationsByProjectId( - take: $take - skip: $skip - projectId: $projectId - searchTerm: $searchTerm - status: $status - includeArchived: $includeArchived - finishStatus: $finishStatus - orderBy: $orderBy - - ) { - recurringDonations { - id - txHash - networkId - flowRate - currency - anonymous - isArchived - status - donor { - id - walletAddress - firstName - email - } - createdAt - } - totalCount - } - } -`; - -export const fetchRecurringDonationsByUserIdQuery = ` - query ( - $take: Int - $skip: Int - $status: String - $includeArchived: Boolean - $orderBy: RecurringDonationSortBy - $finishStatus: FinishStatus - $userId: Int! - $filteredTokens: [String!] - ) { - recurringDonationsByUserId( - take: $take - skip: $skip - orderBy: $orderBy - userId: $userId - status: $status - includeArchived: $includeArchived - finishStatus: $finishStatus - filteredTokens: $filteredTokens - ) { - recurringDonations { - id - txHash - networkId - flowRate - currency - anonymous - status - isArchived - donor { - id - walletAddress - firstName - email - } - project { - id - title - slug - anchorContracts { - id - address - isActive - } - } - createdAt - } - totalCount } } `; @@ -685,10 +550,6 @@ export const fetchAllDonationsQuery = ` anonymous valueUsd amount - recurringDonation{ - id - txHash - } user { id walletAddress @@ -2129,153 +1990,6 @@ export const createAnchorContractAddressQuery = ` } `; -export const createRecurringDonationQuery = ` - mutation ($projectId: Int!, - $networkId: Int!, - $txHash: String! - $flowRate: String! - $currency: String! - $anonymous: Boolean - $isBatch: Boolean - ) { - createRecurringDonation( - projectId: $projectId - networkId: $networkId - txHash:$txHash - flowRate: $flowRate - currency:$currency - anonymous:$anonymous - isBatch:$isBatch - ) { - txHash - networkId - anonymous - isArchived - isBatch - } - } -`; - -export const updateRecurringDonationQueryById = ` - mutation ( - $recurringDonationId: Int!, - $projectId: Int!, - $networkId: Int!, - $currency: String!, - $txHash: String - $flowRate: String - $anonymous: Boolean - $isArchived: Boolean - $status: String - ) { - updateRecurringDonationParamsById( - recurringDonationId: $recurringDonationId - projectId: $projectId - networkId: $networkId - currency:$currency - txHash:$txHash - anonymous:$anonymous - flowRate:$flowRate - status:$status - isArchived:$isArchived - ) { - txHash - networkId - currency - flowRate - anonymous - status - isArchived - finished - } - } -`; - -export const updateRecurringDonationQuery = ` - mutation ( - $projectId: Int!, - $networkId: Int!, - $currency: String!, - $txHash: String - $flowRate: String - $anonymous: Boolean - $isArchived: Boolean - $status: String - ) { - updateRecurringDonationParams( - projectId: $projectId - networkId: $networkId - currency:$currency - txHash:$txHash - anonymous:$anonymous - flowRate:$flowRate - status:$status - isArchived:$isArchived - ) { - txHash - networkId - currency - flowRate - anonymous - status - isArchived - finished - } - } -`; - -export const fetchRecurringDonationsCount = ` - query ( - $fromDate: String - $toDate: String - $networkId: Float - $onlyVerified: Boolean - ) { - recurringDonationsCountPerDate ( - fromDate: $fromDate - toDate: $toDate - networkId: $networkId - onlyVerified: $onlyVerified - ) { - total - totalPerMonthAndYear { - total - date - } - totalPerToken { - token - total - } - } - } -`; - -export const fetchRecurringDonationsTotalUSD = ` - query ( - $fromDate: String - $toDate: String - $networkId: Float - $onlyVerified: Boolean - ) { - recurringDonationsTotalStreamedUsdPerDate ( - fromDate: $fromDate - toDate: $toDate - networkId: $networkId - onlyVerified: $onlyVerified - ) { - total - totalPerMonthAndYear { - total - date - } - totalPerToken { - token - total - } - } - } -`; - export const fetchDonationMetricsQuery = ` query ( $startDate: String! diff --git a/test/testUtils.ts b/test/testUtils.ts index 99c5aee6e..b147a7cc6 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -33,7 +33,6 @@ import { MainCategory } from '../src/entities/mainCategory'; import { Category, CATEGORY_NAMES } from '../src/entities/category'; import { FeaturedUpdate } from '../src/entities/featuredUpdate'; import { ChainType } from '../src/types/network'; -import { RecurringDonation } from '../src/entities/recurringDonation'; import { AnchorContractAddress } from '../src/entities/anchorContractAddress'; import { findProjectById } from '../src/repositories/projectRepository'; import { ProjectAddress } from '../src/entities/projectAddress'; @@ -577,7 +576,6 @@ export const SEED_DATA = { website: 'https://thegivingblock.com', disableUpdateEnforcement: true, disableNotifications: true, - disableRecurringDonations: true, supportCustomTokens: false, }, { @@ -1984,51 +1982,6 @@ 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 anonymous = params?.donationData?.anonymous || false; - const anchorContractAddressId = - params?.donationData?.anchorContractAddressId || - ( - await saveAnchorContractDirectlyToDb({ - creatorId: donorId, - projectId, - }) - ).id; - return RecurringDonation.create({ - flowRate: params?.donationData?.flowRate || '10', - totalUsdStreamed: params?.donationData?.totalUsdStreamed || 0, - status: params?.donationData?.status || 'pending', - networkId: params?.donationData?.networkId || NETWORK_IDS.OPTIMISM_SEPOLIA, - currency: params?.donationData?.currency || 'USDT', - finished: - params?.donationData?.finished !== undefined - ? params?.donationData?.finished - : false, - isArchived: - params?.donationData?.isArchived !== undefined - ? params?.donationData?.isArchived - : false, - isBatch: - params?.donationData?.isBatch !== undefined - ? params?.donationData?.isBatch - : false, - txHash: params?.donationData?.txHash || generateRandomEtheriumAddress(), - anonymous, - donorId, - projectId, - anchorContractAddressId, - createdAt: params?.donationData?.createdAt || moment(), - }).save(); -}; - export const saveCategoryDirectlyToDb = async (categoryData: CategoryData) => { return Category.create(categoryData as Category).save(); };