Skip to content

Conversation

@arati-tekdi
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Oct 7, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Certificate template management: create, edit, fetch, and list templates.
    • Map templates to courses and list course-template mappings.
    • Retrieve user certificates with pagination and filtering (by user, course, template, type).
  • Improvements

    • Increased request body limits (up to 500MB) for JSON and form data, enabling larger uploads.
    • Added validation on tenant identifiers for certificate endpoints.
    • Expanded public routes to cover new certificate-related operations.

Walkthrough

Adds new certificate-related endpoints, DTOs, entities, and service logic for templates and course-template mappings. Expands routing, increases request body size limits, updates the certificate module to include new repositories, and implements CRUD-style flows and listings with filtering, pagination, and tenant validation.

Changes

Cohort / File(s) Summary
Routing updates
src/constant/routeConfig.js
Added POST routes under /interface/v1/tracking/certificate/... for template, course-template, course-template/list, templates-list, and get-template; inSequence: false, orchestrated: false, target package shiksha-tracking.
Server middleware
src/main.ts
Registered body-parser JSON and URL-encoded middleware with 500mb limits before Swagger setup.
Certificate controller
src/modules/certificate/certificate.contoller.ts
Replaced old template API with new endpoints: createTemplate, addCourseTemplate, getTemplate, getTemplates, getCourseTemplateList, editTemplate. Enforces TenantID UUID via headers. Added DTO and isUUID imports.
Certificate module wiring
src/modules/certificate/certificate.module.ts
Updated TypeOrmModule.forFeature to include UserCourseCertificate, Templates, CourseTemplate.
Certificate service logic
src/modules/certificate/certificate.service.ts
Injected repositories for Templates and CourseTemplate. Implemented createTemplate, saveTemplate, addCourseTemplate, getTemplate, getTemplates, getCourseTemplateList, editTemplate, and getUserCertificates with filtering, pagination, external service integration, and local persistence.
DTOs for certificate flows
src/modules/certificate/dto/... (add-course-template-dto.ts, course-template-list-dto.ts, create-template-dto.ts, edit-template-dto.ts, get-template-dto.ts, get-templates-dto.ts, get-user-certificates-dto.ts)
Added DTOs with validation and transformation for create/edit template, add course-template, get template(s), list course-templates, and get user certificates with filters and pagination; Swagger annotations where provided.
Entities
src/modules/certificate/entities/templates.ts, src/modules/certificate/entities/course_templates.ts
Added TypeORM entities Templates and CourseTemplate with UUID primary keys, metadata fields, timestamps, and uniqueness on course_templates.contextId.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as CertificateController
  participant Svc as CertificateService
  participant Ext as External Template Service
  participant DB as DB (Templates, CourseTemplate)

  rect rgba(200,230,255,0.3)
  note over Client,API: Create Template
  Client->>API: POST /certificate/template {template,title,template_type?,userId} + TenantID
  API->>Svc: createTemplate(template,userId,title,res,req)
  Svc->>Ext: POST createTemplate(template,...)
  Ext-->>Svc: templateId, type
  Svc->>DB: saveTemplate(templateId, template_type/type, title, userId, req)
  DB-->>Svc: upsert OK
  Svc-->>API: result with template metadata
  API-->>Client: 200 payload
  end

  rect rgba(220,255,220,0.3)
  note over Client,API: Add Course-Template Mapping
  Client->>API: POST /certificate/course-template {templateId,contextId,userId}
  API->>Svc: addCourseTemplate(templateId,contextId,userId,res,req)
  Svc->>DB: upsert course_templates by contextId
  DB-->>Svc: OK
  Svc-->>API: mapping result
  API-->>Client: 200 payload
  end

  rect rgba(255,240,200,0.3)
  note over Client,API: Get Templates (list)
  Client->>API: POST /certificate/templates-list {filters,limit,offset}
  API->>Svc: getTemplates(filters,res)
  Svc->>DB: SELECT templates LEFT JOIN course_templates + filters + pagination
  DB-->>Svc: rows
  Svc-->>API: list + counts
  API-->>Client: 200 payload
  end

  rect rgba(255,220,220,0.3)
  note over Client,API: Get Template (detail)
  Client->>API: POST /certificate/get-template {templateId}
  API->>Svc: getTemplate(templateId,res)
  Svc->>Ext: GET template by id
  Ext-->>Svc: template data
  Svc->>DB: fetch local template + course-template by templateId
  DB-->>Svc: local data
  Svc-->>API: merged response
  API-->>Client: 200 payload
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request lacks any description text, so there is no contextual information summarizing the objectives or the scope of the introduced changes. Without a description, reviewers must infer the intent from the diff alone, which reduces clarity and slows the review process. Please add a brief description that summarizes the new dynamic template endpoints, DTOs, entity updates, and middleware adjustments to clarify the purpose and scope of this PR for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the main purpose of the changeset by referring to the addition of dynamic template functionality and the assignment of templates to courses, which aligns directly with the new endpoints and service logic introduced in the PR. It clearly signals the primary feature enhancements without extraneous detail. Reviewers scanning the history will understand the key focus from the title alone. Therefore it meets the criteria for a concise and specific summary of the main change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 7, 2025

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
src/modules/certificate/certificate.contoller.ts (1)

