diff --git a/src/migrations/1731900000000-AddSorobanEscrowFieldsToInvoice.ts b/src/migrations/1731900000000-AddSorobanEscrowFieldsToInvoice.ts new file mode 100644 index 0000000..d1e0c25 --- /dev/null +++ b/src/migrations/1731900000000-AddSorobanEscrowFieldsToInvoice.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddSorobanEscrowFieldsToInvoice1731900000000 + implements MigrationInterface +{ + name = "AddSorobanEscrowFieldsToInvoice1731900000000"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + "invoices", + new TableColumn({ + name: "soroban_contract_id", + type: "varchar", + length: "56", + isNullable: true, + }), + ); + + await queryRunner.addColumn( + "invoices", + new TableColumn({ + name: "onchain_status", + type: "enum", + enum: ["UNINITIALIZED", "ACTIVE", "SETTLED", "REFUNDED", "PAUSED"], + isNullable: true, + }), + ); + + await queryRunner.addColumn( + "invoices", + new TableColumn({ + name: "creation_tx_hash", + type: "varchar", + length: "64", + isNullable: true, + }), + ); + + await queryRunner.addColumn( + "invoices", + new TableColumn({ + name: "last_synced_ledger", + type: "bigint", + isNullable: true, + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("invoices", "last_synced_ledger"); + await queryRunner.dropColumn("invoices", "creation_tx_hash"); + await queryRunner.dropColumn("invoices", "onchain_status"); + await queryRunner.dropColumn("invoices", "soroban_contract_id"); + } +} diff --git a/src/models/Invoice.model.ts b/src/models/Invoice.model.ts index a9fdc3f..24b50f9 100644 --- a/src/models/Invoice.model.ts +++ b/src/models/Invoice.model.ts @@ -10,7 +10,7 @@ import { JoinColumn, Index, } from "typeorm"; -import { InvoiceStatus } from "../types/enums"; +import { InvoiceStatus, SorobanEscrowStatus } from "../types/enums"; @Entity("invoices") export class Invoice { @@ -61,6 +61,18 @@ export class Invoice { @Column({ name: "rejection_reason", type: "text", nullable: true }) rejectionReason!: string | null; + @Column({ name: "soroban_contract_id", type: "varchar", length: 56, nullable: true }) + sorobanContractId?: string | null; + + @Column({ name: "onchain_status", type: "enum", enum: SorobanEscrowStatus, nullable: true }) + onchainStatus?: SorobanEscrowStatus | null; + + @Column({ name: "creation_tx_hash", type: "varchar", length: 64, nullable: true }) + creationTxHash?: string | null; + + @Column({ name: "last_synced_ledger", type: "bigint", nullable: true }) + lastSyncedLedger?: string | null; + @CreateDateColumn({ name: "created_at" }) createdAt!: Date; diff --git a/src/types/enums.ts b/src/types/enums.ts index 7b403b0..29f2234 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -25,6 +25,14 @@ export enum InvoiceStatus { REJECTED = "rejected", } +export enum SorobanEscrowStatus { + UNINITIALIZED = "UNINITIALIZED", + ACTIVE = "ACTIVE", + SETTLED = "SETTLED", + REFUNDED = "REFUNDED", + PAUSED = "PAUSED", +} + export enum InvestmentStatus { PENDING = "pending", CONFIRMED = "confirmed", diff --git a/tests/migrations/add-soroban-escrow-fields-to-invoice.test.ts b/tests/migrations/add-soroban-escrow-fields-to-invoice.test.ts new file mode 100644 index 0000000..657417c --- /dev/null +++ b/tests/migrations/add-soroban-escrow-fields-to-invoice.test.ts @@ -0,0 +1,66 @@ +import "reflect-metadata"; +import { DataSource, QueryRunner } from "typeorm"; +import { AddSorobanEscrowFieldsToInvoice1731900000000 } from "../../src/migrations/1731900000000-AddSorobanEscrowFieldsToInvoice"; + +describe("AddSorobanEscrowFieldsToInvoice migration", () => { + let dataSource: DataSource; + let queryRunner: QueryRunner; + + beforeEach(async () => { + dataSource = new DataSource({ + type: "sqlite", + database: ":memory:", + entities: [], + synchronize: false, + }); + await dataSource.initialize(); + queryRunner = dataSource.createQueryRunner(); + await queryRunner.query(`CREATE TABLE "invoices" ("id" varchar PRIMARY KEY)`); + }); + + afterEach(async () => { + await queryRunner.release(); + await dataSource.destroy(); + }); + + it("adds all nullable escrow tracking columns with the requested types", async () => { + const migration = new AddSorobanEscrowFieldsToInvoice1731900000000(); + + await migration.up(queryRunner); + + const table = await queryRunner.getTable("invoices"); + expect(table).toBeDefined(); + expect(table?.findColumnByName("soroban_contract_id")).toMatchObject({ + type: "varchar", + length: "56", + isNullable: true, + }); + expect(table?.findColumnByName("onchain_status")).toMatchObject({ + type: "varchar", + enum: ["UNINITIALIZED", "ACTIVE", "SETTLED", "REFUNDED", "PAUSED"], + isNullable: true, + }); + expect(table?.findColumnByName("creation_tx_hash")).toMatchObject({ + type: "varchar", + length: "64", + isNullable: true, + }); + expect(table?.findColumnByName("last_synced_ledger")).toMatchObject({ + type: "bigint", + isNullable: true, + }); + }); + + it("removes all escrow tracking columns when reverted", async () => { + const migration = new AddSorobanEscrowFieldsToInvoice1731900000000(); + await migration.up(queryRunner); + + await migration.down(queryRunner); + + const table = await queryRunner.getTable("invoices"); + expect(table?.findColumnByName("soroban_contract_id")).toBeUndefined(); + expect(table?.findColumnByName("onchain_status")).toBeUndefined(); + expect(table?.findColumnByName("creation_tx_hash")).toBeUndefined(); + expect(table?.findColumnByName("last_synced_ledger")).toBeUndefined(); + }); +});