-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added draftDonation resolver and entity * Added few tests Incomplete matchDraftDoantions * Implemented draft donation matching logic * Prepared draft donation matching worker draft * Added support for draft donation matching worker run * Updated delete expired draft donations Integrated delete expired draft donation to draft donation matcher job * Added enum creation to migration file * Added sequence to migration file * Reorder sql qury run * Fixed id colum creation issue in migration file * Removed settings from test.env * rename draft worker --------- Co-authored-by: Carlos <[email protected]>
- Loading branch information
1 parent
9128bb8
commit f5d4319
Showing
33 changed files
with
1,629 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { MigrationInterface, QueryRunner } from 'typeorm'; | ||
|
||
export class AddDraftDonationTable1707738577647 implements MigrationInterface { | ||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(` | ||
DO $$ | ||
BEGIN | ||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'draft_donation_chaintype_enum') THEN | ||
CREATE TYPE public.draft_donation_chaintype_enum AS ENUM | ||
('EVM', 'SOLANA'); | ||
END IF; | ||
ALTER TYPE public.draft_donation_chaintype_enum | ||
OWNER TO postgres; | ||
END$$;`); | ||
|
||
await queryRunner.query(` | ||
DO $$ | ||
BEGIN | ||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'draft_donation_status_enum') THEN | ||
CREATE TYPE public.draft_donation_status_enum AS ENUM | ||
('pending', 'matched', 'failed'); | ||
END IF; | ||
ALTER TYPE public.draft_donation_status_enum | ||
OWNER TO postgres; | ||
END$$; | ||
`); | ||
|
||
await queryRunner.query(` | ||
CREATE TABLE IF NOT EXISTS public.draft_donation | ||
( | ||
"id" SERIAL NOT NULL, | ||
"networkId" integer NOT NULL, | ||
"chainType" draft_donation_chaintype_enum NOT NULL DEFAULT 'EVM'::draft_donation_chaintype_enum, | ||
status draft_donation_status_enum NOT NULL DEFAULT 'pending'::draft_donation_status_enum, | ||
"toWalletAddress" character varying COLLATE pg_catalog."default" NOT NULL, | ||
"fromWalletAddress" character varying COLLATE pg_catalog."default" NOT NULL, | ||
"tokenAddress" character varying COLLATE pg_catalog."default", | ||
currency character varying COLLATE pg_catalog."default" NOT NULL, | ||
anonymous boolean, | ||
amount real NOT NULL, | ||
"projectId" integer, | ||
"userId" integer, | ||
"createdAt" timestamp without time zone NOT NULL DEFAULT now(), | ||
"referrerId" character varying COLLATE pg_catalog."default", | ||
"expectedCallData" character varying COLLATE pg_catalog."default", | ||
"errorMessage" character varying COLLATE pg_catalog."default", | ||
CONSTRAINT "PK_4f2eb58b84fb470edcd483c78af" PRIMARY KEY (id) | ||
) | ||
TABLESPACE pg_default; | ||
ALTER TABLE IF EXISTS public.draft_donation | ||
OWNER to postgres; | ||
CREATE INDEX IF NOT EXISTS "IDX_287bf9818fca5b436122847223" | ||
ON public.draft_donation USING btree | ||
("userId" ASC NULLS LAST) | ||
TABLESPACE pg_default | ||
WHERE status = 'pending'::draft_donation_status_enum; | ||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_af180374473ea402e7595196a6" | ||
ON public.draft_donation USING btree | ||
("fromWalletAddress" COLLATE pg_catalog."default" ASC NULLS LAST, "toWalletAddress" COLLATE pg_catalog."default" ASC NULLS LAST, "networkId" ASC NULLS LAST, amount ASC NULLS LAST, currency COLLATE pg_catalog."default" ASC NULLS LAST) | ||
TABLESPACE pg_default | ||
WHERE status = 'pending'::draft_donation_status_enum; | ||
CREATE INDEX IF NOT EXISTS "IDX_029453ee31e092317f7f96ee3b" | ||
ON public.draft_donation USING btree | ||
("createdAt" ASC NULLS LAST) | ||
TABLESPACE pg_default; | ||
CREATE INDEX IF NOT EXISTS "IDX_ff4b8666a0090d059f00c59216" | ||
ON public.draft_donation USING btree | ||
(status ASC NULLS LAST) | ||
TABLESPACE pg_default | ||
WHERE status = 'pending'::draft_donation_status_enum; | ||
`); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { Field, ID, Int, ObjectType } from 'type-graphql'; | ||
import { | ||
PrimaryGeneratedColumn, | ||
Column, | ||
Entity, | ||
BaseEntity, | ||
Index, | ||
CreateDateColumn, | ||
} from 'typeorm'; | ||
import { ChainType } from '../types/network'; | ||
|
||
export const DRAFT_DONATION_STATUS = { | ||
PENDING: 'pending', | ||
MATCHED: 'matched', | ||
FAILED: 'failed', | ||
}; | ||
|
||
@Entity() | ||
@ObjectType() | ||
// To mark the draft donation as matched, when the donation is created in DonationResolver | ||
@Index( | ||
['fromWalletAddress', 'toWalletAddress', 'networkId', 'amount', 'currency'], | ||
{ | ||
where: `status = '${DRAFT_DONATION_STATUS.PENDING}'`, | ||
unique: true, | ||
}, | ||
) | ||
export class DraftDonation extends BaseEntity { | ||
@Field(type => ID) | ||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@Field() | ||
@Column({ nullable: false }) | ||
networkId: number; | ||
|
||
// // TODO: support safeTransactionId | ||
// @Field() | ||
// @Column({ nullable: true }) | ||
// safeTransactionId?: string; | ||
|
||
@Field(type => String) | ||
@Column({ | ||
type: 'enum', | ||
enum: ChainType, | ||
default: ChainType.EVM, | ||
}) | ||
chainType: ChainType; | ||
|
||
@Field() | ||
@Column({ | ||
type: 'enum', | ||
enum: DRAFT_DONATION_STATUS, | ||
default: DRAFT_DONATION_STATUS.PENDING, | ||
}) | ||
@Index({ where: `status = '${DRAFT_DONATION_STATUS.PENDING}'` }) | ||
status: string; | ||
|
||
@Field() | ||
@Column() | ||
toWalletAddress: string; | ||
|
||
@Field() | ||
@Column() | ||
fromWalletAddress: string; | ||
|
||
@Field({ nullable: true }) | ||
@Column({ nullable: true }) | ||
tokenAddress: string; | ||
|
||
@Field() | ||
@Column() | ||
currency: string; | ||
|
||
@Field({ nullable: true }) | ||
@Column({ nullable: true }) | ||
anonymous: boolean; | ||
|
||
@Field() | ||
@Column({ type: 'real' }) | ||
amount: number; | ||
|
||
@Field() | ||
@Column({ nullable: true }) | ||
projectId: number; | ||
|
||
@Field() | ||
@Column({ nullable: true }) | ||
@Index({ where: `status = '${DRAFT_DONATION_STATUS.PENDING}'` }) | ||
userId: number; | ||
|
||
@Index() | ||
@Field(type => Date) | ||
@CreateDateColumn() | ||
createdAt: Date; | ||
|
||
@Field(type => String, { nullable: true }) | ||
@Column({ nullable: true }) | ||
referrerId?: string; | ||
|
||
// Expected call data used only for matching ERC20 transfers | ||
// Is calculated and saved once during the matching time, and will be used in next iterations | ||
@Field(type => String, { nullable: true }) | ||
@Column({ nullable: true }) | ||
expectedCallData?: string; | ||
|
||
@Field({ nullable: true }) | ||
@Column({ nullable: true }) | ||
errorMessage?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Create a draft donation | ||
|
||
import { expect } from 'chai'; | ||
import { generateRandomEtheriumAddress } from '../../test/testUtils'; | ||
import { | ||
DRAFT_DONATION_STATUS, | ||
DraftDonation, | ||
} from '../entities/draftDonation'; | ||
import { | ||
delecteExpiredDraftDonations, | ||
markDraftDonationStatusMatched, | ||
} from './draftDonationRepository'; | ||
|
||
// Mark the draft donation as matched | ||
describe('draftDonationRepository', () => { | ||
beforeEach(async () => { | ||
await DraftDonation.clear(); | ||
}); | ||
|
||
it('should mark a draft donation as matched', async () => { | ||
// Setup | ||
const draftDonation = await DraftDonation.create({ | ||
networkId: 1, | ||
status: DRAFT_DONATION_STATUS.PENDING, | ||
toWalletAddress: generateRandomEtheriumAddress(), | ||
fromWalletAddress: generateRandomEtheriumAddress(), | ||
tokenAddress: generateRandomEtheriumAddress(), | ||
currency: 'GIV', | ||
anonymous: false, | ||
amount: 0.01, | ||
}); | ||
|
||
await draftDonation.save(); | ||
|
||
await markDraftDonationStatusMatched({ | ||
fromWalletAddress: draftDonation.fromWalletAddress, | ||
toWalletAddress: draftDonation.toWalletAddress, | ||
networkId: draftDonation.networkId, | ||
currency: draftDonation.currency, | ||
amount: draftDonation.amount, | ||
}); | ||
|
||
const updatedDraftDonation = await DraftDonation.findOne({ | ||
where: { | ||
id: draftDonation.id, | ||
}, | ||
}); | ||
|
||
expect(updatedDraftDonation?.status).equal(DRAFT_DONATION_STATUS.MATCHED); | ||
}); | ||
|
||
it('should clear expired draft donations', async () => { | ||
// create a draft donation with createdAt two hours ago, and one with createdAt one hour ago | ||
await DraftDonation.create({ | ||
networkId: 1, | ||
status: DRAFT_DONATION_STATUS.PENDING, | ||
toWalletAddress: generateRandomEtheriumAddress(), | ||
fromWalletAddress: generateRandomEtheriumAddress(), | ||
tokenAddress: generateRandomEtheriumAddress(), | ||
currency: 'GIV', | ||
anonymous: false, | ||
amount: 1, | ||
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000), | ||
}).save(); | ||
|
||
await DraftDonation.create({ | ||
networkId: 1, | ||
status: DRAFT_DONATION_STATUS.PENDING, | ||
toWalletAddress: generateRandomEtheriumAddress(), | ||
fromWalletAddress: generateRandomEtheriumAddress(), | ||
tokenAddress: generateRandomEtheriumAddress(), | ||
currency: 'GIV', | ||
anonymous: false, | ||
amount: 1, | ||
createdAt: new Date(Date.now() - 1 * 60 * 60 * 1000), | ||
}).save(); | ||
|
||
await delecteExpiredDraftDonations(1.5); | ||
|
||
const count = await DraftDonation.createQueryBuilder().getCount(); | ||
|
||
expect(count).equal(1); | ||
}); | ||
}); |
Oops, something went wrong.