Skip to content

Create Seller Role on Registration (with Store Profile)#141

Merged
Villarley merged 5 commits intoStarShopCr:Developfrom
Leinx31:main
Aug 19, 2025
Merged

Create Seller Role on Registration (with Store Profile)#141
Villarley merged 5 commits intoStarShopCr:Developfrom
Leinx31:main

Conversation

@Leinx31
Copy link
Contributor

@Leinx31 Leinx31 commented Aug 13, 2025

🚀 StarShop Pull Request

Mark with an x all the checkboxes that apply (like [x])

  • Closes #
  • Added tests (if necessary)
  • Run tests
  • Run formatting
  • Evidence attached
  • Commented the code

📌 Type of Change

  • Documentation (updates to README, docs, or comments)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

📝 Changes description

Added the back end code to register either as a buyer or seller and the code to ensure the distinction of either role and data.


📸 Evidence (A photo is required as evidence)

Screenshot 2025-08-12 at 18-54-21 Swagger UI

⏰ Time spent breakdown

1 Hour analyzing
2 Hours working in the implementation


🌌 Comments

I ensured that the implementation wont interfere with future updates and features. Thanks a lot for letting me help with the development of this amazing project!


Thank you for contributing to StarShop, we are glad that you have chosen us as your project of choice and we hope that you continue to contribute to this great project, so that together we can make our mark at the top!

Summary by CodeRabbit

  • New Features

    • Role-based user registration now supports location, country, and role-specific buyer/seller profile data with strict validation.
    • Seller onboarding auto-creates a default store; full store management added (create, update, delete, my stores, search, browse, stats, admin status updates).
  • API Changes

    • User ID removed from authentication and user responses; payloads include profile fields instead.
  • Documentation

    • Added comprehensive guides: README, Implementation Guide, Technical Specification, User Registration API, and Store System docs.
  • Chores

    • Disabled automatic database schema synchronization across all environments.

@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds role-aware user profile extensions (location, country, buyerData, sellerData), enforces cross-field validation, and updates responses to omit user id. Introduces Stores module with entity, DTOs, service, controller, and migrations, including default store creation on seller registration. Disables TypeORM synchronize globally. Updates types, middlewares, tests, and documentation.

Changes

Cohort / File(s) Change summary
Documentation
docs/README.md, docs/IMPLEMENTATION_GUIDE.md, docs/TECHNICAL_SPECIFICATION.md, docs/user-registration.md, docs/store-system.md
Adds comprehensive docs for registration, validation, data model changes, stores system, migrations, testing, and operations.
Bootstrapping & Config
src/app.module.ts, src/config/index.ts
Registers StoresModule and Store entity; hard-codes TypeORM synchronize=false.
Migrations
src/migrations/1751199237000-AddUserFields.ts, src/migrations/1751199238000-CreateStoresTable.ts
Adds user columns (location, country, buyerData, sellerData); creates stores table with indexes and FK to users.
Stores Module
src/modules/stores/stores.module.ts, src/modules/stores/entities/store.entity.ts, src/modules/stores/dto/store.dto.ts, src/modules/stores/services/store.service.ts, src/modules/stores/controllers/store.controller.ts
New Stores feature: entity, DTOs, CRUD/search/status service, controller with role-based guards; service supports default store creation for sellers.
Auth Module—Services/Controllers
src/modules/auth/services/auth.service.ts, src/modules/auth/controllers/auth.controller.ts, src/modules/auth/auth.module.ts
Adds StoreService dependency; persists new user fields; validates role-specific data; triggers default store creation for sellers; removes id from auth responses.
Auth Module—DTOs
src/modules/auth/dto/auth.dto.ts, src/modules/auth/dto/auth-response.dto.ts
Expands Register/Update DTOs with location/country/buyerData/sellerData and custom role-aware validator; removes id from public user DTOs.
Auth Middleware
src/modules/auth/middleware/authorize-roles.middleware.ts, src/modules/auth/middleware/jwt-auth.middleware.ts
Adjusts role casting and membership checks; loosens req.user typing with type assertion.
Users Module
src/modules/users/entities/user.entity.ts, src/modules/users/services/user.service.ts, src/modules/users/controllers/user.controller.ts
Adds new fields to User entity and relation to stores; extends create/update logic and responses; enforces role-data constraints.
Legacy/User DTO
src/dtos/UserDTO.ts
Adds IsRoleSpecificData decorator; extends CreateUserDto with location/country/buyerData/sellerData validations.
Shared Types & Middleware
src/modules/shared/middleware/auth.middleware.ts, src/modules/shared/middleware/session.middleware.ts, src/modules/shared/types/auth-request.type.ts, src/types/express.d.ts, src/middleware/auth.middleware.ts
Updates role mapping defaults to BUYER; switches includes→some checks; adds new optional user fields to request/user types; applies type assertions on req.user.
Tests—Auth
src/modules/auth/tests/auth.service.spec.ts
Injects mock StoreService; updates user fixtures with new fields; adjusts role enum usage.
Tests—Files
src/modules/files/tests/file.controller.spec.ts, src/modules/files/tests/file.service.spec.ts, src/modules/files/tests/test-utils.ts
Updates mock users to include new fields (location, country, buyerData, sellerData, stores).
Wishlist Types/Tests
src/modules/wishlist/common/types/auth-request.type.ts, src/modules/wishlist/tests/wishlist.controller.spec.ts
Extends AuthRequest.user with new fields; updates tests to use Role.BUYER.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant AuthController
  participant AuthService
  participant UserRepo as Users Repository
  participant RoleService
  participant StoreService
  participant StoreRepo as Stores Repository

  Client->>AuthController: POST /auth/register (walletAddress, role, ... buyer/seller data)
  AuthController->>AuthService: registerWithWallet(data)
  AuthService->>UserRepo: Find existing by wallet
  alt User exists
    AuthService->>UserRepo: Update fields (location/country/buyerData/sellerData)
  else Create new user
    AuthService->>UserRepo: Save new user with fields
    AuthService->>RoleService: Assign role to user
  end
  opt role == seller
    AuthService->>StoreService: createDefaultStore(user.id, sellerData)
    StoreService->>UserRepo: Validate seller exists/role
    StoreService->>StoreRepo: Create Store (PENDING_APPROVAL)
    StoreService-->>AuthService: Store or error (logged)
  end
  AuthService-->>AuthController: User (no id), token meta
  AuthController-->>Client: 200 { success, data.user, expiresIn }
Loading
sequenceDiagram
  autonumber
  actor Seller
  participant StoreController
  participant StoreService
  participant StoreRepo as Stores Repository

  Seller->>StoreController: POST /stores (JWT, CreateStoreDto)
  StoreController->>StoreService: createStore(sellerId, dto)
  StoreService->>StoreRepo: Save store (PENDING_APPROVAL)
  StoreService-->>StoreController: Store
  StoreController-->>Seller: 201 { success, data }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

