diff --git a/.github/workflows/backend-develop-deploy.yml b/.github/workflows/backend-develop-deploy.yml index 32b53c6f..b882abf3 100644 --- a/.github/workflows/backend-develop-deploy.yml +++ b/.github/workflows/backend-develop-deploy.yml @@ -1,6 +1,6 @@ #It is deploying on server 15.206.144.84 For Dev Environment only please note. -name: Deploy to DEV -on: +name: Deploy to DEV +on: push: branches: - develop diff --git a/src/adapters/diksha/dikshaCourse.adapter.ts b/src/adapters/diksha/dikshaCourse.adapter.ts index 6f49409a..fe5bad3c 100644 --- a/src/adapters/diksha/dikshaCourse.adapter.ts +++ b/src/adapters/diksha/dikshaCourse.adapter.ts @@ -118,6 +118,8 @@ export class DikshaCourseService implements IServicelocator { `/api/content/v1/read/${value}?fields=transcripts,ageGroup,appIcon,artifactUrl,attributions,attributions,audience,author,badgeAssertions,board,body,channel,code,concepts,contentCredits,contentType,contributors,copyright,copyrightYear,createdBy,createdOn,creator,creators,description,displayScore,domain,editorState,flagReasons,flaggedBy,flags,framework,gradeLevel,identifier,itemSetPreviewUrl,keywords,language,languageCode,lastUpdatedOn,license,mediaType,medium,mimeType,name,originData,osId,owner,pkgVersion,publisher,questions,resourceType,scoreDisplayConfig,status,streamingUrl,subject,template,templateId,totalQuestions,totalScore,versionKey,visibility,year,primaryCategory,additionalCategories,interceptionPoints,interceptionType&licenseDetails=name,description,url`, }; + console.log("config", config) + const response = await axios(config); const data = response?.data; @@ -150,6 +152,7 @@ export class DikshaCourseService implements IServicelocator { } public async getCourseHierarchy(value: any, type: any) { + console.log("value", value) var axios = require("axios"); if (type == "assessment") { let config = { diff --git a/src/adapters/hasura/altBulkUploadStudent.adapter.ts b/src/adapters/hasura/altBulkUploadStudent.adapter.ts index 6b59ee90..276365ab 100644 --- a/src/adapters/hasura/altBulkUploadStudent.adapter.ts +++ b/src/adapters/hasura/altBulkUploadStudent.adapter.ts @@ -8,13 +8,15 @@ import { ALTStudentService } from "./altStudent.adapter"; import { getPassword, getToken } from "./adapter.utils"; import { ErrorResponse } from "src/error-response"; import { SuccessResponse } from "src/success-response"; +import { ALTHasuraUserService } from "./altUser.adapter"; @Injectable() export class ALTBulkUploadStudentService { constructor( private httpService: HttpService, private studentService: ALTStudentService, private groupMembershipService: GroupMembershipService, - private groupService: HasuraGroupService + private groupService: HasuraGroupService, + private altUserService: ALTHasuraUserService ) {} public async createStudents( @@ -48,7 +50,6 @@ export class ALTBulkUploadStudentService { try { for (const student of bulkStudentDto.students) { student.groups = []; - student.password = getPassword(8); student.status = true; const studentRes: any = await this.studentService.createAndAddToGroup( request, diff --git a/src/adapters/hasura/altLessonTracking.adapter.ts b/src/adapters/hasura/altLessonTracking.adapter.ts index 09d0fe6e..5c1f9972 100644 --- a/src/adapters/hasura/altLessonTracking.adapter.ts +++ b/src/adapters/hasura/altLessonTracking.adapter.ts @@ -49,7 +49,7 @@ export class ALTLessonTrackingService { public async getExistingLessonTrackingRecords( request: any, lessonId: string, - moduleId: string + moduleId?: string ) { const decoded: any = jwt_decode(request.headers.authorization); const altUserId = @@ -57,7 +57,9 @@ export class ALTLessonTrackingService { const altLessonTrackingRecord = { query: `query GetLessonTrackingData ($userId:uuid!, $lessonId:String, $moduleId:String) { - LessonProgressTracking(where: {userId: {_eq: $userId}, lessonId: {_eq: $lessonId}, moduleId: {_eq: $moduleId}}) { + LessonProgressTracking(where: {userId: {_eq: $userId}, lessonId: {_eq: $lessonId},${ + moduleId ? `, moduleId: {_eq: $moduleId}` : "" + }}) { userId moduleId lessonId @@ -67,11 +69,12 @@ export class ALTLessonTrackingService { attempts timeSpent contentType + lessonProgressId } }`, variables: { userId: altUserId, lessonId: lessonId, - moduleId: moduleId, + ...(moduleId && { moduleId }), }, }; @@ -340,6 +343,7 @@ export class ALTLessonTrackingService { // handling baseline assessment if (numberOfRecords === 0) { altLessonTrackingDto.attempts = 1; + return await this.createALTLessonTracking( request, altLessonTrackingDto @@ -373,7 +377,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 +570,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 +598,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 +817,400 @@ export class ALTLessonTrackingService { } } } + public async addLessonTracking( + request: any, + altLessonTrackingDto: ALTLessonTrackingDto, + programId: string, + subject: string + ) { + // 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; + // 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; + + // Fetch existing lesson tracking records + let recordList: any; + try { + recordList = await this.getExistingLessonTrackingRecords( + request, + altLessonTrackingDto.lessonId + ); + } 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; + + // 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); + // 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!", + }); + } + // Loop through program rules to process the lesson tracking + + // Check lessonId and courseId in programRules + const currentLessonId = altLessonTrackingDto.lessonId; + let courseId; + + for (const program of programRules.prog) { + if (program.contentId === currentLessonId) { + courseId = program.courseId; // Fetch courseId for the matched lessonId + break; + } + } + + if (!courseId) { + return new ErrorResponse({ + errorCode: "400", + errorMessage: `Lesson ID ${currentLessonId} not found in program rules.`, + }); + } + + // Call checkLessonAndModuleExistInCourse with the found courseId + const checkLessonExist = await this.checkLessonAndModuleExistInCourse({ + ...altLessonTrackingDto, + courseId, + }); + if (checkLessonExist instanceof ErrorResponse) { + return checkLessonExist; // Return the error directly + } + const moduleId = checkLessonExist.data; + altLessonTrackingDto.courseId = courseId; + altLessonTrackingDto.moduleId = moduleId; + + 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 == courseId) { + flag = true; + 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: 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( + altLessonTrackingDto: ALTLessonTrackingDto + ) { + 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; + + let moduleId = null; + + for (const module of data.children) { + if (module.children) { + // Search for the lesson in the nested `children` array + const foundLesson = module.children.find( + (lesson) => lesson.identifier === altLessonTrackingDto.lessonId + ); + + if (foundLesson) { + // Assign the `parent` of the lesson as `moduleId` + moduleId = foundLesson.parent; + break; // Exit the loop once the lesson is found + } + } + } + + // Check if the lesson was found + if (!moduleId) { + return new ErrorResponse({ + errorCode: "404", + errorMessage: "ModuleId Not Found", + }); + } + // If lesson is found, return success response + return new SuccessResponse({ + statusCode: 200, + message: "Lesson found in the course hierarchy", + data: moduleId, + }); + } } diff --git a/src/adapters/hasura/altProgramAssociation.adapter.ts b/src/adapters/hasura/altProgramAssociation.adapter.ts index f349270a..16804bb9 100644 --- a/src/adapters/hasura/altProgramAssociation.adapter.ts +++ b/src/adapters/hasura/altProgramAssociation.adapter.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@nestjs/common"; +import { HttpException, Injectable } from "@nestjs/common"; import { HttpService } from "@nestjs/axios"; import { SuccessResponse } from "src/success-response"; import { ALTSubjectListDto } from "src/altProgramAssociation/dto/altSubjectList.dto"; @@ -7,6 +7,7 @@ import { ProgramAssociationDto } from "src/altProgramAssociation/dto/altProgramA import { UpdateALTProgramAssociationDto } from "src/altProgramAssociation/dto/updateAltProgramAssociation.dto"; import { ErrorResponse } from "src/error-response"; import { ALTProgramAssociationSearch } from "src/altProgramAssociation/dto/searchAltProgramAssociation.dto"; +import jwt_decode from "jwt-decode"; Injectable(); export class ALTProgramAssociationService { @@ -325,4 +326,177 @@ export class ALTProgramAssociationService { data: altProgramList, }); } + + + public async getGlaUserContent( + request: any, + altTermsProgramDto: TermsProgramtoRulesDto, + page: any, + limit: any + ) { + + const decoded: any = jwt_decode(request.headers.authorization); + const altUserId = decoded["https://hasura.io/jwt/claims"]["x-hasura-user-id"]; + + console.log("altUserId", altUserId) + console.log("altTermsProgramDto", altTermsProgramDto) + + // get programtoRulesData + + const programtoRulesData = await this.termsProgramtoRulesData(altTermsProgramDto, request) + + // get altcoursetrackingdetails + const promises = programtoRulesData.map((item) => + this.altCourseTrackingDetails(item.contentId, altUserId, request) + ); + + const results = await Promise.allSettled(promises); + console.log("results", results) + + + function isFulfilled(result: PromiseSettledResult): result is PromiseFulfilledResult { + return result.status === 'fulfilled'; + } + + // Filter for fulfilled results, then map to access the data + const trackingDetails = results + .filter(isFulfilled) // Use the type guard to filter only fulfilled results + .map(result => result.value.data.ContentBrowseTracking) // Now TypeScript knows `value` exists + .flat(); + + console.log("programtoRulesData", programtoRulesData) + console.log("trackingDetails", trackingDetails) + + const seenContentIds = new Set(trackingDetails.map(item => item.contentId)); + + // Filter out seen courses from programtoRulesData + const unseenProgramRules = programtoRulesData.filter(item => !seenContentIds.has(item.contentId)); + + console.log("unseenProgramRules", unseenProgramRules); + + // pagination + + let paginatedData = this.paginateData(unseenProgramRules, page, limit); + console.log(paginatedData); + + if (paginatedData.length < limit) { + const additionalData = programtoRulesData.filter( + (item) => !seenContentIds.has(item.courseId) && !unseenProgramRules.includes(item) + ); + const additionalPaginatedData = this.paginateData(additionalData, 1, limit - paginatedData.length); + paginatedData = [...paginatedData, ...additionalPaginatedData]; + } + + console.log("paginatedData with fallback", paginatedData); + + + + return new SuccessResponse({ + statusCode: 200, + message: "Ok.", + data: paginatedData, + }); + } + + async termsProgramtoRulesData(altTermsProgramDto, request) { + const TermsProgramtoRulesData = { + query: `query GetRules ($subject:String,$programId:uuid!){ + ProgramTermAssoc(where: + { + subject: {_eq: $subject} + programId: {_eq: $programId}, + }) + { rules } + }`, + variables: { + // board: altTermsProgramDto.board, + // medium: altTermsProgramDto.medium, + // grade: altTermsProgramDto.grade, + subject: altTermsProgramDto.subject, + programId: altTermsProgramDto.programId, + }, + }; + + const configData = { + method: "post", + url: process.env.ALTHASURA, + headers: { + "Authorization": request.headers.authorization, + "Content-Type": "application/json", + }, + data: TermsProgramtoRulesData, + }; + + 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.ProgramTermAssoc; + + console.log("result", JSON.parse(result[0].rules).prog) + + return JSON.parse(result[0].rules).prog + } + + async altCourseTrackingDetails(contentId, altUserId, request) { + + console.log("contentId", contentId) + console.log("altUserId", altUserId) + + + const ProgressTrackingDetails = { + query: `query GetProgressDetails($contentId: String, $userId: uuid!) { + ContentBrowseTracking(where: { + contentId: { _eq: $contentId }, + userId: { _eq: $userId } + }) { + contentId + userId + status + programId + } + }`, + variables: { + contentId: contentId, + userId: altUserId, + }, + }; + + const configData = { + method: "post", + url: process.env.ALTHASURA, + headers: { + "Authorization": request.headers.authorization, + "Content-Type": "application/json", + }, + data: ProgressTrackingDetails, + }; + + try { + const response = await this.axios(configData); + const altcoursetrackingdetails = response.data + console.log("altcoursetrackingdetails", altcoursetrackingdetails) + return altcoursetrackingdetails + + } catch (error) { + throw new HttpException( + 'data not found', + error.response?.status || 500, + ); + } + + + } + + paginateData(data, page = 1, limit = 5) { + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + + return data.slice(startIndex, endIndex); + } } diff --git a/src/adapters/hasura/altStudent.adapter.ts b/src/adapters/hasura/altStudent.adapter.ts index b0675b7a..210300fa 100644 --- a/src/adapters/hasura/altStudent.adapter.ts +++ b/src/adapters/hasura/altStudent.adapter.ts @@ -288,7 +288,7 @@ export class ALTStudentService { errorMessage: `Create and add to group failed Old and new school does not match,`, }); } else { - // console.log(newCreatedStudent, "new Created user"); + //console.log(newCreatedStudent, "new Created user"); return new ErrorResponse({ errorCode: "400", errorMessage: `Create and add to group failed , ${newCreatedStudent?.errorMessage}`, @@ -393,7 +393,7 @@ export class ALTStudentService { for (const item of result) { const studentMapping = { userId: item?.user?.userId ? `${item.user.userId}` : "", - password: await decryptPassword(item?.user.password), + password: item?.user.password ? `${item.user.password}` : "", studentId: item?.studentId ? `${item.studentId}` : "", board: item?.board ? `${item.board}` : "", religion: item?.religion ? `${item.religion}` : "", @@ -465,7 +465,7 @@ export class ALTStudentService { "district", "schoolUdise", "username", - "schoolName", + "udiseCode", "class", "board", "grade", @@ -506,8 +506,8 @@ export class ALTStudentService { filterQuery += `user: {GroupMemberships: {Groupx: {grade: {_eq: "${parseInt( studentSearchDto.filters["grade"] )}"}}}}`; - } else if (e === "schoolName") { - schoolFilter += `School: {name: {_eq: "${studentSearchDto.filters[e]?.eq}"}}`; + } else if (e === "udiseCode") { + schoolFilter += `School: {udiseCode: {_eq: "${studentSearchDto.filters[e]?.eq}"}}`; //if class is not passed and only school is passed then it should get appended to the main query filterQuery += schoolFilter; } else if (e === "class") { @@ -1045,6 +1045,7 @@ export class ALTStudentService { query: `query GetSchoolList { School (distinct_on: udiseCode, ${filterQuery}) { name + udiseCode } } diff --git a/src/adapters/hasura/altTeacher.adapter.ts b/src/adapters/hasura/altTeacher.adapter.ts index 4cec6191..3a7a5f07 100644 --- a/src/adapters/hasura/altTeacher.adapter.ts +++ b/src/adapters/hasura/altTeacher.adapter.ts @@ -428,7 +428,7 @@ export class ALTTeacherService { for (const item of result) { const teacherMapping = { userId: item?.user?.userId ? `${item.user.userId}` : "", - password: await decryptPassword(item?.user.password), + password: item?.user.password ? ` ${item.user.password}` : "", teacherId: item?.teacherId ? `${item.teacherId}` : "", groups: item?.groups ? item.groups : [], board: item?.board ? `${item.board}` : "", diff --git a/src/adapters/hasura/altUser.adapter.ts b/src/adapters/hasura/altUser.adapter.ts index e5260c3e..0771e884 100644 --- a/src/adapters/hasura/altUser.adapter.ts +++ b/src/adapters/hasura/altUser.adapter.ts @@ -105,22 +105,26 @@ export class ALTHasuraUserService { const response = await getToken(); // generating if required bulkToken = response.data.access_token; } + userDto.username = userDto.username.replace(/^\s*$|\n/g, ""); if (!userDto.username) { - userDto.username = getUsername(userDto); + userDto.username = await this.getUsername(userDto, request, altUserRoles); } + if (!userDto.email) { userDto.email = userDto.username + "@yopmail.com"; } if (!userDto.password) { - userDto.password = getPassword(8); + userDto.password = userDto.username; } const userSchema = new UserDto(userDto, true); + const usernameExistsInKeycloak = await checkIfUsernameExistsInKeycloak( userDto.username, bulkToken ); + if (usernameExistsInKeycloak?.data[0]?.username) { // console.log("check in db", usernameExistsInKeycloak?.data[0]?.id); const usernameExistsInDB: any = await this.getUserByUsername( @@ -128,6 +132,7 @@ export class ALTHasuraUserService { request, altUserRoles ); + if (usernameExistsInDB?.statusCode === 200) { if (usernameExistsInDB?.data) { // console.log(usernameExistsInDB, "usernameExistsInDB"); @@ -145,6 +150,7 @@ export class ALTHasuraUserService { null, usernameExistsInKeycloak?.data[0]?.id ); + // console.log(resetPasswordRes ,"pres"); if (resetPasswordRes.statusCode !== 204) { return new ErrorResponse({ @@ -176,6 +182,7 @@ export class ALTHasuraUserService { userSchema, altUserRoles ); + return { user: newlyCreatedUser, isNewlyCreated: true, @@ -229,7 +236,7 @@ export class ALTHasuraUserService { }); } // db?? - const databaseResponse = this.createUserInDatabase( + const databaseResponse = await this.createUserInDatabase( request, userDto, userSchema, @@ -256,7 +263,7 @@ export class ALTHasuraUserService { keycloakUserId, altUserRoles ) { - const encryptedPassword = await encryptPassword(userDto["password"]); + const encryptedPassword = userDto["password"]; const encPass = encryptedPassword.toString(); let query = ""; @@ -878,4 +885,69 @@ export class ALTHasuraUserService { }); } } + public async getUsername(obj, request, altUserRoles) { + const [firstName, lastName] = obj.name.split(" "); + + // Step 1: Extract initials + const initials = `${firstName[0].toLowerCase()}${ + lastName ? lastName[0].toLowerCase() : "" + }`; + + const dob = obj.dateOfBirth + .trim() + .replace(/^(\d{4})-(\d{2})-(\d{2})$/, "$3$2$1"); // Convert to ddmmyyyy + + // Step 3: Create the base username + let initialUsername = `${initials}${dob}`; + + const uniqueUsername = await this.ensureUniqueUsername( + initialUsername, + request, + altUserRoles + ); + + return uniqueUsername; + } + async ensureUniqueUsername(baseUsername, request, altUserRoles) { + let username = baseUsername; + let count = 0; // Start count from 0, to get "01" suffix for the first increment + + // Check if the exact base username is taken + while (await this.isUsernameTaken(username, request, altUserRoles)) { + // Increment count and format it as "01", "02", etc. + count++; + const suffix = String(count).padStart(2, "0"); // Formats as "01", "02", etc. + username = `${baseUsername}${suffix}`; + } + + return username; + } + + // Function to check if a username is taken in the database + async isUsernameTaken(username, request, altUserRoles) { + const data = { + query: `query GetUserByUsername($username:String) { + Users(where: {username: {_eq: $username}}) { + userId + } + }`, + variables: { username: username }, + }; + + const headers = { + Authorization: request.headers.authorization, + "x-hasura-role": getUserRole(altUserRoles), + "Content-Type": "application/json", + }; + + const config = { + method: "post", + url: process.env.REGISTRYHASURA, + headers: headers, + data: data, + }; + + const response = await this.axios(config); + return response.data.data.Users.length > 0; // Returns true if username is taken + } } 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); + } } diff --git a/src/altProgramAssociation/altProgramAssociation.controller.ts b/src/altProgramAssociation/altProgramAssociation.controller.ts index 1b007958..f60c5d80 100644 --- a/src/altProgramAssociation/altProgramAssociation.controller.ts +++ b/src/altProgramAssociation/altProgramAssociation.controller.ts @@ -13,6 +13,7 @@ import { Request, CacheInterceptor, Inject, + Query, } from "@nestjs/common"; import { ApiTags, @@ -120,4 +121,25 @@ export class ALTProgramAssociationController { altProgramSearch ); } + + @Post("/glaUserContent") + @ApiBasicAuth("access-token") + @ApiOkResponse({ description: "ALT Rules" }) + @ApiForbiddenResponse({ description: "Forbidden" }) + @ApiBody({ type: TermsProgramtoRulesDto }) + public async getGlaUserContent( + @Req() request: Request, + @Body() altTermstoRulesDto: TermsProgramtoRulesDto, + @Query('page') page: any, + @Query('limit') limit: any + ) { + + return this.altProgramAssociationService.getGlaUserContent( + request, + altTermstoRulesDto, + page, + limit + ); + } + } diff --git a/src/altStudent/altStudent.controller.ts b/src/altStudent/altStudent.controller.ts index 81144848..f61ecf55 100644 --- a/src/altStudent/altStudent.controller.ts +++ b/src/altStudent/altStudent.controller.ts @@ -26,13 +26,14 @@ import { ClassSerializerInterceptor, SerializeOptions, Req, - Res + Res, } from "@nestjs/common"; import { StudentDto } from "./dto/alt-student.dto"; // import { StudentSearchDto } from "./dto/student-search.dto"; import { ALTStudentService } from "src/adapters/hasura/altStudent.adapter"; import { StudentSearchDto } from "src/student/dto/student-search.dto"; import { ALTStudentSearchDto } from "./dto/alt-student-search.dto"; +import { Response } from "express"; @ApiTags("ALT Student") @Controller("student") @@ -109,11 +110,11 @@ export class ALTStudentController { strategy: "excludeAll", }) public async getStateList( - @Req() request :Request, - @Res() response :Response, - @Body() body?:any , - ){ - return this.altStudentService.getStateList(request,body,response); + @Req() request: Request, + @Res() response: Response, + @Body() body?: any + ) { + return this.altStudentService.getStateList(request, body, response); } @Post("/getDistrictList") @UsePipes(ValidationPipe) @@ -125,11 +126,11 @@ export class ALTStudentController { strategy: "excludeAll", }) public async getDistrictList( - @Req() request :Request, - @Res() response :Response, - @Body() body :any , - ){ - return this.altStudentService.getDistrictList(request,body,response); + @Req() request: Request, + @Res() response: Response, + @Body() body: any + ) { + return this.altStudentService.getDistrictList(request, body, response); } @Post("/getBlockList") @UsePipes(ValidationPipe) @@ -141,11 +142,11 @@ export class ALTStudentController { strategy: "excludeAll", }) public async getBlockList( - @Req() request :Request, - @Res() response :Response, - @Body() body:any , - ){ - return this.altStudentService.getBlockList(request,body,response); + @Req() request: Request, + @Res() response: Response, + @Body() body: any + ) { + return this.altStudentService.getBlockList(request, body, response); } @Post("/getSchoolList") @UsePipes(ValidationPipe) @@ -157,11 +158,11 @@ export class ALTStudentController { strategy: "excludeAll", }) public async getSchoolList( - @Req() request :Request, - @Res() response :Response, - @Body() body:any , - ){ - return this.altStudentService.getSchoolList(request,body,response); + @Req() request: Request, + @Res() response: Response, + @Body() body: any + ) { + return this.altStudentService.getSchoolList(request, body, response); } @Post("/getClass") @UsePipes(ValidationPipe) @@ -173,11 +174,11 @@ export class ALTStudentController { strategy: "excludeAll", }) public async getClass( - @Req() request :Request, - @Res() response :Response, - @Body() body:any , - ){ - return this.altStudentService.getClass(request,body,response); + @Req() request: Request, + @Res() response: Response, + @Body() body: any + ) { + return this.altStudentService.getClass(request, body, response); } @Put("/:id") @UsePipes(ValidationPipe) @@ -187,10 +188,10 @@ export class ALTStudentController { @ApiForbiddenResponse({ description: "Forbidden" }) @UseInterceptors(ClassSerializerInterceptor) public async updateStudent( - @Param("id") id : string, - @Req() request : Request, - @Body() body : any - ){ - return this.altStudentService.updateStudent(id,request,body); + @Param("id") id: string, + @Req() request: Request, + @Body() body: any + ) { + return this.altStudentService.updateStudent(id, request, body); } } diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 189332d7..bdfe8989 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -109,6 +109,7 @@ export class CourseController { @Req() request: Request ) { if (adapter === "diksha") { + console.log("adapter", adapter) return this.dikshaProvider.getCourseHierarchy(courseId, type, request); } else if (adapter === "khanacademy") { return this.khanAcademyProvider.getCourseHierarchy(