All files / src/platform/gateway/services api-idempotency.service.ts

0% Statements 0/19
0% Branches 0/6
0% Functions 0/4
0% Lines 0/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47                                                                                             
import { Injectable, Logger } from '@nestjs/common';
import { AppException } from '../../../common/errors/app.exception';
import { ErrorCode } from '../../../common/errors/error-codes.enum';
import { HttpStatus } from '@nestjs/common';
 
interface IdempotencyRecord {
  response: any;
  statusCode: number;
  createdAt: Date;
}
 
@Injectable()
export class ApiIdempotencyService {
  private readonly logger = new Logger(ApiIdempotencyService.name);
  // In production this is backed by Redis with a TTL. In-memory here for local dev.
  private readonly store = new Map<string, IdempotencyRecord>();
  private readonly TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
 
  buildKey(tenantId: string, idempotencyKey: string): string {
    return `idempotency:${tenantId}:${idempotencyKey}`;
  }
 
  async get(key: string): Promise<IdempotencyRecord | undefined> {
    const record = this.store.get(key);
    if (!record) return undefined;
 
    if (Date.now() - record.createdAt.getTime() > this.TTL_MS) {
      this.store.delete(key);
      return undefined;
    }
    return record;
  }
 
  async set(key: string, response: any, statusCode: number): Promise<void> {
    this.store.set(key, { response, statusCode, createdAt: new Date() });
  }
 
  async checkAndEnforce(
    tenantId: string,
    idempotencyKey: string,
  ): Promise<IdempotencyRecord | null> {
    const key = this.buildKey(tenantId, idempotencyKey);
    const existing = await this.get(key);
    return existing ?? null;
  }
}