1-11: Missing import for Request type.

The Request type is used in method parameters (lines 68, 116, 140) but is not imported from express. This will cause TypeScript compilation errors.

Apply this diff to add the missing import:

 import {
   Controller,
   Post,
   Body,
   Res,
   Headers,
   Req,
   StreamableFile,
   BadRequestException,
   Put,
 } from '@nestjs/common';
 import {
   ApiInternalServerErrorResponse,
   ApiBadRequestResponse,
   ApiNotFoundResponse,
   ApiOkResponse,
 } from '@nestjs/swagger';
-import { Response } from 'express';
+import { Request, Response } from 'express';
🧹 Nitpick comments (7)
src/modules/certificate/dto/add-course-template-dto.ts (1)

4-14: Consider using @IsUUID() for ID fields.

If templateId, contextId, and userId represent UUIDs (common for entity IDs), use @IsUUID() instead of @IsString() for stricter validation and better error messages.

Apply this diff:

-import { IsNotEmpty, IsString } from 'class-validator';
+import { IsNotEmpty, IsUUID } from 'class-validator';

 export class AddCourseTemplateDto {
   @IsNotEmpty()
-  @IsString()
+  @IsUUID()
   templateId: string;

   @IsNotEmpty()
-  @IsString()
+  @IsUUID()
   contextId: string;

   @IsNotEmpty()
-  @IsString()
+  @IsUUID()
   userId: string;
 }
src/modules/certificate/dto/edit-template-dto.ts (1)

12-24: Consider consolidating type and template_type fields.

Both type (required) and template_type (optional) use identical @IsIn(['certificate', 'badge']) validation. This duplication might be intentional for backward compatibility, but consider whether both fields are necessary.

src/modules/certificate/dto/course-template-list-dto.ts (1)

13-17: Add maximum limit validation.

The limit field lacks an upper bound, potentially allowing excessively large queries that could impact performance or enable DoS attacks.

Apply this diff:

-import { IsOptional, IsString, IsNumber, Min } from 'class-validator';
+import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator';

   @IsOptional()
   @Type(() => Number)
   @IsNumber()
   @Min(1)
+  @Max(100)
   limit?: number = 10;
src/modules/certificate/certificate.contoller.ts (4)

161-161: Consider using GET instead of POST for retrieval operations.

The endpoints getTemplate, getTemplates, and getCourseTemplateList use POST but perform read-only operations. REST conventions recommend GET for retrieval operations.

If you need to send complex filter/pagination parameters, consider:

  1. Using GET with query parameters
  2. Using GET with a request body (though non-standard, some frameworks support it)
  3. If POST is intentional due to large payloads, document this decision

Example refactor for getTemplate:

- @Post('get-template')
+ @Get('template/:templateId')
  async getTemplate(
-   @Body() getTemplateDto: GetTemplateDto,
+   @Param('templateId') templateId: string,
    @Res() response: Response,
-   @Headers() headers,
+   @TenantId() tenantId: string,
  ) {
-   const tenantId = headers['tenantid'];
-   if (!tenantId || !isUUID(tenantId)) {
-     throw new BadRequestException('Please add valid Tenant ID');
-   }
    return this.certificateService.getTemplate(
-     getTemplateDto.templateId,
+     templateId,
+     tenantId,
      response,
    );
  }

