Skip to content

Task#00000 Referral Tracking implementation#741

Open
Tusharmahajan12 wants to merge 4 commits into
tekdi:aspire-leadersfrom
Tusharmahajan12:new_asp_apr24
Open

Task#00000 Referral Tracking implementation#741
Tusharmahajan12 wants to merge 4 commits into
tekdi:aspire-leadersfrom
Tusharmahajan12:new_asp_apr24

Conversation

@Tusharmahajan12
Copy link
Copy Markdown
Collaborator

@Tusharmahajan12 Tusharmahajan12 commented May 15, 2026

Summary by CodeRabbit

  • New Features
    • Complete referral management: create, list, filter, import (CSV) and bulk operations
    • Historical slug tracking and slug resolution for legacy links
    • User attribution with generated referral links shown on profiles and in search results
    • Referral reporting endpoint to generate paginated user/cohort referral reports
    • Referral data surfaced during form submissions and user creation flows

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@Tusharmahajan12 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 6 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: efc55762-6a78-4a5f-992c-219503746994

📥 Commits

Reviewing files that changed from the base of the PR and between aa4e070 and 0a11fa4.

📒 Files selected for processing (3)
  • src/referrals/dto/referral-report.dto.ts
  • src/referrals/referrals.service.ts
  • src/referrals/utils/referral-slug.util.ts

Walkthrough

Adds a ReferralsModule: DTOs, enums, TypeORM entities, slug utilities, ReferralsService (CRUD, import, bulk, reporting), controller endpoints (including report), Elasticsearch referLink support, and wiring into app, forms, and Postgres user creation to record user attribution.

Changes

Referral Management System

