Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add early access rounds #37

Merged
merged 6 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,4 @@ ABC_LAUNCH_DATA_SOURCE=

QACC_NETWORK_ID=
QACC_DONATION_TOKEN_ADDRESS=
QACC_EARLY_ACCESS_ROUND_FINISH_TIMESTAMP=
ABC_LAUNCHER_ADAPTER=
41 changes: 41 additions & 0 deletions migration/1724799772891-addEarlyAccessRoundTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddEarlyAccessRoundTable1724799772891
implements MigrationInterface
{
name = 'AddEarlyAccessRoundTable1724799772891';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "donation" RENAME COLUMN "earlyAccessRound" TO "earlyAccessRoundId"`,
);
await queryRunner.query(
`CREATE TABLE "early_access_round" ("id" SERIAL NOT NULL, "roundNumber" integer NOT NULL, "startDate" TIMESTAMP NOT NULL, "endDate" TIMESTAMP NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_e2f9598b0bbed3f05ca5c49fedc" UNIQUE ("roundNumber"), CONSTRAINT "PK_b128520615d2666c576399b07d3" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "donation" DROP COLUMN "earlyAccessRoundId"`,
);
await queryRunner.query(
`ALTER TABLE "donation" ADD "earlyAccessRoundId" integer`,
);
await queryRunner.query(
`ALTER TABLE "donation" ADD CONSTRAINT "FK_635e96839361920b7f80da1dd51" FOREIGN KEY ("earlyAccessRoundId") REFERENCES "early_access_round"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "donation" DROP CONSTRAINT "FK_635e96839361920b7f80da1dd51"`,
);
await queryRunner.query(
`ALTER TABLE "donation" DROP COLUMN "earlyAccessRoundId"`,
);
await queryRunner.query(
`ALTER TABLE "donation" ADD "earlyAccessRoundId" boolean DEFAULT false`,
);
await queryRunner.query(`DROP TABLE "early_access_round"`);
await queryRunner.query(
`ALTER TABLE "donation" RENAME COLUMN "earlyAccessRoundId" TO "earlyAccessRound"`,
);
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@
"test:projectResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/projectResolver.test.ts ./src/resolvers/projectResolver.allProject.test.ts",
"test:chainvineResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/chainvineResolver.test.ts",
"test:qfRoundResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/qfRoundResolver.test.ts",
"test:earlyAccessRoundRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/earlyAccessRoundRepository.test.ts",
"test:earlyAccessRoundResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/earlyAccessRoundResolver.test.ts",
"test:qfRoundHistoryResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/qfRoundHistoryResolver.test.ts",
"test:projectVerificationResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/projectVerificationFormResolver.test.ts",
"test:projectRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/projectRepository.test.ts",
Expand Down
11 changes: 8 additions & 3 deletions src/entities/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Project } from './project';
import { User } from './user';
import { QfRound } from './qfRound';
import { ChainType } from '../types/network';
import { EarlyAccessRound } from './earlyAccessRound';

