diff --git a/src/adapters/hasura/altLessonTracking.adapter.ts b/src/adapters/hasura/altLessonTracking.adapter.ts index 09d0fe6e..8e271042 100644 --- a/src/adapters/hasura/altLessonTracking.adapter.ts +++ b/src/adapters/hasura/altLessonTracking.adapter.ts @@ -67,6 +67,7 @@ export class ALTLessonTrackingService { attempts timeSpent contentType + lessonProgressId } }`, variables: { userId: altUserId, @@ -340,6 +341,7 @@ export class ALTLessonTrackingService { // handling baseline assessment if (numberOfRecords === 0) { altLessonTrackingDto.attempts = 1; + return await this.createALTLessonTracking( request, altLessonTrackingDto @@ -373,7 +375,6 @@ export class ALTLessonTrackingService { // if course content handling creation and updation of lesson with module if (numberOfRecords === 0) { altLessonTrackingDto.attempts = 1; - const lessonTrack: any = await this.createALTLessonTracking( request, altLessonTrackingDto @@ -567,10 +568,13 @@ export class ALTLessonTrackingService { request: any, lessonId: string, updateAltLessonTrackDto: UpdateALTLessonTrackingDto, - lastAttempt: number + lastAttempt: number, + data?: any ) { const decoded: any = jwt_decode(request.headers.authorization); const userId = decoded["https://hasura.io/jwt/claims"]["x-hasura-user-id"]; + const courseId = data?.courseId; + const moduleId = data?.moduleId; const updateAltLessonTracking = new UpdateALTLessonTrackingDto( updateAltLessonTrackDto @@ -592,30 +596,40 @@ export class ALTLessonTrackingService { } }); - let altLessonUpdateTrackingData = {}; + let altLessonUpdateTrackingData; if (!lastAttempt) { altLessonUpdateTrackingData = { - query: `mutation updateAltLessonTracking ($userId:uuid!, $lessonId:String) { - update_LessonProgressTracking(where: {lessonId: {_eq: $lessonId}, userId: {_eq: $userId}}, _set: {${newUpdateAltLessonTracking}}) { + query: `mutation updateAltLessonTracking ($userId:uuid!, $lessonId:String, $courseId: String, $moduleId: String) { + update_LessonProgressTracking(where: {lessonId: {_eq: $lessonId}, userId: {_eq: $userId},courseId: {_eq: $courseId}, moduleId: {_eq:$moduleId} }, _set: {${newUpdateAltLessonTracking}}) { affected_rows + returning{ + lessonProgressId + } } }`, variables: { userId: userId, lessonId: lessonId, + courseId: courseId, + moduleId: moduleId, }, }; } else { altLessonUpdateTrackingData = { - query: `mutation updateAltLessonTracking ($userId:uuid!, $lessonId:String, $lastAttempt:Int) { - update_LessonProgressTracking(where: {lessonId: {_eq: $lessonId}, userId: {_eq: $userId} ,attempts: {_eq: $lastAttempt}}, _set: {${newUpdateAltLessonTracking}}) { + query: `mutation updateAltLessonTracking ($userId:uuid!, $lessonId:String, $lastAttempt:Int, $courseId: String, $moduleId: String) { + update_LessonProgressTracking(where: {lessonId: {_eq: $lessonId}, userId: {_eq: $userId}, courseId: {_eq: $courseId}, moduleId: {_eq:$moduleId} ,attempts: {_eq: $lastAttempt}}, _set: {${newUpdateAltLessonTracking}}) { affected_rows + returning{ + lessonProgressId + } } }`, variables: { userId: userId, lessonId: lessonId, + courseId: courseId, + moduleId: moduleId, lastAttempt: lastAttempt, }, }; @@ -801,4 +815,378 @@ export class ALTLessonTrackingService { } } } + public async addLessonTracking( + request: any, + altLessonTrackingDto: ALTLessonTrackingDto, + programId: string, + subject: string + ) { + // Step 1: Check if the lesson and module exist for the given course + const checkLessonExist = await this.checkLessonAndModuleExistInCourse( + request, + altLessonTrackingDto, + programId, + subject + ); + if (checkLessonExist instanceof ErrorResponse) { + return checkLessonExist; // Return the error directly + } + + // Step 2: Validate the `scoreDetails` field + const scoreDetails = altLessonTrackingDto?.scoreDetails; + + if (Array.isArray(scoreDetails) && !scoreDetails.length) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: "Score Details is empty", + }); + } + + if (Object.keys(scoreDetails).length === 0) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: "Score Details is empty", + }); + } + let lessonProgressId; + // Step 3: Decode the JWT token for user identification + + const decoded: any = jwt_decode(request.headers.authorization); + altLessonTrackingDto.userId = + decoded["https://hasura.io/jwt/claims"]["x-hasura-user-id"]; + altLessonTrackingDto.createdBy = altLessonTrackingDto.userId; + altLessonTrackingDto.updatedBy = altLessonTrackingDto.userId; + altLessonTrackingDto.timeSpent = + altLessonTrackingDto.timeSpent > 0 ? altLessonTrackingDto.timeSpent : 0; + altLessonTrackingDto.programId = programId; + + // Step 4: Fetch existing lesson tracking records + let recordList: any; + try { + recordList = await this.getExistingLessonTrackingRecords( + request, + altLessonTrackingDto.lessonId, + altLessonTrackingDto.moduleId + ); + } catch (error) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: + error?.response?.data?.errorMessage || + "Can't fetch existing records.", + }); + } + if (!recordList?.data) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: recordList?.errorMessage, + }); + } + + // Determine if a record already exists + lessonProgressId = recordList?.data[0] + ? recordList?.data[0]?.lessonProgressId + : 0; + + // Step 5: Fetch program details + let currentProgramDetails: any = {}; + try { + currentProgramDetails = await this.programService.getProgramDetailsById( + request, + programId + ); + } catch (error) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: + error?.response?.data?.errorMessage || "Can't fetch program details.", + }); + } + // Map program details to DTO for fetching rules + + const paramData = new TermsProgramtoRulesDto(currentProgramDetails.data); + // Step 6: Fetch program rules for the given subject + + let progTermData: any = {}; + try { + progTermData = await this.altProgramAssociationService.getRules(request, { + programId: programId, + board: paramData[0].board, + medium: paramData[0].medium, + grade: paramData[0].grade, + subject: subject, + }); + } catch (error) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: "Program Rules not found for given subject!", + }); + } + let programRules: any; + if (progTermData?.data[0]?.rules) { + programRules = JSON.parse(progTermData.data[0].rules); + } else { + return new ErrorResponse({ + errorCode: "400", + errorMessage: "Program Rules not found for given subject!", + }); + } + // Step 7: Loop through program rules to process the lesson tracking + + let flag = false; + let tracklessonModule; + + if (altLessonTrackingDto.userId) { + for (const course of programRules?.prog) { + const numberOfRecords = parseInt(recordList?.data.length); + // Check if the course matches + + if (course.contentId == altLessonTrackingDto.courseId) { + if (course.contentType === "assessment") { + if (numberOfRecords === 0) { + // No record exists for this lessonId and moduleId + altLessonTrackingDto.attempts = 1; // First attempt + // Create a new LessonProgressTracking record + const lessonTrack: any = await this.createALTLessonTracking( + request, + altLessonTrackingDto + ); + lessonProgressId = lessonTrack?.data?.lessonProgressId; + } else if (numberOfRecords === 1) { + // Update or reject if the assessment is completed + const existingRecord = recordList.data[0]; + + if (existingRecord.status !== "completed") { + // If the status is "inprogress", update it to "completed" + await this.updateALTLessonTracking( + request, + altLessonTrackingDto.lessonId, + altLessonTrackingDto, + 0, + { + courseId: altLessonTrackingDto.courseId, + moduleId: altLessonTrackingDto.moduleId, + } + ); + } else if (existingRecord.status === "completed") { + // If the status is "completed", do not update or create a new record + return new ErrorResponse({ + errorCode: "400", + errorMessage: "Record for Assessment already exists!", + }); + } + } + } else if (course.contentType == "course") { + // if course content handling creation and updation of lesson with module + if (numberOfRecords === 0) { + altLessonTrackingDto.attempts = 1; + const lessonTrack: any = await this.createALTLessonTracking( + request, + altLessonTrackingDto + ); + + lessonProgressId = lessonTrack?.data?.lessonProgressId; + + if ( + altLessonTrackingDto.status === "completed" && + lessonTrack?.statusCode === 200 + ) { + tracklessonModule = await this.lessonToModuleTracking( + request, + altLessonTrackingDto, + programId, + subject, + false + ); + } + //adding an entry into LessonProgressAttemptTracking Table + await this.logLessonAttemptProgressTracking( + request, + altLessonTrackingDto, + lessonProgressId + ); + + return { + lessonTrack: lessonTrack, + tracking: tracklessonModule, + }; + } else if (numberOfRecords >= 1) { + const lastRecord = await this.getLastLessonTrackingRecord( + request, + altLessonTrackingDto.lessonId, + altLessonTrackingDto.moduleId, + numberOfRecords + ).catch(function (error) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: error, + }); + }); + + if (!lastRecord[0]?.status) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: lastRecord + "Error getting last record", + }); + } + + if (lastRecord[0]?.status !== "completed") { + const lessonTrack: any = await this.updateALTLessonTracking( + request, + altLessonTrackingDto.lessonId, + altLessonTrackingDto, + lastRecord[0]?.attempts, + { + courseId: altLessonTrackingDto.courseId, + moduleId: altLessonTrackingDto.moduleId, + } + ); + + // Adding to module only when its first attempt and increasing count in module for lesson + if ( + altLessonTrackingDto.status === "completed" && + lastRecord[0].attempts === 1 && + lessonTrack?.statusCode === 200 + ) { + tracklessonModule = await this.lessonToModuleTracking( + request, + altLessonTrackingDto, + programId, + subject, + false + ); + } + + await this.logLessonAttemptProgressTracking( + request, + altLessonTrackingDto, + lessonProgressId + ); + + return { + lessonTrack: lessonTrack, + tracking: tracklessonModule, + }; + } else if (lastRecord[0]?.status === "completed") { + await this.logLessonAttemptProgressTracking( + request, + altLessonTrackingDto, + lessonProgressId + ); + // Do not update any records if the lesson is already completed + return new ErrorResponse({ + errorCode: "409", + errorMessage: "Lesson is already completed", + }); + } else { + return new ErrorResponse({ + errorCode: "400", + errorMessage: lastRecord, + }); + } + } + } + } + } + // If no valid course is found in the program + if (!flag) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: `Course provided does not exist in the current program.`, + }); + } + } + } + public async logLessonAttemptProgressTracking( + request, + data, + lessonProgressId + ) { + const decoded: any = jwt_decode(request.headers.authorization); + const userId = decoded["https://hasura.io/jwt/claims"]["x-hasura-user-id"]; + const query = { + query: `mutation MyMutation($score: Int, $scoreDetails: String, $status: String, $timeSpent: Int, $userId: uuid, $createdBy: String, $updatedBy: String, $lessonProgressId: Int) { + insert_LessonProgressAttemptTracking(objects: {score: $score, scoreDetails: $scoreDetails, status: $status, timeSpent: $timeSpent, userId: $userId, createdBy: $createdBy, updatedBy: $updatedBy, lessonProgressId: $lessonProgressId}) { + affected_rows + } +} + + +`, + variables: { + userId: userId, + createdBy: userId, + updatedBy: userId, + score: data.score, + scoreDetails: data.scoreDetails, + status: data.status, + timeSpent: data.timeSpent, + lessonProgressId: lessonProgressId, + }, + }; + const configData = { + method: "post", + url: process.env.ALTHASURA, + headers: { + Authorization: request.headers.authorization, + "Content-Type": "application/json", + }, + data: query, + }; + const response = await this.axios(configData); + + if (response?.data?.errors) { + return new ErrorResponse({ + errorCode: response.data.errors[0].extensions, + errorMessage: response.data.errors[0].message, + }); + } + + const result = response.data.data.update_LessonProgressTracking; + + return new SuccessResponse({ + statusCode: 200, + message: "Ok.", + data: result, + }); + } + public async checkLessonAndModuleExistInCourse( + request: any, + altLessonTrackingDto: ALTLessonTrackingDto, + programId: string, + subject: string + ) { + const currentUrl = process.env.SUNBIRDURL; + const config = { + method: "get", + url: `${currentUrl}/api/course/v1/hierarchy/${altLessonTrackingDto.courseId}?orgdetails=orgName,email&licenseDetails=name,description,url`, + }; + + const courseHierarchy = await this.axios(config); + const data = courseHierarchy?.data.result.content; + + // Find the specified module + const currentModule = data.children.find( + (item) => item.identifier === altLessonTrackingDto.moduleId + ); + + if (!currentModule) { + return new ErrorResponse({ + errorCode: "404", + errorMessage: "Module not found in the course", + }); + } + + // Check if the lesson exists within the module + const lessonExists = currentModule.children.some( + (item) => item.identifier === altLessonTrackingDto.lessonId + ); + + if (!lessonExists) { + return new ErrorResponse({ + errorCode: "404", + errorMessage: "Lesson not found in the module", + }); + } + } } diff --git a/src/altLessonTracking/altLessonTracking.controller.ts b/src/altLessonTracking/altLessonTracking.controller.ts index 7aca8a50..9fb9e7aa 100644 --- a/src/altLessonTracking/altLessonTracking.controller.ts +++ b/src/altLessonTracking/altLessonTracking.controller.ts @@ -123,4 +123,14 @@ export class ALTLessonTrackingController { altLessonTrackingSearch ); } + @Post("/altAddLessonTracking") + @ApiBasicAuth("access-token") + public async addLessonTracking( + @Req() request: Request, + @Body() altLessonTrackingDto: ALTLessonTrackingDto, + @Query("program") programId: string, + @Query("subject") subject: string, + ){ + return this.altLessonTrackingService.addLessonTracking(request,altLessonTrackingDto,programId,subject); + } }