Skip to content

Commit 0ff29b6

Browse files
committed
add vesting schedule entity and resolver, and update package.json with new test scripts and also add a migration to fill the data of vesting schedules
1 parent 0cc0805 commit 0ff29b6

10 files changed

+403
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class CreateVestingScheduleTable1746613421852
4+
implements MigrationInterface
5+
{
6+
name = 'CreateVestingScheduleTable1746613421852';
7+
8+
public async up(queryRunner: QueryRunner): Promise<void> {
9+
await queryRunner.query(
10+
`CREATE TABLE "vesting_schedule" (
11+
"id" SERIAL NOT NULL,
12+
"name" character varying NOT NULL,
13+
"start" TIMESTAMP NOT NULL,
14+
"cliff" TIMESTAMP NOT NULL,
15+
"end" TIMESTAMP NOT NULL,
16+
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
17+
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
18+
CONSTRAINT "PK_vesting_schedule_id" PRIMARY KEY ("id")
19+
)`,
20+
);
21+
}
22+
23+
public async down(queryRunner: QueryRunner): Promise<void> {
24+
await queryRunner.query(`DROP TABLE "vesting_schedule"`);
25+
}
26+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class PopulateVestingSchedules1746613421853
4+
implements MigrationInterface
5+
{
6+
name = 'PopulateVestingSchedules1746613421853';
7+
8+
public async up(queryRunner: QueryRunner): Promise<void> {
9+
// Define the vesting schedule data
10+
const vestingSchedules = [
11+
{
12+
name: 'Season 1 projects',
13+
start: '2024-10-29',
14+
cliff: '2025-10-29',
15+
end: '2026-10-29',
16+
},
17+
{
18+
name: 'Season 2 projects',
19+
start: '2025-04-11',
20+
cliff: '2026-04-11',
21+
end: '2027-04-11',
22+
},
23+
{
24+
name: 'R1 Season 1 buyers',
25+
start: '2024-12-20',
26+
cliff: '2025-06-20',
27+
end: '2025-12-20',
28+
},
29+
{
30+
name: 'R2 Season 1 buyers',
31+
start: '2025-05-13',
32+
cliff: '2025-10-13',
33+
end: '2026-03-13',
34+
},
35+
{
36+
name: 'R2 Season 2 buyers',
37+
start: '2025-05-13',
38+
cliff: '2025-11-13',
39+
end: '2026-05-13',
40+
},
41+
];
42+
43+
// Insert each vesting schedule
44+
for (const schedule of vestingSchedules) {
45+
await queryRunner.query(
46+
`INSERT INTO "vesting_schedule" ("name", "start", "cliff", "end")
47+
VALUES ($1, $2, $3, $4)`,
48+
[schedule.name, schedule.start, schedule.cliff, schedule.end],
49+
);
50+
}
51+
}
52+
53+
public async down(queryRunner: QueryRunner): Promise<void> {
54+
// Define the schedule names that were inserted
55+
const scheduleNames = [
56+
'Season 1 projects',
57+
'Season 2 projects',
58+
'R1 Season 1 buyers',
59+
'R2 Season 1 buyers',
60+
'R2 Season 2 buyers',
61+
];
62+
63+
// Remove the inserted vesting schedules
64+
for (const name of scheduleNames) {
65+
await queryRunner.query(
66+
`DELETE FROM "vesting_schedule" WHERE "name" = $1`,
67+
[name],
68+
);
69+
}
70+
}
71+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@
191191
"test:inverterScript": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/scripts/syncDataWithInverter.test.ts",
192192
"test:healthCheck": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/server/bootstrap.test.ts",
193193
"test:tokenPriceResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/tokenPriceResolver.test.ts",
194+
"test:vestingScheduleRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/vestingScheduleRepository.test.ts",
195+
"test:vestingScheduleResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/vestingScheduleResolver.test.ts",
194196
"start": "NODE_ENV=development ts-node-dev --project ./tsconfig.json --respawn ./src/index.ts",
195197
"start:test": "NODE_ENV=development ts-node-dev --project ./tsconfig.json --respawn ./test.ts",
196198
"serve": "pm2 startOrRestart ecosystem.config.js --node-args='--max-old-space-size=8192'",

src/entities/entities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { SwapTransaction } from './swapTransaction';
4040
import { QaccPointsHistory } from './qaccPointsHistory';
4141
import { UserRankMaterializedView } from './userRanksMaterialized';
4242
import { VestingData } from './vestingData';
43+
import { VestingSchedule } from './vestingSchedule';
4344
import { TokenPriceHistory } from './tokenPriceHistory';
4445

4546
export const getEntities = (): DataSourceOptions['entities'] => {
@@ -94,6 +95,7 @@ export const getEntities = (): DataSourceOptions['entities'] => {
9495
SwapTransaction,
9596
UserRankMaterializedView,
9697
VestingData,
98+
VestingSchedule,
9799
TokenPriceHistory,
98100
];
99101
};

src/entities/vestingSchedule.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Field, ID, ObjectType } from 'type-graphql';
2+
import {
3+
BaseEntity,
4+
Column,
5+
CreateDateColumn,
6+
Entity,
7+
PrimaryGeneratedColumn,
8+
UpdateDateColumn,
9+
} from 'typeorm';
10+
11+
@Entity()
12+
@ObjectType()
13+
export class VestingSchedule extends BaseEntity {
14+
@Field(_type => ID)
15+
@PrimaryGeneratedColumn()
16+
readonly id: number;
17+
18+
@Field()
19+
@Column()
20+
name: string;
21+
22+
@Field()
23+
@Column('timestamp')
24+
start: Date;
25+
26+
@Field()
27+
@Column('timestamp')
28+
cliff: Date;
29+
30+
@Field()
31+
@Column('timestamp')
32+
end: Date;
33+
34+
@Field()
35+
@CreateDateColumn()
36+
createdAt: Date;
37+
38+
@Field()
39+
@UpdateDateColumn()
40+
updatedAt: Date;
41+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { assert } from 'chai';
2+
import {
3+
findAllVestingSchedules,
4+
findVestingScheduleById,
5+
} from './vestingScheduleRepository';
6+
import { VestingSchedule } from '../entities/vestingSchedule';
7+
import { saveUserDirectlyToDb, SEED_DATA } from '../../test/testUtils';
8+
import { User } from '../entities/user';
9+
10+
describe(
11+
'VestingSchedule Repository test cases',
12+
vestingScheduleRepositoryTestCases,
13+
);
14+
15+
function vestingScheduleRepositoryTestCases() {
16+
let user: User;
17+
let vestingSchedule1: VestingSchedule;
18+
19+
beforeEach(async () => {
20+
// Create test user
21+
user = await saveUserDirectlyToDb(SEED_DATA.FIRST_USER.email);
22+
23+
// Create test vesting schedules
24+
vestingSchedule1 = await VestingSchedule.create({
25+
name: 'Team Vesting',
26+
start: new Date('2024-01-01'),
27+
cliff: new Date('2024-06-01'),
28+
end: new Date('2025-01-01'),
29+
}).save();
30+
31+
await VestingSchedule.create({
32+
name: 'Advisor Vesting',
33+
start: new Date('2024-03-01'),
34+
cliff: new Date('2024-09-01'),
35+
end: new Date('2025-03-01'),
36+
}).save();
37+
});
38+
39+
afterEach(async () => {
40+
// Clean up test data
41+
await VestingSchedule.delete({});
42+
await User.delete({ id: user.id });
43+
});
44+
45+
describe('findAllVestingSchedules', () => {
46+
it('should return all vesting schedules ordered by start date ASC', async () => {
47+
const vestingSchedules = await findAllVestingSchedules();
48+
49+
assert.equal(vestingSchedules.length, 2);
50+
assert.equal(vestingSchedules[0].name, 'Team Vesting');
51+
assert.equal(vestingSchedules[1].name, 'Advisor Vesting');
52+
53+
// Verify ordering by start date
54+
assert.isTrue(
55+
vestingSchedules[0].start.getTime() <=
56+
vestingSchedules[1].start.getTime(),
57+
);
58+
});
59+
60+
it('should return empty array when no vesting schedules exist', async () => {
61+
await VestingSchedule.delete({});
62+
const vestingSchedules = await findAllVestingSchedules();
63+
64+
assert.equal(vestingSchedules.length, 0);
65+
});
66+
});
67+
68+
describe('findVestingScheduleById', () => {
69+
it('should return vesting schedule by id', async () => {
70+
const foundVestingSchedule = await findVestingScheduleById(
71+
vestingSchedule1.id,
72+
);
73+
74+
assert.isNotNull(foundVestingSchedule);
75+
assert.equal(foundVestingSchedule!.id, vestingSchedule1.id);
76+
assert.equal(foundVestingSchedule!.name, 'Team Vesting');
77+
assert.deepEqual(foundVestingSchedule!.start, vestingSchedule1.start);
78+
assert.deepEqual(foundVestingSchedule!.cliff, vestingSchedule1.cliff);
79+
assert.deepEqual(foundVestingSchedule!.end, vestingSchedule1.end);
80+
});
81+
82+
it('should return null when vesting schedule does not exist', async () => {
83+
const foundVestingSchedule = await findVestingScheduleById(999999);
84+
85+
assert.isNull(foundVestingSchedule);
86+
});
87+
});
88+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { VestingSchedule } from '../entities/vestingSchedule';
2+
3+
export const findAllVestingSchedules = async (): Promise<VestingSchedule[]> => {
4+
return VestingSchedule.find({
5+
order: {
6+
start: 'ASC',
7+
},
8+
});
9+
};
10+
11+
export const findVestingScheduleById = async (
12+
id: number,
13+
): Promise<VestingSchedule | null> => {
14+
return VestingSchedule.findOne({
15+
where: { id },
16+
});
17+
};

src/resolvers/resolvers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RoundsResolver } from './roundsResolver';
1818
import { QAccResolver } from './qAccResolver';
1919
import { QaccPointsHistoryResolver } from './qaccPointsHistoryResolver';
2020
import { TokenPriceResolver } from './tokenPriceResolver';
21+
import { VestingScheduleResolver } from './vestingScheduleResolver';
2122

2223
// eslint-disable-next-line @typescript-eslint/ban-types
2324
export const getResolvers = (): Function[] => {
@@ -46,5 +47,6 @@ export const getResolvers = (): Function[] => {
4647
QAccResolver,
4748
QaccPointsHistoryResolver,
4849
TokenPriceResolver,
50+
VestingScheduleResolver,
4951
];
5052
};

0 commit comments

Comments
 (0)