For list endpoints with filters, GET with query parameters is still preferable:

- @Post('templates-list')
+ @Get('templates')
  async getTemplates(
-   @Body() getTemplatesDto: GetTemplatesDto,
+   @Query() getTemplatesDto: GetTemplatesDto,
    @Res() response: Response,
-   @Headers() headers,
+   @TenantId() tenantId: string,
  ) {
    return this.certificateService.getTemplates(
      getTemplatesDto,
+     tenantId,
      response,
    );
  }

Also applies to: 181-181, 198-198


117-117: Add type annotation for headers parameter.

The headers parameter lacks a type annotation, reducing type safety. Consider adding at least any or preferably a more specific type.

  async createTemplate(
    @Body() createTemplateDto: CreateTemplateDto,
    @Res() response: Response,
    @Req() request: Request,
-   @Headers() headers,
+   @Headers() headers: Record<string, string>,
  ) {

Better approach: Use the custom decorator suggested earlier (@TenantId()) to eliminate the need for the headers parameter entirely.

Also applies to: 141-141, 165-165, 185-185, 202-202, 223-223


123-129: Pass entire DTOs to service methods for better encapsulation.

Several endpoints extract individual fields from DTOs before passing them to service methods (createTemplate, addCourseTemplate, getTemplate, editTemplate). This approach:

  • Tightly couples the controller to service implementation details
  • Makes it harder to add new DTO fields without updating multiple locations
  • Is inconsistent with getTemplates and getCourseTemplateList which correctly pass entire DTOs

Refactor service methods to accept DTOs directly:

  async createTemplate(
    @Body() createTemplateDto: CreateTemplateDto,
    @Res() response: Response,
    @Req() request: Request,
+   @TenantId() tenantId: string,
  ) {
    return this.certificateService.createTemplate(
-     createTemplateDto.template,
-     createTemplateDto.userId,
-     createTemplateDto.title,
+     createTemplateDto,
+     tenantId,
      response,
      request,
    );
  }

Then update the service method signature:

// In certificate.service.ts
async createTemplate(
  createTemplateDto: CreateTemplateDto,
  tenantId: string,
  response: Response,
  request: Request,
) {
  // Access fields via createTemplateDto.template, createTemplateDto.userId, etc.
}

Also applies to: 147-153, 171-174, 229-236


121-121: Improve error message specificity.

The error message "Please add valid Tenant ID" doesn't distinguish between:

  • Missing tenant ID header
  • Invalid UUID format

Consider a more specific message:

  const tenantId = headers['tenantid'];
  if (!tenantId || !isUUID(tenantId)) {
-   throw new BadRequestException('Please add valid Tenant ID');
+   throw new BadRequestException(
+     !tenantId 
+       ? 'Missing required header: tenantid'
+       : 'Invalid tenantid header: must be a valid UUID'
+   );
  }

Or if using the suggested custom decorator/guard, improve the error message there.

Also applies to: 145-145, 169-169, 189-189, 206-206, 227-227

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 842f716 and d7d2971.

📒 Files selected for processing (14)
  • src/constant/routeConfig.js (2 hunks)
  • src/main.ts (2 hunks)
  • src/modules/certificate/certificate.contoller.ts (3 hunks)
  • src/modules/certificate/certificate.module.ts (1 hunks)
  • src/modules/certificate/certificate.service.ts (3 hunks)
  • src/modules/certificate/dto/add-course-template-dto.ts (1 hunks)
  • src/modules/certificate/dto/course-template-list-dto.ts (1 hunks)
  • src/modules/certificate/dto/create-template-dto.ts (1 hunks)
  • src/modules/certificate/dto/edit-template-dto.ts (1 hunks)
  • src/modules/certificate/dto/get-template-dto.ts (1 hunks)
  • src/modules/certificate/dto/get-templates-dto.ts (1 hunks)
  • src/modules/certificate/dto/get-user-certificates-dto.ts (1 hunks)
  • src/modules/certificate/entities/course_templates.ts (1 hunks)
  • src/modules/certificate/entities/templates.ts (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
src/constant/routeConfig.js

[error] 457-457: Expected a statement but instead found ','.

Expected a statement here.

(parse)


[error] 459-459: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 466-466: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 469-469: Expected a statement but instead found ']'.

Expected a statement here.

(parse)


[error] 470-470: Expected a statement but instead found ','.

Expected a statement here.

(parse)


[error] 472-472: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 479-479: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 482-482: Expected a statement but instead found ']'.

Expected a statement here.

(parse)


[error] 483-483: Expected a statement but instead found ','.

Expected a statement here.

(parse)


[error] 485-485: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 492-492: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 495-495: Expected a statement but instead found ']'.

Expected a statement here.

(parse)


[error] 496-496: Expected a statement but instead found ','.

Expected a statement here.

(parse)


[error] 498-498: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 505-505: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 508-508: Expected a statement but instead found ']'.

Expected a statement here.

(parse)


[error] 509-509: Expected a statement but instead found ','.

Expected a statement here.

(parse)


[error] 511-511: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 518-518: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 521-521: Expected a statement but instead found ']'.

Expected a statement here.

(parse)

🔇 Additional comments (3)
src/modules/certificate/certificate.module.ts (1)

8-18: LGTM!

The entity imports and TypeOrmModule registration follow standard NestJS/TypeORM patterns.

src/constant/routeConfig.js (1)

458-522: LGTM!

The new certificate template routes follow the existing pattern and structure consistently. The static analysis warnings appear to be false positives - the JSON structure is valid.

src/modules/certificate/dto/get-template-dto.ts (1)

4-6: Leave @IsString() for templateId—the templateId column in Templates is defined as varchar (not a UUID type), so switching to @IsUUID() would reject any non-UUID strings.

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ValidationPipe, BadRequestException } from '@nestjs/common';
import { json, urlencoded } from 'express';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused imports.

The json and urlencoded imports from express are no longer used after introducing body-parser middleware.

Apply this diff:

-import { json, urlencoded } from 'express';
📝 Committable suggestion

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

Suggested change
import { json, urlencoded } from 'express';
🤖 Prompt for AI Agents
In src/main.ts around line 5, the imports "json" and "urlencoded" from 'express'
are unused after switching to body-parser; remove these unused imports by
deleting them from the import statement (or replace the import with only the
remaining used identifiers) so the file no longer imports json and urlencoded.

Comment on lines +25 to +26
app.use(bodyParser.json({ limit: '500mb' }));
app.use(bodyParser.urlencoded({ limit: '500mb', extended: true }));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Reduce the payload size limit and remove duplicate middleware.

The 500mb limit is excessive and poses security risks (DoS attacks, resource exhaustion). Additionally, lines 44-45 register express's built-in middleware inside the listen callback, which is ineffective (middleware registered after listen is never invoked) and redundant with the body-parser middleware here.

Apply this diff to set a reasonable limit and remove the ineffective duplicate:

-  app.use(bodyParser.json({ limit: '500mb' }));
-  app.use(bodyParser.urlencoded({ limit: '500mb', extended: true }));
+  app.use(bodyParser.json({ limit: '10mb' }));
+  app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));

