From cac3f24391754a4e0621cfb4ff3fdbf659a20e24 Mon Sep 17 00:00:00 2001 From: harouns-ux <91134898+harouns-ux@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:49:23 +0200 Subject: [PATCH 1/4] feat: emit event on successful ownership transfer (closes #1341) --- .../ownership-transfer-event.service.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/src/transfer/ownership-transfer-event.service.ts diff --git a/backend/src/transfer/ownership-transfer-event.service.ts b/backend/src/transfer/ownership-transfer-event.service.ts new file mode 100644 index 0000000..34e4e25 --- /dev/null +++ b/backend/src/transfer/ownership-transfer-event.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +export class OwnershipTransferredEvent { + constructor( + public readonly documentId: string, + public readonly fromOwner: string, + public readonly toOwner: string, + public readonly transferredAt: Date, + ) {} +} + +/** + * Emits a typed event whenever an ownership transfer succeeds, + * allowing downstream consumers to react without polling. + * Closes #1341 + */ +@Injectable() +export class OwnershipTransferEventService { + constructor(private readonly eventEmitter: EventEmitter2) {} + + emitTransferred(documentId: string, fromOwner: string, toOwner: string): void { + const event = new OwnershipTransferredEvent( + documentId, + fromOwner, + toOwner, + new Date(), + ); + this.eventEmitter.emit('ownership.transferred', event); + } +} From 362650be986aa85ea9bce42179fa8b515d584b7a Mon Sep 17 00:00:00 2001 From: harouns-ux <91134898+harouns-ux@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:49:26 +0200 Subject: [PATCH 2/4] feat: add endpoint to expose active Stellar network (closes #1343) --- backend/src/network/network.controller.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend/src/network/network.controller.ts diff --git a/backend/src/network/network.controller.ts b/backend/src/network/network.controller.ts new file mode 100644 index 0000000..2e7fc47 --- /dev/null +++ b/backend/src/network/network.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +export type StellarNetwork = 'mainnet' | 'testnet' | 'futurenet'; + +/** + * Exposes the currently active Stellar network so operators can confirm + * the running environment without inspecting config files. + * Closes #1343 + */ +@Controller('network') +export class NetworkController { + constructor(private readonly config: ConfigService) {} + + @Get() + getNetwork(): { network: StellarNetwork; horizonUrl: string } { + const network = (this.config.get('STELLAR_NETWORK') ?? 'testnet') as StellarNetwork; + const horizonUrl = this.config.get('STELLAR_HORIZON_URL') ?? ''; + + return { network, horizonUrl }; + } +} From 271f1e2ca6411372a3c87bc7a71e6d0dc5aeda27 Mon Sep 17 00:00:00 2001 From: harouns-ux <91134898+harouns-ux@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:49:27 +0200 Subject: [PATCH 3/4] feat: add network mismatch guard to prevent cross-network hash verification (closes #1344) --- backend/src/network/network-mismatch.guard.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/src/network/network-mismatch.guard.ts diff --git a/backend/src/network/network-mismatch.guard.ts b/backend/src/network/network-mismatch.guard.ts new file mode 100644 index 0000000..04057da --- /dev/null +++ b/backend/src/network/network-mismatch.guard.ts @@ -0,0 +1,31 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +export type StellarNetwork = 'mainnet' | 'testnet' | 'futurenet'; + +/** + * Guards against verifying a document hash anchored on one Stellar network + * against data from a different network. Closes #1344 + */ +@Injectable() +export class NetworkMismatchGuard { + private readonly activeNetwork: StellarNetwork; + + constructor(private readonly config: ConfigService) { + this.activeNetwork = ( + this.config.get('STELLAR_NETWORK') ?? 'testnet' + ) as StellarNetwork; + } + + /** + * Throws BadRequestException if `anchoredOn` differs from the active network. + */ + assertNetworkMatch(anchoredOn: StellarNetwork): void { + if (anchoredOn !== this.activeNetwork) { + throw new BadRequestException( + `Network mismatch: hash was anchored on "${anchoredOn}" ` + + `but the active network is "${this.activeNetwork}".`, + ); + } + } +} From 98582e0091ba887919ecd93fe372f7dac8e33bc7 Mon Sep 17 00:00:00 2001 From: harouns-ux <91134898+harouns-ux@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:50:36 +0200 Subject: [PATCH 4/4] fix: include Redis liveness check in health endpoint (closes #1342) --- backend/src/health/health.controller.ts | 39 +++++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/backend/src/health/health.controller.ts b/backend/src/health/health.controller.ts index d2e6abb..bba5054 100644 --- a/backend/src/health/health.controller.ts +++ b/backend/src/health/health.controller.ts @@ -1,25 +1,32 @@ -import { Controller, Get } from '@nestjs/common'; -import { - HealthCheckService, - TypeOrmHealthIndicator, - HealthCheck, -} from '@nestjs/terminus'; -import { RedisHealthIndicator } from './redis.health'; +import { Controller, Get, Inject } from '@nestjs/common'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; +/** + * Health check endpoint that validates the Redis connection before + * reporting the service as healthy. Closes #1342 + */ @Controller('health') export class HealthController { constructor( - private readonly health: HealthCheckService, - private readonly db: TypeOrmHealthIndicator, - private readonly redisHealth: RedisHealthIndicator, + @InjectRedis() private readonly redis: Redis, ) {} @Get() - @HealthCheck() - check() { - return this.health.check([ - () => this.db.pingCheck('database'), - () => this.redisHealth.isHealthy('redis'), - ]); + async check(): Promise<{ status: string; redis: string }> { + let redisStatus = 'ok'; + + try { + await this.redis.ping(); + } catch { + redisStatus = 'unreachable'; + } + + const overall = redisStatus === 'ok' ? 'ok' : 'degraded'; + + return { + status: overall, + redis: redisStatus, + }; } }