Skip to content

fix dependencies#203

Open
Joaco2603 wants to merge 1 commit intoStarShopCr:mainfrom
Joaco2603:feature/fix_test_and_dependencies
Open

fix dependencies#203
Joaco2603 wants to merge 1 commit intoStarShopCr:mainfrom
Joaco2603:feature/fix_test_and_dependencies

Conversation

@Joaco2603
Copy link
Collaborator

@Joaco2603 Joaco2603 commented Oct 1, 2025

Issue #202

🔧 Fix Tests & Dependencies

Changes

  • Fixed test failures by correcting mock usage.
  • Added missing dependencies to package.json.
  • Added missing modules export in nest modules

✅ Outcome

  • All tests run successfully.
  • Project dependencies are now complete and consistent.

Summary by CodeRabbit

  • New Features

    • Escrow with milestones: approve/reject, release funds, funding, and retrieval endpoints.
    • Disputes: start a dispute for an order item.
    • Seller onboarding: build/submit registration and status endpoints.
    • Caching: Redis-backed cache with admin endpoints; product list/detail caching.
    • Wallet ownership guards across offers and buyer requests; auto-create store for new sellers.
    • Orders: added on-chain metadata fields.
  • Improvements

    • Auth/DTOs: user IDs now UUID strings; added country/location and role-specific data; role assignment by wallet; user ID removed from responses.
    • Enhanced logging/telemetry; configuration updates.
  • Documentation

    • Escrow module README.
  • Tests

    • Extensive unit and E2E coverage for new flows.

@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Caution

Review failed

Failed to post review comments

Walkthrough

Adds a Redis-backed caching subsystem and integrates it into products. Introduces extensive escrow functionality (accounts, milestones, funding, approvals/releases) plus a parallel “escrows” flow. Migrates user IDs to UUIDs across domains, shifts auth and services to walletAddress-centric logic, adds disputes and on-chain order metadata, expands user fields, adds seller Soroban registration, strengthens observability, and updates numerous migrations and tests.

Changes