Then remove lines 44-45:

   await app.listen(3000, () => {
     console.log(`Server is running on port 3000`);
-    app.use(json({ limit: '30mb' }));
-    app.use(urlencoded({ extended: true, limit: '30mb' }));
   });
📝 Committable suggestion

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

Suggested change
app.use(bodyParser.json({ limit: '500mb' }));
app.use(bodyParser.urlencoded({ limit: '500mb', extended: true }));
// register body-parser with a safer payload limit
app.use(bodyParser.json({ limit: '10mb' }));
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
// start the server (remove redundant middleware from here)
await app.listen(3000, () => {
console.log(`Server is running on port 3000`);
});
🤖 Prompt for AI Agents
In src/main.ts around lines 25-26, the app is using bodyParser with an excessive
500mb limit and duplicate middleware is later registered ineffectively inside
the listen callback (lines 44-45); change the bodyParser limits to a reasonable
value such as '10mb' for both json and urlencoded, and remove the duplicate
express.json()/express.urlencoded() registrations at lines 44-45 (delete those
lines so middleware is only registered once before listen).

Comment on lines +119 to +122
const tenantId = headers['tenantid'];
if (!tenantId || !isUUID(tenantId)) {
throw new BadRequestException('Please add valid Tenant ID');
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated tenant validation logic.

The tenant validation logic is repeated identically across all six new endpoints. This violates the DRY principle and makes the code harder to maintain.

Consider extracting this logic into:

  1. A custom guard (preferred for NestJS):
// tenant-validation.guard.ts
import { Injectable, CanActivate, ExecutionContext, BadRequestException } from '@nestjs/common';
import { isUUID } from 'class-validator';

@Injectable()
export class TenantValidationGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const tenantId = request.headers['tenantid'];
    
    if (!tenantId || !isUUID(tenantId)) {
      throw new BadRequestException('Please add valid Tenant ID');
    }
    
    // Optionally attach to request for later use
    request.tenantId = tenantId;
    return true;
  }
}