export const DONATION_STATUS = {
PENDING: 'pending',
Expand Down Expand Up @@ -257,9 +258,13 @@ export class Donation extends BaseEntity {
@Column('decimal', { precision: 5, scale: 2, nullable: true })
donationPercentage?: number;

@Field(_type => Boolean, { nullable: false })
@Column({ nullable: true, default: false })
earlyAccessRound: boolean;
@Field(_type => EarlyAccessRound, { nullable: true })
@ManyToOne(_type => EarlyAccessRound, { eager: true, nullable: true })
earlyAccessRound: EarlyAccessRound | null;

@RelationId((donation: Donation) => donation.earlyAccessRound)
@Column({ nullable: true })
earlyAccessRoundId: number;

static async findXdaiGivDonationsWithoutPrice() {
return this.createQueryBuilder('donation')
Expand Down
37 changes: 37 additions & 0 deletions src/entities/earlyAccessRound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
BaseEntity,
Column,
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { Field, ID, ObjectType, Int } from 'type-graphql';

@Entity()
@ObjectType()
export class EarlyAccessRound extends BaseEntity {
@Field(_type => ID)
@PrimaryGeneratedColumn()
id: number;

@Field(() => Int)
@Column({ unique: true })
roundNumber: number;

@Field(() => Date)
@Column()
startDate: Date;

@Field(() => Date)
@Column()
endDate: Date;

@Field(() => Date)
@CreateDateColumn()
createdAt: Date;

@Field(() => Date)
@UpdateDateColumn()
updatedAt: Date;
}
2 changes: 2 additions & 0 deletions src/entities/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ProjectActualMatchingView } from './ProjectActualMatchingView';
import { ProjectSocialMedia } from './projectSocialMedia';
import { UserQfRoundModelScore } from './userQfRoundModelScore';
import { UserEmailVerification } from './userEmailVerification';
import { EarlyAccessRound } from './earlyAccessRound';

export const getEntities = (): DataSourceOptions['entities'] => {
return [
Expand Down Expand Up @@ -76,5 +77,6 @@ export const getEntities = (): DataSourceOptions['entities'] => {
Sybil,
ProjectFraud,
UserQfRoundModelScore,
EarlyAccessRound,
];
};
93 changes: 93 additions & 0 deletions src/repositories/earlyAccessRoundRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { expect } from 'chai';
import { EarlyAccessRound } from '../entities/earlyAccessRound';
import {
findAllEarlyAccessRounds,
findActiveEarlyAccessRound,
} from './earlyAccessRoundRepository';
import { saveRoundDirectlyToDb } from '../../test/testUtils';

describe('EarlyAccessRound Repository Test Cases', () => {
beforeEach(async () => {
// Clean up data before each test case
await EarlyAccessRound.delete({});
});

afterEach(async () => {
// Clean up data after each test case
await EarlyAccessRound.delete({});
});

it('should save a new Early Access Round directly to the database', async () => {
const roundData = {
roundNumber: 1,
startDate: new Date('2024-09-01'),
endDate: new Date('2024-09-05'),
};

const savedRound = await saveRoundDirectlyToDb(roundData);

expect(savedRound).to.be.an.instanceof(EarlyAccessRound);
expect(savedRound.roundNumber).to.equal(roundData.roundNumber);
expect(savedRound.startDate.toISOString()).to.equal(
roundData.startDate.toISOString(),
);
expect(savedRound.endDate.toISOString()).to.equal(
roundData.endDate.toISOString(),
);
});

it('should find all Early Access Rounds', async () => {
// Save a couple of rounds first
await saveRoundDirectlyToDb({
roundNumber: 1,
startDate: new Date('2024-09-01'),
endDate: new Date('2024-09-05'),
});
await saveRoundDirectlyToDb({
roundNumber: 2,
startDate: new Date('2024-09-06'),
endDate: new Date('2024-09-10'),
});

const rounds = await findAllEarlyAccessRounds();

expect(rounds).to.be.an('array');
expect(rounds.length).to.equal(2);
expect(rounds[0]).to.be.an.instanceof(EarlyAccessRound);
expect(rounds[1]).to.be.an.instanceof(EarlyAccessRound);
});

it('should find the active Early Access Round', async () => {
const activeRoundData = {
roundNumber: 1,
startDate: new Date(new Date().setDate(new Date().getDate() - 1)), // yesterday
endDate: new Date(new Date().setDate(new Date().getDate() + 1)), // tomorrow
};

const inactiveRoundData = {
roundNumber: 2,
startDate: new Date(new Date().getDate() + 1),
endDate: new Date(new Date().getDate() + 2),
};

// Save both active and inactive rounds
await saveRoundDirectlyToDb(activeRoundData);
await saveRoundDirectlyToDb(inactiveRoundData);

const activeRound = await findActiveEarlyAccessRound();

expect(activeRound).to.be.an.instanceof(EarlyAccessRound);
expect(activeRound?.roundNumber).to.equal(activeRoundData.roundNumber);
expect(activeRound?.startDate.toISOString()).to.equal(
activeRoundData.startDate.toISOString(),
);
expect(activeRound?.endDate.toISOString()).to.equal(
activeRoundData.endDate.toISOString(),
);
});

it('should return null when no active Early Access Round is found', async () => {
const activeRound = await findActiveEarlyAccessRound();
expect(activeRound).to.be.null;
});
});
32 changes: 32 additions & 0 deletions src/repositories/earlyAccessRoundRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { EarlyAccessRound } from '../entities/earlyAccessRound';
import { logger } from '../utils/logger';

export const findAllEarlyAccessRounds = async (): Promise<
EarlyAccessRound[]
> => {
try {
return EarlyAccessRound.createQueryBuilder('earlyAccessRound')
.orderBy('earlyAccessRound.startDate', 'ASC')
.getMany();
} catch (error) {
logger.error('Error fetching all Early Access rounds', { error });
throw new Error('Error fetching Early Access rounds');
}
};

// Find the currently active Early Access Round
export const findActiveEarlyAccessRound =
async (): Promise<EarlyAccessRound | null> => {
const currentDate = new Date();

try {
const query = EarlyAccessRound.createQueryBuilder('earlyAccessRound')
.where('earlyAccessRound.startDate <= :currentDate', { currentDate })
.andWhere('earlyAccessRound.endDate >= :currentDate', { currentDate });

return query.getOne();
} catch (error) {
logger.error('Error fetching active Early Access round', { error });
throw new Error('Error fetching active Early Access round');
}
};
2 changes: 1 addition & 1 deletion src/resolvers/donationResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ function createDonationTestCases() {
assert.equal(donation?.referrerWallet, user2.walletAddress);
assert.isOk(donation?.referralStartTimestamp);
assert.isNotOk(donation?.qfRound);
assert.isTrue(donation?.earlyAccessRound);
// assert.isTrue(donation?.earlyAccessRound);
});
it('should create a donation in an active qfRound', async () => {
sinon.stub(qacc, 'isEarlyAccessRound').returns(false);
Expand Down
5 changes: 3 additions & 2 deletions src/resolvers/donationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
DraftDonation,
} from '../entities/draftDonation';
import qacc from '../utils/qacc';
import { findActiveEarlyAccessRound } from '../repositories/earlyAccessRoundRepository';

const draftDonationEnabled = process.env.ENABLE_DRAFT_DONATION === 'true';
@ObjectType()
Expand Down Expand Up @@ -875,7 +876,7 @@ export class DonationResolver {
logger.error('get chainvine wallet address error', e);
}
}
if (!qacc.isEarlyAccessRound()) {
if (!(await qacc.isEarlyAccessRound())) {
const activeQfRoundForProject =
await relatedActiveQfRoundForProject(projectId);
if (
Expand Down Expand Up @@ -903,7 +904,7 @@ export class DonationResolver {
}
await donation.save();
} else {
donation.earlyAccessRound = true;
donation.earlyAccessRound = await findActiveEarlyAccessRound();
await donation.save();
}
let priceChainId;
Expand Down
Loading
Loading