Poem

A hop, a skip, a store appears,
From seller’s wink to “pending” cheers.
New fields sprout up—country, town—
Buyer, seller, data down.
Sync is off, migrations tread;
I thump approval, whiskers spread.
Carrot-commit: default store bred! 🥕🏪🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

- Removed user ID from the AuthController and related DTOs to streamline response data.
- Updated example values in DTOs for consistency and clarity.
@Villarley Villarley changed the base branch from main to Develop August 19, 2025 16:31
Copy link
Contributor

@Villarley Villarley left a comment

Choose a reason for hiding this comment

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

LGTM

@Villarley Villarley merged commit ac6a642 into StarShopCr:Develop Aug 19, 2025
0 of 2 checks passed
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: 29

Caution

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

⚠️ Outside diff range comments (7)
src/modules/shared/middleware/auth.middleware.ts (1)

80-90: Don't instantiate middleware with null deps; throw ForbiddenException (403) for RBAC failure

  • Creating a new AuthMiddleware(null, null) to reuse mapRoleToEnum is a DI smell and can break later if the constructor is extended.
  • Throwing ReferenceError yields a 500; use ForbiddenException for authorization failures.

Apply this diff to make requireRole pure and return proper HTTP semantics:

 export const requireRole = (
   roleName: 'buyer' | 'seller' | 'admin'
 ): ((req: AuthenticatedRequest, res: Response, next: NextFunction) => void) => {
-  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
-    const requiredRole = new AuthMiddleware(null, null).mapRoleToEnum(roleName);
-    if (!req.user || !req.user.role.some(role => role === requiredRole)) {
-      throw new ReferenceError('Insufficient permissions');
-    }
-    next();
-  };
+  const ROLE_NAME_TO_ENUM: Record<'buyer' | 'seller' | 'admin', Role> = {
+    admin: Role.ADMIN,
+    seller: Role.SELLER,
+    buyer: Role.BUYER,
+  };
+  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
+    const requiredRole = ROLE_NAME_TO_ENUM[roleName];
+    if (!req.user || !req.user.role?.includes(requiredRole)) {
+      throw new ForbiddenException('Insufficient permissions');
+    }
+    next();
+  };
 };

And update imports:

// Add ForbiddenException to the import list
import { Injectable, NestMiddleware, UnauthorizedException, ForbiddenException } from '@nestjs/common';
src/types/express.d.ts (1)

20-21: Inconsistent id typing between Request.user and Express.User

Request.user.id is string | number while Express.User.id is string. This inconsistency propagates to controllers and causes brittle comparisons.