Then apply the guard to endpoints:

+ @UseGuards(TenantValidationGuard)
  @Post('template')
  async createTemplate(
    @Body() createTemplateDto: CreateTemplateDto,
    @Res() response: Response,
    @Req() request: Request,
-   @Headers() headers,
  ) {
-   const tenantId = headers['tenantid'];
-   if (!tenantId || !isUUID(tenantId)) {
-     throw new BadRequestException('Please add valid Tenant ID');
-   }
    return this.certificateService.createTemplate(
      createTemplateDto.template,
      createTemplateDto.userId,
      createTemplateDto.title,
+     request.tenantId,
      response,
      request,
    );
  }
  1. Or a custom decorator for simpler extraction:
// tenant-id.decorator.ts
import { createParamDecorator, ExecutionContext, BadRequestException } from '@nestjs/common';
import { isUUID } from 'class-validator';

export const TenantId = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const tenantId = request.headers['tenantid'];
    
    if (!tenantId || !isUUID(tenantId)) {
      throw new BadRequestException('Please add valid Tenant ID');
    }
    
    return tenantId;
  },
);

Usage:

  @Post('template')
  async createTemplate(
    @Body() createTemplateDto: CreateTemplateDto,
    @Res() response: Response,
    @Req() request: Request,
-   @Headers() headers,
+   @TenantId() tenantId: string,
  ) {
-   const tenantId = headers['tenantid'];
-   if (!tenantId || !isUUID(tenantId)) {
-     throw new BadRequestException('Please add valid Tenant ID');
-   }
    return this.certificateService.createTemplate(
      createTemplateDto.template,
      createTemplateDto.userId,
      createTemplateDto.title,
+     tenantId,
      response,
      request,
    );
  }

Also applies to: 143-146, 167-170, 187-190, 204-207, 225-228

Comment on lines +119 to +129
const tenantId = headers['tenantid'];
if (!tenantId || !isUUID(tenantId)) {
throw new BadRequestException('Please add valid Tenant ID');
}
return this.certificateService.createTemplate(
createTemplateDto.template,
createTemplateDto.userId,
createTemplateDto.title,
response,
request,
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Tenant ID validated but not used in service call.

The tenant ID is validated for format but never passed to certificateService.createTemplate(). This means:

  • The validation only checks UUID format, not actual tenant context
  • Multi-tenancy is not enforced at the service layer
  • Templates might not be properly isolated by tenant

This pattern repeats across all new endpoints and could lead to data leakage or unauthorized access across tenants.

Please verify whether tenant-based isolation is implemented in the service layer:


🏁 Script executed:

#!/bin/bash
# Check if service methods accept tenantId parameter
ast-grep --pattern $'createTemplate($$$) {
  $$$
}'

# Check for tenant-related filtering in service implementations
rg -nP -A10 'createTemplate\(' src/modules/certificate/certificate.service.ts

Length of output: 1053


Enforce tenant isolation by passing tenantId

Controller validates tenantId but doesn’t forward it; service uses a static SCHEMA_ID. Update certificateService.createTemplate (signature and implementation in src/modules/certificate/certificate.service.ts) to accept tenantId and use it for tenant-specific schema operations instead of the config value.

