From 862a1ebf610b1064958fd4147d5cd6e6160e4b54 Mon Sep 17 00:00:00 2001 From: Arati Tekdi Date: Thu, 20 Feb 2025 23:06:36 +0530 Subject: [PATCH] Added certificate and user certificate module --- src/app.module.ts | 4 + .../certificate/certificate.contoller.ts | 91 +++++ src/modules/certificate/certificate.module.ts | 14 + .../certificate/certificate.service.ts | 312 ++++++++++++++++++ .../certificate/dto/issue-certificate-dto.ts | 29 ++ .../entities/user_course_certificate.ts | 45 +++ .../dto/create-user-certificate-dto.ts | 28 ++ .../user_certificate.contoller.ts | 83 +++++ .../user_certificate.module.ts | 13 + .../user_certificate.service..ts | 262 +++++++++++++++ 10 files changed, 881 insertions(+) create mode 100644 src/modules/certificate/certificate.contoller.ts create mode 100644 src/modules/certificate/certificate.module.ts create mode 100644 src/modules/certificate/certificate.service.ts create mode 100644 src/modules/certificate/dto/issue-certificate-dto.ts create mode 100644 src/modules/certificate/entities/user_course_certificate.ts create mode 100644 src/modules/user_certificate/dto/create-user-certificate-dto.ts create mode 100644 src/modules/user_certificate/user_certificate.contoller.ts create mode 100644 src/modules/user_certificate/user_certificate.module.ts create mode 100644 src/modules/user_certificate/user_certificate.service..ts diff --git a/src/app.module.ts b/src/app.module.ts index ab6ec71..c1a404a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,6 +7,8 @@ import { MemoryStore } from 'cache-manager-memory-store'; import { ConfigModule } from '@nestjs/config'; import { TrackingAssessmentModule } from 'src/modules/tracking_assessment/tracking_assessment.module'; import { TrackingContentModule } from 'src/modules/tracking_content/tracking_content.module'; +import { CertificateModule } from './modules/certificate/certificate.module'; +import { UserCertificateModule } from './modules/user_certificate/user_certificate.module'; @Module({ imports: [ @@ -15,6 +17,8 @@ import { TrackingContentModule } from 'src/modules/tracking_content/tracking_con ConfigModule.forRoot({ isGlobal: true }), DatabaseModule, CacheModule.register({ isGlobal: true, store: MemoryStore }), + CertificateModule, + UserCertificateModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/modules/certificate/certificate.contoller.ts b/src/modules/certificate/certificate.contoller.ts new file mode 100644 index 0000000..b12d8eb --- /dev/null +++ b/src/modules/certificate/certificate.contoller.ts @@ -0,0 +1,91 @@ +import { Controller, Post, Body, Res, UseGuards, Req } from '@nestjs/common'; +import { + ApiInternalServerErrorResponse, + ApiBadRequestResponse, + ApiNotFoundResponse, + ApiOkResponse, +} from '@nestjs/swagger'; +import { Response } from 'express'; +import { CertificateService } from './certificate.service'; +import { IssueCredentialDto } from './dto/issue-certificate-dto'; + +@Controller('certificate') +export class CertificateController { + constructor(private readonly certificateService: CertificateService) {} + // API to generate DID + @ApiOkResponse({ description: 'DID generated successfully' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('generateDid') + async generateDid( + @Body() createCertificateDto: any, + @Res() response: Response, + ) { + return this.certificateService.generateDid( + createCertificateDto.userId, + response, + ); + } + // API to create schema + @ApiOkResponse({ description: 'Credential schema created successfully' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('schema') + async createCredentialSchema( + @Body() createCertificateDto: any, + @Res() response: Response, + ) { + return this.certificateService.createCredentialSchema( + createCertificateDto.schema, + response, + ); + } + // API to create template + @ApiOkResponse({ description: 'Template created successfully' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @ApiNotFoundResponse({ description: 'Certificate Not Found.' }) + @Post('template') + async createTemplate( + @Body() createCertificateDto: any, + @Res() response: Response, + ) { + return this.certificateService.createTemplate( + createCertificateDto.schemaId, + createCertificateDto.template, + response, + ); + } + + // // API to issue certificate + @ApiOkResponse({ description: 'Certificate issued successfully.' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('issue') + async issueCertificate( + @Body() issueCertificateDto: IssueCredentialDto, + @Res() response: Response, + @Req() request: Request, + ) { + return await this.certificateService.issueCertificateAfterCourseCompletion( + issueCertificateDto, + request, + response, + ); + } + // API to render certificate + @ApiOkResponse({ description: 'Certificate rendered successfully.' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('render') + async renderCertificate( + @Body() renderCertificateDto: any, + @Res() response: Response, + ) { + return await this.certificateService.renderCredentials( + renderCertificateDto.credentialId, + renderCertificateDto.templateId, + response, + ); + } +} diff --git a/src/modules/certificate/certificate.module.ts b/src/modules/certificate/certificate.module.ts new file mode 100644 index 0000000..25211c6 --- /dev/null +++ b/src/modules/certificate/certificate.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { CertificateService } from './certificate.service'; +import { CertificateController } from './certificate.contoller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserCourseCertificate } from './entities/user_course_certificate'; +import { LoggerService } from 'src/common/logger/logger.service'; +import { AxiosRequest } from 'src/common/middleware/axios.middleware'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserCourseCertificate])], + controllers: [CertificateController], + providers: [CertificateService, LoggerService, AxiosRequest], +}) +export class CertificateModule {} diff --git a/src/modules/certificate/certificate.service.ts b/src/modules/certificate/certificate.service.ts new file mode 100644 index 0000000..fe1c7bb --- /dev/null +++ b/src/modules/certificate/certificate.service.ts @@ -0,0 +1,312 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import axios from 'axios'; +import APIResponse from 'src/common/utils/response'; +import { LoggerService } from 'src/common/logger/logger.service'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { UserCourseCertificate } from './entities/user_course_certificate'; +import { Repository } from 'typeorm'; +import { Response } from 'express'; + +@Injectable() +export class CertificateService { + constructor( + @InjectRepository(UserCourseCertificate) + private userCourseCertificateRepository: Repository, + private configService: ConfigService, + private loggerService: LoggerService, + ) {} + async generateDid(userId: string, res: Response) { + let apiId = 'api.generate.did'; + try { + this.loggerService.log( + 'base: ', + this.configService.get('RC_IDENTITY_API_BASE_URL'), + ); + const url = + this.configService.get('RC_IDENTITY_API_BASE_URL') + + '/did/generate'; + const response = await axios.post( + url, + { + content: [ + { + alsoKnownAs: [userId], + services: [ + { + id: 'IdentityHub', + type: 'IdentityHub', + serviceEndpoint: { + '@context': 'schema.identity.foundation/hub', + '@type': 'UserServiceEndpoint', + instance: ['did:test:hub.id'], + }, + }, + ], + method: 'upai', + }, + ], + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + return APIResponse.success( + res, + apiId, + response.data, + HttpStatus.OK, + 'DID generated successfully', + ); + } catch (error) { + this.loggerService.error('Error generating DID:', error); + return APIResponse.error( + res, + apiId, + error.message, + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async generateDidByUserId(userId: string) { + try { + this.loggerService.log( + 'base: ', + this.configService.get('RC_IDENTITY_API_BASE_URL'), + ); + const url = + this.configService.get('RC_IDENTITY_API_BASE_URL') + + '/did/generate'; + const response = await axios.post( + url, + { + content: [ + { + alsoKnownAs: [userId], + services: [ + { + id: 'IdentityHub', + type: 'IdentityHub', + serviceEndpoint: { + '@context': 'schema.identity.foundation/hub', + '@type': 'UserServiceEndpoint', + instance: ['did:test:hub.id'], + }, + }, + ], + method: 'upai', + }, + ], + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + return response.data[0].id; + } catch (error) { + this.loggerService.error('Error generating DID:', error); + return error; + } + } + async createCredentialSchema(schema, res: Response) { + const apiId = 'api.create.credentialSchema'; + try { + const url = + this.configService.get('RC_CREDENTIAL_SCHEMA_API_BASE_URL') + + '/schemas'; + const response = await axios.post(url, ...schema, { + headers: { + 'Content-Type': 'application/json', + }, + }); + return APIResponse.success( + res, + apiId, + response.data, + HttpStatus.CREATED, + 'Credential schema created successfully', + ); + } catch (error) { + this.loggerService.error('Error creating credential schema:', error); + return APIResponse.error( + res, + apiId, + 'Error creating credential schema', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async createTemplate(schemaId: string, template: string, res: Response) { + const apiId = 'api.create.template'; + try { + const url = + this.configService.get('RC_CREDENTIAL_SCHEMA_API_BASE_URL') + + '/schemas/template'; + const response = await axios.post( + url, + { + schemaId: schemaId, + schemaVersion: '1.0.0', + template: template, + type: 'Handlebar', + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + return APIResponse.success( + res, + apiId, + response.data, + HttpStatus.CREATED, + 'Template created successfully', + ); + } catch (error) { + return APIResponse.error( + res, + apiId, + 'Error creating template', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async issueCertificateAfterCourseCompletion( + issueCredential, + request, + response, + ) { + let apiId = 'api.issueCertificate'; + try { + //get credentialId + let learnerDid = await this.generateDidByUserId(issueCredential.userId); + this.loggerService.log('learnerDid: ', learnerDid); + + const url = + this.configService.get('RC_CREDENTIALS_API_BASE_URL') + + 'credentials/issue'; + this.loggerService.log('url: ', url); + const credetialObj = { + credential: { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + this.configService.get('VERIFICATION_SCHEMA_URL'), + ], + type: [ + 'VerifiableCredential', + this.configService.get('VERIFICATION_SCHEMA_NAME'), + ], + issuer: this.configService.get('CERTIFICATE_ISSUER_DID'), + issuanceDate: issueCredential.issuanceDate, + expirationDate: issueCredential.expirationDate, + credentialSubject: { + id: learnerDid, + type: this.configService.get('VERIFICATION_SCHEMA_NAME'), + firstName: issueCredential.firstName, + middleName: '', + lastName: issueCredential.lastName, + userId: issueCredential.userId, + courseId: issueCredential.courseId, + courseName: issueCredential.courseName, + }, + }, + credentialSchemaId: this.configService.get('SCHEMA_ID'), + credentialSchemaVersion: '1.0.0', + tags: [], + }; + const issueResponse = await axios.post(url, credetialObj, { + headers: { + 'Content-Type': 'application/json', + }, + }); + //update status to view certificate + const updateResponse = await this.updateUserCertificate({ + userId: issueCredential.userId, + courseId: issueCredential.courseId, + issuedOn: issueCredential.issuanceDate, + status: 'viewCertificate', + certificateId: issueResponse.data.credential.id, + }); + + return APIResponse.success( + response, + apiId, + issueResponse.data, + HttpStatus.OK, + 'Credential issued successfully', + ); + } catch (error) { + this.loggerService.log(error); + this.loggerService.error('Error while issuing credentials:', error); + return APIResponse.error( + response, + apiId, + 'Error while issuing credentials', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async updateUserCertificate(data) { + try { + const userCertificate = + await this.userCourseCertificateRepository.findOne({ + where: { userId: data.userId, courseId: data.courseId }, + }); + if (userCertificate) { + userCertificate.certificateId = data.certificateId; + userCertificate.issuedOn = data.issuedOn; + userCertificate.status = data.status; + await this.userCourseCertificateRepository.save(userCertificate); + } + this.loggerService.log('Successfully updated user certificate'); + } catch (error) { + this.loggerService.error('Error while updating usercertificate', error); + } + } + async renderCredentials( + credentialId: string, + templateId: string, + res: Response, + ) { + const apiId = 'api.get.Certificate'; + try { + let url = + this.configService.get('RC_CREDENTIALS_API_BASE_URL') + + '/credentials/' + + credentialId; + const response = await axios.get(url, { + headers: { + templateid: templateId, + Accept: 'text/html', + }, + }); + + return APIResponse.success( + res, + apiId, + response.data, + HttpStatus.OK, + 'Credential rendered successfully', + ); + } catch (error) { + this.loggerService.error('Error fetching credentials:', error); + return APIResponse.error( + res, + apiId, + 'Error fetching credentials', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/modules/certificate/dto/issue-certificate-dto.ts b/src/modules/certificate/dto/issue-certificate-dto.ts new file mode 100644 index 0000000..1be1811 --- /dev/null +++ b/src/modules/certificate/dto/issue-certificate-dto.ts @@ -0,0 +1,29 @@ +import { IsDateString, IsOptional, IsString, IsUUID } from 'class-validator'; + +export class IssueCredentialDto { + @IsDateString() + issuanceDate: string; + + @IsDateString() + expirationDate: string; + + @IsString() + firstName: string; + + @IsOptional() + @IsString() + middleName?: string; + + @IsString() + lastName: string; + + @IsUUID() + @IsString() + userId: string; + + @IsString() + courseId: string; + + @IsString() + courseName: string; +} diff --git a/src/modules/certificate/entities/user_course_certificate.ts b/src/modules/certificate/entities/user_course_certificate.ts new file mode 100644 index 0000000..1214273 --- /dev/null +++ b/src/modules/certificate/entities/user_course_certificate.ts @@ -0,0 +1,45 @@ +import { + Entity, + Column, + CreateDateColumn, + UpdateDateColumn, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity({ name: 'user_course_certificate' }) +export class UserCourseCertificate { + @PrimaryGeneratedColumn('uuid') + usercertificateId: string; + + @Column({ type: 'uuid' }) + userId: string; + + @Column() + courseId: string; + + @Column({ type: 'uuid' }) + tenantId: string; + + @Column() + certificateId: string; + + @Column() + status: string; + + @Column({ type: 'timestamptz', nullable: true }) + issuedOn: Date; + + @CreateDateColumn({ + type: 'timestamptz', + default: () => 'now()', + nullable: false, + }) + createdOn: Date; + + @UpdateDateColumn({ + type: 'timestamptz', + default: () => 'now()', + nullable: false, + }) + updatedOn: Date; +} diff --git a/src/modules/user_certificate/dto/create-user-certificate-dto.ts b/src/modules/user_certificate/dto/create-user-certificate-dto.ts new file mode 100644 index 0000000..c5c0e59 --- /dev/null +++ b/src/modules/user_certificate/dto/create-user-certificate-dto.ts @@ -0,0 +1,28 @@ +import { + IsUUID, + IsString, + IsOptional, + IsDate, + IsNotEmpty, +} from 'class-validator'; + +export class CreateCertificateDto { + @IsOptional() + @IsUUID() + tenantId: string; + + @IsUUID() + userId: string; + + @IsString() + @IsNotEmpty() + courseId: string; + + @IsString() + @IsOptional() + certificateId: string | null; + + @IsOptional() + @IsDate() + issuedOn?: Date; +} diff --git a/src/modules/user_certificate/user_certificate.contoller.ts b/src/modules/user_certificate/user_certificate.contoller.ts new file mode 100644 index 0000000..3cc4f73 --- /dev/null +++ b/src/modules/user_certificate/user_certificate.contoller.ts @@ -0,0 +1,83 @@ +import { Controller, Post, Body, Req, Res } from '@nestjs/common'; +import { + ApiInternalServerErrorResponse, + ApiBadRequestResponse, + ApiOkResponse, +} from '@nestjs/swagger'; +import { Response } from 'express'; +import { UserCertificateService } from './user_certificate.service..js'; +import { CreateCertificateDto } from './dto/create-user-certificate-dto.js'; + +@Controller('user_certificate') +export class UserCertificateController { + constructor( + private readonly userCertificateService: UserCertificateService, + ) {} + + @ApiOkResponse({ description: 'User enrolled for course successfully' }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('status/create') + async enrollUserForCourse( + @Body() createUserCertificateDto: CreateCertificateDto, + @Res() response: Response, + @Req() request: Request, + ) { + return this.userCertificateService.enrollUserForCourse( + createUserCertificateDto, + response, + request, + ); + } + @ApiOkResponse({ + description: 'User status for course successfully updated to completed.', + }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('status/update') + async updateUserStatusForCourse( + @Body() createUserCertificateDto: CreateCertificateDto, + @Res() response: Response, + @Req() request: Request, + ) { + return this.userCertificateService.updateUserStatusForCourse( + createUserCertificateDto, + response, + request, + ); + } + @ApiOkResponse({ + description: 'User status for course fetched successfully', + }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('status/get') + async fetchUserStatusForCourse( + @Body() createUserCertificateDto: CreateCertificateDto, + @Res() response: Response, + @Req() request: Request, + ) { + return this.userCertificateService.fetchUserStatusForCourse( + createUserCertificateDto, + response, + request, + ); + } + @ApiOkResponse({ + description: 'Users status for courses fetched successfully', + }) + @ApiInternalServerErrorResponse({ description: 'Internal Server Error.' }) + @ApiBadRequestResponse({ description: 'Bad Request.' }) + @Post('status/search') + async searchUsersCourses( + @Body() searchObj: Record, + @Res() response: Response, + @Req() request: Request, + ) { + return this.userCertificateService.searchUsersCourses( + searchObj, + response, + request, + ); + } +} diff --git a/src/modules/user_certificate/user_certificate.module.ts b/src/modules/user_certificate/user_certificate.module.ts new file mode 100644 index 0000000..2f2e689 --- /dev/null +++ b/src/modules/user_certificate/user_certificate.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { UserCertificateController } from './user_certificate.contoller'; +import { UserCertificateService } from './user_certificate.service.'; + +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserCourseCertificate } from '../certificate/entities/user_course_certificate'; +import { LoggerService } from 'src/common/logger/logger.service'; +@Module({ + imports: [TypeOrmModule.forFeature([UserCourseCertificate])], + controllers: [UserCertificateController], + providers: [UserCertificateService, LoggerService], +}) +export class UserCertificateModule {} diff --git a/src/modules/user_certificate/user_certificate.service..ts b/src/modules/user_certificate/user_certificate.service..ts new file mode 100644 index 0000000..1672df2 --- /dev/null +++ b/src/modules/user_certificate/user_certificate.service..ts @@ -0,0 +1,262 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import { Response } from 'express'; + +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserCourseCertificate } from '../certificate/entities/user_course_certificate'; +import { CreateCertificateDto } from './dto/create-user-certificate-dto'; +import APIResponse from 'src/common/utils/response'; +import { LoggerService } from 'src/common/logger/logger.service'; +import { ConfigService } from '@nestjs/config'; +const axios = require('axios'); + +@Injectable() +export class UserCertificateService { + constructor( + @InjectRepository(UserCourseCertificate) + private userCourseCertificateRepository: Repository, + private loggerService: LoggerService, + private configService: ConfigService, + ) {} + + async enrollUserForCourse( + createUserCertificateDto: CreateCertificateDto, + response: Response, + request: Request, + ) { + let apiId = 'api.create.userEnrollment'; + const tenantId = request.headers['tenantid']; + if (!tenantId) { + return APIResponse.error( + response, + apiId, + 'tenantId is required in the header', + 'BAD_REQUEST', + HttpStatus.BAD_REQUEST, + ); + } + try { + // Save the user certificate object to the database + let data = new UserCourseCertificate(); + data.userId = createUserCertificateDto.userId; + data.courseId = createUserCertificateDto.courseId; + data.tenantId = createUserCertificateDto.tenantId; + data.status = 'enrolled'; + + //check if record with tenantId, userId and courseId exist + const userCertificate = + await this.userCourseCertificateRepository.findOne({ + where: { + userId: data.userId, + courseId: data.courseId, + tenantId: data.tenantId, + }, + }); + if (userCertificate) { + return APIResponse.error( + response, + apiId, + 'User already enrolled for course', + 'User already enrolled for course', + HttpStatus.OK, + ); + } + const result = await this.userCourseCertificateRepository.save(data); + + return APIResponse.success( + response, + apiId, + result, + HttpStatus.OK, + 'User enrolled for course successfully', + ); + } catch (error) { + this.loggerService.error( + 'Error while enrolling user to the course', + error, + ); + return APIResponse.error( + response, + apiId, + 'Error creating user certificate', + 'BAD_REQUEST', + HttpStatus.BAD_REQUEST, + ); + } + } + async updateUserStatusForCourse(data, response, request: Request) { + let apiId = 'api.update.courseStatus'; + const tenantId = request.headers['tenantid']; + if (!tenantId) { + return APIResponse.error( + response, + apiId, + 'tenantId is required in the header', + 'BAD_REQUEST', + HttpStatus.BAD_REQUEST, + ); + } + try { + const userCertificate = + await this.userCourseCertificateRepository.findOne({ + where: { + userId: data.userId, + courseId: data.courseId, + tenantId: data.tenantId, + }, + }); + if (userCertificate) { + userCertificate.certificateId = data.certificateId; + userCertificate.issuedOn = data.issuedOn; + userCertificate.status = 'completed'; + let updateResult = + await this.userCourseCertificateRepository.save(userCertificate); + if (updateResult) { + this.loggerService.log( + 'User status for course successfully updated to completed.', + ); + return APIResponse.success( + response, + apiId, + updateResult, + HttpStatus.OK, + 'User status for course successfully updated to completed.', + ); + } + } else { + this.loggerService.error( + 'User enrollment for course not exists', + 'User enrollment for course not exists', + ); + return APIResponse.error( + response, + apiId, + 'User enrollment for course not exists', + 'BAD_REQUEST', + HttpStatus.OK, + ); + } + } catch (error) { + this.loggerService.error( + 'Error while updating user status for course', + error, + ); + return APIResponse.error( + response, + apiId, + 'Error while updating user status for course', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async fetchUserStatusForCourse(data, response, request: Request) { + let apiId = 'api.get.courseStatus'; + const tenantId = request.headers['tenantid']; + if (!tenantId) { + return APIResponse.error( + response, + apiId, + 'tenantId is required in the header', + 'BAD_REQUEST', + HttpStatus.BAD_REQUEST, + ); + } + try { + const userCertificate = + await this.userCourseCertificateRepository.findOne({ + where: { + userId: data.userId, + courseId: data.courseId, + tenantId: data.tenantId, + }, + }); + if (userCertificate) { + this.loggerService.log('User status for course fetched successfully'); + return APIResponse.success( + response, + apiId, + userCertificate, + HttpStatus.OK, + 'User status for course fetched successfully', + ); + } else { + return APIResponse.error( + response, + apiId, + 'User enrollment for course not exists', + 'BAD_REQUEST', + HttpStatus.OK, + ); + } + } catch (error) { + this.loggerService.error( + 'Error while fetching user status for course', + error, + ); + return APIResponse.error( + response, + apiId, + 'Error while fetching user status for course', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async searchUsersCourses(searchObj, response, request) { + const filters = searchObj.filters; + let apiId = 'api.get.searchList'; + try { + const queryBuilder = + this.userCourseCertificateRepository.createQueryBuilder( + 'UserCourseCertificate', + ); + + // Dynamically build query based on filters object + Object.keys(filters).forEach((key) => { + const value = filters[key]; + + if (Array.isArray(value) && value.length > 0) { + // Array filter - use IN clause + queryBuilder.andWhere( + `UserCourseCertificate.${key} IN (:...${key})`, + { + [key]: value, + }, + ); + } else if (value) { + // Single value filter - use equality + queryBuilder.andWhere(`UserCourseCertificate.${key} = :${key}`, { + [key]: value, + }); + } + }); + const count = await queryBuilder.getCount(); + queryBuilder.limit(searchObj.limit).offset(searchObj.offset); + const result = await queryBuilder.getMany(); + this.loggerService.log('Users status for courses fetched successfully'); + return APIResponse.success( + response, + apiId, + { + data: result, + count: count, + }, + HttpStatus.OK, + 'Users status for courses fetched successfully', + ); + } catch (error) { + this.loggerService.error( + 'Error while fetching user status for courses', + error, + ); + return APIResponse.error( + response, + apiId, + 'Error while fetching user status for courses', + 'INTERNAL_SERVER_ERROR', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +}