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
2 changes: 2 additions & 0 deletions apps/backend/src/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Escrow } from './modules/escrow/entities/escrow.entity';
import { Party } from './modules/escrow/entities/party.entity';
import { Condition } from './modules/escrow/entities/condition.entity';
import { EscrowEvent } from './modules/escrow/entities/escrow-event.entity';
import { EscrowEventStore } from './modules/escrow/entities/escrow-event-store.entity';
import { Dispute } from './modules/escrow/entities/dispute.entity';
import { Notification } from './notifications/entities/notification.entity';
import { NotificationPreference } from './notifications/entities/notification-preference.entity';
Expand All @@ -28,6 +29,7 @@ export default new DataSource({
Party,
Condition,
EscrowEvent,
EscrowEventStore,
Dispute,
Notification,
NotificationPreference,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

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

public async up(queryRunner: QueryRunner): Promise<void> {
// Create the escrow_event_store table with SQLite syntax
await queryRunner.query(
`
CREATE TABLE "escrow_event_store" (
"id" varchar PRIMARY KEY NOT NULL,
"escrowId" varchar NOT NULL,
"version" integer NOT NULL,
"eventType" varchar NOT NULL,
"actorId" varchar,
"payload" text NOT NULL DEFAULT '{}',
"occurredAt" datetime NOT NULL,
"txHash" varchar,
"idempotencyKey" varchar NOT NULL,
UNIQUE ("escrowId", "idempotencyKey")
)
`,
);

// Create index on (escrowId, version) for efficient replay
await queryRunner.query(
`CREATE INDEX "IDX_escrow_version" ON "escrow_event_store" ("escrowId", "version")`,
);

// Create index on (eventType, occurredAt) for global filtering
await queryRunner.query(
`CREATE INDEX "IDX_event_type_occurred" ON "escrow_event_store" ("eventType", "occurredAt")`,
);

// Create index on escrowId alone for fast lookups
await queryRunner.query(
`CREATE INDEX "IDX_event_escrow_id" ON "escrow_event_store" ("escrowId")`,
);

// Create index on actorId for admin audit queries
await queryRunner.query(
`CREATE INDEX "IDX_event_actor_id" ON "escrow_event_store" ("actorId")`,
);

// Create index on idempotencyKey for deduplication checks
await queryRunner.query(
`CREATE INDEX "IDX_idempotency_key" ON "escrow_event_store" ("idempotencyKey")`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
// Drop indexes
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_idempotency_key"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_event_actor_id"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_event_escrow_id"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_event_type_occurred"`);
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_escrow_version"`);

// Drop table
await queryRunner.query(`DROP TABLE IF EXISTS "escrow_event_store"`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
Index,
Unique,
} from 'typeorm';
import { EscrowEventType } from '../enums/escrow-event-type.enum';

@Entity('escrow_event_store')
@Unique('UQ_escrow_idempotency', ['escrowId', 'idempotencyKey'])
@Index('IDX_escrow_version', ['escrowId', 'version'])
@Index('IDX_event_type_occurred', ['eventType', 'occurredAt'])
export class EscrowEventStore {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ type: 'uuid' })
escrowId: string;

@Column({ type: 'int' })
version: number; // monotonically increasing per escrow

@Column({ type: 'varchar' })
eventType: EscrowEventType;

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

@Column({ type: 'jsonb', default: {} })
payload: Record<string, unknown>;

@Column({ type: 'timestamptz' })
occurredAt: Date;

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

@Column({ type: 'uuid' })
idempotencyKey: string; // prevents duplicate events
}
17 changes: 17 additions & 0 deletions apps/backend/src/modules/escrow/enums/escrow-event-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export enum EscrowEventType {
CREATED = 'CREATED',
FUNDED = 'FUNDED',
CONDITION_FULFILLED = 'CONDITION_FULFILLED',
CONDITION_CONFIRMED = 'CONDITION_CONFIRMED',
MILESTONE_RELEASED = 'MILESTONE_RELEASED',
PARTY_INVITED = 'PARTY_INVITED',
PARTY_ACCEPTED = 'PARTY_ACCEPTED',
PARTY_REJECTED = 'PARTY_REJECTED',
DISPUTE_FILED = 'DISPUTE_FILED',
DISPUTE_RESOLVED = 'DISPUTE_RESOLVED',
RELEASED = 'RELEASED',
CANCELLED = 'CANCELLED',
EXPIRED = 'EXPIRED',
REFUND_PROCESSED = 'REFUND_PROCESSED',
EXPIRATION_WARNING = 'EXPIRATION_WARNING',
}
5 changes: 5 additions & 0 deletions apps/backend/src/modules/escrow/escrow.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Escrow } from './entities/escrow.entity';
import { Party } from './entities/party.entity';
import { Condition } from './entities/condition.entity';
import { EscrowEvent } from './entities/escrow-event.entity';
import { EscrowEventStore } from './entities/escrow-event-store.entity';
import { Dispute } from './entities/dispute.entity';
import { EscrowService } from './services/escrow.service';
import { EscrowSchedulerService } from './services/escrow-scheduler.service';
import { EscrowEventStoreService } from './services/escrow-event-store.service';
import { EscrowController } from './controllers/escrow.controller';
import { EscrowSchedulerController } from './controllers/escrow-scheduler.controller';
import { EventsController } from './controllers/events.controller';
Expand All @@ -33,6 +35,7 @@ import { EscrowIpfsSyncService } from './services/escrow-ipfs-sync.service';
Party,
Condition,
EscrowEvent,
EscrowEventStore,
Dispute,
User,
AllowedAsset,
Expand All @@ -46,6 +49,7 @@ import { EscrowIpfsSyncService } from './services/escrow-ipfs-sync.service';
providers: [
EscrowService,
EscrowSchedulerService,
EscrowEventStoreService,
EscrowStellarIntegrationService,
EscrowAccessGuard,
EscrowExpireGuard,
Expand All @@ -59,6 +63,7 @@ import { EscrowIpfsSyncService } from './services/escrow-ipfs-sync.service';
exports: [
EscrowService,
EscrowSchedulerService,
EscrowEventStoreService,
EscrowLifecycleService,
EscrowFundingService,
EscrowDisputeService,
Expand Down
Loading