Apply this diff to align on string | number:

     interface User {
-      id: string;
+      id: string | number;

Also applies to: 40-41

src/modules/users/controllers/user.controller.ts (2)

121-127: Authorization bug: enum-vs-string role check and id type mismatch can block valid updates

  • req.user.role is an array of Role enums (from middleware), but you're comparing to 'admin' (string). This will never match.
  • userId is a number (ParseIntPipe), while req.user.id may be a string — strict inequality can fail even for the same user.

Apply this diff to normalize id and perform a robust admin check:

-    const currentUserId = req.user?.id;
-    const currentUserRole = req.user?.role?.[0];
+    const currentUserId =
+      typeof req.user?.id === 'string'
+        ? parseInt(req.user.id as string, 10)
+        : (req.user?.id as number | undefined);
+    const roles = (req.user?.role ?? []) as Array<Role | string>;
+    const isAdmin = roles.includes(Role.ADMIN) || roles.includes('admin');
 
     // Check if user is updating their own profile or is admin
-    if (userId !== currentUserId && currentUserRole !== 'admin') {
+    if (userId !== currentUserId && !isAdmin) {
       throw new UnauthorizedError('You can only update your own profile');
     }

Optional: Replace UnauthorizedError with a 403 (Forbidden) to better reflect RBAC failure.


159-165: Same authorization bug here: enum-vs-string role check and id type mismatch

Mirror the fix from updateUser for getUserById.

Apply this diff:

-    const currentUserId = req.user?.id;
-    const currentUserRole = req.user?.role?.[0];
+    const currentUserId =
+      typeof req.user?.id === 'string'
+        ? parseInt(req.user.id as string, 10)
+        : (req.user?.id as number | undefined);
+    const roles = (req.user?.role ?? []) as Array<Role | string>;
+    const isAdmin = roles.includes(Role.ADMIN) || roles.includes('admin');
 
     // Check if user is accessing their own profile or is admin
-    if (userId !== currentUserId && currentUserRole !== 'admin') {
+    if (userId !== currentUserId && !isAdmin) {
       throw new UnauthorizedError('Access denied');
     }
src/modules/users/services/user.service.ts (2)

54-66: Avoid creating users without roles; make save + role assignment atomic

If the role is missing, the code silently skips assignment, leaving a user without roles. Wrap the save and role assignment in a single transaction and fail if the role doesn’t exist.

Apply this diff to make the operation atomic and fail if role is not found:

-    const saved = await this.userRepository.save(user);
-
-    // assign role to user_roles table
-    const roleRepo = AppDataSource.getRepository(Role);
-    const userRoleRepo = AppDataSource.getRepository(UserRole);
-    const role = await roleRepo.findOne({ where: { name: data.role } });
-    if (role) {
-      const userRole = userRoleRepo.create({
-        userId: saved.id,
-        roleId: role.id,
-        user: saved,
-        role,
-      });
-      await userRoleRepo.save(userRole);
-    }
-    return saved;
+    return await AppDataSource.transaction(async (trx) => {
+      const saved = await trx.getRepository(User).save(user);
+      const role = await trx.getRepository(Role).findOne({ where: { name: data.role } });
+      if (!role) {
+        throw new BadRequestError('Role not found');
+      }
+      const userRole = trx.getRepository(UserRole).create({
+        userId: saved.id,
+        roleId: role.id,
+        user: saved,
+        role,
+      });
+      await trx.getRepository(UserRole).save(userRole);
+      return saved;
+    });

95-105: Don’t use truthy checks for updates; use explicit undefined checks

Using if (data.email) / if (data.name) blocks clearing values (e.g., empty string) and treats falsy values as “do not update”.

Apply this diff:

-    if (data.email) {
+    if (data.email !== undefined) {
       const existingUser = await this.userRepository.findOne({ where: { email: data.email } });
       if (existingUser && existingUser.id !== user.id) {
         throw new BadRequestError('Email already in use');
       }
       user.email = data.email;
     }
 
-    if (data.name) {
+    if (data.name !== undefined) {
       user.name = data.name;
     }
src/modules/auth/services/auth.service.ts (1)

133-144: Ensure role exists; consider transaction to avoid user without roles

If the role isn’t found, you currently proceed without assigning, leaving the user without roles. At minimum, throw; ideally, wrap user save + role assignment in a transaction.

Apply this minimal diff:

     const userRole = await this.roleRepository.findOne({ where: { name: data.role } });
-    if (userRole) {
+    if (userRole) {
       const userRoleEntity = this.userRoleRepository.create({
         userId: savedUser.id,
         roleId: userRole.id,
         user: savedUser,
         role: userRole,
       });
       await this.userRoleRepository.save(userRoleEntity);
-    }
+    } else {
+      throw new BadRequestError('Role not found');
+    }

If you prefer, I can provide a transaction-based refactor similar to the UserService suggestion.

🧹 Nitpick comments (45)
src/modules/files/tests/test-utils.ts (1)

7-10: Mock additions look consistent; verify null handling in validators

Adding location, country, buyerData, sellerData aligns with the new entity. If DTO validation is involved in these tests, note that sellerData: null will only be skipped if @IsOptional runs before @isObject on that property. See my note in UserDTO to place @IsOptional first.

Optional verification: if any test exercises DTO validation, prefer undefined over null here:

-  sellerData: null,
+  sellerData: undefined,
src/dtos/UserDTO.ts (3)

85-93: Place @IsOptional before other validators to ensure null/undefined are skipped

Guard optionality first to avoid @isObject failing when value is null. Also consider narrowing type from any to a safe object.

   @ApiPropertyOptional({
     description: 'Buyer-specific data (only allowed if role is buyer)',
     example: { preferences: ['electronics', 'books'] },
   })
-  @IsRoleSpecificData({ message: 'buyerData is only allowed for buyers' })
-  @IsObject({ message: 'Buyer data must be an object' })
-  @IsOptional()
-  buyerData?: any;
+  @IsOptional()
+  @IsRoleSpecificData({ message: 'buyerData is only allowed for buyers' })
+  @IsObject({ message: 'Buyer data must be an object' })
+  buyerData?: Record<string, unknown>;

94-101: Mirror the optional-first order for sellerData

Same rationale as buyerData. This keeps behavior consistent and avoids null pitfalls.

   @ApiPropertyOptional({
     description: 'Seller-specific data (only allowed if role is seller)',
     example: { businessName: 'Tech Store', categories: ['electronics'] },
   })
-  @IsRoleSpecificData({ message: 'sellerData is only allowed for sellers' })
-  @IsObject({ message: 'Seller data must be an object' })
-  @IsOptional()
-  sellerData?: any;
+  @IsOptional()
+  @IsRoleSpecificData({ message: 'sellerData is only allowed for sellers' })
+  @IsObject({ message: 'Seller data must be an object' })
+  sellerData?: Record<string, unknown>;

16-57: Custom decorator works; minor cleanup on typing

The decorator is fine. Prefer using the lower-case TypeScript primitive for the “object” parameter type.

-function IsRoleSpecificData(validationOptions?: ValidationOptions) {
-  return function (object: Object, propertyName: string) {
+function IsRoleSpecificData(validationOptions?: ValidationOptions) {
+  return function (object: object, propertyName: string) {
src/modules/auth/middleware/authorize-roles.middleware.ts (1)

12-14: Avoid unsafe cast; normalize roles to array before checking

Casting req.user.role as Role[] will mask runtime issues if it’s actually a single role. Normalize then some(...) to keep it robust.

Apply this diff:

-      const userRoles = req.user.role as Role[];
-      const hasAllowedRole = userRoles.some((role) => allowedRoles.includes(role));
+      const userRoles: Role[] = Array.isArray(req.user.role)
+        ? (req.user.role as Role[])
+        : [req.user.role as Role];
+      const hasAllowedRole = userRoles.some((role) => allowedRoles.includes(role));

Optional follow-up: consider throwing ForbiddenException for insufficient permissions and mapping 401 for unauthenticated vs 403 for forbidden to align status codes with semantics.

src/modules/shared/middleware/session.middleware.ts (1)

38-43: Avoid as any; include the new user fields on req.user and cast to Express.User.

This cast likely compensates for the newly-augmented user shape (location, country, buyerData, sellerData). Prefer populating those fields and using a precise type to preserve type-safety.

Apply:

-    req.user = {
-      id: user.id,
-      walletAddress: user.walletAddress,
-      role: user.userRoles.map((ur) => ur.role.name as Role),
-    } as any;
+    req.user = {
+      id: user.id,
+      walletAddress: user.walletAddress,
+      role: user.userRoles.map((ur) => ur.role.name as Role),
+      location: user.location ?? null,
+      country: user.country ?? null,
+      buyerData: user.buyerData ?? null,
+      sellerData: user.sellerData ?? null,
+    } as Express.User;

If Express.User isn’t augmented accordingly, either:

  • adjust the global augmentation in src/types/express.d.ts to include these fields (prefer optional), or
  • introduce a dedicated type (e.g., AuthenticatedUser) and use that instead of any.
src/modules/files/tests/file.controller.spec.ts (1)

93-97: Extended User fields in test mocks align with the updated model.

Nice addition of location, country, buyerData, sellerData to keep mocks in sync.

Two small follow-ups:

  • If Role.USER was replaced (as elsewhere in this PR), update the request mock to use BUYER for consistency (current: Line 36 uses Role.USER).
  • Consider typing mockUser as Partial to reduce the need for double casting.

For the role enum consistency, update the request mock:

// Line 36 (in this spec)
role: [Role.BUYER],

Optionally, prefer Partial for mocks:

const mockUser: Partial<User> = {
  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: [],
};
// Use as needed where a full User is required in return values
src/modules/files/tests/file.service.spec.ts (2)

47-67: Reduce duplication by centralizing mockUser creation.

The mockUser object is repeated across tests with identical fields. Consider introducing a small factory helper to keep tests DRY and easier to evolve when the user shape changes again.

Example helper you can place near the top of this file:

function buildMockUser(overrides: Partial<any> = {}) {
  return {
    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: [],
    wishlist: [],
    stores: [],
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

Then use buildMockUser() in each test, overriding only what differs.


298-341: Add an explicit unauthorized-ownership test case for deleteFile.

You cover “not found” and “owned” scenarios. Add a test where a file exists but uploadedById does not match the caller, asserting:

  • no provider delete is called (neither cloudinary nor s3)
  • service returns false

I can draft the additional test if you want.

Also applies to: 343-386

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

45-52: Avoid as any; include the new user fields and rely on augmented Express typings.

You’ve likely added the new fields to Express.User elsewhere. Populate them here and drop the as any cast.

     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;
+      role: user.userRoles?.map((ur) => ur.role.name as Role) || [decoded.role as Role],
+      location: user.location ?? undefined,
+      country: user.country ?? undefined,
+      buyerData: user.buyerData ?? undefined,
+      sellerData: user.sellerData ?? undefined,
+    };

Ensure your Express type augmentation file (e.g., src/types/express.d.ts) is included via typeRoots or a global import so this compiles without any.

src/modules/wishlist/common/types/auth-request.type.ts (1)

4-17: De-duplicate AuthRequest by reusing the shared type.

There’s a Shared AuthenticatedRequest type elsewhere in the codebase. To avoid drift, re-export or alias it here instead of maintaining a separate duplicate.

Apply:

-import { Request } from 'express';
-import { Role } from '../../../../types/role';
-
-export interface AuthRequest extends Request {
-  user: {
-    id: string | number;
-    walletAddress: string;
-    name?: string;
-    email?: string;
-    role: Role[];
-    location?: string;
-    country?: string;
-    buyerData?: any;
-    sellerData?: any;
-    createdAt?: Date;
-    updatedAt?: Date;
-  };
-}
+export { AuthenticatedRequest as AuthRequest } from '../../../shared/types/auth-request.type';
src/modules/shared/middleware/auth.middleware.ts (2)

40-43: Defaulting unknown roles to BUYER — confirm intended least-privilege fallback

Mapping both 'user' and any unknown role names to BUYER changes prior semantics. This is fine from a least-privilege standpoint, but please confirm there are no other role strings (e.g., 'moderator', 'support') that should be recognized explicitly. Consider telemetry/logging for unknown role names.


11-23: Duplicate AuthenticatedRequest type — consolidate to a single source of truth

This interface duplicates definitions in src/modules/shared/types/auth-request.type.ts and src/types/express.d.ts. Keep just the module augmentation in src/types/express.d.ts (preferred), and import/use that everywhere to avoid divergence (e.g., id type, role type).

src/modules/shared/types/auth-request.type.ts (1)

4-18: Type duplication and potential drift across interfaces

This AuthenticatedRequest duplicates the Request augmentation in src/types/express.d.ts and the local interface in auth.middleware.ts. There’s already divergence on id types across files. Recommend keeping only the express.d.ts augmentation and referencing it project-wide.

src/types/express.d.ts (1)

55-57: Avoid re-declaring AuthenticatedRequest here if you already augment Request

You can rely on Express.Request augmentation alone (user?: …) and remove this extra interface, or ensure all code imports this one consistently. Multiple parallel types are easy to desync.

src/modules/users/entities/user.entity.ts (2)

29-33: Enforce documented length constraints for location/country

Docs state max 100 chars. Add length: 100 to the columns to enforce at DB level.

-  @Column({ nullable: true })
+  @Column({ nullable: true, length: 100 })
   location?: string;

-  @Column({ nullable: true })
+  @Column({ nullable: true, length: 100 })
   country?: string;

53-55: Relation looks correct; consider optionality and cascade semantics

Mapping to Store.seller is correct. Minor: mark as optional (stores?: Store[]) and evaluate if you need onDelete/cascade behaviors on the ManyToOne side for cleanup semantics.

docs/user-registration.md (1)

7-7: Align endpoint path with API base (/api/v1)

Elsewhere (README, examples) use /api/v1/users. Update for consistency.

-`POST /users`
+`POST /api/v1/users`
src/modules/auth/tests/auth.service.spec.ts (2)

176-194: Add a seller registration test that asserts default store creation

Given sellers should trigger default store creation (non-fatal on failure), add coverage to assert createDefaultStore is called and errors are swallowed.

Example test to add:

it('should create a default store for seller registration (non-fatal on failure)', async () => {
  const mockUserRepository = {
    findOne: jest.fn().mockResolvedValue(null),
    create: jest.fn().mockReturnValue({ id: 2, walletAddress: mockWalletAddress, userRoles: [{ role: { name: 'seller' } }] }),
    save: jest.fn().mockResolvedValue({ id: 2, walletAddress: mockWalletAddress, userRoles: [{ role: { name: 'seller' } }] }),
  };
  (authService as any).userRepository = mockUserRepository;

  const mockStoreService = (authService as any).storeService as { createDefaultStore: jest.Mock };
  mockStoreService.createDefaultStore.mockResolvedValue(undefined);

  const result = await authService.registerWithWallet({
    walletAddress: mockWalletAddress,
    role: UserRoleEnum.SELLER,
    name: 'Seller',
    email: 'seller@example.com',
    sellerData: { businessName: 'My Store' },
  });

  expect(result.user?.id).toBe(2);
  expect(mockStoreService.createDefaultStore).toHaveBeenCalledWith(2, { businessName: 'My Store' });

  // simulate non-fatal failure
  mockStoreService.createDefaultStore.mockRejectedValueOnce(new Error('boom'));
  await expect(
    authService.registerWithWallet({
      walletAddress: 'GD6LXK4RB6D522ECACFVUEOKPCYBGQ6SKYONMVNIUOWUAIRNLSYAOB4R',
      role: UserRoleEnum.SELLER,
      sellerData: {},
    })
  ).resolves.toBeDefined();
});

116-146: Optional: Assert token cookie behavior or token issuance

If the controller sets an HttpOnly cookie, consider asserting jwtService.sign inputs and that expiresIn matches config (even if the API body omits token). This tightens contract coverage.

docs/README.md (3)

29-42: Add language to fenced code block (markdownlint MD040)

Specify text for the ASCII architecture diagram.

-```
+```text
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │   Controller    │    │   Service       │    │   Repository    │
 │   (Validation)  │───▶│   (Business     │───▶│   (Database)    │
 │                 │    │    Logic)       │    │                 │
 └─────────────────┘    └─────────────────┘    └─────────────────┘
          │                       │                       │
          ▼                       ▼                       ▼
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │   DTOs         │    │   Entities      │    │   Migrations    │
 │   (Input/      │    │   (Data Model)  │    │   (Schema       │
 │    Output)     │    │                 │    │    Changes)     │
 └─────────────────┘    └─────────────────┘    └─────────────────┘
-```
+```

166-181: Add language to code structure fenced block (markdownlint MD040)

Use text for the tree.

-```
+```text
 src/
 ├── modules/
 │   ├── users/           # User management
 │   │   ├── entities/    # Database entities
 │   │   ├── controllers/ # API endpoints
 │   │   ├── services/    # Business logic
 │   │   └── tests/       # Test files
 │   └── auth/            # Authentication
 │       ├── dto/         # Data transfer objects
 │       ├── services/    # Auth services
 │       └── tests/       # Auth tests
 ├── types/               # TypeScript types
 ├── migrations/          # Database migrations
 └── docs/               # Documentation
-```
+```

362-362: Avoid using emphasis as a heading (markdownlint MD036)

Convert the emphasized line to a proper heading.

-**Built with ❤️ for the StarShop community**
+### Built with ❤️ for the StarShop community
docs/IMPLEMENTATION_GUIDE.md (2)

22-35: Add language to fenced code block (markdownlint MD040)

Use text for the ASCII architecture diagram.

-```
+```text
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │   Controller    │    │   Service       │    │   Repository    │
 │   (Validation)  │───▶│   (Business     │───▶│   (Database)    │
 │                 │    │    Logic)       │    │                 │
 └─────────────────┘    └─────────────────┘    └─────────────────┘
          │                       │                       │
          ▼                       ▼                       ▼
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │   DTOs         │    │   Entities      │    │   Migrations    │
 │   (Input/      │    │   (Data Model)  │    │   (Schema       │
 │    Output)     │    │                 │    │    Changes)     │
 └─────────────────┘    └─────────────────┘    └─────────────────┘
-```
+```

297-305: Migration snippet: prefer consistent casing and JSONB

If your DB uses camelCase columns via naming strategy, the snippet is fine. Otherwise, prefer snake_case and ensure jsonb matches actual migration.

-ADD COLUMN "buyerData" jsonb,
-ADD COLUMN "sellerData" jsonb;
+ADD COLUMN "buyer_data" jsonb,
+ADD COLUMN "seller_data" jsonb;

If the codebase uses camelCase columns, consider adding a note clarifying the naming strategy.

src/modules/users/services/user.service.ts (1)

27-41: Role-specific data validation is good; clarify admin policy and align with AuthService/DTOs

  • Current logic requires buyerData/sellerData for buyer/seller. AuthService’s registerWithWallet does not enforce “required”. DTOs mark them optional.
  • Define whether admin is allowed to carry buyer/seller data; if not, add a check.

Would you like me to update the DTOs and AuthService to enforce the same “required/forbidden” rules, and add an admin restriction?

docs/TECHNICAL_SPECIFICATION.md (2)

201-215: UserResponse shape likely mismatches runtime model for roles

The codebase models roles via userRoles relation; a single role: string field may be inconsistent. Consider primaryRole plus roles array, or omit role here.

Apply this diff to reflect a safer, extensible response:

-interface UserResponse {
+interface UserResponse {
   id: number;
   walletAddress: string;
   name: string;
   email: string;
-  role: string;
+  primaryRole?: string;
+  roles?: string[];
   location?: string;
   country?: string;
   buyerData?: any;
   sellerData?: any;
   createdAt?: Date;
   updatedAt?: Date;
 }

314-328: Align error class examples with actual implementation (extends AppError)

In code, BadRequestError extends AppError (with HTTP status). The doc’s example extends Error and may confuse consumers.

Apply this diff:

-export class BadRequestError extends Error {
-  constructor(message: string) {
-    super(message);
-    this.name = 'BadRequestError';
-  }
-}
+export class BadRequestError extends AppError {
+  constructor(message: string) {
+    super(400, message);
+  }
+}
 
-export class UnauthorizedError extends Error {
-  constructor(message: string) {
-    super(message);
-    this.name = 'UnauthorizedError';
-  }
-}
+export class UnauthorizedError extends AppError {
+  constructor(message: string) {
+    super(401, message);
+  }
+}
src/migrations/1751199238000-CreateStoresTable.ts (1)

11-16: Minor: prefer int + generated increment over serial for portability

Using type: 'int' with isGenerated: true and generationStrategy: 'increment' avoids serial-specific assumptions and aligns with TypeORM conventions.

Would you like a patch to switch to int + generationStrategy?

src/modules/auth/services/auth.service.ts (2)

145-153: Log with a structured logger instead of console.error

Use your logger abstraction to capture context (userId, role) and error data. Avoid console in production paths.

I can wire this to your existing logger if you share its location.


211-218: updateUser duplicates logic; reuse UserService and enforce role/email rules

This path bypasses email uniqueness checks and role-specific update constraints present in UserService.

Consider delegating to UserService:

-  async updateUser(userId: number, updateData: { 
+  async updateUser(userId: number, updateData: { 
     name?: string; 
     email?: string; 
     location?: string; 
     country?: string; 
     buyerData?: any; 
     sellerData?: any; 
   }): Promise<User> {
-    const user = await this.userRepository.findOne({ where: { id: userId } });
-    if (!user) {
-      throw new BadRequestError('User not found');
-    }
-    // Update user data
-    Object.assign(user, updateData);
-    await this.userRepository.save(user);
-    return this.getUserById(String(userId));
+    return this.userService.updateUser(String(userId), updateData);
   }

Also applies to: 225-230

src/modules/auth/dto/auth.dto.ts (1)

5-46: Avoid duplicating custom validators across modules; centralize IsRoleSpecificData

This validator appears in multiple places per PR summary. Centralize it (e.g., src/common/validators) to prevent drift and duplicated logic.

I can extract and update imports across DTOs if you want a patch.

docs/store-system.md (3)

99-139: Consider documenting idempotent default store creation

The pseudo-implementation checks for an existing default by name. If the seller changes their name, the check will miss and a second “default” store is created. Recommend documenting an isDefault boolean (unique per seller) or another stable discriminator to ensure idempotency across name changes.


188-198: Swagger table: minor formatting/grammar nits

The Markdown table renders but LanguageTool flags minor issues. Optional polish:

  • Ensure consistent header alignment and spacing.
  • “Role Required” column values: consider code formatting (e.g., Seller, Admin) for consistency.

No functional impact.


9-16: Minor grammar/style improvements (optional)

Examples:

  • “when seller registers” → “when a seller registers”
  • Use consistent punctuation in bullet points; some lines lack terminal punctuation while others include it.

Purely editorial—no functional impact.

I can sweep the doc for grammar/style in one pass if you’d like.

Also applies to: 101-109, 403-418, 499-509

src/modules/stores/dto/store.dto.ts (2)

59-66: Optionally validate coordinates as numbers

If you intend to accept numeric coordinates, consider a nested DTO with IsNumber() and ValidateNested rather than a raw object. Minimal improvement (optional):

  • Create a CoordinatesDto with proper number validation.
  • Replace @IsObject() with @ValidateNested() and @Type(() => CoordinatesDto).

This prevents string values slipping through.

I can draft CoordinatesDto and wire it in.


245-294: Response DTO shape vs returned entity

StoreResponseDto matches the intended output, but the controller returns full entities directly. Without explicit mapping or class-transformer, extra relations (e.g., seller) may leak. Consider mapping entities to StoreResponseDto or using @Exclude/@Expose on the entity with a global ClassSerializerInterceptor.

I can add a lightweight mapper function or wire ClassSerializerInterceptor where appropriate.

src/modules/stores/controllers/store.controller.ts (3)

59-64: Fix array responses in Swagger

type: [StoreResponseDto] is not the preferred way. Use ApiOkResponse with isArray or an explicit schema inside your { success, data } wrapper:

  • data: { type: 'array', items: { $ref: getSchemaPath(StoreResponseDto) } }

Also applies to: 141-148, 157-164


118-139: DELETE response Swagger mismatch

The handler returns { success, message } but no schema is documented. Consider:

-  @ApiResponse({
-    status: 200,
-    description: 'Store deleted successfully',
-  })
+  @ApiOkResponse({
+    description: 'Store deleted successfully',
+    schema: {
+      type: 'object',
+      properties: {
+        success: { type: 'boolean', example: true },
+        message: { type: 'string', example: 'Store deleted successfully' }
+      }
+    }
+  })

141-175: Consider caching for public read endpoints

GET /stores and GET /stores/search are good candidates for caching (e.g., Nest CacheInterceptor or a service-layer cache with TTL) to reduce DB load under traffic spikes.

I can wire CacheInterceptor for these endpoints or add a service-level cache abstraction if you prefer finer control.

Also applies to: 177-197

src/modules/stores/services/store.service.ts (4)

30-37: Default store idempotency is fragile (name-based uniqueness)

You derive “existing store” by matching a generated name. If seller changes their name, a second default may be created. Consider persisting an isDefault flag and querying by { sellerId, isDefault: true }, with a partial unique index on (sellerId) WHERE isDefault = true. If you do this, set the field during creation and adjust the existence check.

Happy to draft the entity field, migration, and the query change.

Also applies to: 39-58


192-208: Guard verifiedAt semantics and enforce valid status values

With the controller change to ParseEnumPipe, invalid statuses will be blocked earlier. Optionally, add a simple guard here to be defensive if this service is called from elsewhere.

Also consider resetting verifiedAt when moving to non-ACTIVE statuses if that matches your business logic.


148-154: Add ordering tie-breakers for stable pagination

When ordering by rating and then reviewCount, consider adding createdAt DESC as a final tiebreaker to ensure stable sorting across equal metrics (helps cursor-based pagination later).

Also applies to: 183-187


222-233: Type a StoreStatsDto and avoid any

Define a StoreStatsDto to document the response. This improves Swagger and makes refactors safer.

I can add StoreStatsDto, export it from DTOs, and wire it into the controller/service.

src/modules/stores/entities/store.entity.ts (1)

80-82: Numeric transformer for decimal columns (optional)

Postgres decimal types are returned as strings by default. If you need rating as a number in JSON, add a transformer.

Example:

export class ColumnNumericTransformer {
  to(data?: number): number | null {
    return typeof data === 'number' ? data : data == null ? null : Number(data);
  }
  from(data?: string | null): number | null {
    return data == null ? null : parseFloat(data);
  }
}

Then:

-  @Column({ type: 'decimal', precision: 3, scale: 2, nullable: true })
+  @Column({ type: 'decimal', precision: 3, scale: 2, nullable: true, transformer: new ColumnNumericTransformer() })
   rating?: number;

Comment on lines +49 to +62
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR UNIQUE,
name VARCHAR,
wallet_address VARCHAR UNIQUE NOT NULL,
location VARCHAR, -- NEW FIELD
country VARCHAR, -- NEW FIELD
buyer_data JSONB, -- NEW FIELD
seller_data JSONB, -- NEW FIELD
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Column naming inconsistency (snake_case vs camelCase)

"Users Table" DDL shows wallet_address, buyer_data, etc., while the later migration snippet (Lines 298-304) uses "buyerData", "sellerData". Align docs to the actual migration/entity naming (and naming strategy) to prevent confusion.

Run to confirm actual migration columns:


🏁 Script executed:

#!/bin/bash
fd -t f -g '*AddUserFields*.ts' | while read -r f; do
  echo "== $f ==";
  rg -n -C1 'location|country|buyer|seller' "$f";
done

Length of output: 654


Align documentation column names with actual migrations

The migration adds buyerData and sellerData (camelCase), not buyer_data/seller_data. Update the docs snippet accordingly to prevent confusion:

• File: docs/IMPLEMENTATION_GUIDE.md (Lines 49–62)

  • Change buyer_data JSONB,buyerData JSONB,
  • Change seller_data JSONB,sellerData JSONB,

Revised snippet:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR UNIQUE,
  name VARCHAR,
  wallet_address VARCHAR UNIQUE NOT NULL,
  location VARCHAR,                    -- NEW FIELD
  country VARCHAR,                     -- NEW FIELD
  buyerData JSONB,                     -- NEW FIELD
  sellerData JSONB,                    -- NEW FIELD
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
🤖 Prompt for AI Agents
In docs/IMPLEMENTATION_GUIDE.md around lines 49 to 62, the SQL example uses
snake_case column names buyer_data and seller_data but the migrations use
camelCase buyerData and sellerData; update the SQL snippet to rename those two
columns to buyerData JSONB and sellerData JSONB so the documentation matches the
actual migration column names.

Comment on lines +21 to +86
```typescript
@Entity('stores')
export class Store {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column({ type: 'text', nullable: true })
description?: string;

@Column({ nullable: true })
logo?: string;

@Column({ nullable: true })
banner?: string;

@Column({ type: 'jsonb', nullable: true })
contactInfo?: ContactInfo;

@Column({ type: 'jsonb', nullable: true })
address?: Address;

@Column({ type: 'jsonb', nullable: true })
businessHours?: BusinessHours;

@Column({ type: 'jsonb', nullable: true })
categories?: string[];

@Column({ type: 'jsonb', nullable: true })
tags?: string[];

@Column({ type: 'decimal', precision: 3, scale: 2, nullable: true })
rating?: number;

@Column({ type: 'int', default: 0 })
reviewCount: number;

@Column({ type: 'jsonb', nullable: true })
policies?: Policies;

@Column({ type: 'jsonb', nullable: true })
settings?: StoreSettings;

@Column({
type: 'enum',
enum: StoreStatus,
default: StoreStatus.PENDING_APPROVAL,
})
status: StoreStatus;

@Column({ type: 'boolean', default: false })
isVerified: boolean;

@Column({ type: 'boolean', default: false })
isFeatured: boolean;

@ManyToOne(() => User, (user) => user.stores)
@JoinColumn({ name: 'seller_id' })
seller: User;

@Column()
sellerId: number;
}
```
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clarify entity definition to avoid accidental duplication

The example includes both a relation (with @JoinColumn) and an explicit sellerId column. In TypeORM, this easily results in two physical columns unless carefully aligned. Either:

  • Let the relation create the sellerId column via @JoinColumn({ name: 'sellerId' }), and avoid a separate @Column declaration; or
  • Use @RelationId for the sellerId property derived from the relation.

I’ve provided a safe pattern in the entity review.

🤖 Prompt for AI Agents
In docs/store-system.md around lines 21 to 86, the Store entity declares a
relation with @ManyToOne/@JoinColumn and also defines a separate sellerId
@Column which can create duplicate physical columns; remove the explicit @Column
declaration for sellerId and instead let the relation create the FK by setting
@JoinColumn({ name: 'sellerId' }) on the seller property, or alternatively
replace the separate @Column with a @RelationId(() => Store.seller) sellerId
property so the ID is derived from the relation rather than defined twice.

Comment on lines +79 to +85
@ManyToOne(() => User, (user) => user.stores)
@JoinColumn({ name: 'seller_id' })
seller: User;

@Column()
sellerId: number;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix FK column naming mismatch (seller_id vs sellerId) across docs and code

The Store entity snippet shows @JoinColumn({ name: 'seller_id' }), while the SQL schema defines the FK as sellerId INTEGER ... and the service code queries by sellerId. This inconsistency will lead to duplicate columns or broken FKs if followed literally.

Unify on a single column name, preferably sellerId (camelCase), for:

  • Entity relation JoinColumn
  • Actual DB column and FK
  • Service queries and DTOs

I’ve suggested a concrete entity fix under src/modules/stores/entities/store.entity.ts. Please mirror the same in the docs’ code blocks and DDL.

Also applies to: 363-386

🤖 Prompt for AI Agents
In docs/store-system.md around lines 79-85 (and also apply same change at
363-386), the entity snippet uses @JoinColumn({ name: 'seller_id' }) while the
rest of the code and SQL use sellerId; this mismatch will create duplicate
columns or broken FKs—change the JoinColumn name to 'sellerId' and ensure the
entity column/annotation, the DDL FK name, and all service/DTO references
consistently use sellerId (camelCase) across the docs and example SQL so the ORM
mapping, database schema, and service queries align.

Comment on lines +35 to +36
"walletAddress": "GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
"role": "buyer",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix invalid Stellar wallet address example (length/charset)

Examples must be 56 chars (^G[A-Z2-7]{55}$). The current sample exceeds 56. Replace with a valid-format example to prevent confusion.

-  "walletAddress": "GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
+  "walletAddress": "GD6LXK4RB6D522ECACFVUEOKPCYBGQ6SKYONMVNIUOWUAIRNLSYAOB4Q",
📝 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
"walletAddress": "GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
"role": "buyer",
"walletAddress": "GD6LXK4RB6D522ECACFVUEOKPCYBGQ6SKYONMVNIUOWUAIRNLSYAOB4Q",
"role": "buyer",
🤖 Prompt for AI Agents
In docs/user-registration.md around lines 35 to 36, the example Stellar
walletAddress is invalid (too long and contains unsupported characters); replace
the current sample with a 56-character valid-format example matching
^G[A-Z2-7]{55}$ (i.e., starts with 'G' followed by 55 chars from A-Z and 2-7) so
the documentation shows a correct Stellar public key format.

Comment on lines +48 to +49
"walletAddress": "GXYZABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
"role": "seller",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix seller wallet example to a valid 56-char Stellar address

Current example exceeds 56 characters.

-  "walletAddress": "GXYZABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
+  "walletAddress": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
📝 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
"walletAddress": "GXYZABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890",
"role": "seller",
"walletAddress": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
"role": "seller",
🤖 Prompt for AI Agents
In docs/user-registration.md around lines 48 to 49, the example seller
walletAddress is too long (exceeds the 56-character length required for a valid
Stellar public key); replace the current string with a valid 56-character
Stellar G... public key (ensure it starts with 'G' and is exactly 56 chars) so
the example demonstrates a properly formatted Stellar address.

Comment on lines +64 to +76
const seller = await this.userRepository.findOne({
where: { id: sellerId },
relations: ['userRoles'],
});

if (!seller) {
throw new NotFoundException('Seller not found');
}

// Check if seller has seller role
const hasSellerRole = seller.userRoles.some(ur => ur.role.name === 'seller');
if (!hasSellerRole) {
throw new BadRequestException('Only sellers can create stores');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Role check may throw due to missing role relation; load userRoles.role

relations: ['userRoles'] does not load role inside userRoles, so ur.role.name can be undefined and cause a crash.

Apply:

-    const seller = await this.userRepository.findOne({
-      where: { id: sellerId },
-      relations: ['userRoles'],
-    });
+    const seller = await this.userRepository.findOne({
+      where: { id: sellerId },
+      relations: ['userRoles', 'userRoles.role'],
+    });
...
-    const hasSellerRole = seller.userRoles.some(ur => ur.role.name === 'seller');
+    const hasSellerRole = seller.userRoles?.some((ur) => ur.role?.name === 'seller') ?? false;
📝 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 seller = await this.userRepository.findOne({
where: { id: sellerId },
relations: ['userRoles'],
});
if (!seller) {
throw new NotFoundException('Seller not found');
}
// Check if seller has seller role
const hasSellerRole = seller.userRoles.some(ur => ur.role.name === 'seller');
if (!hasSellerRole) {
throw new BadRequestException('Only sellers can create stores');
const seller = await this.userRepository.findOne({
where: { id: sellerId },
relations: ['userRoles', 'userRoles.role'],
});
if (!seller) {
throw new NotFoundException('Seller not found');
}
// Check if seller has seller role
const hasSellerRole = seller.userRoles?.some((ur) => ur.role?.name === 'seller') ?? false;
if (!hasSellerRole) {
throw new BadRequestException('Only sellers can create stores');
}
🤖 Prompt for AI Agents
In src/modules/stores/services/store.service.ts around lines 64 to 76, the role
lookup can throw because relations only load userRoles but not the nested role,
causing ur.role to be undefined; update the query to load the nested relation
(e.g., include userRoles.role) and make the runtime check defensive (use a
null-safe check for ur.role?.name === 'seller') so missing role objects won't
crash and only users with the seller role pass.

Comment on lines +117 to +128
async updateStore(storeId: number, sellerId: number, updateStoreDto: UpdateStoreDto): Promise<Store> {
const store = await this.storeRepository.findOne({
where: { id: storeId, sellerId },
});

if (!store) {
throw new NotFoundException('Store not found or access denied');
}

Object.assign(store, updateStoreDto);
return await this.storeRepository.save(store);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Block admin-only fields from seller updates

Sellers should not be able to set status, isVerified, or isFeatured. Strip these before persisting.

   async updateStore(storeId: number, sellerId: number, updateStoreDto: UpdateStoreDto): Promise<Store> {
     const store = await this.storeRepository.findOne({
       where: { id: storeId, sellerId },
     });
@@
-    Object.assign(store, updateStoreDto);
+    const { status, isVerified, isFeatured, ...allowed } = (updateStoreDto ?? {}) as any;
+    Object.assign(store, allowed);
     return await this.storeRepository.save(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
async updateStore(storeId: number, sellerId: number, updateStoreDto: UpdateStoreDto): Promise<Store> {
const store = await this.storeRepository.findOne({
where: { id: storeId, sellerId },
});
if (!store) {
throw new NotFoundException('Store not found or access denied');
}
Object.assign(store, updateStoreDto);
return await this.storeRepository.save(store);
}
async updateStore(storeId: number, sellerId: number, updateStoreDto: UpdateStoreDto): Promise<Store> {
const store = await this.storeRepository.findOne({
where: { id: storeId, sellerId },
});
if (!store) {
throw new NotFoundException('Store not found or access denied');
}
const { status, isVerified, isFeatured, ...allowed } = (updateStoreDto ?? {}) as any;
Object.assign(store, allowed);
return await this.storeRepository.save(store);
}
🤖 Prompt for AI Agents
In src/modules/stores/services/store.service.ts around lines 117 to 128, sellers
must be prevented from changing admin-only fields; before assigning and saving,
remove or ignore status, isVerified, and isFeatured from the incoming
updateStoreDto. Implement a whitelist or explicitly delete those three keys from
the DTO (e.g., create a new object with only allowed fields or copy
updateStoreDto and delete status, isVerified, isFeatured) then Object.assign the
sanitized object to store and save; keep the NotFoundException logic intact.

Comment on lines +35 to +39
@Column({ type: 'json', nullable: true })
buyerData?: any;

@Column({ type: 'json', nullable: true })
sellerData?: any;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Use Postgres JSONB and stronger typing for buyer/seller data

Entities declare type: 'json' with any, while docs/migrations reference JSONB. Aligning to jsonb avoids operator inefficiencies and matches migrations. Also, prefer a safer type.

Proposed change:

-  @Column({ type: 'json', nullable: true })
-  buyerData?: any;
+  @Column({ type: 'jsonb', nullable: true })
+  buyerData?: Record<string, unknown>;

-  @Column({ type: 'json', nullable: true })
-  sellerData?: any;
+  @Column({ type: 'jsonb', nullable: true })
+  sellerData?: Record<string, unknown>;

To verify migration alignment, run:


🏁 Script executed:

#!/bin/bash
# Check migration column names/types for user fields
fd -t f -g '*AddUserFields*.ts' | while read -r f; do
  echo "== $f ==";
  sed -n '1,200p' "$f";
done

Length of output: 1019


Align User Entity JSON Columns with Migrations and Add Stronger Typing

The migration src/migrations/1751199237000-AddUserFields.ts adds buyerData and sellerData as jsonb. The entity should match:

  • In src/modules/users/entities/user.entity.ts:
    • Change type: 'json'type: 'jsonb'
    • Replace anyRecord<string, unknown>

Proposed diff:

--- a/src/modules/users/entities/user.entity.ts
+++ b/src/modules/users/entities/user.entity.ts
@@ lines 35-39
-  @Column({ type: 'json', nullable: true })
-  buyerData?: any;
+  @Column({ type: 'jsonb', nullable: true })
+  buyerData?: Record<string, unknown>;

-  @Column({ type: 'json', nullable: true })
-  sellerData?: any;
+  @Column({ type: 'jsonb', nullable: true })
+  sellerData?: Record<string, unknown>;

This ensures alignment with migrations and safer typings.

📝 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
@Column({ type: 'json', nullable: true })
buyerData?: any;
@Column({ type: 'json', nullable: true })
sellerData?: any;
@Column({ type: 'jsonb', nullable: true })
buyerData?: Record<string, unknown>;
@Column({ type: 'jsonb', nullable: true })
sellerData?: Record<string, unknown>;
🤖 Prompt for AI Agents
In src/modules/users/entities/user.entity.ts around lines 35 to 39, the entity
defines buyerData and sellerData as @Column({ type: 'json', nullable: true })
buyerData?: any; and sellerData?: any;, which does not match the migration that
created jsonb columns and uses overly permissive typing; change the column type
to 'jsonb' and replace the any types with Record<string, unknown> for both
properties so the entity matches migrations and has stronger typing.

Comment on lines +107 to +121
if (data.location !== undefined) {
user.location = data.location;
}

if (data.country !== undefined) {
user.country = data.country;
}

if (data.buyerData !== undefined) {
user.buyerData = data.buyerData;
}

if (data.sellerData !== undefined) {
user.sellerData = data.sellerData;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Block cross-role updates based on the persisted user role

A buyer can currently set sellerData and vice versa. Derive role from the user’s roles and enforce symmetric constraints on update.

Apply this diff:

     if (data.location !== undefined) {
       user.location = data.location;
     }
 
     if (data.country !== undefined) {
       user.country = data.country;
     }
 
+    // Enforce role-specific update constraints
+    const roleName = user.userRoles?.[0]?.role?.name;
+    if (roleName === 'buyer' && data.sellerData !== undefined) {
+      throw new BadRequestError('Buyers cannot have seller data');
+    }
+    if (roleName === 'seller' && data.buyerData !== undefined) {
+      throw new BadRequestError('Sellers cannot have buyer data');
+    }
+
     if (data.buyerData !== undefined) {
       user.buyerData = data.buyerData;
     }
 
     if (data.sellerData !== undefined) {
       user.sellerData = data.sellerData;
     }

Comment on lines +85 to 86
role: [Role.BUYER],
},
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Sanity check: ensure no lingering Role.USER usages.

If the enum dropped USER in favor of BUYER/SELLER, update any stale tests or mocks to avoid breakage.

Run this to find any remaining references:

Also applies to: 102-103, 122-123, 140-141


🏁 Script executed:

#!/bin/bash
# Find Role.USER references in TS files (with surrounding context)
rg -nP --type=ts -C2 '\bRole\.USER\b'

# Also check common test mock patterns, if any
rg -nP --type=ts -C2 'role:\s*\[\s*Role\.USER\s*\]'

Length of output: 857


Remove stale Role.USER references in tests

Found a lingering Role.USER usage that will break now that USER has been removed:

  • src/modules/files/tests/file.controller.spec.ts:36
      role: [Role.USER],

Please update this to use the appropriate new enum value (e.g. Role.BUYER or Role.SELLER) so tests continue to pass.

🤖 Prompt for AI Agents
In src/modules/wishlist/tests/wishlist.controller.spec.ts around lines 85-86,
there is a stale reference to Role.USER (or other tests may still reference it);
replace any occurrences of Role.USER with the appropriate new enum value (e.g.,
Role.BUYER or Role.SELLER) that matches the test scenario, update any imports if
needed, and run the test suite to confirm the change passes.

This was referenced Aug 20, 2025
@coderabbitai coderabbitai bot mentioned this pull request Sep 29, 2025
10 tasks
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.

2 participants