Task#00000 Referral Tracking implementation#741
Conversation
WalkthroughThis PR introduces a complete referral management system allowing organizations and individuals to be registered as referral entities with unique slugs and refer-links. The system tracks user attribution to referrals, supports CSV import and bulk operations, integrates with user creation workflows, and adds Elasticsearch search indexing for referral links. ChangesReferral Management System
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
There was a problem hiding this comment.
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.
| await this.historyRepo.save( | ||
| this.historyRepo.create({ | ||
| referralEntityId: entity.id, | ||
| oldSlug: entity.slug, | ||
| newSlug, | ||
| changedBy: changedBy ?? null, | ||
| }) | ||
| ); | ||
| entity.slug = newSlug; | ||
| } |
There was a problem hiding this comment.
The update of the referral slug and the recording of its history should be performed within a database transaction. Currently, if historyRepo.save succeeds but referralRepo.save fails, the system will have an inconsistent state where a slug change is recorded in history but not applied to the entity.
| const csv = String(dto.csv || '').trim(); | ||
| if (!csv) throw new BadRequestException('CSV is empty'); | ||
|
|
||
| const lines = csv.split(/\r?\n/).filter((l) => l.trim().length > 0); |
There was a problem hiding this comment.
| ...dto, | ||
| lastName: dto.lastName ?? null, | ||
| region: dto.region ?? null, | ||
| linkedEntityId: (dto as any).linkedEntityId ?? null, |
| if (dto.contactEmail) { | ||
| const existingEmail = await this.referralRepo.findOne({ where: { contactEmail: dto.contactEmail } }); | ||
| if (existingEmail) { | ||
| throw new ConflictException(`Referral slug already exists for email ${dto.contactEmail}`); |
There was a problem hiding this comment.
The error message is slightly misleading. It mentions 'Referral slug' while the check is actually validating the uniqueness of the contactEmail field.
| throw new ConflictException(`Referral slug already exists for email ${dto.contactEmail}`); | |
| throw new ConflictException(`Referral entity already exists for email ${dto.contactEmail}`); |
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (9)
src/forms/services/form-submission.service.ts (1)
99-99: 💤 Low valueConsider 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 winRemove redundant unique constraint.
The
@Index({ unique: true })decorator already creates a unique constraint, so specifyingunique: truein the@Columndecorator 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 winRemove 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 winConsider adding foreign key relationship for referralEntityId.
The
referralEntityIdcolumn lacks a@ManyToOnerelationship toReferralEntity, 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 winRemove redundant default functions from timestamp columns.
The
default: () => 'now()'is redundant with@CreateDateColumnand@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 winRemove redundant unique constraint.
The
@Index({ unique: true })decorator already creates a unique constraint, so specifyingunique: truein the@Columndecorator 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 winConsider 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 winRemove 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 winConsider adding foreign key relationships.
Both
userIdandreferralEntityIdlack@ManyToOnerelationships 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
📒 Files selected for processing (23)
src/adapters/postgres/postgres-module.tssrc/adapters/postgres/user-adapter.tssrc/app.module.tssrc/common/utils/api-id.config.tssrc/common/utils/response.messages.tssrc/elasticsearch/elasticsearch.service.tssrc/elasticsearch/interfaces/user.interface.tssrc/elasticsearch/user-elasticsearch.service.tssrc/forms/forms.module.tssrc/forms/services/form-submission.service.tssrc/referrals/dto/create-referral-entity.dto.tssrc/referrals/dto/import-referrals.dto.tssrc/referrals/dto/list-referrals.dto.tssrc/referrals/dto/update-referral-slug.dto.tssrc/referrals/entities/referral-entity.entity.tssrc/referrals/entities/referral-slug-history.entity.tssrc/referrals/entities/user-attribution.entity.tssrc/referrals/referrals.controller.tssrc/referrals/referrals.module.tssrc/referrals/referrals.service.tssrc/referrals/referrals.types.tssrc/referrals/utils/referral-slug.util.tssrc/user/dto/user-create.dto.ts
| // Referral attribution (first-touch provided by client; do not block registration) | ||
| let referLinkForElastic: string | null = null; | ||
| try { | ||
| const incomingSlug = (userCreateDto as any)?.referralSlug ?? null; | ||
| const attr = await this.referralsService.createUserAttribution({ | ||
| userId: result.userId, | ||
| incomingSlug, | ||
| }); | ||
| referLinkForElastic = | ||
| attr.referLink ?? | ||
| (attr.resolvedSlug ? buildReferLink(attr.resolvedSlug) : null); | ||
| } catch (refError: any) { | ||
| LoggerUtil.error('Referral attribution failed', refError); | ||
| } |
There was a problem hiding this comment.
Preserve referLink in all Elasticsearch profile rebuild paths.
referLink is computed only during initial create, but profile upsert/rebuild paths can recreate ES docs without it. This causes referral filter/search regressions after document-missing recovery or reindex-style rebuilds.
Suggested fix
private async syncUserToElasticsearch(user: User) {
try {
- const customFields = await this.getFilteredCustomFields(user.userId);
+ const [customFields, referLink] = await Promise.all([
+ this.getFilteredCustomFields(user.userId),
+ this.referralsService.getUserReferLink(user.userId),
+ ]);
let formattedDob: string | null = null;
@@
const profile = {
userId: user.userId,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
middleName: user.middleName || '',
email: user.email || '',
mobile: user.mobile?.toString() || '',
mobile_country_code: user.mobile_country_code || '',
+ referLink: referLink ?? undefined,
dob: formattedDob,
country: user.country,
@@
async (userId: string) => {
const dbUser = await this.usersRepository.findOne({
where: { userId },
});
if (!dbUser) return null;
- const customFields = await this.getFilteredCustomFields(userId);
+ const [customFields, referLink] = await Promise.all([
+ this.getFilteredCustomFields(userId),
+ this.referralsService.getUserReferLink(userId),
+ ]);
let formattedDob: string | null = null;
@@
profile: {
userId: dbUser.userId,
username: dbUser.username,
firstName: dbUser.firstName,
lastName: dbUser.lastName,
middleName: dbUser.middleName || '',
email: dbUser.email || '',
mobile: dbUser.mobile?.toString() || '',
mobile_country_code: dbUser.mobile_country_code || '',
+ referLink: referLink ?? undefined,
dob: formattedDob,
gender: dbUser.gender,🧰 Tools
🪛 ESLint
[error] 2009-2009: Replace 'Referral·attribution·failed' with "Referral·attribution·failed"
(prettier/prettier)
🤖 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/adapters/postgres/user-adapter.ts` around lines 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.
| } | ||
| } | ||
| } | ||
| const referLink = await this.referralsService.getUserReferLink(user.userId); |
There was a problem hiding this comment.
Add error handling around referrals service call.
The call to getUserReferLink lacks error handling. If this service call throws, it will break the entire Elasticsearch user document build. Other external service calls in this method use try-catch blocks for resilience.
🛡️ Proposed fix to add error handling
- const referLink = await this.referralsService.getUserReferLink(user.userId);
+ let referLink = null;
+ try {
+ referLink = await this.referralsService.getUserReferLink(user.userId);
+ } catch (error) {
+ LoggerUtil.warn('Failed to fetch user refer link:', error);
+ }📝 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.
| const referLink = await this.referralsService.getUserReferLink(user.userId); | |
| let referLink = null; | |
| try { | |
| referLink = await this.referralsService.getUserReferLink(user.userId); | |
| } catch (error) { | |
| LoggerUtil.warn('Failed to fetch user refer link:', error); | |
| } |
🤖 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 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.
| import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; | ||
| import { IsArray, IsEmail, IsEnum, IsOptional, IsString, MaxLength } from 'class-validator'; | ||
| import { | ||
| ReferralEntityStatus, | ||
| ReferralEntitySubType, | ||
| ReferralEntityType, | ||
| } from '../referrals.types'; | ||
|
|
||
| export class CreateReferralEntityDto { | ||
| @ApiPropertyOptional({ description: 'Custom slug (any format accepted; will be normalized to lowercase a-z0-9_). If omitted, auto-generated.' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| @MaxLength(100) | ||
| slug?: string; | ||
|
|
||
| @ApiProperty({ description: 'First name (or full org/university name)' }) | ||
| @IsString() | ||
| @MaxLength(255) | ||
| firstName: string; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Last name (optional for org/university)' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| @MaxLength(255) | ||
| lastName?: string; | ||
|
|
||
| @ApiProperty({ enum: ReferralEntityType }) | ||
| @IsEnum(ReferralEntityType) | ||
| type: ReferralEntityType; | ||
|
|
||
| @ApiProperty({ enum: ReferralEntitySubType }) | ||
| @IsEnum(ReferralEntitySubType) | ||
| subType: ReferralEntitySubType; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Region (optional)' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| @MaxLength(100) | ||
| region?: string; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Linked internal entity UUID (optional)' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| linkedEntityId?: string; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Primary contact email (optional)' }) | ||
| @IsOptional() | ||
| @IsEmail() | ||
| @MaxLength(255) | ||
| contactEmail?: string; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Additional emails (array of email strings)', type: [String] }) | ||
| @IsOptional() | ||
| @IsArray() | ||
| @IsEmail({}, { each: true }) | ||
| additionalEmails?: string[]; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Country (optional)' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| @MaxLength(100) | ||
| country?: string; | ||
|
|
||
| @ApiPropertyOptional({ enum: ReferralEntityStatus, default: ReferralEntityStatus.ACTIVE }) | ||
| @IsOptional() | ||
| @IsEnum(ReferralEntityStatus) | ||
| status?: ReferralEntityStatus; | ||
| } |
There was a problem hiding this comment.
Fix formatting to use double quotes.
The file uses single quotes throughout, which violates the project's Prettier configuration. Run Prettier to auto-format all imports, decorators, and string literals.
As per coding guidelines, the code should adhere to the Google JavaScript style guide and project formatting standards.
🧰 Tools
🪛 ESLint
[error] 1-1: Replace '@nestjs/swagger' with "@nestjs/swagger"
(prettier/prettier)
[error] 2-2: Replace ·IsArray,·IsEmail,·IsEnum,·IsOptional,·IsString,·MaxLength·}·from·'class-validator' with ⏎··IsArray,⏎··IsEmail,⏎··IsEnum,⏎··IsOptional,⏎··IsString,⏎··MaxLength,⏎}·from·"class-validator"
(prettier/prettier)
[error] 7-7: Replace '../referrals.types' with "../referrals.types"
(prettier/prettier)
[error] 10-10: Replace ·description:·'Custom·slug·(any·format·accepted;·will·be·normalized·to·lowercase·a-z0-9_).·If·omitted,·auto-generated.' with ⏎····description:⏎······"Custom·slug·(any·format·accepted;·will·be·normalized·to·lowercase·a-z0-9_).·If·omitted,·auto-generated.",⏎·
(prettier/prettier)
[error] 16-16: Replace 'First·name·(or·full·org/university·name)' with "First·name·(or·full·org/university·name)"
(prettier/prettier)
[error] 21-21: Replace ·description:·'Last·name·(optional·for·org/university)' with ⏎····description:·"Last·name·(optional·for·org/university)",⏎·
(prettier/prettier)
[error] 35-35: Replace 'Region·(optional)' with "Region·(optional)"
(prettier/prettier)
[error] 41-41: Replace ·description:·'Linked·internal·entity·UUID·(optional)' with ⏎····description:·"Linked·internal·entity·UUID·(optional)",⏎·
(prettier/prettier)
[error] 46-46: Replace 'Primary·contact·email·(optional)' with "Primary·contact·email·(optional)"
(prettier/prettier)
[error] 52-52: Replace ·description:·'Additional·emails·(array·of·email·strings)',·type:·[String] with ⏎····description:·"Additional·emails·(array·of·email·strings)",⏎····type:·[String],⏎·
(prettier/prettier)
[error] 58-58: Replace 'Country·(optional)' with "Country·(optional)"
(prettier/prettier)
[error] 64-64: Replace ·enum:·ReferralEntityStatus,·default:·ReferralEntityStatus.ACTIVE with ⏎····enum:·ReferralEntityStatus,⏎····default:·ReferralEntityStatus.ACTIVE,⏎·
(prettier/prettier)
🤖 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/dto/create-referral-entity.dto.ts` around lines 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.
| import { ApiProperty } from '@nestjs/swagger'; | ||
| import { IsNotEmpty, IsString } from 'class-validator'; | ||
|
|
||
| export class ImportReferralsDto { | ||
| @ApiProperty({ | ||
| description: | ||
| 'CSV content with header: firstName,lastName,type,subType,region,contactEmail,country', | ||
| }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| csv: string; | ||
| } | ||
|
|
There was a problem hiding this comment.
Fix formatting to use double quotes.
The file uses single quotes throughout, which violates the project's Prettier configuration. Run Prettier to auto-format.
As per coding guidelines, the code should adhere to the Google JavaScript style guide and project formatting standards.
🧰 Tools
🪛 ESLint
[error] 1-1: Replace '@nestjs/swagger' with "@nestjs/swagger"
(prettier/prettier)
[error] 2-2: Replace 'class-validator' with "class-validator"
(prettier/prettier)
[error] 7-7: Replace 'CSV·content·with·header:·firstName,lastName,type,subType,region,contactEmail,country' with "CSV·content·with·header:·firstName,lastName,type,subType,region,contactEmail,country"
(prettier/prettier)
🤖 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/dto/import-referrals.dto.ts` around lines 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.
| import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; | ||
| import { Type } from 'class-transformer'; | ||
| import { IsArray, IsEnum, IsNumber, IsObject, IsOptional, IsString, Min } from 'class-validator'; | ||
| import { ReferralEntitySubType, ReferralEntityType } from '../referrals.types'; | ||
|
|
||
| export class ReferralFiltersDto { | ||
| @ApiPropertyOptional({ enum: ReferralEntityType, description: 'Filter by referral type' }) | ||
| @IsOptional() | ||
| @IsEnum(ReferralEntityType) | ||
| type?: ReferralEntityType; | ||
|
|
||
| @ApiPropertyOptional({ enum: ReferralEntitySubType, description: 'Filter by referral sub-type' }) | ||
| @IsOptional() | ||
| @IsEnum(ReferralEntitySubType) | ||
| subType?: ReferralEntitySubType; | ||
|
|
||
| @ApiPropertyOptional({ type: String, description: 'Search across firstName, lastName, contactEmail, and slug' }) | ||
| @IsOptional() | ||
| @IsString() | ||
| search?: string; | ||
|
|
||
| @ApiPropertyOptional({ type: [String], description: 'Filter by regions' }) | ||
| @IsOptional() | ||
| @IsArray() | ||
| @IsString({ each: true }) | ||
| regions?: string[]; | ||
|
|
||
| @ApiPropertyOptional({ type: [String], description: 'Filter by countries' }) | ||
| @IsOptional() | ||
| @IsArray() | ||
| @IsString({ each: true }) | ||
| countries?: string[]; | ||
| } | ||
|
|
||
| export class ListReferralsDto { | ||
| @ApiPropertyOptional({ description: 'Number of records to return', default: 10 }) | ||
| @IsOptional() | ||
| @Type(() => Number) | ||
| @IsNumber() | ||
| @Min(1) | ||
| limit?: number; | ||
|
|
||
| @ApiPropertyOptional({ description: 'Number of records to skip', default: 0 }) | ||
| @IsOptional() | ||
| @Type(() => Number) | ||
| @IsNumber() | ||
| @Min(0) | ||
| offset?: number; | ||
|
|
||
| @ApiPropertyOptional({ type: ReferralFiltersDto, description: 'Filters to apply' }) | ||
| @IsOptional() | ||
| @IsObject() | ||
| @Type(() => ReferralFiltersDto) | ||
| filters?: ReferralFiltersDto; | ||
| } |
There was a problem hiding this comment.
Fix formatting to use double quotes.
The file uses single quotes throughout, which violates the project's Prettier configuration. Run Prettier to auto-format.
As per coding guidelines, the code should adhere to the Google JavaScript style guide and project formatting standards.
🧰 Tools
🪛 ESLint
[error] 1-1: Replace '@nestjs/swagger' with "@nestjs/swagger"
(prettier/prettier)
[error] 2-2: Replace 'class-transformer' with "class-transformer"
(prettier/prettier)
[error] 3-3: Replace ·IsArray,·IsEnum,·IsNumber,·IsObject,·IsOptional,·IsString,·Min·}·from·'class-validator' with ⏎··IsArray,⏎··IsEnum,⏎··IsNumber,⏎··IsObject,⏎··IsOptional,⏎··IsString,⏎··Min,⏎}·from·"class-validator"
(prettier/prettier)
[error] 4-4: Replace '../referrals.types' with "../referrals.types"
(prettier/prettier)
[error] 7-7: Replace ·enum:·ReferralEntityType,·description:·'Filter·by·referral·type' with ⏎····enum:·ReferralEntityType,⏎····description:·"Filter·by·referral·type",⏎·
(prettier/prettier)
[error] 12-12: Replace ·enum:·ReferralEntitySubType,·description:·'Filter·by·referral·sub-type' with ⏎····enum:·ReferralEntitySubType,⏎····description:·"Filter·by·referral·sub-type",⏎·
(prettier/prettier)
[error] 17-17: Replace ·type:·String,·description:·'Search·across·firstName,·lastName,·contactEmail,·and·slug' with ⏎····type:·String,⏎····description:·"Search·across·firstName,·lastName,·contactEmail,·and·slug",⏎·
(prettier/prettier)
[error] 22-22: Replace 'Filter·by·regions' with "Filter·by·regions"
(prettier/prettier)
[error] 28-28: Replace 'Filter·by·countries' with "Filter·by·countries"
(prettier/prettier)
[error] 36-36: Replace ·description:·'Number·of·records·to·return',·default:·10 with ⏎····description:·"Number·of·records·to·return",⏎····default:·10,⏎·
(prettier/prettier)
[error] 43-43: Replace 'Number·of·records·to·skip' with "Number·of·records·to·skip"
(prettier/prettier)
[error] 50-50: Replace ·type:·ReferralFiltersDto,·description:·'Filters·to·apply' with ⏎····type:·ReferralFiltersDto,⏎····description:·"Filters·to·apply",⏎·
(prettier/prettier)
🤖 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/dto/list-referrals.dto.ts` around lines 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.
| if (dto.firstName !== undefined) entity.firstName = dto.firstName; | ||
| if (dto.lastName !== undefined) entity.lastName = dto.lastName ?? null; | ||
| if (dto.type !== undefined) entity.type = dto.type; | ||
| if (dto.subType !== undefined) entity.subType = dto.subType; |
There was a problem hiding this comment.
Internal-email validation is skipped when type changes without contactEmail.
If dto.type becomes INTERNAL and dto.contactEmail is omitted, no existence check runs for entity.contactEmail. Validate the effective contact email whenever effective type is INTERNAL.
Suggested fix
+ const effectiveType = dto.type ?? entity.type;
+ const effectiveContactEmail = (dto.contactEmail ?? entity.contactEmail)?.toLowerCase().trim();
+ 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`
+ );
+ }
+ }Also applies to: 196-207
🤖 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 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.
| const header = lines[0].split(',').map((h) => h.trim()); | ||
| const idx = (name: string) => header.findIndex((h) => h.toLowerCase() === name.toLowerCase()); | ||
|
|
||
| const iFirst = idx('firstName'); | ||
| const iLast = idx('lastName'); | ||
| const iType = idx('type'); | ||
| const iSubType = idx('subType'); | ||
| const iRegion = idx('region'); | ||
| const iEmail = idx('contactEmail'); | ||
| const iCountry = idx('country'); | ||
|
|
||
| if (iFirst < 0 || iType < 0 || iSubType < 0) { | ||
| throw new BadRequestException('CSV header must include at least: firstName,type,subType'); | ||
| } | ||
|
|
||
| const created: any[] = []; | ||
| const errors: any[] = []; | ||
|
|
||
| for (let rowIdx = 1; rowIdx < lines.length; rowIdx++) { | ||
| const cols = lines[rowIdx].split(',').map((c) => c.trim()); | ||
| const firstName = cols[iFirst] ?? ''; |
There was a problem hiding this comment.
CSV parsing is incorrect for quoted fields.
split(",") breaks valid CSV rows containing quoted commas, causing field shifts and wrong attribution/import failures. This path needs a real CSV parser behavior (quote + escape aware).
🧰 Tools
🪛 ESLint
[error] 277-277: Replace ',' with ","
(prettier/prettier)
[error] 278-278: Insert ⏎·····
(prettier/prettier)
[error] 280-280: Replace 'firstName' with "firstName"
(prettier/prettier)
[error] 281-281: Replace 'lastName' with "lastName"
(prettier/prettier)
[error] 282-282: Replace 'type' with "type"
(prettier/prettier)
[error] 283-283: Replace 'subType' with "subType"
(prettier/prettier)
[error] 284-284: Replace 'region' with "region"
(prettier/prettier)
[error] 285-285: Replace 'contactEmail' with "contactEmail"
(prettier/prettier)
[error] 286-286: Replace 'country' with "country"
(prettier/prettier)
[error] 289-289: Replace 'CSV·header·must·include·at·least:·firstName,type,subType' with ⏎········"CSV·header·must·include·at·least:·firstName,type,subType"⏎······
(prettier/prettier)
[error] 296-296: Replace ',' with ","
(prettier/prettier)
[error] 297-297: Replace '' with ""
(prettier/prettier)
🤖 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 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.
| const batchSize = parseInt(this.configService.get('REFERRAL_BULK_BATCH_SIZE') || '100', 10); | ||
| const created: any[] = []; | ||
| const errors: any[] = []; | ||
|
|
||
| for (let i = 0; i < dtos.length; i += batchSize) { | ||
| const batch = dtos.slice(i, i + batchSize); |
There was a problem hiding this comment.
Guard REFERRAL_BULK_BATCH_SIZE against invalid values.
If parsed batch size is 0, negative, or NaN, the loop can become non-terminating (i += batchSize). Enforce a positive integer fallback before entering the loop.
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;📝 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.
| const batchSize = parseInt(this.configService.get('REFERRAL_BULK_BATCH_SIZE') || '100', 10); | |
| const created: any[] = []; | |
| const errors: any[] = []; | |
| for (let i = 0; i < dtos.length; i += batchSize) { | |
| const batch = dtos.slice(i, i + batchSize); | |
| const parsedBatchSize = Number.parseInt( | |
| this.configService.get("REFERRAL_BULK_BATCH_SIZE") || "100", | |
| 10 | |
| ); | |
| const batchSize = | |
| Number.isInteger(parsedBatchSize) && parsedBatchSize > 0 | |
| ? parsedBatchSize | |
| : 100; | |
| const created: any[] = []; | |
| const errors: any[] = []; | |
| for (let i = 0; i < dtos.length; i += batchSize) { | |
| const batch = dtos.slice(i, i + batchSize); |
🧰 Tools
🪛 ESLint
[error] 340-340: Replace this.configService.get('REFERRAL_BULK_BATCH_SIZE')·||·'100',·10 with ⏎······this.configService.get("REFERRAL_BULK_BATCH_SIZE")·||·"100",⏎······10⏎····
(prettier/prettier)
🤖 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 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.
| export enum ReferralEntityType { | ||
| EXTERNAL = 'external', | ||
| INTERNAL = 'internal', | ||
| } | ||
|
|
||
| export enum ReferralEntitySubType { | ||
| ORGANISATION = 'organisation', | ||
| UNIVERSITY = 'university', | ||
| ALUMNI = 'alumni', | ||
| } | ||
|
|
||
| export enum ReferralEntityStatus { | ||
| ACTIVE = 'active', | ||
| INACTIVE = 'inactive', | ||
| } | ||
|
|
There was a problem hiding this comment.
Fix formatting to use double quotes.
The code uses single quotes throughout, which violates the project's Prettier configuration. Run Prettier to auto-format.
🎨 Proposed fix
export enum ReferralEntityType {
- EXTERNAL = 'external',
- INTERNAL = 'internal',
+ EXTERNAL = "external",
+ INTERNAL = "internal",
}
export enum ReferralEntitySubType {
- ORGANISATION = 'organisation',
- UNIVERSITY = 'university',
- ALUMNI = 'alumni',
+ ORGANISATION = "organisation",
+ UNIVERSITY = "university",
+ ALUMNI = "alumni",
}
export enum ReferralEntityStatus {
- ACTIVE = 'active',
- INACTIVE = 'inactive',
+ ACTIVE = "active",
+ INACTIVE = "inactive",
}As per coding guidelines, the code should adhere to the Google JavaScript style guide and project formatting standards.
📝 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.
| export enum ReferralEntityType { | |
| EXTERNAL = 'external', | |
| INTERNAL = 'internal', | |
| } | |
| export enum ReferralEntitySubType { | |
| ORGANISATION = 'organisation', | |
| UNIVERSITY = 'university', | |
| ALUMNI = 'alumni', | |
| } | |
| export enum ReferralEntityStatus { | |
| ACTIVE = 'active', | |
| INACTIVE = 'inactive', | |
| } | |
| export enum ReferralEntityType { | |
| EXTERNAL = "external", | |
| INTERNAL = "internal", | |
| } | |
| export enum ReferralEntitySubType { | |
| ORGANISATION = "organisation", | |
| UNIVERSITY = "university", | |
| ALUMNI = "alumni", | |
| } | |
| export enum ReferralEntityStatus { | |
| ACTIVE = "active", | |
| INACTIVE = "inactive", | |
| } |
🧰 Tools
🪛 ESLint
[error] 2-2: Replace 'external' with "external"
(prettier/prettier)
[error] 3-3: Replace 'internal' with "internal"
(prettier/prettier)
[error] 7-7: Replace 'organisation' with "organisation"
(prettier/prettier)
[error] 8-8: Replace 'university' with "university"
(prettier/prettier)
[error] 9-9: Replace 'alumni' with "alumni"
(prettier/prettier)
[error] 13-13: Replace 'active' with "active"
(prettier/prettier)
[error] 14-14: Replace 'inactive' with "inactive"
(prettier/prettier)
🤖 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.types.ts` around lines 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.
| export const DEFAULT_REFERRAL_BASE_URL = () => | ||
| `${process.env.FRONTEND_URL}registration`; | ||
|
|
||
| export function buildReferLink(slug: string, baseUrl?: string) { | ||
| const base = baseUrl ?? DEFAULT_REFERRAL_BASE_URL(); | ||
| const s = String(slug || '').trim(); | ||
| return `${base}?refer=${encodeURIComponent(s)}`; |
There was a problem hiding this comment.
Harden refer-link URL construction to avoid malformed links.
The current concatenation can produce invalid links (e.g., missing FRONTEND_URL, missing slash, existing query string). Build the URL via URL/URLSearchParams and fail fast if base URL is unset.
Suggested fix
-export const DEFAULT_REFERRAL_BASE_URL = () =>
- `${process.env.FRONTEND_URL}registration`;
+export const DEFAULT_REFERRAL_BASE_URL = () => {
+ const frontendUrl = process.env.FRONTEND_URL;
+ if (!frontendUrl) {
+ throw new Error("FRONTEND_URL is not configured");
+ }
+ return new URL("registration", frontendUrl).toString();
+};
export function buildReferLink(slug: string, baseUrl?: string) {
- const base = baseUrl ?? DEFAULT_REFERRAL_BASE_URL();
- const s = String(slug || "").trim();
- return `${base}?refer=${encodeURIComponent(s)}`;
+ const base = new URL(baseUrl ?? DEFAULT_REFERRAL_BASE_URL());
+ const s = String(slug || "").trim();
+ base.searchParams.set("refer", s);
+ return base.toString();
}🧰 Tools
🪛 ESLint
[error] 9-9: Replace '' with ""
(prettier/prettier)
🤖 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/utils/referral-slug.util.ts` around lines 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.



Summary by CodeRabbit
Release Notes