Cohort / File(s) Summary
Caching subsystem
package.json, src/cache/*, src/modules/products/products.module.ts, src/modules/products/services/product.service.ts
Adds Redis cache via cache-manager-redis-yet, AppCacheModule, CacheService, decorators, interceptor, controller; integrates caching/invalidation in ProductService; exposes cache barrel exports.
Bootstrap & observability
src/app.module.ts, src/main.ts, src/common/utils/logger.ts, src/common/interceptors/response.interceptor.ts, src/common/filters/http-exception.filter.ts, src/config/index.ts, src/config/env.ts
Wires new modules/entities, disables TypeORM synchronize, adds request start-time middleware, introduces Winston logger and latency/error logging, adds centralized ENV reader.
Auth core & wallet ownership
src/modules/auth/... (controllers, services, guards, decorators, middleware, dto, strategy, index), src/modules/auth/entities/user-role.entity.ts
Shifts to walletAddress-first flows, adds wallet-ownership guards/decorators, role/userId types move to string/UUID, extends Register/Update DTOs (country/location/buyerData/sellerData), integrates StoresModule; adjusts JWT strategy and middlewares.
User fields & DTOs
src/dtos/UserDTO.ts, src/modules/auth/dto/auth.dto.ts, src/modules/auth/dto/auth-response.dto.ts, src/modules/auth/controllers/auth.controller.ts
Adds location, country, buyerData, sellerData with role-based validation; removes id from public user DTOs; propagates country; response shapes updated.
UUID migration & FK updates
src/migrations/1751199237000-MigrateUserIdToUUID.ts, src/migrations/1751199238000-UpdateForeignKeysToUUID.ts, src/modules/auth/entities/user-role.entity.ts, src/modules/offers/entities/offer.entity.ts, src/modules/reviews/entities/review.entity.ts, src/modules/buyer-requests/entities/buyer-request.entity.ts
Converts user and related FK IDs to UUID (string) across entities and migrations; updates column types/decorators accordingly.
BuyerRequests updates
src/modules/buyer-requests/* (entity, service, dto, tests, integration)
Enforces wallet ownership and ownership checks; switches userId to string in API/entity; injects User repo; updates tests to UUIDs and adds in-memory DB setups.
Offers ownership & access
src/modules/offers/services/offers.service.ts, src/modules/offers/services/offer-attachment.service.ts, src/modules/offers/entities/offer.entity.ts, src/modules/offers/offers.module.ts, src/modules/offers/tests/*
Adds seller/buyer wallet ownership validations, injects User repo, aligns sellerId type to string, adjusts attachment checks, updates module and tests (unit/e2e).
Escrow module (accounts/milestones/funding)
src/modules/escrow/... (controller, service, entities, enums, dto, examples, README, tests), src/migrations/1756199900000-CreateEscrowTables.ts, src/migrations/1752000000000-CreateEscrowAndMilestone.ts, src/migrations/1758677273000-AddEscrowMetadata.ts
Adds full escrow lifecycle: entities, DTOs, controller, service, blockchain mock, tests (unit/e2e); DB migrations for escrows, milestones, and orders on-chain metadata.
Secondary Escrows flow
src/modules/escrows/... (entities, dto, controller, service, module, migrations/1752190000000-ExtendMilestoneStatusEnum.ts), src/app.module.ts
Adds parallel escrows domain with milestone status transitions, approve/status endpoints, service logic, and enum extension migration.
Disputes feature
src/modules/disputes/... (controller, service, entities/migration, tests)
Adds Dispute entity, migration, service, and controller to start disputes on order items; includes tests.
Orders on-chain fields
src/modules/orders/entities/order.entity.ts, src/modules/orders/dto/order.dto.ts, src/modules/orders/entities/order-item.entity.ts, src/modules/orders/entities/1695840100000-AddMilestoneAndStatusToOrderItem.ts
Adds on-chain status enum and fields to orders; adds milestone/status to order items with enum and migration; updates DTO.
Seller Soroban registration
src/modules/seller/*, src/migrations/1751199300000-AddSellerSorobanFields.ts
Adds SellerModule with controller and DTOs for build/submit/status; migration adds payout_wallet and seller_onchain_registered to users.
Stores
src/migrations/1751199238000-CreateStoresTable.ts, src/app.module.ts, src/modules/auth/auth.module.ts
Creates stores table with FKs/indexes; imports StoresModule in app/auth modules.
Reviews adjustments
src/modules/reviews/* (controller, dto, entity, service)
Switches userId to string, replaces ProductService dependency with repository-based checks, adjusts controller parsing.
Files tests updates
src/modules/files/tests/*
Test fixtures updated for new user fields, string IDs, and typing; no runtime code changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Ctrl as ProductsController
  participant Svc as ProductService
  participant Int as CacheInterceptor
  participant CS as CacheService
  participant Store as Redis Store

  Client->>Ctrl: GET /products?page=...
  Ctrl->>Int: handler invoked
  Int->>CS: generateKey(entity=product, action=list, params)
  CS->>Store: get(key)
  alt Cache HIT
    Store-->>CS: value
    CS-->>Int: value
    Int-->>Ctrl: cached response
    Ctrl-->>Client: 200 JSON
  else Cache MISS
    Int->>Svc: getAll(...)
    Svc-->>Int: data
    Int->>CS: set(key, data, ttl)
    CS->>Store: set(key, data, ttl)
    Int-->>Ctrl: data
    Ctrl-->>Client: 200 JSON
  end
Loading
sequenceDiagram
  autonumber
  actor Buyer
  actor Seller
  participant EC as EscrowController
  participant ES as EscrowService
  participant DB as DB (Tx)

  Buyer->>EC: POST /escrow/approve-milestone
  EC->>ES: approveMilestone(dto, buyerId)
  ES->>DB: Tx start: load milestone+escrow
  ES->>DB: validate + update milestone(status=APPROVED)
  DB-->>ES: saved milestone
  ES-->>EC: { success, milestone }
  EC-->>Buyer: 200

  Seller->>EC: POST /escrow/release-funds
  EC->>ES: releaseFunds(dto, sellerId)
  ES->>DB: Tx start: validate seller + milestone state
  ES->>DB: update milestone.releasedAt + escrow.releasedAmount
  DB-->>ES: saved
  ES-->>EC: { success, data: release summary }
  EC-->>Seller: 200
Loading
sequenceDiagram
  autonumber
  actor Seller
  participant SC as SellerController
  participant SS as SellerService
  Seller->>SC: POST /seller/contract/build-register {payoutWallet}
  SC->>SS: buildRegister(userId, payoutWallet)
  SS-->>SC: { unsignedXdr, contractAddress }
  SC-->>Seller: 200 { success, data }

  Seller->>SC: POST /seller/contract/submit {signedXdr}
  SC->>SS: submitRegister(userId, signedXdr)
  SS-->>SC: { transactionHash, contractId, payoutWallet, registered }
  SC-->>Seller: 200 { success, data }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • Villarley

Poem

A rabbit taps keys with a hop and a cache,
Redis burrows store every speedy fetch.
Escrows guard carrots in milestone stacks,
UUID trails replace old numeric tracks.
Disputes resolved, orders on-chain hum—
Sellers sign stars, contracts drum.
Ship it—thump-thump—reviews will come! 🥕✨

Pre-merge checks and finishing touches and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “fix dependencies” accurately points to the addition of missing dependencies in package.json, which is part of the PR’s intent, though it omits the accompanying test corrections that were also fixed; it therefore refers to a real aspect of the changeset.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 74

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (19)
src/modules/files/tests/file.service.spec.ts (2)

178-194: ID type inconsistency detected.

Line 179 uses id: 1 (number), but other mockUser instances in this file use id: "1" (string). This inconsistency should be resolved for test data uniformity.

Apply this diff to use string IDs consistently:

       const mockUser = {
-        id: 1,
+        id: '1',
         walletAddress: '0x123',
         name: 'Test User',
         email: 'test@example.com',
         location: 'Test City',
         country: 'Test Country',
         buyerData: {},
         sellerData: null,
         orders: [],
         userRoles: [],
         notifications: [],
         wishlist: [],
         stores: [],
         createdAt: new Date(),
         updatedAt: new Date(),
       };

252-280: Remove commented-out test data.

Lines 267-279 contain a large block of commented-out code (second file in the array). Per coding guidelines: "Do not leave commented-out code in commits."

Apply this diff to remove the commented code:

       const mockFiles: File[] = [
         {
           id: 'f1',
           uploadedById: '1',
           uploadedBy: mockUser,
-
           url: 'https://example.com/test.jpg',
           type: FileType.IMAGE,
           filename: 'test.jpg',
           mimetype: 'image/jpeg',
           size: 1024,
           providerType: 'cloudinary',
           providerPublicId: 'public-id',
           uploadedAt: new Date(),
         },
-        // {
-        //   id: 'f2',
-        //   uploadedById: '1',
-        //   uploadedBy: mockUser,
-        //   url: 'https://example.com/test.pdf',
-        //   type: FileType.DOCUMENT,
-        //   filename: 'test.pdf',
-        //   mimetype: 'application/pdf',
-        //   size: 2048,
-        //   providerType: 's3',
-        //   providerPublicId: 'public-id',
-        //   uploadedAt: new Date(),
-        // },
       ] as File[];
src/modules/auth/strategies/jwt.strategy.ts (1)

47-49: Error handling swallows diagnostic context.

The catch block re-throws a generic UnauthorizedException('Invalid token') which discards the original error details. This makes debugging authentication failures difficult.

Consider logging the original error before re-throwing, or conditionally preserve error messages in non-production environments:

    } catch (error) {
+     this.logger.error('JWT validation failed', error);
      throw new UnauthorizedException('Invalid token');
    }

Note: If the generic message is intentional for security (to avoid leaking information to clients), ensure server-side logging captures the details.

src/modules/auth/middleware/jwt-auth.middleware.ts (1)

45-51: Remove the as any type assertion.

The as any assertion on line 51 directly violates the coding guideline: "Do not use the any type; use unknown or specific types instead." This bypasses type checking and creates maintenance risks.

Define a proper interface for the user payload and use it instead:

+interface AuthenticatedUser {
+  id: string;
+  email: string;
+  walletAddress: string;
+  name: string;
+  role: Role[];
+}
+
 export const jwtAuthMiddleware = async (req: Request, res: Response, next: NextFunction) => {
   // ...
   
   req.user = {
     id: user.id,
     email: user.email,
     walletAddress: user.walletAddress,
     name: user.name,
     role: user.userRoles?.map((ur) => ur.role.name as Role) || [decoded.role as Role],
-  } as any;
+  } as AuthenticatedUser;

Alternatively, extend the Express Request type via declaration merging in a types file to eliminate the assertion entirely.

As per coding guidelines.

src/main.ts (2)

57-60: Replace console.log with proper logging.

Per coding guidelines, console.log and console.error should not be used. Use NestJS Logger instead for structured logging and proper log levels.

Apply this diff:

+import { Logger } from '@nestjs/common';
+
 async function bootstrap(): Promise<void> {
+  const logger = new Logger('Bootstrap');
   const app = await NestFactory.create(AppModule);
   
   // ... 
   
   try {
     const schedulerService = app.get(BuyerRequestSchedulerService);
     const closedCount = await schedulerService.closeExpiredRequests();
-    console.log(`Startup: Closed ${closedCount} expired buyer requests`);
+    logger.log(`Startup: Closed ${closedCount} expired buyer requests`);
   } catch (error) {
-    console.error('Failed to close expired requests on startup:', error);
+    logger.error('Failed to close expired requests on startup:', error);
   }

62-65: Use config object and Logger for environment variables and logging.

Lines 62, 64-65 violate coding guidelines:

  1. Direct process.env.PORT access (should use config object from src/config/env)
  2. Multiple console.log statements (should use NestJS Logger)

Apply this diff:

+import { Logger } from '@nestjs/common';
+import config from './config/env';
+
 async function bootstrap(): Promise<void> {
+  const logger = new Logger('Bootstrap');
   const app = await NestFactory.create(AppModule);
   
   // ...
   
-  const port = process.env.PORT || 3000;
+  const port = config.port || 3000;
   await app.listen(port);
-  console.log(`Application is running on: http://localhost:${port}`);
-  console.log(`Swagger documentation: http://localhost:${port}/docs`);
+  logger.log(`Application is running on: http://localhost:${port}`);
+  logger.log(`Swagger documentation: http://localhost:${port}/docs`);
 }

As per coding guidelines.

src/modules/reviews/dto/review.dto.ts (1)

3-6: Consolidate duplicate DTO definitions.

Two DTOs for creating reviews exist: CreateReviewDTO (lines 3-6, no decorators) and CreateReviewDto (lines 24-41, with validation decorators). This duplication can lead to confusion and inconsistent validation.

Consider removing the undecorated CreateReviewDTO and using only CreateReviewDto with proper validation decorators throughout the codebase, or clearly document why both are needed.

Also applies to: 24-41

src/modules/buyer-requests/tests/buyer-requests.controller.spec.ts (3)

122-142: Inconsistent user ID types in update test.

Same issue: mockRequest.user.id is 1 (number), service called with 1, but expected result has userId: "1" (string).

Apply consistent typing as suggested in the create test review comment.


147-155: Inconsistent user ID types in remove test.

Same issue: mockRequest.user.id is 1 (number) and service called with 1.

Apply consistent typing as suggested in the create test review comment.


160-183: Inconsistent user ID types in close test.

Same issue: mockRequest.user.id is 1 (number), service called with 1, but expected result has userId: "1" (string).

Apply consistent typing as suggested in the create test review comment.

src/common/filters/http-exception.filter.ts (1)

58-58: Replace direct process.env access with the config object.

Accessing process.env.NODE_ENV directly violates the coding guideline to use the config object from src/config/env.

As per coding guidelines.

Apply this diff:

+import config from '../../config/env'; // adjust path as needed
...
-      ...(process.env.NODE_ENV === 'development' && {
+      ...(config.NODE_ENV === 'development' && {
src/middleware/auth.middleware.ts (2)

5-8: Add explicit return type to authMiddleware.

The function lacks an explicit return type, violating the coding guideline.

As per coding guidelines.

Apply this diff:

-export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
+export const authMiddleware = (req: Request, res: Response, next: NextFunction): void => {

10-17: Add explicit return type to middleware function

  • Annotate the returned middleware as : void to satisfy coding guidelines.
  • Remove unnecessary ?. on req.user.role since role: Role[] is always defined.
  • Optionally simplify req.user.role.some(role => role === roleName) to req.user.role.includes(roleName) for clarity.
src/config/index.ts (1)

2-28: Coding guideline violation: Direct process.env access.

The coding guidelines for src/**/*.ts require importing and using the config object from src/config/env instead of accessing process.env directly. This file should import the centralized ENV constant from src/config/env.ts (mentioned in the AI summary) and reference environment variables through that abstraction.

Apply a refactor similar to:

+import { ENV } from './env';
+
 export const config = {
-  port: process.env.PORT || 3000,
-  jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
+  port: ENV.PORT || 3000,
+  jwtSecret: ENV.JWT_SECRET || 'your-secret-key',
   database: {
-    host: process.env.DB_HOST || 'localhost',
-    port: parseInt(process.env.DB_PORT || '5432'),
-    username: process.env.DB_USERNAME || 'postgres',
-    password: process.env.DB_PASSWORD || 'password',
-    name: process.env.DB_DATABASE || 'starshop',
+    host: ENV.DB_HOST || 'localhost',
+    port: parseInt(ENV.DB_PORT || '5432'),
+    username: ENV.DB_USERNAME || 'postgres',
+    password: ENV.DB_PASSWORD || 'password',
+    name: ENV.DB_DATABASE || 'starshop',
     synchronize: false,
-    logging: process.env.NODE_ENV !== 'production',
-    ssl: process.env.DB_SSL === 'true',
+    logging: ENV.NODE_ENV !== 'production',
+    ssl: ENV.DB_SSL === 'true',
   },
   cloudinary: {
-    cloudName: process.env.CLOUDINARY_CLOUD_NAME,
-    apiKey: process.env.CLOUDINARY_API_KEY,
-    apiSecret: process.env.CLOUDINARY_API_SECRET,
+    cloudName: ENV.CLOUDINARY_CLOUD_NAME,
+    apiKey: ENV.CLOUDINARY_API_KEY,
+    apiSecret: ENV.CLOUDINARY_API_SECRET,
   },
   aws: {
-    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
-    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
-    region: process.env.AWS_REGION,
-    bucketName: process.env.AWS_BUCKET_NAME,
+    accessKeyId: ENV.AWS_ACCESS_KEY_ID,
+    secretAccessKey: ENV.AWS_SECRET_ACCESS_KEY,
+    region: ENV.AWS_REGION,
+    bucketName: ENV.AWS_BUCKET_NAME,
   },
   supabase: {
-    url: process.env.SUPABASE_URL,
-    serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
+    url: ENV.SUPABASE_URL,
+    serviceRoleKey: ENV.SUPABASE_SERVICE_ROLE_KEY,
   },

As per coding guidelines.

src/app.module.ts (1)

56-57: Replace direct process.env access with config object.

Lines 56, 57, and 86 directly access process.env, which violates the coding guidelines. Use the config object from src/config/env instead.

The TypeOrmModule.forRoot should be refactored to use ConfigService:

+import { ConfigService } from '@nestjs/config';
+
 @Module({
   imports: [
     ConfigModule.forRoot({ isGlobal: true }),
     ScheduleModule.forRoot(),
     AppCacheModule,
-    TypeOrmModule.forRoot({
-      type: 'postgres',
-      url: process.env.DATABASE_URL,
-      ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : undefined,
+    TypeOrmModule.forRootAsync({
+      inject: [ConfigService],
+      useFactory: (configService: ConfigService) => ({
+        type: 'postgres' as const,
+        url: configService.get<string>('DATABASE_URL'),
+        ssl: configService.get<string>('DB_SSL') === 'true' ? { rejectUnauthorized: false } : undefined,
-      entities: [
+        entities: [
-        /* ... */
+          /* ... */
-      ],
-      synchronize: false,
-      logging: process.env.NODE_ENV === 'development',
-    }),
+        ],
+        synchronize: false,
+        logging: configService.get<string>('NODE_ENV') === 'development',
+      }),
+    }),

As per coding guidelines.

Also applies to: 86-86

src/modules/offers/tests/offer.entity.spec.ts (1)

12-23: Fix incorrect assertion: sellerId type mismatch.

Line 12 assigns offer.sellerId = "1" (string), but line 18 asserts expect(offer.sellerId).toBe(1) (number). This test will fail.

Apply this diff:

      offer.buyerRequestId = 123;
      offer.sellerId = "1";
      offer.title = 'Test Offer';
      offer.description = 'Test offer description';
      offer.price = 100.5;

      expect(offer.buyerRequestId).toBe(123);
-     expect(offer.sellerId).toBe(1);
+     expect(offer.sellerId).toBe("1");
      expect(offer.title).toBe('Test Offer');
      expect(offer.description).toBe('Test offer description');
      expect(offer.price).toBe(100.5);
      expect(offer.status).toBeUndefined(); // default is set by DB
    });
src/common/interceptors/response.interceptor.ts (1)

43-46: Avoid any type annotation.

The formattedResponse variable is typed as any, which violates the coding guideline.

As per coding guidelines.

Apply this diff:

-        const formattedResponse: any = {
+        const formattedResponse: { success: boolean; data: unknown; token?: string } = {
           success: true,
           data,
         };
src/modules/products/services/product.service.ts (1)

45-52: Mark ProductService as injectable before adding injected deps.

With the new CacheService constructor param, Nest will try to resolve this provider. Because the class still lacks @Injectable(), DI metadata isn't emitted and the cache service ends up undefined, breaking every cache call at runtime. Import Injectable and decorate the class (or wire it differently) so DI can supply CacheService.

-import { CacheService } from '../../../cache/cache.service';
-import { Cacheable, CacheInvalidate } from '../../../cache/decorators/cache.decorator';
+import { Injectable } from '@nestjs/common';
+import { CacheService } from '../../../cache/cache.service';
+import { Cacheable, CacheInvalidate } from '../../../cache/decorators/cache.decorator';
@@
-export class ProductService {
+@Injectable()
+export class ProductService {
src/modules/auth/tests/auth.service.spec.ts (1)

256-263: Update getUserById expectation to use string IDs

With User.id now a UUID/string, asserting that findOne receives { id: 1 } keeps the service bound to the old numeric path. On a UUID column that becomes an invalid query (Postgres will throw “invalid input syntax for type uuid”), so the application will no longer fetch users by ID. Please switch the expectation to the string form (and adjust the service if it still coerces to number):

-    expect(mockUserRepository.findOne).toHaveBeenCalledWith({
-      where: { id: 1 },
+    expect(mockUserRepository.findOne).toHaveBeenCalledWith({
+      where: { id: '1' },
       relations: ['userRoles', 'userRoles.role'],
     });
🧹 Nitpick comments (32)
src/modules/files/tests/file.controller.spec.ts (1)

87-100: Inconsistent mock data types across test files.

This inline mockUser definition has several issues:

  1. ID type inconsistency: Line 88 uses id: 1 (number), but file.service.spec.ts consistently uses id: "1" (string). This mismatch can cause confusion and potential test failures.

  2. Implicit any types: Lines 95-96 use buyerData: {} and sellerData: null without explicit types, violating the coding guideline to avoid any types.

  3. Consider reusing test-utils: The mockUser from test-utils.ts already defines these fields. Reusing it would reduce duplication and ensure consistency.

Apply this diff to align types and consider importing from test-utils:

+import { mockUser as baseMockUser } from './test-utils';
+
       const mockUser = {
-        id: 1,
+        ...baseMockUser,
+        id: '1',
         walletAddress: '0x123',
         name: 'Test User',
         email: 'test@example.com',
         password: 'hashed_password',
-        location: 'Test City',
-        country: 'Test Country',
-        buyerData: {},
-        sellerData: null,
         orders: [],
         userRoles: [],
         notifications: [],
       } as unknown as User;
src/modules/files/tests/file.service.spec.ts (3)

114-132: Inconsistent field additions across mockUser instances.

This mockUser includes payoutWallet (line 121), which is not present in other mockUser instances or in test-utils.ts. This inconsistency can lead to confusion about which fields are required for tests.

Consider either:

  1. Adding payoutWallet to all mockUser instances if it's commonly needed, or
  2. Documenting why this specific test requires it

305-321: Implicit any types in mock data.

Lines 312-314 use buyerData: {} and sellerData: null without explicit types. Per coding guidelines: "Do not use the any type; use unknown or specific types instead."

Consider adding explicit types:

       const mockUser = {
         id: 1,
         walletAddress: '0x123',
         name: 'Test User',
         email: 'test@example.com',
         password: 'hashed_password',
         location: 'Test City',
         country: 'Test Country',
-        buyerData: {},
-        sellerData: null,
+        buyerData: {} as Record<string, unknown>,
+        sellerData: null as unknown,
         orders: [],
         userRoles: [],
         notifications: [],
         wishlist: [],
         createdAt: new Date(),
         updatedAt: new Date(),
       };

349-365: Implicit any types in mock data.

Lines 357-358 use buyerData: {} and sellerData: null without explicit types. Same issue as previous mockUser instances.

Apply the same typing fix:

       const mockUser = {
         id: 1,
         walletAddress: '0x123',
         name: 'Test User',
         email: 'test@example.com',
         password: 'hashed_password',
         location: 'Test City',
         country: 'Test Country',
-        buyerData: {},
-        sellerData: null,
+        buyerData: {} as Record<string, unknown>,
+        sellerData: null as unknown,
         orders: [],
         userRoles: [],
         notifications: [],
         wishlist: [],
         createdAt: new Date(),
         updatedAt: new Date(),
       };
src/modules/escrows/services/escrow.service.spec.ts (3)

9-25: Refactor to eliminate any and add explicit return types.

The MockRepo class uses any in multiple places (lines 10, 12, 14, 17) and methods lack explicit return types, violating project coding guidelines.

Apply this diff to improve type safety:

-class MockRepo<T extends { id?: any }> {
+class MockRepo<T extends { id?: string | number }> {
   private entities: T[] = [];
-  findOne = jest.fn(async (options: any) => {
+  findOne = jest.fn(async (options: { where?: { id?: string | number; escrowId?: string }; relations?: string[] }): Promise<T | null> => {
     if (options.where?.id) return this.entities.find(e => e.id === options.where.id) || null;
-    if (options.where?.id && options.relations) return this.entities.find(e => e.id === options.where.id) || null;
     return null;
   });
-  find = jest.fn(async (options: any) => this.entities.filter(e => (options.where?.escrowId ? (e as any).escrowId === options.where.escrowId : true)));
+  find = jest.fn(async (options: { where?: { escrowId?: string } }): Promise<T[]> => {
+    return this.entities.filter(e => (options.where?.escrowId ? (e as any).escrowId === options.where.escrowId : true));
+  });
-  save = jest.fn(async (entity: T) => {
+  save = jest.fn(async (entity: T): Promise<T> => {
     const existingIndex = this.entities.findIndex(e => e.id === entity.id);
     if (existingIndex >= 0) this.entities[existingIndex] = entity;
     else this.entities.push(entity);
     return entity;
   });
-  seed(data: T[]) { this.entities = data; }
+  seed(data: T[]): void { this.entities = data; }
 }

Note: Line 13-14 redundant condition can also be removed (already addressed in diff above).


27-31: Add JSDoc, explicit types, and return type annotation.

The mockTransaction helper uses any types and lacks documentation for its complex transaction-mocking logic, violating coding guidelines.

Apply this diff:

+/**
+ * Mock transaction manager that routes entity operations to the appropriate mock repository.
+ * @param cb - Callback receiving a mock transaction manager
+ * @returns The result of the callback execution
+ */
-const mockTransaction = (cb: any) => cb({
-  findOne: (entity: any, opts: any) => entity === Milestone ? milestoneRepo.findOne(opts) : escrowRepo.findOne(opts),
-  find: (entity: any, opts: any) => milestoneRepo.find(opts),
-  save: (entity: any) => Array.isArray(entity) ? Promise.all(entity.map(e => (e instanceof Milestone ? milestoneRepo.save(e) : escrowRepo.save(e)))) : (entity instanceof Milestone ? milestoneRepo.save(entity) : escrowRepo.save(entity))
+const mockTransaction = <T>(cb: (manager: any) => Promise<T>): Promise<T> => cb({
+  findOne: (entity: any, opts: any): Promise<any> => entity === Milestone ? milestoneRepo.findOne(opts) : escrowRepo.findOne(opts),
+  find: (entity: any, opts: any): Promise<any[]> => milestoneRepo.find(opts),
+  save: (entity: any): Promise<any> => Array.isArray(entity) ? Promise.all(entity.map(e => (e instanceof Milestone ? milestoneRepo.save(e) : escrowRepo.save(e)))) : (entity instanceof Milestone ? milestoneRepo.save(entity) : escrowRepo.save(entity))
 });

54-58: Consider using proper types instead of as any.

Seed data uses as any to bypass type checking. While acceptable in tests, it reduces type safety.

If the entities have required fields causing type errors, consider creating proper test fixtures:

// Seed data
const mockEscrow: Partial<Escrow> = { id: 'escrow1', sellerId: 10, buyerId: 20 };
const mockMilestone: Partial<Milestone> = { id: 'm1', escrowId: 'escrow1', status: MilestoneStatus.PENDING };
escrowRepo.seed([mockEscrow as Escrow]);
milestoneRepo.seed([mockMilestone as Milestone]);

Or create factory functions for test entities.

src/modules/seller/dto/build-register.dto.ts (1)

17-32: Add validation decorators to response DTO.

The BuildRegisterResponseDto lacks validation decorators. While response DTOs are primarily for documentation, adding validation ensures consistency and catches serialization issues during development.

Apply this diff:

+import { IsString, IsNotEmpty, Matches, IsBoolean, ValidateNested, IsObject } from 'class-validator';
+import { Type } from 'class-transformer';
+
+class BuildRegisterDataDto {
+  @ApiProperty({
+    description: 'Unsigned XDR transaction for Soroban contract registration',
+    example: 'AAAAAgAAAABqjgAAAAAA...',
+  })
+  @IsString()
+  @IsNotEmpty()
+  unsignedXdr: string;
+
+  @ApiProperty({
+    description: 'Soroban contract address',
+    example: 'CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+  })
+  @IsString()
+  @IsNotEmpty()
+  contractAddress: string;
+}

 export class BuildRegisterResponseDto {
   @ApiProperty({
     description: 'Success status',
     example: true,
   })
+  @IsBoolean()
   success: boolean;

-  @ApiProperty({
-    description: 'Unsigned XDR transaction for Soroban contract registration',
-    example: 'AAAAAgAAAABqjgAAAAAA...',
-  })
-  data: {
-    unsignedXdr: string;
-    contractAddress: string;
-  };
+  @ValidateNested()
+  @Type(() => BuildRegisterDataDto)
+  data: BuildRegisterDataDto;
 }
src/main.ts (2)

10-10: Add explicit return type.

Per coding guidelines, all functions should have explicit return types. While Promise<void> is present, ensuring consistency across all functions is important.


13-17: Type the request object for the middleware.

The middleware assigns req._startTime without proper typing. This could lead to type safety issues downstream when the interceptor or filter attempts to read this property.

Consider adding a type declaration file to extend the Express Request interface:

// src/types/express.d.ts
declare namespace Express {
  export interface Request {
    _startTime?: number;
  }
}

Then ensure this declaration is included in your tsconfig.json.

src/modules/offers/services/offer-attachment.service.ts (2)

28-46: Consider accepting userId as string for consistency.

The userId parameter is typed as number but compared against offer.sellerId which is a string (UUID). While the conversion works, accepting userId as string from the start would be more consistent with the UUID migration.

Apply this diff:

 async uploadAttachment(
   offerId: string,
   file: Express.Multer.File,
-  userId: number,
+  userId: string,
   provider: 'cloudinary' | 's3' = 'cloudinary'
 ): Promise<OfferAttachmentResponseDto> {
   // ...
   
-  if (offer.sellerId !== userId.toString()) {
+  if (offer.sellerId !== userId) {
     throw new ForbiddenException('You can only add attachments to your own offers');
   }

This aligns with the broader UUID migration and simplifies the comparison logic.


129-141: Consider accepting userId as string for consistency.

Same issue as uploadAttachment: the userId parameter is typed as number but compared against a string sellerId.

Apply this diff:

-async deleteAttachment(attachmentId: string, userId: number): Promise<void> {
+async deleteAttachment(attachmentId: string, userId: string): Promise<void> {
   const attachment = await this.offerAttachmentRepository.findOne({
     where: { id: attachmentId },
     relations: ['offer', 'offer.seller'],
   });

   if (!attachment) {
     throw new NotFoundException('Attachment not found');
   }

-  if (attachment.offer.sellerId !== userId.toString()) {
+  if (attachment.offer.sellerId !== userId) {
     throw new ForbiddenException('You can only delete attachments from your own offers');
   }
src/common/filters/http-exception.filter.ts (1)

16-17: Type the request with custom properties for _startTime.

The request is retrieved without explicit typing, and _startTime is a custom property added by middleware but not reflected in the type system.

Define an interface extending Express Request:

interface RequestWithTiming extends Request {
  _startTime?: number;
  user?: any; // or a proper User type
  route?: { path?: string };
  method?: string;
  originalUrl?: string;
}

Then cast or type the request:

-    const request = ctx.getRequest();
+    const request = ctx.getRequest<RequestWithTiming>();
src/modules/escrow/escrow.module.ts (1)

13-20: Consider exporting services if used by other modules.

The module does not currently export EscrowService or BlockchainService. If other modules need to inject these services, add them to the exports array in the @Module decorator.

src/modules/buyer-requests/dto/buyer-request-response.dto.ts (1)

3-22: Consider converting interface to class with validators.

This response DTO uses an interface, which provides no runtime validation. Per coding guidelines, DTOs should use class-validator decorators. Consider converting BuyerRequestResponseDto to a class-based DTO for consistency and runtime type safety.

Example structure:

import { Expose, Type } from 'class-transformer';
import { IsString, IsNumber, IsEnum, IsOptional, IsDate, IsBoolean, ValidateNested } from 'class-validator';

class UserResponseDto {
  @Expose()
  @IsString()
  id: string;
  
  @Expose()
  @IsString()
  name: string;
  
  @Expose()
  @IsString()
  walletAddress: string;
}

export class BuyerRequestResponseDto {
  @Expose()
  @IsNumber()
  id: number;
  
  @Expose()
  @IsString()
  userId: string;
  
  @Expose()
  @IsOptional()
  @ValidateNested()
  @Type(() => UserResponseDto)
  user?: UserResponseDto;
  
  // ... remaining fields with appropriate validators
}

As per coding guidelines.

src/migrations/1751199237000-AddUserFields.ts (1)

6-15: Consider adding indexes for location and country columns.

If these columns will be used in WHERE clauses or for filtering users by location/country, consider adding indexes to improve query performance.

Apply this diff to add indexes:

   public async up(queryRunner: QueryRunner): Promise<void> {
     // Add new columns to users table
     await queryRunner.query(`
       ALTER TABLE "users" 
       ADD COLUMN "location" character varying NULL,
       ADD COLUMN "country" character varying NULL,
       ADD COLUMN "buyerData" jsonb NULL,
       ADD COLUMN "sellerData" jsonb NULL
     `);
+    
+    // Add indexes for common query patterns
+    await queryRunner.query(`
+      CREATE INDEX "IDX_users_country" ON "users" ("country") WHERE "country" IS NOT NULL
+    `);
+    await queryRunner.query(`
+      CREATE INDEX "IDX_users_location" ON "users" ("location") WHERE "location" IS NOT NULL
+    `);
   }

And update the down() method:

   public async down(queryRunner: QueryRunner): Promise<void> {
+    // Drop indexes first
+    await queryRunner.query(`DROP INDEX IF EXISTS "IDX_users_location"`);
+    await queryRunner.query(`DROP INDEX IF EXISTS "IDX_users_country"`);
+    
     // Remove the columns
     await queryRunner.query(`
       ALTER TABLE "users" 
       DROP COLUMN "location",
       DROP COLUMN "country",
       DROP COLUMN "buyerData",
       DROP COLUMN "sellerData"
     `);
   }
src/modules/disputes/controllers/dispute.controller.ts (1)

12-19: Wrap service call in try-catch for proper error handling.

The controller does not handle errors from the service layer, which could result in unhandled exceptions.

  async startDispute(
    @Body() dto: StartDisputeDto,
    @Req() req: AuthenticatedRequest
  ): Promise<any> {
+   try {
      const buyer = req.user;
      return this.disputeService.startDispute(dto.orderItemId, buyer, dto.reason);
+   } catch (error) {
+     // Let NestJS exception filters handle it, or add custom logging
+     throw error;
+   }
  }

Alternatively, rely on NestJS global exception filters if they are configured. If custom error handling or logging is needed, add it here.

As per coding guidelines.

src/modules/offers/tests/offer.entity.spec.ts (1)

25-35: Inconsistent test assertion on line 33.

This test validates negative price behavior but does not relate to the sellerId type change. However, ensure the assertion is meaningful: testing that offer.price is less than 0 after assigning -50 is trivial.

Consider renaming or clarifying the test purpose, or remove if redundant:

-   it('should create an offer with price validation', () => {
+   it('should allow negative price in memory (would fail in DB)', () => {
      const offer = new Offer();
      offer.buyerRequestId = 123;
      offer.sellerId = "1";
      offer.title = 'Test Offer';
      offer.description = 'Test offer description';
      offer.price = -50; // Invalid in DB, but allowed in-memory

      expect(offer.price).toBeLessThan(0);
    });
src/modules/escrow/services/escrow.service.spec.ts (1)

39-63: Add descriptive test names and consider edge cases.

The tests cover basic success and error cases, which is good. Consider adding tests for:

  • Invalid amount (e.g., negative, zero)
  • Concurrent funding attempts
  • Escrow already funded

Example additional test:

it('throws BadRequestException for zero or negative amount', async () => {
  const repo = dataSource.getRepository(Escrow);
  const escrow = repo.create({ expectedSigner: 'SIGNER1', balance: '0' });
  await repo.save(escrow);

  await expect(
    service.fundEscrow(escrow.id, { signer: 'SIGNER1', amount: 0 })
  ).rejects.toBeInstanceOf(BadRequestException);
});
src/config/env.ts (2)

1-29: Consider Zod schema validation per coding guidelines.

The coding guideline specifies: "Validate all environment variables in the centralized env module using a Zod schema." This implementation uses env-var, which provides runtime validation but differs from the prescribed approach.

As per coding guidelines.

While env-var does offer validation (.required(), .asPortNumber(), etc.), adopting Zod would align with the team's standard and provide additional benefits like static type inference.

Example Zod-based approach:

import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.string().default('development'),
  PORT: z.coerce.number().int().positive().default(3000),
  DATABASE_URL: z.string().min(1),
  JWT_SECRET: z.string().min(1),
  SUPABASE_URL: z.string().url(),
  SUPABASE_ANON_KEY: z.string().min(1),
  REDIS_URL: z.string().url().default('redis://localhost:6379'),
  HORIZON_URL: z.string().url().default('https://horizon-testnet.stellar.org'),
  STELLAR_NETWORK: z.string().default('testnet'),
  EMAIL_SERVICE: z.string().default('gmail'),
  EMAIL_USER: z.string().email(),
  EMAIL_PASSWORD: z.string().min(1),
  BASE_URL: z.string().url().default('http://localhost:3000'),
});

export const ENV = envSchema.parse(process.env);
export type Env = z.infer<typeof envSchema>;

3-29: Add explicit type annotation for ENV.

The ENV constant lacks an explicit type, reducing type safety and IDE autocomplete support.

Apply this diff to add a type:

+interface EnvConfig {
+  NODE_ENV: string;
+  PORT: number;
+  DATABASE_URL: string;
+  JWT_SECRET: string;
+  SUPABASE_URL: string;
+  SUPABASE_ANON_KEY: string;
+  REDIS_URL: string;
+  HORIZON_URL: string;
+  STELLAR_NETWORK: string;
+  EMAIL_SERVICE: string;
+  EMAIL_USER: string;
+  EMAIL_PASSWORD: string;
+  BASE_URL: string;
+}
+
-export const ENV = {
+export const ENV: EnvConfig = {
   NODE_ENV: env.get("NODE_ENV").default("development").asString(),
src/modules/auth/dto/auth.dto.ts (1)

14-16: Remove unused import.

Type is imported from class-transformer but never used—please drop it to keep the file clean.

src/modules/seller/dto/submit-register.dto.ts (1)

24-29: Consider extracting the nested data object to a separate class.

The inline nested object shape is acceptable, but extracting it to a separate class would improve reusability and type safety if this shape is used elsewhere.

Example refactor:

export class RegisterResultDto {
  @ApiProperty({ description: 'Transaction hash' })
  transactionHash: string;

  @ApiProperty({ description: 'Contract ID' })
  contractId: string;

  @ApiProperty({ description: 'Payout wallet address' })
  payoutWallet: string;

  @ApiProperty({ description: 'Registration status' })
  registered: boolean;
}

export class SubmitRegisterResponseDto {
  @ApiProperty({
    description: 'Success status',
    example: true,
  })
  success: boolean;

  @ApiProperty({
    description: 'Registration result with transaction hash',
    type: RegisterResultDto,
  })
  data: RegisterResultDto;
}
src/migrations/1756199900000-CreateEscrowTables.ts (1)

7-12: Consider adding an index on expected_signer.

If escrows will be frequently queried by signer, add an index to improve performance.

CREATE INDEX IF NOT EXISTS escrows_expected_signer_idx ON escrows(expected_signer);
src/modules/auth/controllers/auth.controller.ts (1)

148-154: Consider validating country code format in RegisterUserDto.

While the toUpperCase() transformation is good, ensure the DTO validates that country is a valid ISO 3166-1 alpha-2 code using @IsISO31661Alpha2() decorator from class-validator.

In src/modules/auth/dto/auth.dto.ts (RegisterUserDto):

import { IsOptional, IsISO31661Alpha2 } from 'class-validator';

@ApiProperty({
  description: 'User country code',
  example: 'US',
  required: false,
})
@IsOptional()
@IsISO31661Alpha2()
country?: string;
src/cache/controllers/cache.controller.ts (1)

20-22: Unnecessary await.

Return the Promise directly; drop async/await here.

-  async getStats() {
-    return await this.cacheService.getStats();
-  }
+  getStats(): Promise<unknown> {
+    return this.cacheService.getStats();
+  }

As per coding guidelines.

src/modules/orders/entities/1695840100000-AddMilestoneAndStatusToOrderItem.ts (1)

6-10: Migration looks correct; consider idempotency safeguards.

Optional: add IF NOT EXISTS where supported to reduce re-run fragility in mixed environments.

If targeting Postgres ≥ 9.6, you can use:

ALTER TABLE "order_items" ADD COLUMN IF NOT EXISTS "milestone" varchar(255);
DO $$ BEGIN
  CREATE TYPE "order_item_status_enum" AS ENUM('ACTIVE','DISPUTED','COMPLETED');
EXCEPTION
  WHEN duplicate_object THEN NULL;
END $$;
ALTER TABLE "order_items" ADD COLUMN IF NOT EXISTS "status" "order_item_status_enum" NOT NULL DEFAULT 'ACTIVE';
src/cache/decorators/cache.decorator.ts (2)

35-40: Export and reuse metadata keys; type the decorator.

Avoid magic strings to prevent drift across interceptor/guards.

+export const CACHE_INVALIDATE_METADATA = 'cache_invalidate';
-export const CacheInvalidate = (entity: string, action?: string) => {
-  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
-    SetMetadata('cache_invalidate', { entity, action })(target, propertyKey, descriptor);
+export const CacheInvalidate = (entity: string, action?: string): MethodDecorator => {
+  return (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
+    SetMetadata(CACHE_INVALIDATE_METADATA, { entity, action })(target, propertyKey, descriptor);
     return descriptor;
   };
 };

45-50: Same for CacheClear: avoid any and magic strings.

+export const CACHE_CLEAR_METADATA = 'cache_clear';
-export const CacheClear = () => {
-  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
-    SetMetadata('cache_clear', true)(target, propertyKey, descriptor);
+export const CacheClear = (): MethodDecorator => {
+  return (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
+    SetMetadata(CACHE_CLEAR_METADATA, true)(target, propertyKey, descriptor);
     return descriptor;
   };
 };

As per coding guidelines.

src/cache/interceptors/cache.interceptor.ts (2)

29-31: Support class-level metadata with getAllAndOverride.

This allows applying Cacheable at controller level.

-    const cacheKeyMetadata = this.reflector.get(CACHE_KEY_METADATA, handler);
-    const cacheTtlMetadata = this.reflector.get(CACHE_TTL_METADATA, handler);
+    const cacheKeyMetadata = this.reflector.getAllAndOverride(
+      CACHE_KEY_METADATA,
+      [handler, context.getClass()],
+    );
+    const cacheTtlMetadata = this.reflector.getAllAndOverride(
+      CACHE_TTL_METADATA,
+      [handler, context.getClass()],
+    );

24-52: Optional: guard cache I/O with try/catch to degrade gracefully.

Prevent cache outages from breaking requests; log at warn.

src/modules/escrow/tests/escrow.service.spec.ts (1)

24-99: Type the mocked transaction manager instead of any

Line 24 currently declares transactionManager: any, which breaks our no-any rule and prevents the compiler from catching mistakes in the mocked transaction helpers. Type it as an EntityManager mock so misuse is caught during compilation.

Apply this diff to bring it in line:

-import { Repository, DataSource } from 'typeorm';
+import { Repository, DataSource, EntityManager } from 'typeorm';
...
-  let transactionManager: any;
+  let transactionManager: jest.Mocked<Partial<EntityManager>>;
...
-    transactionManager = {
-      findOne: jest.fn(),
-      save: jest.fn(),
-      create: jest.fn(),
-    };
+    transactionManager = {
+      findOne: jest.fn(),
+      save: jest.fn(),
+      create: jest.fn(),
+    } as jest.Mocked<Partial<EntityManager>>;

As per coding guidelines.

Comment on lines +54 to +55
"cache-manager": "^7.2.2",
"cache-manager-redis-yet": "^5.1.5",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Incompatible cache-manager versions.

cache-manager ^7.2.2 is incompatible with cache-manager-redis-yet ^5.1.5. The latter is a legacy store adapter for cache-manager v5, while v7.x has migrated to Keyv-backed stores.

This will cause runtime failures when the Redis store attempts to register with cache-manager v7.

Based on learnings

Apply this diff to use compatible versions:

-    "cache-manager": "^7.2.2",
-    "cache-manager-redis-yet": "^5.1.5",
+    "cache-manager": "^5.7.6",
+    "cache-manager-redis-yet": "^5.1.5",

OR migrate to the modern Keyv-backed approach:

     "cache-manager": "^7.2.2",
-    "cache-manager-redis-yet": "^5.1.5",
+    "@keyv/redis": "^3.0.1",
+    "keyv": "^5.1.1",

Then update src/cache/cache.module.ts to use the Keyv Redis adapter instead of the legacy store.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"cache-manager": "^7.2.2",
"cache-manager-redis-yet": "^5.1.5",
// package.json, around lines 54–55
"cache-manager": "^5.7.6",
"cache-manager-redis-yet": "^5.1.5",
Suggested change
"cache-manager": "^7.2.2",
"cache-manager-redis-yet": "^5.1.5",
// package.json, around lines 54–55
"cache-manager": "^7.2.2",
"@keyv/redis": "^3.0.1",
"keyv": "^5.1.1",
🤖 Prompt for AI Agents
In package.json around lines 54-55, the project declares cache-manager ^7.2.2
and cache-manager-redis-yet ^5.1.5 which are incompatible; either replace the
redis adapter with a Keyv-backed store or align versions: Option A — change
package.json to use cache-manager ^5.x and keep cache-manager-redis-yet ^5.1.5
then run install and verify src/cache/cache.module.ts still registers the legacy
store; Option B (preferred) — remove cache-manager-redis-yet and add Keyv and
keyv-redis (and any Keyv typings), update package.json accordingly, then modify
src/cache/cache.module.ts to initialize Keyv with the Redis adapter (import
Keyv, import KeyvRedis, instantiate Keyv with the Redis adapter and connection
options) and replace cache-manager registration with the new Keyv-based cache
client, update any code that used cache-manager API to the Keyv API, run tests
and lint to ensure no runtime type errors.

Comment on lines +45 to +46
import { Escrow } from './modules/escrows/entities/escrow.entity';
import { Milestone } from './modules/escrows/entities/milestone.entity';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Clarify escrow vs escrows module naming.

There appear to be two separate escrow-related modules: EscrowModule (from ./modules/escrow/escrow.module) and EscrowsModule (from ./modules/escrows/escrows.module). This naming is confusing and suggests potential architectural duplication.

Run the following script to understand the distinction:

Consider consolidating into a single module if they serve similar purposes, or rename one to better reflect its distinct responsibility (e.g., EscrowAccountsModule vs EscrowTransactionsModule).


🏁 Script executed:

#!/bin/bash
# Description: Investigate the difference between escrow and escrows modules

echo "=== Checking escrow module structure ==="
echo "--- ./modules/escrow ---"
fd . src/modules/escrow -t f -x echo {/}

echo -e "\n--- ./modules/escrows ---"
fd . src/modules/escrows -t f -x echo {/}

echo -e "\n=== Checking exports from each module ==="
echo "--- escrow.module.ts ---"
rg -A 10 "export.*class.*EscrowModule" src/modules/escrow/escrow.module.ts

echo -e "\n--- escrows.module.ts ---"
rg -A 10 "export.*class.*EscrowsModule" src/modules/escrows/escrows.module.ts

echo -e "\n=== Checking for potential conflicts ==="
rg -l "EscrowModule|EscrowsModule" src --type ts

Length of output: 1104


Consolidate duplicate escrow modules.

The escrow (src/modules/escrow) and escrows (src/modules/escrows) modules both define overlapping entities (escrow.entity.ts, milestone.entity.ts), services, controllers, and DTOs—this duplication is confusing. Merge them into a single module or rename one to clearly reflect a distinct domain responsibility (e.g., EscrowAccountsModule vs EscrowMilestonesModule).

🤖 Prompt for AI Agents
In src/app.module.ts around lines 45-46, the code imports Escrow and Milestone
from the duplicated escrows/escrow modules; consolidate the duplicate modules by
choosing one of two approaches: either merge the contents of src/modules/escrow
and src/modules/escrows into a single module (move entities, services,
controllers, DTOs into one folder, update all import paths in app.module.ts and
across the codebase, register only the unified module and entities in Nest
module metadata, and delete the now-empty duplicate folder) or rename one module
to reflect a distinct domain (e.g., EscrowAccounts vs EscrowMilestones) and
refactor its exports and import paths accordingly so there is no overlapping
entity/service/controller; after making the change, update app.module.ts imports
to reference the final module paths, adjust any tests or DI bindings that
referenced the old paths, and run the test suite and a full TypeScript build to
ensure no unresolved imports remain.

Comment on lines +76 to 84
EscrowAccount,
Milestone,

Escrow,
EscrowFundingTx,
Store,
Escrow,
Milestone,
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate entity registrations.

Escrow and Milestone are registered twice in the entities array (lines 77 & 82, and 79 & 83), which is redundant and may cause unexpected behavior.

Apply this diff to remove duplicates:

       EscrowAccount,
       Milestone,
-
-  Escrow,
   EscrowFundingTx,
       Store,
-  Escrow,
-  Milestone,
     ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EscrowAccount,
Milestone,
Escrow,
EscrowFundingTx,
Store,
Escrow,
Milestone,
],
EscrowAccount,
Milestone,
EscrowFundingTx,
Store,
],
🤖 Prompt for AI Agents
In src/app.module.ts around lines 76 to 84, the entities array includes
duplicate registrations of Escrow and Milestone (appearing twice); remove the
duplicate entries so each entity appears only once in the array, leaving a
single Escrow and a single Milestone declaration and preserving the other
entities (EscrowAccount, EscrowFundingTx, Store) in place.

Comment on lines +101 to +105
EscrowModule,
SupabaseModule,
EscrowModule,
StoresModule,
EscrowsModule,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate EscrowModule import.

EscrowModule is imported twice (lines 101 and 103), which will cause a module initialization error.

Apply this diff:

     OffersModule,
     EscrowModule,
     SupabaseModule,
-  EscrowModule,
     StoresModule,
     EscrowsModule,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EscrowModule,
SupabaseModule,
EscrowModule,
StoresModule,
EscrowsModule,
OffersModule,
EscrowModule,
SupabaseModule,
StoresModule,
EscrowsModule,
🤖 Prompt for AI Agents
In src/app.module.ts around lines 101 to 105 there is a duplicate import of
EscrowModule (appears on lines 101 and 103); remove the second occurrence (line
103) from the module's imports array so EscrowModule is only imported once,
leaving the other imports (SupabaseModule, StoresModule, EscrowsModule) intact.

Comment on lines +4 to +36
import * as redisStore from 'cache-manager-redis-yet';
import { CacheService } from './cache.service';
import { CacheController } from './controllers/cache.controller';
import { JwtService } from '@nestjs/jwt';
import { Role, RoleService, UserRole } from '@/modules/auth';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
imports: [
TypeOrmModule.forFeature([Role, UserRole]),
CacheModule.registerAsync({
isGlobal: true,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
const redisUrl = configService.get<string>('REDIS_URL');
const ttl = parseInt(configService.get<string>('CACHE_TTL_SECONDS') ?? '60', 10);
const prefix = configService.get<string>('CACHE_PREFIX') ?? 'app:';

if (!redisUrl) {
throw new Error('REDIS_URL environment variable is required for caching');
}

return {
store: redisStore,
url: redisUrl,
ttl,
prefix,
retryStrategy: (times: number) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix Redis store registration (await the factory).

cache-manager-redis-yet exports an async redisStore factory; passing the function itself means Nest receives { store: Function }, so cache operations explode at runtime (get/set are undefined). Import the named export and await it before returning the cache options.

-import * as redisStore from 'cache-manager-redis-yet';
+import { redisStore } from 'cache-manager-redis-yet';
@@
-        return {
-          store: redisStore,
-          url: redisUrl,
-          ttl,
-          prefix,
-          retryStrategy: (times: number) => {
-            const delay = Math.min(times * 50, 2000);
-            return delay;
-          },
-          maxRetriesPerRequest: 3,
-        };
+        return {
+          store: await redisStore({
+            url: redisUrl,
+            ttl,
+            keyPrefix: prefix,
+            retryStrategy: (times: number) => Math.min(times * 50, 2000),
+            maxRetriesPerRequest: 3,
+          }),
+          ttl,
+        };
🤖 Prompt for AI Agents
In src/cache/cache.module.ts around lines 4 to 36, the Redis store is being
passed as the async factory function itself (so Nest receives { store: Function
} and cache methods are undefined); import the named async factory export from
'cache-manager-redis-yet' (not the whole module), call and await that factory
inside the useFactory, assign the awaited result to the store property in the
returned options, and keep the other options
(url/ttl/prefix/retryStrategy/maxRetriesPerRequest) unchanged.

Comment on lines +18 to +41
approve(
@Param('escrowId') escrowId: string,
@Param('milestoneId') milestoneId: string,
@Request() req: AuthRequest
) {
return this.escrowService.approveMilestone(escrowId, milestoneId, Number(req.user.id));
}

@Patch(':escrowId/milestones/:milestoneId/status')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.SELLER)
changeStatus(
@Param('escrowId') escrowId: string,
@Param('milestoneId') milestoneId: string,
@Body() body: UpdateMilestoneStatusDto,
@Request() req: AuthRequest
) {
return this.escrowService.changeMilestoneStatus(
escrowId,
milestoneId,
Number(req.user.id),
body.status as MilestoneStatus
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Annotate controller method return types.

Per our src/**/*.ts guidelines we need explicit return types on every method. Please update approve and changeStatus to return the actual Promise<...> type (e.g., the DTO returned by EscrowService) so the controller complies.

🤖 Prompt for AI Agents
In src/modules/escrows/controllers/escrow.controller.ts lines 18-41, the
controller methods approve and changeStatus lack explicit return types; update
their signatures to return the concrete Promise<T> types that EscrowService
methods return (import the appropriate DTO/response types from the service or
DTO files and annotate approve as Promise<...> and changeStatus as
Promise<...>), ensure required imports are added at the top, and run typecheck
to fix any mismatches—do not use plain Promise<any>, use the exact DTO/response
types returned by EscrowService.

Comment on lines +1 to +8
import { IsEnum } from 'class-validator';
import { MilestoneStatus } from '../entities/milestone.entity';

// Seller-changeable statuses (not including APPROVED which is buyer action)
export class UpdateMilestoneStatusDto {
@IsEnum(MilestoneStatus, { message: 'Invalid milestone status' })
status: MilestoneStatus; // Expect READY | IN_PROGRESS | DELIVERED
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add OpenAPI decorator for API documentation.

The DTO correctly validates the status field using @IsEnum, but it's missing OpenAPI documentation decorators. Since the project uses @nestjs/swagger (per dependencies), DTOs should include @ApiProperty decorators for consistent API documentation generation.

Apply this diff to add OpenAPI documentation:

 import { IsEnum } from 'class-validator';
+import { ApiProperty } from '@nestjs/swagger';
 import { MilestoneStatus } from '../entities/milestone.entity';
 
 // Seller-changeable statuses (not including APPROVED which is buyer action)
 export class UpdateMilestoneStatusDto {
   @IsEnum(MilestoneStatus, { message: 'Invalid milestone status' })
+  @ApiProperty({
+    enum: MilestoneStatus,
+    description: 'New milestone status (seller-changeable only: READY, IN_PROGRESS, DELIVERED)',
+  })
   status: MilestoneStatus; // Expect READY | IN_PROGRESS | DELIVERED
 }

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { IsEnum } from 'class-validator';
import { MilestoneStatus } from '../entities/milestone.entity';
// Seller-changeable statuses (not including APPROVED which is buyer action)
export class UpdateMilestoneStatusDto {
@IsEnum(MilestoneStatus, { message: 'Invalid milestone status' })
status: MilestoneStatus; // Expect READY | IN_PROGRESS | DELIVERED
}
import { IsEnum } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { MilestoneStatus } from '../entities/milestone.entity';
// Seller-changeable statuses (not including APPROVED which is buyer action)
export class UpdateMilestoneStatusDto {
@IsEnum(MilestoneStatus, { message: 'Invalid milestone status' })
@ApiProperty({
enum: MilestoneStatus,
description: 'New milestone status (seller-changeable only: READY, IN_PROGRESS, DELIVERED)',
})
status: MilestoneStatus; // Expect READY | IN_PROGRESS | DELIVERED
}
🤖 Prompt for AI Agents
In src/modules/escrows/dto/update-milestone-status.dto.ts lines 1-8, the DTO is
missing OpenAPI metadata; add an import for ApiProperty from '@nestjs/swagger'
and annotate the status property with @ApiProperty({ enum: MilestoneStatus,
description: 'Milestone status', example: MilestoneStatus.READY }) so the
generated Swagger docs include the enum, description and an example; keep the
existing @IsEnum validation intact.

Comment on lines +77 to 92
const createdFile: File = {
id: 'uuid',
url: mockFile.path,
type: FileType.IMAGE,
filename: mockFile.originalname,
mimetype: mockFile.mimetype,
size: mockFile.size,
type: FileType.IMAGE,
url: mockFile.path,
providerType: 'cloudinary',
providerPublicId: mockFile.filename,
uploadedById: '1',
uploadedAt: new Date(),
uploadedById: '1',
uploadedBy: mockUser,
// uploadedById: mockUser.id.toString(),
// uploadedAt: new Date(),
// uploadedBy: mockUser,
} as File;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove commented-out code.

Lines 89-91 contain commented-out code. Per coding guidelines: "Do not leave commented-out code in commits."

Apply this diff to remove the commented code:

       const createdFile: File = {
         id: 'uuid',
         filename: mockFile.originalname,
         mimetype: mockFile.mimetype,
         size: mockFile.size,
         type: FileType.IMAGE,
         url: mockFile.path,
         providerType: 'cloudinary',
         providerPublicId: mockFile.filename,
         uploadedAt: new Date(),
         uploadedById: '1',
         uploadedBy: mockUser,
-        // uploadedById: mockUser.id.toString(),
-        // uploadedAt: new Date(),
-        // uploadedBy: mockUser,
       } as File;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createdFile: File = {
id: 'uuid',
url: mockFile.path,
type: FileType.IMAGE,
filename: mockFile.originalname,
mimetype: mockFile.mimetype,
size: mockFile.size,
type: FileType.IMAGE,
url: mockFile.path,
providerType: 'cloudinary',
providerPublicId: mockFile.filename,
uploadedById: '1',
uploadedAt: new Date(),
uploadedById: '1',
uploadedBy: mockUser,
// uploadedById: mockUser.id.toString(),
// uploadedAt: new Date(),
// uploadedBy: mockUser,
} as File;
const createdFile: File = {
id: 'uuid',
filename: mockFile.originalname,
mimetype: mockFile.mimetype,
size: mockFile.size,
type: FileType.IMAGE,
url: mockFile.path,
providerType: 'cloudinary',
providerPublicId: mockFile.filename,
uploadedAt: new Date(),
uploadedById: '1',
uploadedBy: mockUser,
} as File;
🤖 Prompt for AI Agents
In src/modules/files/tests/file.service.spec.ts around lines 77 to 92, there are
three commented-out lines (previously at lines 89-91) left in the createdFile
object literal; remove those commented lines so the test contains only active
properties (delete the lines "// uploadedById: mockUser.id.toString(),", "//
uploadedAt: new Date(),", and "// uploadedBy: mockUser,") and save the file with
no leftover commented-out code.

Comment on lines +7 to +10
location: 'Test City',
country: 'Test Country',
buyerData: {},
sellerData: null,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type inconsistency and missing explicit types for mock data.

Several issues with the new fields:

  1. ID type mismatch: Line 3 uses id: 1 (number), but file.service.spec.ts consistently uses id: "1" (string) for mockUser. This inconsistency can lead to test failures or false positives.

  2. Implicit any type: buyerData: {} and sellerData: null lack explicit types. Per coding guidelines, avoid implicit any types.

Apply this diff to align with the string ID convention and add explicit types:

 export const mockUser = {
-  id: 1,
+  id: '1',
   walletAddress: '0x123456789abcdef',
   name: 'Test User',
   email: 'test@example.com',
   location: 'Test City',
   country: 'Test Country',
-  buyerData: {},
-  sellerData: null,
+  buyerData: {} as Record<string, unknown>,
+  sellerData: null as unknown,
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
location: 'Test City',
country: 'Test Country',
buyerData: {},
sellerData: null,
export const mockUser = {
id: '1',
walletAddress: '0x123456789abcdef',
name: 'Test User',
email: 'test@example.com',
location: 'Test City',
country: 'Test Country',
buyerData: {} as Record<string, unknown>,
sellerData: null as unknown,
};
🤖 Prompt for AI Agents
In src/modules/files/tests/test-utils.ts around lines 7 to 10, change the id
field from a number to a string to match the "1" convention used across tests,
and replace the implicit-any mock fields by giving buyerData an explicit typed
shape (for example Record<string, unknown> or the actual BuyerData interface)
and sellerData an explicit nullable type (e.g., Record<string, unknown> | null
or SellerData | null) so both fields are typed rather than implicitly any.

Comment on lines +31 to +38
@Expose()
escrow_contract_id?: string;

@Expose()
payment_tx_hash?: string;

@Expose()
onchain_status?: OnchainStatus;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add validation decorators to the new optional fields.

The new blockchain-related fields lack validation decorators. Per coding guidelines, DTOs should use class-validator decorators for data validation.

Apply this diff to add proper validation:

+import { IsOptional, IsString, IsEnum } from 'class-validator';
+
 export class OrderDto {
   // ... existing fields ...
   
   @Expose()
+  @IsOptional()
+  @IsString()
   escrow_contract_id?: string;

   @Expose()
+  @IsOptional()
+  @IsString()
   payment_tx_hash?: string;

   @Expose()
+  @IsOptional()
+  @IsEnum(OnchainStatus)
   onchain_status?: OnchainStatus;

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Expose()
escrow_contract_id?: string;
@Expose()
payment_tx_hash?: string;
@Expose()
onchain_status?: OnchainStatus;
import { Expose } from 'class-transformer';
import { IsOptional, IsString, IsEnum } from 'class-validator';
export class OrderDto {
// ... existing fields ...
@Expose()
@IsOptional()
@IsString()
escrow_contract_id?: string;
@Expose()
@IsOptional()
@IsString()
payment_tx_hash?: string;
@Expose()
@IsOptional()
@IsEnum(OnchainStatus)
onchain_status?: OnchainStatus;
}
🤖 Prompt for AI Agents
In src/modules/orders/dto/order.dto.ts around lines 31 to 38, the three new
optional blockchain fields lack class-validator decorators; add validation by
decorating escrow_contract_id and payment_tx_hash with @IsOptional() and
@IsString(), and decorate onchain_status with @IsOptional() and
@IsEnum(OnchainStatus). Also ensure you import IsOptional, IsString, and IsEnum
from 'class-validator' (and keep the existing @Expose imports) so the DTO
validates these fields correctly at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant