Skip to content
Open
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: 1 addition & 0 deletions harvest-finance/backend/src/database/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {
InsuranceSubscription,
SubscriptionStatus,
} from './insurance-subscription.entity';
export { InsuranceClaim, InsuranceClaimStatus } from './insurance-claim.entity';
export { Notification, NotificationType } from './notification.entity';
export { Order, OrderStatus } from './order.entity';
export { Reward, RewardStatus } from './reward.entity';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,75 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Vault } from './vault.entity';
import { User } from './user.entity';

export enum InsuranceClaimStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
REJECTED = 'REJECTED',
}

/**
* Records a payout claim from the insurance fund to a depositor.
* The claim is created during an incident workflow and later updated
* when the payout is processed on‑chain.
*/
@Entity('insurance_claims')
@Index('idx_insurance_claim_vault', ['vaultId'])
@Index('idx_insurance_claim_user', ['depositorId'])
@Index('idx_insurance_claims_vault', ['vaultId'])
@Index('idx_insurance_claims_depositor', ['depositorId'])
@Index('idx_insurance_claims_status', ['status'])
export class InsuranceClaim {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ name: 'vault_id' })
vaultId: string;

@ManyToOne(() => Vault, (vault) => vault.id, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'vault_id' })
vault: Vault;

@Column({ name: 'depositor_id' })
depositorId: string;

@ManyToOne(() => User, (user) => user.id, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'depositor_id' })
depositor: User;

@Column({ type: 'decimal', precision: 18, scale: 8 })
@Column({
type: 'decimal',
precision: 18,
scale: 8,
})
lossAmount: number;

@Column({ type: 'decimal', precision: 18, scale: 8 })
@Column({
type: 'decimal',
precision: 18,
scale: 8,
})
payoutAmount: number;

@Column({ type: 'enum', enum: InsuranceClaimStatus, default: InsuranceClaimStatus.PENDING })
@Column({
type: 'enum',
enum: InsuranceClaimStatus,
default: InsuranceClaimStatus.PENDING,
})
status: InsuranceClaimStatus;

@Column({ type: 'text', nullable: true, name: 'transaction_hash' })
transactionHash: string | null;

@Column({ type: 'text', nullable: true })
reason: string | null;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}

@ManyToOne(() => Vault, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'vault_id' })
vault: Vault;

@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'depositor_id' })
depositor: User;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
import { CreateInsuranceClaims1700000000013 } from './1700000000013-CreateInsuranceClaims';

describe('CreateInsuranceClaims1700000000013', () => {
let migration: CreateInsuranceClaims1700000000013;
let queryRunner: QueryRunner;

beforeEach(() => {
migration = new CreateInsuranceClaims1700000000013();
queryRunner = {
createTable: jest.fn(),
dropTable: jest.fn(),
dropIndex: jest.fn(),
} as any;
});

it('should create insurance claims table with correct structure', async () => {
await migration.up(queryRunner);

expect(queryRunner.createTable).toHaveBeenCalledWith(
expect.objectContaining({
name: 'insurance_claims',
columns: expect.arrayContaining([
expect.objectContaining({ name: 'id', type: 'uuid', isPrimary: true }),
expect.objectContaining({ name: 'vault_id', type: 'uuid' }),
expect.objectContaining({ name: 'depositor_id', type: 'uuid' }),
expect.objectContaining({ name: 'loss_amount', type: 'decimal' }),
expect.objectContaining({ name: 'payout_amount', type: 'decimal' }),
expect.objectContaining({ name: 'status', type: 'enum' }),
expect.objectContaining({ name: 'transaction_hash', type: 'text' }),
expect.objectContaining({ name: 'reason', type: 'text' }),
]),
}),
);
});

it('should have correct enum values for status', async () => {
await migration.up(queryRunner);
const call = (queryRunner.createTable as jest.Mock).mock.calls[0][0];
const statusColumn = call.columns.find((c: any) => c.name === 'status');

expect(statusColumn.enum).toEqual(['PENDING', 'COMPLETED', 'FAILED', 'REJECTED']);
expect(statusColumn.default).toBe("'PENDING'");
});

it('should have foreign key to vaults table', async () => {
await migration.up(queryRunner);
const call = (queryRunner.createTable as jest.Mock).mock.calls[0][0];

expect(call.foreignKeys).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'fk_insurance_claims_vault',
referencedTableName: 'vaults',
}),
]),
);
});

it('should have foreign key to users table', async () => {
await migration.up(queryRunner);
const call = (queryRunner.createTable as jest.Mock).mock.calls[0][0];

expect(call.foreignKeys).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'fk_insurance_claims_depositor',
referencedTableName: 'users',
}),
]),
);
});

it('should drop table on migration down', async () => {
await migration.down(queryRunner);

expect(queryRunner.dropIndex).toHaveBeenCalled();
expect(queryRunner.dropTable).toHaveBeenCalledWith('insurance_claims');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';

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

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'insurance_claims',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'uuid_generate_v4()',
},
{
name: 'vault_id',
type: 'uuid',
isNullable: false,
},
{
name: 'depositor_id',
type: 'uuid',
isNullable: false,
},
{
name: 'loss_amount',
type: 'decimal',
precision: 18,
scale: 8,
isNullable: false,
},
{
name: 'payout_amount',
type: 'decimal',
precision: 18,
scale: 8,
isNullable: false,
},
{
name: 'status',
type: 'enum',
enum: ['PENDING', 'COMPLETED', 'FAILED', 'REJECTED'],
default: "'PENDING'",
},
{
name: 'transaction_hash',
type: 'text',
isNullable: true,
},
{
name: 'reason',
type: 'text',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp with time zone',
default: 'CURRENT_TIMESTAMP',
},
{
name: 'updated_at',
type: 'timestamp with time zone',
default: 'CURRENT_TIMESTAMP',
},
],
foreignKeys: [
{
name: 'fk_insurance_claims_vault',
columnNames: ['vault_id'],
referencedTableName: 'vaults',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
},
{
name: 'fk_insurance_claims_depositor',
columnNames: ['depositor_id'],
referencedTableName: 'users',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
},
],
}),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropIndex('insurance_claims', 'idx_insurance_claims_vault');
await queryRunner.dropIndex('insurance_claims', 'idx_insurance_claims_depositor');
await queryRunner.dropIndex('insurance_claims', 'idx_insurance_claims_status');
await queryRunner.dropTable('insurance_claims');
}
}
Loading
Loading