🤖 Prompt for AI Agents
In src/modules/certificate/certificate.contoller.ts around lines 119-129, the
controller validates tenantId but does not forward it to the service; update the
call to this.certificateService.createTemplate(...) to pass the validated
tenantId as an additional argument. Then update the signature and implementation
in src/modules/certificate/certificate.service.ts (and any call sites) to accept
tenantId and use it for schema/DB operations (replace usage of static
CONFIG.SCHEMA_ID/SCHEMA constant with the passed tenantId) so all
tenant-specific operations use the provided tenantId instead of the config
value.

Comment on lines +533 to +584
let userCertQuery = this.userCourseCertificateRepository
.createQueryBuilder('uc')
.select([
'uc.usercertificateId',
'uc.userId',
'uc.contextId',
'uc.contextType',
'uc.tenantId',
'uc.certificateId',
'uc.status',
'uc.issuedOn',
'uc.createdOn',
'uc.updatedOn',
'uc.createdBy',
'uc.updatedBy',
'uc.certificate_type',
]);

// Apply filters only if they are provided
if (userIds && userIds.length > 0) {
userCertQuery = userCertQuery.andWhere('uc.userId IN (:...userIds)', {
userIds,
});
}

if (certificateIds && certificateIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificateId IN (:...certificateIds)',
{ certificateIds },
);
}

if (courseIds && courseIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.contextId IN (:...courseIds)',
{ courseIds },
);
}

if (templateIds && templateIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'ct.templateId IN (:...templateIds)',
{ templateIds },
);
}

if (certificateTypes && certificateTypes.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificate_type IN (:...certificateTypes)',
{ certificateTypes },
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix missing join before filtering by template IDs

When templateIds is provided we add AND ct.templateId IN (...), but the query never joins course_templates. Any such request currently crashes with “missing FROM-clause entry for table ‘ct’”. Join the table before referencing ct so the filter works.

-      if (templateIds && templateIds.length > 0) {
-        userCertQuery = userCertQuery.andWhere(
-          'ct.templateId IN (:...templateIds)',
-          { templateIds },
-        );
-      }
+      if (templateIds && templateIds.length > 0) {
+        userCertQuery = userCertQuery
+          .leftJoin('course_templates', 'ct', 'ct.contextId = uc.contextId')
+          .andWhere('ct.templateId IN (:...templateIds)', { templateIds });
+      }
📝 Committable suggestion

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

Suggested change
let userCertQuery = this.userCourseCertificateRepository
.createQueryBuilder('uc')
.select([
'uc.usercertificateId',
'uc.userId',
'uc.contextId',
'uc.contextType',
'uc.tenantId',
'uc.certificateId',
'uc.status',
'uc.issuedOn',
'uc.createdOn',
'uc.updatedOn',
'uc.createdBy',
'uc.updatedBy',
'uc.certificate_type',
]);
// Apply filters only if they are provided
if (userIds && userIds.length > 0) {
userCertQuery = userCertQuery.andWhere('uc.userId IN (:...userIds)', {
userIds,
});
}
if (certificateIds && certificateIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificateId IN (:...certificateIds)',
{ certificateIds },
);
}
if (courseIds && courseIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.contextId IN (:...courseIds)',
{ courseIds },
);
}
if (templateIds && templateIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'ct.templateId IN (:...templateIds)',
{ templateIds },
);
}
if (certificateTypes && certificateTypes.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificate_type IN (:...certificateTypes)',
{ certificateTypes },
);
}
// Apply filters only if they are provided
if (userIds && userIds.length > 0) {
userCertQuery = userCertQuery.andWhere('uc.userId IN (:...userIds)', {
userIds,
});
}
if (certificateIds && certificateIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificateId IN (:...certificateIds)',
{ certificateIds },
);
}
if (courseIds && courseIds.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.contextId IN (:...courseIds)',
{ courseIds },
);
}
if (templateIds && templateIds.length > 0) {
userCertQuery = userCertQuery
.leftJoin('course_templates', 'ct', 'ct.contextId = uc.contextId')
.andWhere('ct.templateId IN (:...templateIds)', { templateIds });
}
if (certificateTypes && certificateTypes.length > 0) {
userCertQuery = userCertQuery.andWhere(
'uc.certificate_type IN (:...certificateTypes)',
{ certificateTypes },
);
}
🤖 Prompt for AI Agents
In src/modules/certificate/certificate.service.ts around lines 533 to 584, the
query applies a filter on ct.templateId without joining the course_templates
table first, causing a "missing FROM-clause entry for table 'ct'"; fix by adding
a join to course_templates with alias ct (joining ct.courseId to uc.contextId or
the appropriate FK) before applying the templateIds AND condition, and scope
that join to run only when templateIds are provided (use an inner join since
you’re filtering by it).