Layer / File(s) Summary
Types and DTOs
src/referrals/referrals.types.ts, src/referrals/dto/*, src/user/dto/user-create.dto.ts
Adds enums for referral types/subtypes/status and DTOs for create, list, update, import, and report requests; adds optional referralSlug to UserCreateDto.
Database entities and schema
src/referrals/entities/*
Adds ReferralEntity, ReferralSlugHistory, and UserAttribution TypeORM entities with indexes, audit timestamps, and slug-history constraints.
Slug generation and normalization utilities
src/referrals/utils/referral-slug.util.ts
Provides slug normalization, validation, random base36 generation, name-based slug composition, and refer-link builder using FRONTEND_URL.
ReferralsService core logic
src/referrals/referrals.service.ts
Implements create/list/resolve/update, slug-uniqueness helpers, createUserAttribution/getUserReferLink, CSV import, bulk insert, and getReferralReport with dynamic raw-SQL builder and pagination.
Controller and Module
src/referrals/referrals.module.ts, src/referrals/referrals.controller.ts
New ReferralsModule exporting ReferralsService; controller exposes authenticated endpoints for create, list, resolve, update, import, bulk, and report with standardized API responses.
Elasticsearch mapping and search
src/elasticsearch/interfaces/user.interface.ts, src/elasticsearch/user-elasticsearch.service.ts, src/elasticsearch/elasticsearch.service.ts
Adds optional profile.referLink to the interface, index mapping, normalization for indexing, wildcard referLink filtering in searches, and includes referLink in search hit transformation.
User creation and form submission wiring
src/adapters/postgres/user-adapter.ts, src/forms/services/form-submission.service.ts
Injects ReferralsService to record non-blocking user attribution after DB create and to include referLink in ES profile; forms service also fetches referLink for indexed profiles.
Module imports & API constants/messages
src/app.module.ts, src/adapters/postgres/postgres-module.ts, src/forms/forms.module.ts, src/common/utils/api-id.config.ts, src/common/utils/response.messages.ts
Registers ReferralsModule across App, Postgres adapter, and Forms module; adds referral API ID entries and referral-related success messages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tekdi/user-microservice#464: Modifies the Postgres user creation flow; overlaps with this PR's PostgresUserService attribution and referLink propagation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main objective—implementing referral tracking functionality—and aligns with the comprehensive changes across multiple modules that enable referral entity management, user attribution, and reporting.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive referral management system, including modules for tracking referral entities, slug history, and user attribution. Key features include automated slug generation, CSV import capabilities, and integration with Elasticsearch for filtering users by referral links. Feedback focused on improving data consistency through transactions during slug updates, adopting a more robust CSV parsing strategy, and refining code quality by removing unnecessary type casts and correcting misleading error messages.

Comment thread src/referrals/referrals.service.ts Outdated
Comment thread src/referrals/referrals.service.ts
Comment thread src/referrals/referrals.service.ts Outdated
Comment thread src/referrals/referrals.service.ts Outdated
Copy link
Copy Markdown

@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: 17

🧹 Nitpick comments (9)
src/forms/services/form-submission.service.ts (1)

99-99: 💤 Low value

Consider adding a trailing comma for consistency.

Adding a trailing comma after the last parameter improves diff cleanliness when parameters are added later and maintains consistency with common TypeScript/JavaScript style guides.

✨ Suggested change
-    private readonly referralsService: ReferralsService
+    private readonly referralsService: ReferralsService,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/forms/services/form-submission.service.ts` at line 99, The constructor
parameter list ending with "private readonly referralsService: ReferralsService"
should include a trailing comma for style consistency; update the constructor
(the parameter declaration containing referralsService) to add a comma after
that parameter so future diffs remain cleaner and align with TypeScript/JS style
guides.
src/referrals/entities/referral-slug-history.entity.ts (3)

17-19: ⚡ Quick win

Remove redundant unique constraint.

The @Index({ unique: true }) decorator already creates a unique constraint, so specifying unique: true in the @Column decorator is redundant.

♻️ Proposed fix
  `@Index`({ unique: true })
-  `@Column`({ type: 'varchar', length: 100, unique: true })
+  `@Column`({ type: 'varchar', length: 100 })
  oldSlug: string;

As per coding guidelines, the code should adhere to best practices for TypeORM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-slug-history.entity.ts` around lines 17 - 19,
Remove the redundant unique constraint on the oldSlug column: keep the `@Index`({
unique: true }) decorator and remove the unique: true option from the `@Column`
decorator on the oldSlug property in the ReferralSlugHistory entity (look for
oldSlug, `@Index` and `@Column` in the file) so the unique constraint is defined
only once via the index.

27-28: ⚡ Quick win

Remove redundant default function from timestamp column.

The default: () => 'now()' is redundant with @CreateDateColumn, which automatically handles the timestamp default.

♻️ Proposed fix
-  `@CreateDateColumn`({ type: 'timestamp', default: () => 'now()' })
+  `@CreateDateColumn`({ type: 'timestamp' })
  changedAt: Date;

As per coding guidelines, the code should adhere to best practices for TypeORM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-slug-history.entity.ts` around lines 27 - 28,
The `@CreateDateColumn` decorator on the changedAt property currently includes a
redundant default: () => 'now()'; remove that default option so the decorator is
simply `@CreateDateColumn`({ type: 'timestamp' }) (or just `@CreateDateColumn`())
and leave the changedAt: Date; property intact to let TypeORM manage the
timestamp automatically.

14-15: ⚡ Quick win

Consider adding foreign key relationship for referralEntityId.

The referralEntityId column lacks a @ManyToOne relationship to ReferralEntity, which would enable TypeORM to manage referential integrity and allow easier querying with joins.

🔗 Proposed enhancement
+import { ManyToOne, JoinColumn } from 'typeorm';
+import { ReferralEntity } from './referral-entity.entity';

+  `@ManyToOne`(() => ReferralEntity, { onDelete: 'CASCADE' })
+  `@JoinColumn`({ name: 'referralEntityId' })
+  referralEntity: ReferralEntity;
+
  `@Column`({ type: 'uuid' })
  referralEntityId: string;

As per coding guidelines, the code should adhere to best practices for TypeORM and database integrity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-slug-history.entity.ts` around lines 14 - 15,
The referralEntityId column in ReferralSlugHistory should be modeled as a
foreign key relation to ReferralEntity: add a `@ManyToOne`(() => ReferralEntity, {
nullable: false }) relation property (e.g., referralEntity) on the
ReferralSlugHistory class and annotate it with `@JoinColumn`({ name:
'referralEntityId' }), keeping or adding the referralEntityId `@Column`({ type:
'uuid' }) if you want the explicit FK field; this lets TypeORM enforce
referential integrity and enables join queries using the
ReferralSlugHistory.referralEntity and the ReferralEntity entity.
src/referrals/entities/referral-entity.entity.ts (3)

59-63: ⚡ Quick win

Remove redundant default functions from timestamp columns.

The default: () => 'now()' is redundant with @CreateDateColumn and @UpdateDateColumn, which automatically handle timestamps.

♻️ Proposed fix
-  `@CreateDateColumn`({ type: 'timestamp', default: () => 'now()' })
+  `@CreateDateColumn`({ type: 'timestamp' })
  createdAt: Date;

-  `@UpdateDateColumn`({ type: 'timestamp', default: () => 'now()' })
+  `@UpdateDateColumn`({ type: 'timestamp' })
  updatedAt: Date;

As per coding guidelines, the code should adhere to best practices for TypeORM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-entity.entity.ts` around lines 59 - 63,
Remove the redundant default functions on the timestamp columns: the
`@CreateDateColumn` and `@UpdateDateColumn` decorators already manage timestamps, so
update the referral entity by removing the "default: () => 'now()'" option from
the `@CreateDateColumn` and `@UpdateDateColumn` decorators for createdAt and
updatedAt (keep the decorators and the createdAt/updatedAt fields intact).

28-30: ⚡ Quick win

Remove redundant unique constraint.

The @Index({ unique: true }) decorator already creates a unique constraint, so specifying unique: true in the @Column decorator is redundant.

♻️ Proposed fix
  `@Index`({ unique: true })
-  `@Column`({ type: 'varchar', length: 100, unique: true })
+  `@Column`({ type: 'varchar', length: 100 })
  slug: string;

As per coding guidelines, the code should adhere to best practices for TypeORM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-entity.entity.ts` around lines 28 - 30, The
Column decorator on the slug field redundantly declares unique: true while there
is already an `@Index`({ unique: true }) on the same field; remove the unique:
true option from the Column decorator so the unique constraint is defined only
by `@Index`, leaving slug: string and `@Index`({ unique: true }) intact (update any
related tests/migrations if they rely on the duplicate option).

47-48: ⚡ Quick win

Consider using JSON or array column type for additionalEmails.

Storing an email array as a varchar(500) string requires manual serialization/deserialization. TypeORM supports JSON columns or Postgres array types (text[]) which handle this automatically and provide better type safety.

💡 Alternative approaches

Option 1: JSON column (database-agnostic)

-  `@Column`({ type: 'varchar', length: 500, nullable: true })
-  additionalEmails: string | null;
+  `@Column`({ type: 'json', nullable: true })
+  additionalEmails: string[] | null;

Option 2: Postgres array (if using PostgreSQL)

-  `@Column`({ type: 'varchar', length: 500, nullable: true })
-  additionalEmails: string | null;
+  `@Column`({ type: 'text', array: true, nullable: true })
+  additionalEmails: string[] | null;

As per coding guidelines, the code should adhere to best practices for performance and type safety.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/referral-entity.entity.ts` around lines 47 - 48, The
additionalEmails column currently uses a varchar string which requires manual
serialization; update the Referral entity's additionalEmails property to use a
typed array/JSON column instead: change the Column decorator on additionalEmails
to either a JSON column (e.g., type: 'json', property type string[] | null) for
a DB-agnostic solution, or to a Postgres array (e.g., type: 'text', array: true,
property type string[] | null) if you are on PostgreSQL; ensure any repository
code that reads/writes additionalEmails treats it as an array and remove manual
split/join serialization.
src/referrals/entities/user-attribution.entity.ts (2)

22-23: ⚡ Quick win

Remove redundant default function from timestamp column.

The default: () => 'now()' is redundant with @CreateDateColumn, which automatically handles the timestamp default.

♻️ Proposed fix
-  `@CreateDateColumn`({ type: 'timestamp', default: () => 'now()' })
+  `@CreateDateColumn`({ type: 'timestamp' })
  createdAt: Date;

As per coding guidelines, the code should adhere to best practices for TypeORM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/user-attribution.entity.ts` around lines 22 - 23, The
CreateDateColumn on the createdAt property in the UserAttribution entity
redundantly specifies default: () => 'now()'; remove the default option so the
decorator is used as intended—locate the createdAt field annotated with
`@CreateDateColumn` in user-attribution.entity (the createdAt property) and delete
the default: () => 'now()' configuration, leaving simply `@CreateDateColumn`({
type: 'timestamp' }) createdAt: Date; to rely on TypeORM's built-in behavior.

8-14: ⚡ Quick win

Consider adding foreign key relationships.

Both userId and referralEntityId lack @ManyToOne relationships to their respective entities. Adding these would enable TypeORM to manage referential integrity and support cascading operations.

🔗 Proposed enhancement
+import { ManyToOne, JoinColumn } from 'typeorm';
+import { User } from '../../user/user.entity'; // Adjust path as needed
+import { ReferralEntity } from './referral-entity.entity';

+  `@ManyToOne`(() => User, { onDelete: 'CASCADE' })
+  `@JoinColumn`({ name: 'userId' })
+  user: User;
+
  `@Index`()
  `@Column`({ type: 'uuid' })
  userId: string;

+  `@ManyToOne`(() => ReferralEntity, { onDelete: 'SET NULL', nullable: true })
+  `@JoinColumn`({ name: 'referralEntityId' })
+  referralEntity: ReferralEntity;
+
  `@Index`()
  `@Column`({ type: 'uuid', nullable: true })
  referralEntityId: string | null;

As per coding guidelines, the code should adhere to best practices for TypeORM and database integrity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/entities/user-attribution.entity.ts` around lines 8 - 14, Add
proper TypeORM relations for the userId and referralEntityId columns: replace or
augment the raw columns with `@ManyToOne` relations (e.g., add properties user:
User and referralEntity: ReferralEntity) decorated with `@ManyToOne`(() => User, u
=> u.<backRef> , { onDelete: 'CASCADE' }) and `@JoinColumn`({ name: 'userId' })
for user, and similarly `@ManyToOne`(() => ReferralEntity, r => r.<backRef> , {
onDelete: 'SET NULL' }) with `@JoinColumn`({ name: 'referralEntityId' }) for
referralEntity; also import User and ReferralEntity and remove
duplicate/conflicting column metadata or ensure the numeric/uuid `@Column`
definitions remain in sync with the relation metadata (use nullable on
referralEntityId).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/postgres/user-adapter.ts`:
- Around line 1997-2010: The code only computes referLinkForElastic during
initial creation (referLinkForElastic, referralsService.createUserAttribution,
buildReferLink, result.userId), so ES upsert/rebuild paths miss it; extract this
logic into a reusable helper (e.g., getReferLinkForElastic(userId,
incomingSlug?)) that tries referralsService.createUserAttribution (or an
equivalent service call to fetch existing attribution) and falls back to
buildReferLink when resolvedSlug exists, and call that helper from both the
initial create flow and any profile upsert/rebuild code paths so referLink is
always populated for Elasticsearch documents.

In `@src/forms/services/form-submission.service.ts`:
- Line 3163: Wrap the call to
this.referralsService.getUserReferLink(user.userId) in a try-catch to prevent
exceptions from bubbling up and breaking the Elasticsearch user document build;
if the call fails assign a safe fallback (e.g., null or empty string) to
referLink and log the error (use the existing logger instance, e.g.,
this.logger.error) with contextual info including user.userId and the caught
error. Ensure you only change the area around the existing const referLink =
await this.referralsService.getUserReferLink(user.userId); statement so other
logic that consumes referLink continues to work with the fallback.

In `@src/referrals/dto/create-referral-entity.dto.ts`:
- Around line 1-68: The file CreateReferralEntityDto uses single quotes across
imports, decorators and string literals which violates the project's
Prettier/formatting rules; run the project's Prettier formatter (or your
configured formatter) on src/referrals/dto/create-referral-entity.dto.ts to
convert all single quotes to double quotes and normalize spacing for imports,
decorator metadata (ApiProperty/ApiPropertyOptional), and string literals (e.g.,
descriptions and type arrays) so formatting matches the repository standard and
linter/prettier checks pass.

In `@src/referrals/dto/import-referrals.dto.ts`:
- Around line 1-13: The file ImportReferralsDto uses single quotes for string
literals and should be formatted with double quotes per project Prettier
settings; update all string literals (e.g., the ApiProperty description, import
specifiers if necessary) to use double quotes and then run Prettier (or your
editor formatter) to apply the project’s formatting rules so ImportReferralsDto,
its csv property, and decorators (`@ApiProperty`, `@IsString`, `@IsNotEmpty`) follow
the configured style.

In `@src/referrals/dto/list-referrals.dto.ts`:
- Around line 1-55: The file uses single quotes for imports and decorators;
update all string literals to use double quotes to match project Prettier
settings (e.g., in ReferralFiltersDto and ListReferralsDto usage:
ApiPropertyOptional type strings, enum references, and import statements). Run
the project's Prettier formatter (or replace single quotes with double quotes
across the file) and ensure linting passes for symbols ReferralFiltersDto,
ListReferralsDto, ReferralEntityType, and ReferralEntitySubType.
- Around line 50-54: The filters property in ListReferralsDto currently uses
`@Type`(() => ReferralFiltersDto) but lacks nested validation; add
`@ValidateNested`() (from class-validator) to the filters property so the
transformed object is validated against ReferralFiltersDto constraints, ensuring
the decorator is applied alongside `@IsOptional`(), `@IsObject`(), `@Type`(() =>
ReferralFiltersDto) on the filters?: ReferralFiltersDto field.

In `@src/referrals/dto/update-referral-slug.dto.ts`:
- Around line 1-62: The file UpdateReferralSlugDto uses single quotes for
imports and strings; update all string literals and import quote marks to double
quotes and run the project's Prettier/formatting script (or your editor's
formatter) to apply project-wide rules so the file conforms to the Google style
and Prettier config; ensure you re-save UpdateReferralSlugDto and commit only
the formatting changes (no logic changes).

In `@src/referrals/entities/referral-entity.entity.ts`:
- Around line 1-64: The file uses single quotes instead of the project's
double-quote style; update all string literals and import/ decorator metadata to
use double quotes (e.g., the import lines, Entity name string, Column type
values, default values, and any other quoted strings found in the ReferralEntity
class and its decorators) and then run Prettier to reformat — target symbols:
the top-level imports, the `@Entity` decorator, all
`@Column/`@CreateDateColumn/@UpdateDateColumn options, and the ReferralEntity
class declaration to ensure consistent double-quote formatting throughout.

In `@src/referrals/entities/referral-slug-history.entity.ts`:
- Around line 1-30: Replace all single-quoted string literals with double quotes
in this entity (e.g., decorator arguments like Entity("ReferralSlugHistory"),
PrimaryGeneratedColumn("uuid"), Column({ type: "uuid" }), Index({ unique: true
}) and Column length/type values) and then run Prettier to apply project
formatting; ensure exported class ReferralSlugHistory and its properties (id,
referralEntityId, oldSlug, newSlug, changedBy, changedAt) retain their types and
decorators exactly as before while only changing quote style and running the
formatter.

In `@src/referrals/entities/user-attribution.entity.ts`:
- Around line 1-24: The file uses single quotes for string literals which
violates Prettier; update all string literals to use double quotes (including
import module specifiers, Entity name, Column type strings,
PrimaryGeneratedColumn argument, and CreateDateColumn default SQL string) in the
UserAttribution class and its decorators (see imports and decorators like
Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index) and then run
Prettier to auto-format the file to ensure it matches the project's formatting
rules.

In `@src/referrals/referrals.controller.ts`:
- Around line 109-111: The bulkInsert handler currently uses `@Body`() with
ValidationPipe which won't validate each array element; update the `@Body`
decorator to use ParseArrayPipe so each item is validated: replace `@Body`() dtos:
CreateReferralEntityDto[] with `@Body`(new ParseArrayPipe({ items:
CreateReferralEntityDto })) dtos: CreateReferralEntityDto[] (keep the existing
ValidationPipe on the method), referencing the bulkInsert method and
CreateReferralEntityDto to enforce per-item validation.

In `@src/referrals/referrals.service.ts`:
- Around line 277-297: The CSV parsing currently uses naive split(',') in the
header and row loops (header = lines[0].split(',') and cols =
lines[rowIdx].split(',')), which breaks when fields are quoted or contain
escaped commas; replace this with a proper CSV parse step (e.g., use a CSV
parser library or a quote-aware parser function) before computing header/indexes
(idx, iFirst, iLast, etc.) and before iterating rows so header and cols are
constructed from parsed records rather than string.split, preserving quoted
fields and escapes; ensure the rest of the logic (references to iFirst, iType,
iSubType, firstName, etc.) consumes the parsed record arrays/objects.
- Around line 36-37: Normalize dto.contactEmail (trim + toLowerCase) before any
uniqueness checks and before persisting: create a normalizedEmail =
dto.contactEmail?.trim().toLowerCase() and use that when calling
this.referralRepo.findOne and when assigning to the entity/DTO saved to the DB;
apply the same normalization in the update path (the code around the update
handler and the block that currently queries in lines handling updates) so both
the create and update flows use the normalized value and prevent case-variant
duplicates. Ensure you also update dto.contactEmail or the entity field to the
normalizedEmail prior to saving.
- Around line 182-185: When updating referral fields (the dto → entity block
handling dto.firstName/lastName/type/subType/contactEmail), ensure you compute
the effective values (let effectiveType = dto.type ?? entity.type and let
effectiveContactEmail = dto.contactEmail ?? entity.contactEmail) and, if
effectiveType === 'INTERNAL', run the existing "internal contact existence"
validation against effectiveContactEmail before persisting changes; apply the
same fix to the similar block around the 196-207 logic so that changing type to
INTERNAL without supplying contactEmail still validates the stored
entity.contactEmail.
- Around line 340-345: The parsed batchSize from
this.configService.get('REFERRAL_BULK_BATCH_SIZE') must be validated before the
for loop that uses i += batchSize; ensure the value assigned to batchSize is a
positive integer (e.g., parseInt(...,10), check for NaN, <= 0, or non-finite)
and if invalid replace it with a safe default (like 100) before iterating over
dtos; update the variable used in the loop (batchSize) so the loop cannot be
non-terminating and add a brief comment referencing batchSize and dtos to
clarify the guard.

In `@src/referrals/referrals.types.ts`:
- Around line 1-16: The enum string literals use single quotes and must be
formatted with double quotes to satisfy Prettier; update the string values in
ReferralEntityType, ReferralEntitySubType, and ReferralEntityStatus to use
double quotes (or run the project's Prettier/formatter) so entries like EXTERNAL
= "external", ORGANISATION = "organisation", and ACTIVE = "active" follow the
project's formatting rules.

In `@src/referrals/utils/referral-slug.util.ts`:
- Around line 4-10: DEFAULT_REFERRAL_BASE_URL currently returns a
possibly-undefined FRONTEND_URL string and buildReferLink concatenates it
directly causing malformed links; change DEFAULT_REFERRAL_BASE_URL to throw /
return error fast when process.env.FRONTEND_URL is not set, and rewrite
buildReferLink to construct the link using the standard URL and URLSearchParams
APIs (use new URL(base) to normalize slashes and preserve existing path/query,
then append or set the "refer" search param via url.searchParams.set('refer',
encodeURIComponent(slug.trim())), and return url.toString()); update references
to DEFAULT_REFERRAL_BASE_URL() and buildReferLink(slug, baseUrl?) accordingly so
missing base URL fails fast and existing query strings are preserved.

---

Nitpick comments:
In `@src/forms/services/form-submission.service.ts`:
- Line 99: The constructor parameter list ending with "private readonly
referralsService: ReferralsService" should include a trailing comma for style
consistency; update the constructor (the parameter declaration containing
referralsService) to add a comma after that parameter so future diffs remain
cleaner and align with TypeScript/JS style guides.

In `@src/referrals/entities/referral-entity.entity.ts`:
- Around line 59-63: Remove the redundant default functions on the timestamp
columns: the `@CreateDateColumn` and `@UpdateDateColumn` decorators already manage
timestamps, so update the referral entity by removing the "default: () =>
'now()'" option from the `@CreateDateColumn` and `@UpdateDateColumn` decorators for
createdAt and updatedAt (keep the decorators and the createdAt/updatedAt fields
intact).
- Around line 28-30: The Column decorator on the slug field redundantly declares
unique: true while there is already an `@Index`({ unique: true }) on the same
field; remove the unique: true option from the Column decorator so the unique
constraint is defined only by `@Index`, leaving slug: string and `@Index`({ unique:
true }) intact (update any related tests/migrations if they rely on the
duplicate option).
- Around line 47-48: The additionalEmails column currently uses a varchar string
which requires manual serialization; update the Referral entity's
additionalEmails property to use a typed array/JSON column instead: change the
Column decorator on additionalEmails to either a JSON column (e.g., type:
'json', property type string[] | null) for a DB-agnostic solution, or to a
Postgres array (e.g., type: 'text', array: true, property type string[] | null)
if you are on PostgreSQL; ensure any repository code that reads/writes
additionalEmails treats it as an array and remove manual split/join
serialization.

In `@src/referrals/entities/referral-slug-history.entity.ts`:
- Around line 17-19: Remove the redundant unique constraint on the oldSlug
column: keep the `@Index`({ unique: true }) decorator and remove the unique: true
option from the `@Column` decorator on the oldSlug property in the
ReferralSlugHistory entity (look for oldSlug, `@Index` and `@Column` in the file) so
the unique constraint is defined only once via the index.
- Around line 27-28: The `@CreateDateColumn` decorator on the changedAt property
currently includes a redundant default: () => 'now()'; remove that default
option so the decorator is simply `@CreateDateColumn`({ type: 'timestamp' }) (or
just `@CreateDateColumn`()) and leave the changedAt: Date; property intact to let
TypeORM manage the timestamp automatically.
- Around line 14-15: The referralEntityId column in ReferralSlugHistory should
be modeled as a foreign key relation to ReferralEntity: add a `@ManyToOne`(() =>
ReferralEntity, { nullable: false }) relation property (e.g., referralEntity) on
the ReferralSlugHistory class and annotate it with `@JoinColumn`({ name:
'referralEntityId' }), keeping or adding the referralEntityId `@Column`({ type:
'uuid' }) if you want the explicit FK field; this lets TypeORM enforce
referential integrity and enables join queries using the
ReferralSlugHistory.referralEntity and the ReferralEntity entity.

In `@src/referrals/entities/user-attribution.entity.ts`:
- Around line 22-23: The CreateDateColumn on the createdAt property in the
UserAttribution entity redundantly specifies default: () => 'now()'; remove the
default option so the decorator is used as intended—locate the createdAt field
annotated with `@CreateDateColumn` in user-attribution.entity (the createdAt
property) and delete the default: () => 'now()' configuration, leaving simply
`@CreateDateColumn`({ type: 'timestamp' }) createdAt: Date; to rely on TypeORM's
built-in behavior.
- Around line 8-14: Add proper TypeORM relations for the userId and
referralEntityId columns: replace or augment the raw columns with `@ManyToOne`
relations (e.g., add properties user: User and referralEntity: ReferralEntity)
decorated with `@ManyToOne`(() => User, u => u.<backRef> , { onDelete: 'CASCADE'
}) and `@JoinColumn`({ name: 'userId' }) for user, and similarly `@ManyToOne`(() =>
ReferralEntity, r => r.<backRef> , { onDelete: 'SET NULL' }) with `@JoinColumn`({
name: 'referralEntityId' }) for referralEntity; also import User and
ReferralEntity and remove duplicate/conflicting column metadata or ensure the
numeric/uuid `@Column` definitions remain in sync with the relation metadata (use
nullable on referralEntityId).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8c6c8ce-08e1-4006-a964-06f66fec9a76

📥 Commits

Reviewing files that changed from the base of the PR and between faa92d5 and 72dbb26.

📒 Files selected for processing (23)
  • src/adapters/postgres/postgres-module.ts
  • src/adapters/postgres/user-adapter.ts
  • src/app.module.ts
  • src/common/utils/api-id.config.ts
  • src/common/utils/response.messages.ts
  • src/elasticsearch/elasticsearch.service.ts
  • src/elasticsearch/interfaces/user.interface.ts
  • src/elasticsearch/user-elasticsearch.service.ts
  • src/forms/forms.module.ts
  • src/forms/services/form-submission.service.ts
  • src/referrals/dto/create-referral-entity.dto.ts
  • src/referrals/dto/import-referrals.dto.ts
  • src/referrals/dto/list-referrals.dto.ts
  • src/referrals/dto/update-referral-slug.dto.ts
  • src/referrals/entities/referral-entity.entity.ts
  • src/referrals/entities/referral-slug-history.entity.ts
  • src/referrals/entities/user-attribution.entity.ts
  • src/referrals/referrals.controller.ts
  • src/referrals/referrals.module.ts
  • src/referrals/referrals.service.ts
  • src/referrals/referrals.types.ts
  • src/referrals/utils/referral-slug.util.ts
  • src/user/dto/user-create.dto.ts

Comment thread src/adapters/postgres/user-adapter.ts
Comment thread src/forms/services/form-submission.service.ts
Comment thread src/referrals/dto/create-referral-entity.dto.ts
Comment thread src/referrals/dto/import-referrals.dto.ts
Comment thread src/referrals/dto/list-referrals.dto.ts Outdated
Comment thread src/referrals/referrals.service.ts
Comment thread src/referrals/referrals.service.ts
Comment thread src/referrals/referrals.service.ts Outdated
Comment thread src/referrals/referrals.types.ts
Comment thread src/referrals/utils/referral-slug.util.ts Outdated
Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (5)
src/referrals/referrals.service.ts (4)

342-347: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard batchSize before using it in loop increment.

0 or negative values can make the bulk loop non-terminating.

Suggested fix
-    const batchSize = parseInt(this.configService.get('REFERRAL_BULK_BATCH_SIZE') || '100', 10);
+    const parsedBatchSize = Number.parseInt(
+      this.configService.get("REFERRAL_BULK_BATCH_SIZE") || "100",
+      10,
+    );
+    const batchSize =
+      Number.isInteger(parsedBatchSize) && parsedBatchSize > 0
+        ? parsedBatchSize
+        : 100;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.service.ts` around lines 342 - 347, The loop uses
batchSize parsed from this.configService.get('REFERRAL_BULK_BATCH_SIZE') and can
hang if batchSize is 0 or negative; before the for loop that slices dtos,
validate batchSize is a positive integer (e.g., if parsed value <= 0 or NaN, set
it to a safe default like 100 or throw a clear error), then proceed to use
batchSize in the for (let i = 0; i < dtos.length; i += batchSize) loop to ensure
termination and predictable batching.

38-57: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize contactEmail before create-time uniqueness check and persistence.

Case/whitespace variants can bypass uniqueness checks and create duplicate logical identities.

Suggested fix
   async createReferralEntity(dto: CreateReferralEntityDto, createdBy?: string) {
-    if (dto.contactEmail) {
-      const existingEmail = await this.referralRepo.findOne({ where: { contactEmail: dto.contactEmail } });
+    const normalizedEmail = dto.contactEmail?.trim().toLowerCase();
+    if (normalizedEmail) {
+      const existingEmail = await this.referralRepo.findOne({ where: { contactEmail: normalizedEmail } });
       if (existingEmail) {
-        throw new ConflictException(`Referral slug already exists for email ${dto.contactEmail}`);
+        throw new ConflictException(`Referral slug already exists for email ${normalizedEmail}`);
       }
 
       if (dto.type === ReferralEntityType.INTERNAL) {
-        const existingUser = await this.userRepo.findOne({ where: { email: dto.contactEmail.toLowerCase() } });
+        const existingUser = await this.userRepo.findOne({ where: { email: normalizedEmail } });
         if (!existingUser) {
-          throw new BadRequestException(`Internal user email ${dto.contactEmail} does not exist in the system`);
+          throw new BadRequestException(`Internal user email ${normalizedEmail} does not exist in the system`);
         }
       }
     }
@@
-      contactEmail: dto.contactEmail ?? null,
+      contactEmail: normalizedEmail ?? null,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.service.ts` around lines 38 - 57, Normalize
dto.contactEmail (trim and toLowerCase) before any uniqueness or existence
checks and before persisting: compute a normalizedEmail from dto.contactEmail
(e.g., dto.contactEmail?.trim().toLowerCase()), use normalizedEmail in the
referralRepo.findOne uniqueness check and in the userRepo.findOne check for
ReferralEntityType.INTERNAL, and ensure the object passed to referralRepo.create
stores contactEmail: normalizedEmail (or null if absent). Update references to
dto.contactEmail in the shown block to use this normalized value so
case/whitespace variants cannot bypass checks.

197-210: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update path should validate effective INTERNAL email even when contactEmail is omitted.

Changing type to INTERNAL without sending contactEmail skips existence validation for the stored email.

Suggested fix
-    if (dto.contactEmail !== undefined && dto.contactEmail !== entity.contactEmail) {
-      const existingEmail = await this.referralRepo.findOne({ where: { contactEmail: dto.contactEmail } });
+    const effectiveType = dto.type ?? entity.type;
+    const effectiveContactEmail = (dto.contactEmail ?? entity.contactEmail)?.trim().toLowerCase();
+
+    if (dto.contactEmail !== undefined && effectiveContactEmail !== entity.contactEmail) {
+      const existingEmail = await this.referralRepo.findOne({ where: { contactEmail: effectiveContactEmail } });
       if (existingEmail && existingEmail.id !== entity.id) {
-        throw new ConflictException(`Contact email '${dto.contactEmail}' is already used by another referral`);
+        throw new ConflictException(`Contact email '${effectiveContactEmail}' is already used by another referral`);
       }
-      const resolvedType = dto.type ?? entity.type;
-      if (resolvedType === ReferralEntityType.INTERNAL) {
-        const existingUser = await this.userRepo.findOne({ where: { email: dto.contactEmail.toLowerCase() } });
-        if (!existingUser) {
-          throw new BadRequestException(`Internal user email ${dto.contactEmail} does not exist in the system`);
-        }
-      }
-      entity.contactEmail = dto.contactEmail;
+      entity.contactEmail = effectiveContactEmail;
+    }
+
+    if (effectiveType === ReferralEntityType.INTERNAL) {
+      if (!effectiveContactEmail) {
+        throw new BadRequestException('Internal referral must have contactEmail');
+      }
+      const existingUser = await this.userRepo.findOne({ where: { email: effectiveContactEmail } });
+      if (!existingUser) {
+        throw new BadRequestException(`Internal user email ${effectiveContactEmail} does not exist in the system`);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.service.ts` around lines 197 - 210, The update
currently only validates internal-user existence when dto.contactEmail is
provided; instead compute resolvedType = dto.type ?? entity.type first and if
resolvedType === ReferralEntityType.INTERNAL ensure the effectiveEmail =
(dto.contactEmail ?? entity.contactEmail) exists and passes lookup via
this.userRepo.findOne({ where: { email: effectiveEmail.toLowerCase() } })
throwing BadRequestException if not found; keep the uniqueness check for
dto.contactEmail when present (using this.referralRepo.findOne and id
comparison) and only assign entity.contactEmail = dto.contactEmail after these
validations.

279-299: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

CSV parsing is not quote-safe.

Using split(",") breaks valid CSV rows with quoted commas, causing column shifts and bad imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.service.ts` around lines 279 - 299, The CSV parsing
uses naive split(',') which breaks on quoted fields (see header, idx, and the
loop that builds cols and firstName), so replace the ad-hoc parsing with a
proper CSV parser (e.g., papaparse or csv-parse) to read lines into columns
safely and preserve quoted commas; update the code that computes header,
idx('firstName'...), and the rows loop to use the parser's output (parsed header
array and parsed rows) and then use those indices (iFirst, iLast, iType,
iSubType, iRegion, iEmail, iCountry) to access values instead of split(',') so
quoted fields are handled correctly and column alignment is preserved.
src/referrals/referrals.controller.ts (1)

110-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use ParseArrayPipe for bulk DTO item validation.

ValidationPipe does not reliably validate each element for a plain top-level CreateReferralEntityDto[] body.

Suggested fix
-import { Body, Controller, Get, Param, Patch, Post, Query, UsePipes, ValidationPipe, UseFilters, Res, HttpStatus, UseGuards, Req } from '`@nestjs/common`';
+import { Body, Controller, Get, Param, Patch, Post, Query, UsePipes, ValidationPipe, UseFilters, Res, HttpStatus, UseGuards, Req, ParseArrayPipe } from '`@nestjs/common`';
@@
-  async bulkInsert(`@Body`() dtos: CreateReferralEntityDto[], `@Req`() request: RequestWithUser, `@Res`() response: Response) {
+  async bulkInsert(
+    `@Body`(new ParseArrayPipe({ items: CreateReferralEntityDto })) dtos: CreateReferralEntityDto[],
+    `@Req`() request: RequestWithUser,
+    `@Res`() response: Response
+  ) {
As per coding guidelines, "The code adheres to best practices associated with nestjs framework."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.controller.ts` around lines 110 - 112, The bulkInsert
endpoint in referrals.controller.ts currently uses a plain ValidationPipe on a
top-level CreateReferralEntityDto[] which won't validate each array element;
update the bulkInsert method to use Nest's ParseArrayPipe configured with the
item type CreateReferralEntityDto and include a ValidationPipe (e.g.,
ParseArrayPipe({ items: CreateReferralEntityDto, whitelist: true, transform:
true }) or ParseArrayPipe({ items: CreateReferralEntityDto, options: {
transform: true, whitelist: true } })) so each DTO in the array is individually
validated before reaching the bulkInsert(`@Body`() dtos:
CreateReferralEntityDto[], ...) handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/referrals/dto/referral-report.dto.ts`:
- Around line 71-73: The filters property on ReferralReportReportDto isn't being
validated recursively; add class-validator/class-transformer decorators to
enforce nested DTO validation by applying `@ValidateNested`() and use `@Type`(() =>
ReferralReportFiltersDto) on the filters?: ReferralReportFiltersDto property so
the constraints inside ReferralReportFiltersDto (e.g., `@IsString`, `@IsArray`,
`@IsEnum`) are executed; keep the existing `@IsOptional`() and
`@ApiPropertyOptional`({ type: ReferralReportFiltersDto }) in place and adjust
imports as needed.
- Around line 22-25: The slug_id property is only validated as a string; add
`@IsUUID`() to slug_id (keep `@IsOptional`()) so invalid UUIDs return 400s; for the
filters property, add `@ValidateNested`() and `@Type`(() => <FiltersDtoClass>)
(replace <FiltersDtoClass> with the actual nested DTO class used for filters) to
ensure nested DTO properties are validated, and import Type from
class-transformer and ValidateNested/IsUUID from class-validator accordingly.

---

Duplicate comments:
In `@src/referrals/referrals.controller.ts`:
- Around line 110-112: The bulkInsert endpoint in referrals.controller.ts
currently uses a plain ValidationPipe on a top-level CreateReferralEntityDto[]
which won't validate each array element; update the bulkInsert method to use
Nest's ParseArrayPipe configured with the item type CreateReferralEntityDto and
include a ValidationPipe (e.g., ParseArrayPipe({ items: CreateReferralEntityDto,
whitelist: true, transform: true }) or ParseArrayPipe({ items:
CreateReferralEntityDto, options: { transform: true, whitelist: true } })) so
each DTO in the array is individually validated before reaching the
bulkInsert(`@Body`() dtos: CreateReferralEntityDto[], ...) handler.

In `@src/referrals/referrals.service.ts`:
- Around line 342-347: The loop uses batchSize parsed from
this.configService.get('REFERRAL_BULK_BATCH_SIZE') and can hang if batchSize is
0 or negative; before the for loop that slices dtos, validate batchSize is a
positive integer (e.g., if parsed value <= 0 or NaN, set it to a safe default
like 100 or throw a clear error), then proceed to use batchSize in the for (let
i = 0; i < dtos.length; i += batchSize) loop to ensure termination and
predictable batching.
- Around line 38-57: Normalize dto.contactEmail (trim and toLowerCase) before
any uniqueness or existence checks and before persisting: compute a
normalizedEmail from dto.contactEmail (e.g.,
dto.contactEmail?.trim().toLowerCase()), use normalizedEmail in the
referralRepo.findOne uniqueness check and in the userRepo.findOne check for
ReferralEntityType.INTERNAL, and ensure the object passed to referralRepo.create
stores contactEmail: normalizedEmail (or null if absent). Update references to
dto.contactEmail in the shown block to use this normalized value so
case/whitespace variants cannot bypass checks.
- Around line 197-210: The update currently only validates internal-user
existence when dto.contactEmail is provided; instead compute resolvedType =
dto.type ?? entity.type first and if resolvedType ===
ReferralEntityType.INTERNAL ensure the effectiveEmail = (dto.contactEmail ??
entity.contactEmail) exists and passes lookup via this.userRepo.findOne({ where:
{ email: effectiveEmail.toLowerCase() } }) throwing BadRequestException if not
found; keep the uniqueness check for dto.contactEmail when present (using
this.referralRepo.findOne and id comparison) and only assign entity.contactEmail
= dto.contactEmail after these validations.
- Around line 279-299: The CSV parsing uses naive split(',') which breaks on
quoted fields (see header, idx, and the loop that builds cols and firstName), so
replace the ad-hoc parsing with a proper CSV parser (e.g., papaparse or
csv-parse) to read lines into columns safely and preserve quoted commas; update
the code that computes header, idx('firstName'...), and the rows loop to use the
parser's output (parsed header array and parsed rows) and then use those indices
(iFirst, iLast, iType, iSubType, iRegion, iEmail, iCountry) to access values
instead of split(',') so quoted fields are handled correctly and column
alignment is preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b1019cdd-6497-4e4c-84f3-c2320b826d96

📥 Commits

Reviewing files that changed from the base of the PR and between 72dbb26 and b92bc29.

📒 Files selected for processing (5)
  • src/common/utils/api-id.config.ts
  • src/common/utils/response.messages.ts
  • src/referrals/dto/referral-report.dto.ts
  • src/referrals/referrals.controller.ts
  • src/referrals/referrals.service.ts
✅ Files skipped from review due to trivial changes (1)
  • src/common/utils/response.messages.ts

Comment thread src/referrals/dto/referral-report.dto.ts
Comment thread src/referrals/dto/referral-report.dto.ts
Copy link
Copy Markdown

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/elasticsearch/user-elasticsearch.service.ts (1)

777-785: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate or coerce referLink filter value appropriately.

If value is not a string, JSON.stringify(value) produces a JSON-formatted string (e.g., '{"foo":"bar"}' or '["abc"]') that won't meaningfully match referLink values. This leads to confusing search behavior.

Consider either:

  • Validating that value is a string and throwing a descriptive error for other types
  • Using .toString() for primitives and rejecting objects/arrays explicitly
  • Documenting that only string values are accepted
🔧 Proposed fix
 if (field === 'referLink') {
-  const referLinkValue = typeof value === 'string' ? value : JSON.stringify(value);
+  if (typeof value !== 'string') {
+    throw new Error('referLink filter must be a string');
+  }
+  const referLinkValue = value;
   searchQuery.bool.filter.push({
     wildcard: {
       'profile.referLink': `*${referLinkValue.toLowerCase()}*`,
     },
   });
   return;
 }

As per coding guidelines, the code should adhere to NestJS best practices for input validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/elasticsearch/user-elasticsearch.service.ts` around lines 777 - 785, The
referLink branch in the search builder treats non-string values by
JSON.stringify(value), which yields JSON that won't meaningfully match
profile.referLink; update the logic in the method that builds searchQuery (the
block checking if (field === 'referLink') and pushing to
searchQuery.bool.filter) to first validate/coerce value: if value is a string
use it as-is (lowercased), if it's a primitive (number/boolean) use
value.toString(), and if it's an object/array throw or return a descriptive
validation error (or reject the request) instead of JSON.stringify; ensure the
error follows NestJS validation conventions so callers receive a clear message
about accepted types.
♻️ Duplicate comments (1)
src/referrals/referrals.service.ts (1)

342-342: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard batchSize before using it as loop increment.

Line 342 can yield 0, negative, or NaN; with Line 346 (i += batchSize) this can stall or break batching.

Suggested fix
-    const batchSize = Number.parseInt(this.configService.get('REFERRAL_BULK_BATCH_SIZE') || '100', 10);
+    const parsedBatchSize = Number.parseInt(
+      this.configService.get('REFERRAL_BULK_BATCH_SIZE') || '100',
+      10,
+    );
+    const batchSize =
+      Number.isInteger(parsedBatchSize) && parsedBatchSize > 0
+        ? parsedBatchSize
+        : 100;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/referrals/referrals.service.ts` at line 342, The parsed batchSize from
this.configService.get('REFERRAL_BULK_BATCH_SIZE') can be 0, negative, or NaN
which will break the batching loop that uses i += batchSize; validate and
normalize batchSize before the loop (where batchSize is declared) by coercing it
to a positive integer and falling back to a safe default (e.g., 100) — e.g.,
compute a sanitizedBatchSize = Number.isFinite(batchSize) && batchSize > 0 ?
Math.floor(batchSize) : 100 and then use sanitizedBatchSize for the loop
increment (and any related logic) instead of batchSize.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/elasticsearch/user-elasticsearch.service.ts`:
- Around line 777-785: The referLink branch in the search builder treats
non-string values by JSON.stringify(value), which yields JSON that won't
meaningfully match profile.referLink; update the logic in the method that builds
searchQuery (the block checking if (field === 'referLink') and pushing to
searchQuery.bool.filter) to first validate/coerce value: if value is a string
use it as-is (lowercased), if it's a primitive (number/boolean) use
value.toString(), and if it's an object/array throw or return a descriptive
validation error (or reject the request) instead of JSON.stringify; ensure the
error follows NestJS validation conventions so callers receive a clear message
about accepted types.

---

Duplicate comments:
In `@src/referrals/referrals.service.ts`:
- Line 342: The parsed batchSize from
this.configService.get('REFERRAL_BULK_BATCH_SIZE') can be 0, negative, or NaN
which will break the batching loop that uses i += batchSize; validate and
normalize batchSize before the loop (where batchSize is declared) by coercing it
to a positive integer and falling back to a safe default (e.g., 100) — e.g.,
compute a sanitizedBatchSize = Number.isFinite(batchSize) && batchSize > 0 ?
Math.floor(batchSize) : 100 and then use sanitizedBatchSize for the loop
increment (and any related logic) instead of batchSize.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e88f6b3f-eb10-4151-9065-c58aeb7041ff

📥 Commits

Reviewing files that changed from the base of the PR and between b92bc29 and aa4e070.

📒 Files selected for processing (4)
  • src/elasticsearch/user-elasticsearch.service.ts
  • src/referrals/dto/list-referrals.dto.ts
  • src/referrals/referrals.service.ts
  • src/referrals/utils/referral-slug.util.ts

@sonarqubecloud
Copy link
Copy Markdown

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