@@ -0,0 +1,24 @@
import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The Max import is unused.

Apply this diff:

-import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator';
+import { IsOptional, IsString, IsNumber, Min } from 'class-validator';
📝 Committable suggestion

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

Suggested change
import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator';
import { IsOptional, IsString, IsNumber, Min } from 'class-validator';
🤖 Prompt for AI Agents
In src/modules/certificate/dto/course-template-list-dto.ts around line 1, the
imported symbol `Max` from 'class-validator' is not used; remove `Max` from the
import list so only IsOptional, IsString, IsNumber, and Min are imported to
clean up unused imports and avoid linting errors.

@@ -0,0 +1,18 @@
import { IsString, IsNotEmpty, IsOptional, isString } from 'class-validator';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The isString import (lowercase) is unused.

Apply this diff:

-import { IsString, IsNotEmpty, IsOptional, isString } from 'class-validator';
+import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
📝 Committable suggestion

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

Suggested change
import { IsString, IsNotEmpty, IsOptional, isString } from 'class-validator';
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
🤖 Prompt for AI Agents
In src/modules/certificate/dto/create-template-dto.ts around line 1, remove the
unused lowercase isString import from the class-validator import list; update
the import statement to only include IsString, IsNotEmpty, and IsOptional (and
run lint/compile to ensure no other usages rely on isString).

Comment on lines +16 to +17
@IsString()
userId: string;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add @isnotempty() to userId.

The userId field lacks @IsNotEmpty() validation. Since it's a required field without @IsOptional(), it should validate for non-empty values.

Apply this diff:

+  @IsNotEmpty()
   @IsString()
   userId: string;
📝 Committable suggestion

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

Suggested change
@IsString()
userId: string;
@IsNotEmpty()
@IsString()
userId: string;
🤖 Prompt for AI Agents
In src/modules/certificate/dto/create-template-dto.ts around lines 16 to 17, the
userId property has @IsString() but is missing @IsNotEmpty(); add the
@IsNotEmpty() decorator above the userId field to enforce non-empty validation
(since the field is required and not optional), ensuring class-validator will
reject empty strings.

Comment on lines +5 to +28
@ApiProperty({
description: 'Page number for pagination',
example: 1,
required: false,
default: 1
})
@IsOptional()
@IsNumber()
@Min(1)
page?: number = 1;

@ApiProperty({
description: 'Number of records per page',
example: 10,
required: false,
default: 10,
minimum: 1,
maximum: 100
})
@IsOptional()
@IsNumber()
@Min(1)
@Max(100)
limit?: number = 10;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add numeric transformation before validation

@IsNumber() runs on plain query-string values (e.g., '1', '10'). Without @Type(() => Number) (or implicit conversion), these validations fail and every request with page/limit query params is rejected. Please add the transformation decorator so the DTO accepts standard HTTP query values.

   @IsOptional()
+  @Type(() => Number)
   @IsNumber()
   @Min(1)
   page?: number = 1;
@@
   })
   @IsOptional()
+  @Type(() => Number)
   @IsNumber()
   @Min(1)
   @Max(100)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/certificate/dto/get-user-certificates-dto.ts around lines 5 to
28, the DTO applies @IsNumber() to query params but doesn't convert string query
values to numbers; add @Type(() => Number) from class-transformer above both
page and limit properties (before validation decorators) so '1'/'10' are
transformed to numbers, and update imports to include Type from
'class-transformer' if missing.

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