diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 5d8534e..fec4576 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -3447,4 +3447,77 @@ export const quizTrackingRelation = relations( references: [zuvyModuleQuiz.id], }), }) +); + + +export const zuvyModuleForm = main.table('zuvy_module_form', { + id: serial('id').primaryKey().notNull(), + chapterId: integer('chapter_id').references(() => zuvyModuleChapter.id).notNull(), + question: text('question'), + options: jsonb('options'), + typeId: integer('type_id').references(() => zuvyQuestionTypes.id), + isRequired: boolean('is_required').notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }), + usage: integer('usage').default(0), +}); + +export const zuvyQuestionTypes = main.table('zuvy_question_type', { + id: serial('id').primaryKey().notNull(), + questionType: varchar('question_type'), +}); + +export const zuvyFormTracking = main.table("zuvy_form_tracking", { + id: serial("id").primaryKey().notNull(), + userId: integer("user_id").references(() => users.id), + moduleId: integer("module_id"), + questionId: integer("question_id"), + chapterId: integer("chapter_id"), + status: varchar("status", { length: 255 }), + // assessmentSubmissionId: integer("assessment_submission_id").references(() => zuvyAssessmentSubmission.id, { + // onDelete: 'cascade', + // onUpdate: 'cascade', + // }), + chosenOptions: text("chosen_options").array(), + answer: text("answer"), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).defaultNow(), +}); + +// export const zuvyFormTrackingRelations = relations(zuvyFormTracking, ({ one }) => ({ + +// formSubmissions: one(zuvyAssessmentSubmission, { +// fields: [zuvyFormTracking.assessmentSubmissionId], +// references: [zuvyAssessmentSubmission.id] +// }) + +// })) + +// export const formChapterRelations = relations( +// zuvyCourseModules, +// ({many, one }) => ({ +// moduleChapterData: many(zuvyModuleChapter), +// chapterTrackingData: many(zuvyChapterTracking), +// moduleTracking: many(zuvyModuleTracking), +// formTrackingData: many(zuvyFormTracking), +// moduleFormData: many (zuvyModuleForm) +// }), +// ); + +export const formModuleRelation= relations( + zuvyModuleForm, + ({many})=>({ + formTrackingData: many(zuvyFormTracking) + }) + +); + +export const formTrackingRelation = relations( + zuvyFormTracking, + ({ one }) => ({ + formQuestion: one(zuvyModuleForm, { + fields: [zuvyFormTracking.questionId], + references: [zuvyModuleForm.id], + }), + }) ); \ No newline at end of file diff --git a/src/controller/content/content.controller.ts b/src/controller/content/content.controller.ts index 763cfd1..90daf43 100644 --- a/src/controller/content/content.controller.ts +++ b/src/controller/content/content.controller.ts @@ -39,7 +39,11 @@ import { UpdateOpenEndedDto, CreateTagDto, projectDto, - CreateChapterDto + CreateChapterDto, + formBatchDto, + editFormBatchDto, + CreateTypeDto, + CreateAndEditFormBody } from './dto/content.dto'; import { CreateProblemDto } from '../codingPlatform/dto/codingPlatform.dto'; import { difficulty } from 'drizzle/schema'; @@ -599,4 +603,99 @@ export class ContentController { } return this.contentService.getAssessmentDetailsOfOpenEnded(assessmentOutsourseId, userId); } + + + @Post('/createQuestionType') + @ApiOperation({ summary: 'Create a Question Type for the form' }) + @ApiBearerAuth() + async createQuestionType(@Body() questionType: CreateTypeDto) { + const res = await this.contentService.createQuestionType(questionType); + return res; + } + + @Get('/allQuestionType') + @ApiOperation({ summary: 'Get all the available Question Types' }) + @ApiBearerAuth() + async getAllQuestionTypes() { + const res = await this.contentService.getAllQuestionTypes(); + return res; + } + + @Post('/form') + @ApiOperation({ summary: 'Create a form' }) + @ApiBearerAuth() + async createFormForModule ( + @Query('chapterId') chapterId: number, + @Body() formQuestion: formBatchDto + ){ + const res = await this.contentService.createFormForModule( + chapterId, + formQuestion + ); + return res; + } + + @Get('/allFormQuestions/:chapterId') + @ApiOperation({ summary: 'Get all form Questions' }) + @ApiQuery({ + name: 'typeId', + required: false, + type: Number, + description: 'typeId', + }) + @ApiQuery({ + name: 'searchTerm', + required: false, + type: String, + description: 'Search by name or email', + }) + @ApiBearerAuth() + async getAllFormQuestions( + @Param('chapterId') chapterId: number, + @Query('typeId') typeId: number, + @Query('searchTerm') searchTerm: string, + ): Promise { + const res = await this.contentService.getAllFormQuestions( + chapterId, + typeId, + searchTerm, + ); + return res; + } + + @Post('/editform') + @ApiOperation({ summary: 'Create a form' }) + @ApiBearerAuth() + async editFormForModule( + @Query('chapterId') chapterId: number, + @Body() formQuestions: editFormBatchDto) { + const res = await this.contentService.editFormQuestions( + chapterId, + formQuestions + ); + return res; + } + + + @Post('/createAndEditForm/:chapterId') + @ApiOperation({ summary: 'Create a form' }) + @ApiBearerAuth() + async createAndEditForm( + @Param('chapterId') chapterId: number, + @Body() formQuestions: CreateAndEditFormBody) { + const res = await this.contentService.createAndEditFormQuestions( + chapterId, + formQuestions + ); + return res; + } + + // @Delete('/deleteFormQuestion') + // @ApiOperation({ summary: 'Delete form question' }) + // @ApiBearerAuth() + // async deleteFormQuestion(@Body() questionIds: deleteQuestionDto) { + // const res = await this.contentService.deleteForm(questionIds); + // return res; + // } + } diff --git a/src/controller/content/content.service.ts b/src/controller/content/content.service.ts index 3f22067..a186b89 100644 --- a/src/controller/content/content.service.ts +++ b/src/controller/content/content.service.ts @@ -51,10 +51,16 @@ import { deleteQuestionDto, UpdateOpenEndedDto, CreateTagDto, - projectDto + projectDto, + formBatchDto, + editFormBatchDto, + CreateTypeDto, + CreateAndEditFormBody, + formDto } from './dto/content.dto'; import { CreateProblemDto } from '../codingPlatform/dto/codingPlatform.dto'; import { PatchBootcampSettingDto } from '../bootcamp/dto/bootcamp.dto'; +import { isNullOrUndefined } from 'util'; // import Strapi from "strapi-sdk-js" const { ZUVY_CONTENT_URL, ZUVY_CONTENTS_API_URL } = process.env; // INPORTING env VALUSE ZUVY_CONTENT @@ -414,7 +420,7 @@ export class ContentService { } } - async createChapterForModule(moduleId: number, topicId: number,order:number, bootcampId: number, ) { + async createChapterForModule(moduleId: number, topicId: number, order: number, bootcampId: number,) { try { let newAssessment; let chapterData; @@ -511,7 +517,7 @@ export class ContentService { let modules = data.map((module: any) => { return { id: module.id, - name: module['projectData'].length > 0 ? module['projectData'][0]['title']: module.name, + name: module['projectData'].length > 0 ? module['projectData'][0]['title'] : module.name, description: module.description, typeId: module.typeId, order: module.order, @@ -545,7 +551,7 @@ export class ContentService { .select() .from(zuvyCourseModules) .where(eq(zuvyCourseModules.id, moduleId)); - + // const assessment = await db.query.zuvyOutsourseAssessments.findMany({ // where: (outsourseAssessments, { eq }) => // eq(outsourseAssessments.moduleId, module[0].id), @@ -592,28 +598,28 @@ export class ContentService { } async formatedChapterDetails(chapterDetails: any) { - try{ + try { chapterDetails.Quizzes = chapterDetails?.Quizzes.map((Quizzes) => { - let quizDetails = {...Quizzes.Quiz, } + let quizDetails = { ...Quizzes.Quiz, } delete Quizzes.Quiz - return {...Quizzes, ...quizDetails} + return { ...Quizzes, ...quizDetails } }) chapterDetails.OpenEndedQuestions = chapterDetails?.OpenEndedQuestions.map((OpenEndedQuestions) => { - let openEndedDetails = {...OpenEndedQuestions.OpenEndedQuestion, } + let openEndedDetails = { ...OpenEndedQuestions.OpenEndedQuestion, } delete OpenEndedQuestions.OpenEndedQuestion - return {...OpenEndedQuestions, ...openEndedDetails} + return { ...OpenEndedQuestions, ...openEndedDetails } }) chapterDetails.CodingQuestions = chapterDetails?.CodingQuestions.map((CodingQuestions) => { let codingOutsourseId = CodingQuestions.id - let codingDetails = {codingOutsourseId, ...CodingQuestions.CodingQuestion} + let codingDetails = { codingOutsourseId, ...CodingQuestions.CodingQuestion } delete CodingQuestions.CodingQuestion - return {...CodingQuestions, ...codingDetails} + return { ...CodingQuestions, ...codingDetails } }) - + return chapterDetails - } catch(err) { + } catch (err) { throw err; } } @@ -636,33 +642,33 @@ export class ContentService { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, - where: (zuvyOutsourseQuizzes, {sql}) => sql`${zuvyOutsourseQuizzes.bootcampId} = ${bootcampId} AND ${zuvyOutsourseQuizzes.chapterId} = ${chapterId}`, + where: (zuvyOutsourseQuizzes, { sql }) => sql`${zuvyOutsourseQuizzes.bootcampId} = ${bootcampId} AND ${zuvyOutsourseQuizzes.chapterId} = ${chapterId}`, with: { - Quiz:true + Quiz: true } }, - OpenEndedQuestions:{ + OpenEndedQuestions: { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, - where: (zuvyOutsourseOpenEndedQuestions, {sql}) => sql`${zuvyOutsourseOpenEndedQuestions.bootcampId} = ${bootcampId} AND ${zuvyOutsourseOpenEndedQuestions.chapterId} = ${chapterId} AND ${zuvyOutsourseOpenEndedQuestions.moduleId} = ${moduleId}`, + where: (zuvyOutsourseOpenEndedQuestions, { sql }) => sql`${zuvyOutsourseOpenEndedQuestions.bootcampId} = ${bootcampId} AND ${zuvyOutsourseOpenEndedQuestions.chapterId} = ${chapterId} AND ${zuvyOutsourseOpenEndedQuestions.moduleId} = ${moduleId}`, with: { - OpenEndedQuestion:true + OpenEndedQuestion: true } }, - CodingQuestions:{ + CodingQuestions: { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, - where: (zuvyOutsourseCodingQuestions, {sql}) => sql`${zuvyOutsourseCodingQuestions.bootcampId} = ${bootcampId} AND ${zuvyOutsourseCodingQuestions.chapterId} = ${chapterId}`, + where: (zuvyOutsourseCodingQuestions, { sql }) => sql`${zuvyOutsourseCodingQuestions.bootcampId} = ${bootcampId} AND ${zuvyOutsourseCodingQuestions.chapterId} = ${chapterId}`, with: { - CodingQuestion:{ + CodingQuestion: { columns: { id: true, title: true, @@ -680,10 +686,10 @@ export class ContentService { return formatedData; } const chapterDetails = await db - .select() - .from(zuvyModuleChapter) - .where(eq(zuvyModuleChapter.id, chapterId)); - + .select() + .from(zuvyModuleChapter) + .where(eq(zuvyModuleChapter.id, chapterId)); + const modifiedChapterDetails: { id: number; title: string; @@ -728,6 +734,17 @@ export class ContentService { ) : []; modifiedChapterDetails.codingQuestionDetails = codingProblemDetails; + } else if (chapterDetails[0].topicId == 7) { + const formDetails = + chapterDetails[0].formQuestions !== null + ? await db + .select() + .from(zuvyModuleForm) + .where( + sql`${inArray(zuvyModuleForm.id, Object.values(chapterDetails[0].formQuestions))}`, + ) + : []; + modifiedChapterDetails.formQuestionDetails = formDetails; } else { let content = [ { @@ -968,6 +985,38 @@ export class ContentService { .set({ usage: sql`${zuvyCodingQuestions.usage}::numeric + 1` }) .where(eq(zuvyCodingQuestions.id, editData.codingQuestions)); } + } else if (editData.formQuestions) { + if (editData.formQuestions.length == 0) { + editData.formQuestions = null; + } + const earlierFormIds = + chapter[0].formQuestions != null + ? Object.values(chapter[0].formQuestions) + : []; + const remainingFormIds = + editData.formQuestions != null && earlierFormIds.length > 0 + ? earlierFormIds.filter( + (questionId) => !editData.formQuestions.includes(questionId), + ) + : []; + const toUpdateIds = + editData.formQuestions != null && earlierFormIds.length > 0 + ? editData.formQuestions.filter( + (questionId) => !earlierFormIds.includes(questionId), + ) + : editData.formQuestions; + if (remainingFormIds.length > 0) { + await db + .update(zuvyModuleForm) + .set({ usage: sql`${zuvyModuleForm.usage}::numeric - 1` }) + .where(sql`${inArray(zuvyModuleForm.id, remainingFormIds)}`); + } + if (toUpdateIds.length > 0) { + await db + .update(zuvyModuleForm) + .set({ usage: sql`${zuvyModuleForm.usage}::numeric + 1` }) + .where(sql`${inArray(zuvyModuleForm.id, toUpdateIds)}`); + } } await db .update(zuvyModuleChapter) @@ -991,7 +1040,7 @@ export class ContentService { .insert(zuvyModuleAssessment) .values(assessment) .returning(); - + return newAssessment; } catch (err) { throw err; @@ -1013,8 +1062,8 @@ export class ContentService { OutsourseCodingQuestions: true }, }); - - if (assessment == undefined ||assessment.length == 0) { + + if (assessment == undefined || assessment.length == 0) { throw ({ status: 'error', statusCode: 404, @@ -1023,11 +1072,11 @@ export class ContentService { } else { let { bootcampId, moduleId, chapterId, ModuleAssessment, OutsourseQuizzes, OutsourseOpenEndedQuestions, OutsourseCodingQuestions } = assessment[0]; let { mcqIds, openEndedQuestionIds, codingProblemIds, title, description, ...OutsourseAssessmentData__ } = assessmentBody; - + let assessment_id = ModuleAssessment.id; - - let assessmentData = { title, description}; - + + let assessmentData = { title, description }; + // filter out the ids that are not in the assessment let existingQuizIds = OutsourseQuizzes.map((q) => q.quiz_id).filter(id => id !== null); let existingOpenEndedQuestionIds = OutsourseOpenEndedQuestions.map((q) => q.openEndedQuestionId).filter(id => id !== null); @@ -1040,7 +1089,7 @@ export class ContentService { let quizIdsToAdd = mcqIds.filter((id) => !existingQuizIds.includes(id)); let openEndedQuestionIdsToAdd = openEndedQuestionIds.filter((id) => !existingOpenEndedQuestionIds.includes(id)); let codingQuestionIdsToAdd = codingProblemIds.filter((id) => !existingCodingQuestionIds.includes(id)); - + // Delete operations if (quizIdsToDelete.length > 0) { await db @@ -1057,21 +1106,21 @@ export class ContentService { .delete(zuvyOutsourseCodingQuestions) .where(sql`${zuvyOutsourseCodingQuestions.assessmentOutsourseId} = ${assessmentOutsourseId} AND ${inArray(zuvyOutsourseCodingQuestions.codingQuestionId, codingQuestionIdsToDelete)}`); } - + // Update assessment data let updatedOutsourseAssessment = await db.update(zuvyOutsourseAssessments).set(OutsourseAssessmentData__).where(eq(zuvyOutsourseAssessments.id, assessmentOutsourseId)).returning(); - + let updatedAssessment = await db .update(zuvyModuleAssessment) .set(assessmentData) .where(eq(zuvyModuleAssessment.id, assessment_id)) .returning(); - + // Insert new data let mcqArray = quizIdsToAdd.map(id => ({ quiz_id: id, bootcampId, chapterId, assessmentOutsourseId })); let openEndedQuestionsArray = openEndedQuestionIdsToAdd.map(id => ({ openEndedQuestionId: id, bootcampId, moduleId, chapterId, assessmentOutsourseId })); let codingProblemsArray = codingQuestionIdsToAdd.map(id => ({ codingQuestionId: id, bootcampId, moduleId, chapterId, assessmentOutsourseId })); - + if (mcqArray.length > 0) { let createZOMQ = await db.insert(zuvyOutsourseQuizzes).values(mcqArray).returning(); if (createZOMQ.length > 0) { @@ -1082,7 +1131,7 @@ export class ContentService { .where(sql`${inArray(zuvyModuleQuiz.id, toUpdateIds)}`); } } - + if (openEndedQuestionsArray.length > 0) { let createZOOQ = await db.insert(zuvyOutsourseOpenEndedQuestions).values(openEndedQuestionsArray).returning(); if (createZOOQ.length > 0) { @@ -1093,7 +1142,7 @@ export class ContentService { .where(sql`${inArray(zuvyOpenEndedQuestions.id, toUpdateIds)}`); } } - + if (codingProblemsArray.length > 0) { let createZOCQ = await db.insert(zuvyOutsourseCodingQuestions).values(codingProblemsArray).returning(); if (createZOCQ.length > 0) { @@ -1104,7 +1153,7 @@ export class ContentService { .where(sql`${inArray(zuvyCodingQuestions.id, toUpdateIds)}`); } } - } + } return { status: 'success', code: 200, @@ -1192,8 +1241,8 @@ export class ContentService { // openEndedQuesDetails, // codingQuesDetails, // }; - // } - return assessment; + // } + return assessment; } catch (err) { throw err; } @@ -1638,16 +1687,16 @@ export class ContentService { throw err; } } - async getStudentsOfAssessment(assessmentId:number, chapterId: number, moduleId: number, bootcampId: number, req) { + async getStudentsOfAssessment(assessmentId: number, chapterId: number, moduleId: number, bootcampId: number, req) { try { - let {id} = req.user[0]; + let { id } = req.user[0]; const assessment = await db.query.zuvyOutsourseAssessments.findMany({ where: (zuvyOutsourseAssessments, { eq }) => sql`${zuvyOutsourseAssessments.assessmentId} = ${assessmentId} AND ${zuvyOutsourseAssessments.bootcampId} = ${bootcampId} AND ${zuvyOutsourseAssessments.chapterId} = ${chapterId} AND ${zuvyOutsourseAssessments.moduleId} = ${moduleId}`, with: { submitedOutsourseAssessments: { where: (zuvyAssessmentSubmission, { eq }) => eq(zuvyAssessmentSubmission.userId, id), - columns:{ + columns: { id: true, marks: true, userId: true, @@ -1661,7 +1710,7 @@ export class ContentService { columns: { // id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { Quiz: true, @@ -1671,7 +1720,7 @@ export class ContentService { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { OpenEndedQuestion: true @@ -1681,7 +1730,7 @@ export class ContentService { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { CodingQuestion: true @@ -1689,17 +1738,17 @@ export class ContentService { }, }, }) - if (assessment == undefined ||assessment.length == 0) { + if (assessment == undefined || assessment.length == 0) { throw ({ status: 'error', statusCode: 404, message: 'Assessment not found', }); - } - assessment[0]["totalQuizzes"] = assessment[0]?.Quizzes.length|| 0; + } + assessment[0]["totalQuizzes"] = assessment[0]?.Quizzes.length || 0; assessment[0]["totalOpenEndedQuestions"] = assessment[0]?.OpenEndedQuestions.length || 0; assessment[0]["totalCodingQuestions"] = assessment[0]?.CodingQuestions.length || 0; - + delete assessment[0].Quizzes; delete assessment[0].OpenEndedQuestions; delete assessment[0].CodingQuestions; @@ -1716,7 +1765,7 @@ export class ContentService { */ async startAssessmentForStudent(assessmentOutsourseId: number, req) { try { - let {id} = req.user[0]; + let { id } = req.user[0]; const assessment = await db.query.zuvyOutsourseAssessments.findMany({ where: (zuvyOutsourseAssessments, { eq }) => eq(zuvyOutsourseAssessments.id, assessmentOutsourseId), @@ -1726,17 +1775,17 @@ export class ContentService { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { - CodingQuestion:true + CodingQuestion: true } }, Quizzes: { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { Quiz: true, @@ -1746,7 +1795,7 @@ export class ContentService { columns: { id: true, assessmentOutsourseId: true, - bootcampId:true + bootcampId: true }, with: { OpenEndedQuestion: true @@ -1761,32 +1810,32 @@ export class ContentService { statusCode: 404, message: 'Assessment not found', }); - } + } let startedAt = new Date().toISOString(); let submission; - submission = await db.select().from(zuvyAssessmentSubmission).where( sql`${zuvyAssessmentSubmission.userId} = ${id} AND ${zuvyAssessmentSubmission.assessmentOutsourseId} = ${assessmentOutsourseId} AND ${zuvyAssessmentSubmission.submitedAt} IS NULL`); + submission = await db.select().from(zuvyAssessmentSubmission).where(sql`${zuvyAssessmentSubmission.userId} = ${id} AND ${zuvyAssessmentSubmission.assessmentOutsourseId} = ${assessmentOutsourseId} AND ${zuvyAssessmentSubmission.submitedAt} IS NULL`); if (submission.length == 0) { - submission = await db.insert(zuvyAssessmentSubmission).values({userId: id, assessmentOutsourseId, startedAt }).returning(); + submission = await db.insert(zuvyAssessmentSubmission).values({ userId: id, assessmentOutsourseId, startedAt }).returning(); } - + let formatedData = await this.formatedChapterDetails(assessment[0]); formatedData.Quizzes = formatedData.Quizzes.length formatedData.OpenEndedQuestions = formatedData.OpenEndedQuestions.length - return {...formatedData, submission: submission[0]}; + return { ...formatedData, submission: submission[0] }; } catch (err) { throw err; } } - async getAssessmentDetailsOfQuiz(assessment_outsourse_id:number, userId){ + async getAssessmentDetailsOfQuiz(assessment_outsourse_id: number, userId) { try { const assessment = await db.query.zuvyOutsourseQuizzes.findMany({ where: (zuvyOutsourseQuizzes, { eq }) => eq(zuvyOutsourseQuizzes.assessmentOutsourseId, assessment_outsourse_id), with: { - submissionsData:{ + submissionsData: { where: (zuvyQuizTracking, { eq }) => eq(zuvyQuizTracking.userId, userId), columns: { id: true, @@ -1798,7 +1847,7 @@ export class ContentService { }, Quiz: { columns: { - id:true, + id: true, question: true, options: true, difficulty: true, @@ -1810,20 +1859,20 @@ export class ContentService { }) if (assessment.length == 0) { return []; - } + } return assessment; } catch (err) { throw err; } } - async getAssessmentDetailsOfOpenEnded(assessment_outsourse_id:number, userId){ + async getAssessmentDetailsOfOpenEnded(assessment_outsourse_id: number, userId) { try { const assessment = await db.query.zuvyOutsourseOpenEndedQuestions.findMany({ where: (zuvyOutsourseOpenEndedQuestions, { eq }) => eq(zuvyOutsourseOpenEndedQuestions.assessmentOutsourseId, assessment_outsourse_id), with: { - submissionsData:{ + submissionsData: { where: (zuvyOpenEndedQuestionSubmission, { eq }) => eq(zuvyOpenEndedQuestionSubmission.userId, userId), columns: { id: true, @@ -1845,7 +1894,7 @@ export class ContentService { }) if (assessment.length == 0) { return []; - } + } return assessment; } catch (err) { throw err; @@ -1853,4 +1902,368 @@ export class ContentService { } + async getAllQuestionTypes() { + try { + const allQuestionTypes = await db.select().from(zuvyQuestionTypes); + if (allQuestionTypes.length > 0) { + return { + status: 'success', + code: 200, + allQuestionTypes, + }; + } else { + return []; + } + } catch (err) { + throw err; + } + } + + async createQuestionType(questionType: CreateTypeDto) { + try { + const newQuestionType = await db.insert(zuvyQuestionTypes).values(questionType).returning(); + if (newQuestionType.length > 0) { + return { + status: 'success', + code: 200, + newQuestionType, + }; + } else { + return { + status: 'error', + code: 404, + message: 'Question Type is not created.Please try again.', + }; + } + } catch (err) { + throw err; + } + } + + + async createFormForModule(chapterId: number, form: formBatchDto) { + try { + + if (isNaN(chapterId)) { + return { + status: "error", + code: 400, + message: "Invalid chapterId. Please provide a valid number." + }; + } + + const formQuestion = form.questions.map((f) => ({ + chapterId, + question: f.question, + options: f.options, + typeId: f.typeId, + isRequired: f.isRequired, + })); + + const allFieldsFilled = formQuestion.every(question => question.question !== null && question.options !== null && question.typeId !== null && question.isRequired !== null); + if (!allFieldsFilled) { + return { + status: "error", + code: 400, + message: " One or more fields are empty. Please try again." + }; + } + + const result = await db + .insert(zuvyModuleForm) + .values(formQuestion) + .returning(); + + + const formIds = result.length > 0 ? result.map(obj => obj.id) : []; + + const existingChapter = await db + .select() + .from(zuvyModuleChapter) + .where(eq(zuvyModuleChapter.id, chapterId)) + .limit(1); + + const chapter = existingChapter[0] as { formQuestions: number[] }; + + const existingFormQuestions = chapter.formQuestions || []; + + const updatedFormQuestions = [...existingFormQuestions, ...formIds]; + + const updatedChapter = await db + .update(zuvyModuleChapter) + .set({ + formQuestions: updatedFormQuestions + }) + .where(eq(zuvyModuleChapter.id, formQuestion[0].chapterId)) + .returning(); + + + + if (result.length > 0 || updatedChapter.length > 0) { + return { + + status: "success", + code: 200, + result, + updatedChapter + } + } + else { + return { + status: "error", + code: 404, + message: "Form questions did not create successfully.Please try again" + } + } + } catch (err) { + throw err; + } + } + + async getAllFormQuestions( + chapterId: number, + typeId: number, + searchTerm: string = '', + ) { + try { + let queryString; + if (!Number.isNaN(typeId) && questionType == undefined) { + queryString = sql`${zuvyModuleForm.typeId} = ${typeId}`; + } + const result = await db + .select() + .from(zuvyModuleForm) + .where( + and( + queryString, + sql`${zuvyModuleForm.chapterId} = ${chapterId}`, + sql`((LOWER(${zuvyModuleForm.question}) LIKE '%' || ${searchTerm.toLowerCase()} || '%'))`, + ), + ); + return result; + } catch (err) { + throw err; + } + } + + + async editFormQuestions(chapterId: number, editFormDetails: editFormBatchDto) { + try { + const editFormQuestions = editFormDetails.questions.map((f) => ({ + chapterId, + id: f.id, + question: f.question, + options: f.options, + typeId: f.typeId, + isRequired: f.isRequired, + })); + + const allFieldsFilled = editFormQuestions.every( + (question) => + question.question !== null && + question.options !== null && + question.typeId !== null && + question.isRequired !== null + ); + + if (!allFieldsFilled) { + return { + status: "error", + code: 400, + message: "One or more fields are empty. Please try again.", + }; + } + + const results = []; + + const existingFormRecords = await db + .select() + .from(zuvyModuleForm) + .where(eq(zuvyModuleForm.chapterId, chapterId)); + + const existingFormIds = existingFormRecords.map((record) => record.id); + const providedFormIds = editFormQuestions.map((question) => question.id); + const idsToRemove = existingFormIds.filter(id => !providedFormIds.includes(id)); + + + if (idsToRemove.length > 0) { + await db + .delete(zuvyModuleForm) + .where(inArray(zuvyModuleForm.id, idsToRemove)); + } + + for (const formQuestion of editFormQuestions) { + if (formQuestion.id) { + const existingRecord = await db + .select() + .from(zuvyModuleForm) + .where(eq(zuvyModuleForm.id, formQuestion.id)) + + if (existingRecord) { + const result = await db + .update(zuvyModuleForm) + .set({ + chapterId: formQuestion.chapterId, + question: formQuestion.question, + options: formQuestion.options, + typeId: formQuestion.typeId, + isRequired: formQuestion.isRequired, + }) + .where(eq(zuvyModuleForm.id, formQuestion.id)) + .returning(); + results.push(result); + } else { + return { + status: "Error", + code: 400, + message: "Form question id(s) are invalid", + }; + } + } else { + return { + status: "Error", + code: 400, + message: "Form question ids(s) are missing", + }; + } + } + + + const formIds = results.flat().map((obj) => obj.id); + + const updatedChapter = await db + .update(zuvyModuleChapter) + .set({ + formQuestions: formIds, + }) + .where(eq(zuvyModuleChapter.id, chapterId)) + .returning(); + + return { + status: "success", + code: 200, + message: "Form questions are updated successfully", + results, + updatedChapter, + }; + } catch (error) { + throw error; + } + } + + + // async deleteForm(id: deleteQuestionDto) { + // try { + // const usedForm = await db + // .select() + // .from(zuvyModuleForm) + // .where( + // sql`${inArray(zuvyModuleForm.id, id.questionIds)} and ${zuvyModuleForm.usage} > 0`, + // ); + // let deletedQuestions; + // if (usedForm.length > 0) { + // const usedIds = usedForm.map((form) => form.id); + // const remainingIds = id.questionIds.filter( + // (questionId) => !usedIds.includes(questionId), + // ); + // deletedQuestions = + // remainingIds.length > 0 + // ? await db + // .delete(zuvyModuleForm) + // .where(sql`${inArray(zuvyModuleForm.id, remainingIds)}`) + // .returning() + // : []; + // if (deletedQuestions.length > 0) { + // return { + // status: 'success', + // code: 200, + // message: `Form questions which is used in other places like chapters and assessment cannot be deleted`, + // }; + // } else { + // return { + // status: 'error', + // code: 400, + // message: `Questions cannot be deleted`, + // }; + // } + // } + // deletedQuestions = await db + // .delete(zuvyModuleForm) + // .where(sql`${inArray(zuvyModuleForm.id, id.questionIds)}`) + // .returning(); + // if (deletedQuestions.length > 0) { + // return { + // status: 'success', + // code: 200, + // message: 'The form questions has been deleted successfully', + // }; + // } else { + // return { + // status: 'error', + // code: 400, + // message: `Questions cannot be deleted`, + // }; + // } + // } catch (err) { + // throw err; + // } + // } + + + + + async createAndEditFormQuestions(chapterId: number, form: CreateAndEditFormBody) { + try { + + if (isNaN(chapterId)) { + return { + status: "error", + code: 400, + message: "Invalid chapterId. Please provide a valid number." + }; + } + + + if (form.formQuestionDto && !(form.editFormQuestionDto)) { + await this.createFormForModule(chapterId, form.formQuestionDto); + + + } else if (form.editFormQuestionDto && !(form.formQuestionDto)) { + await this.editFormQuestions(chapterId, form.editFormQuestionDto); + + } else if (form.editFormQuestionDto && form.formQuestionDto) { + + await this.editFormQuestions(chapterId, form.editFormQuestionDto); + await this.createFormForModule(chapterId, form.formQuestionDto); + }else{ + return { + status: "error", + code: 400, + message: "Invalid input." + }; + } + + const res1 = await db + .select() + .from(zuvyModuleForm) + .where(eq(zuvyModuleForm.chapterId, chapterId)) + + const res2 = await db + .select() + .from(zuvyModuleChapter) + .where(eq(zuvyModuleChapter.id, chapterId)) + + + return { + status: "success", + code: 200, + message: "Form questions are updated successfully", + res1, + res2, + }; + + } catch (err) { + throw err; + } + } } diff --git a/src/controller/content/dto/content.dto.ts b/src/controller/content/dto/content.dto.ts index 9f306d8..46c11ef 100644 --- a/src/controller/content/dto/content.dto.ts +++ b/src/controller/content/dto/content.dto.ts @@ -817,4 +817,227 @@ export class CreateChapterDto { @IsOptional() @IsNumber() order: number; +} + +export class CreateTypeDto{ + @ApiProperty({ + type: String, + example : 'Multiple Choice', + required: true + }) + + @IsString() + @IsNotEmpty() + questionType : string +} + +export class formDto { + + @ApiProperty({ + type: String, + example: 'What is your opinion about the course?', + }) + @IsString() + @IsOptional() + question: string; + + @ApiProperty({ + type: 'object', + example: { + 1: 'Option 1', + 2: 'Option 2', + 3: 'Option 3', + 4: 'Option 4', + } + }) + @IsObject() + @IsOptional() + options: object; + + @ApiProperty({ + type: Number, + example: 2, + }) + @IsNumber() + @IsOptional() + typeId: number; + + @ApiProperty({ + type: Boolean, + example: false + }) + @IsOptional() + @IsBoolean() + isRequired: boolean; + +} + +export class formBatchDto { + @ApiProperty({ + type: [formDto], + example: [ + { + question: 'What do you like about the course?', + options: { + 1: 'Option 1', + 2: 'Option 2', + 3: 'Option 3', + 4: 'Option 4', + }, + typeId: 1, + isRequired:false, + }, + { + question: 'What do you want to improve about the course?', + options: { + 1: 'Paris', + 2: 'London', + 3: 'Berlin', + 4: 'Rome', + }, + typeId: 2, + isRequired:false, + }, + { + question: 'What is your opinion about the course?', + typeId: 3, + isRequired:false, + }, + { + question: 'Choose date of opting the course', + typeId: 4, + isRequired:false, + }, + { + question: 'Choose time of opting the course', + typeId: 5, + isRequired:false, + }, + ], + required: true, + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => formDto) + questions: formDto[]; +} + +export class editFormDto { + + @ApiProperty({ + type: Number, + example: 1, + required: true + }) + @IsNumber() + id: number; + + @ApiProperty({ + type: String, + example: 'What is your opinion about the course?', + }) + @IsString() + @IsOptional() + question: string; + + @ApiProperty({ + type: 'object', + example: { + 1: 'Option 1', + 2: 'Option 2', + 3: 'Option 3', + 4: 'Option 4', + } + }) + @IsObject() + @IsOptional() + options: object; + + @ApiProperty({ + type: Number, + example: 1, + }) + @IsNumber() + @IsOptional() + typeId: number; + + @ApiProperty({ + type: Boolean, + example: false + }) + @IsOptional() + @IsBoolean() + isRequired: boolean; +} + +export class editFormBatchDto { + @ApiProperty({ + type: [editFormDto], + example: [ + { + id: 1, + question: 'What is the national animal of India?', + options: { + 1: 'Option 1', + 2: 'Option 2', + 3: 'Option 3', + 4: 'Option 4', + }, + typeId: 1, + isRequired:false, + }, + { + id: 2, + question: 'What is the capital of France?', + options: { + 1: 'Paris', + 2: 'London', + 3: 'Berlin', + 4: 'Rome', + }, + typeId: 2, + isRequired:false, + }, + { + id: 3, + question: 'What is the national animal of India?', + typeId: 3, + isRequired:false, + }, + { + id: 4, + question: 'Choose date of opting the course', + typeId: 4, + isRequired:false, + }, + { + id: 5, + question: 'Choose time of opting the course', + typeId: 5, + isRequired:false, + }, + ], + required: true, + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => editFormDto) + questions: editFormDto[]; + +} + +export class CreateAndEditFormBody { + @ApiProperty({ type: formBatchDto }) + @IsOptional() + @ValidateNested() + @Type(() => formBatchDto) + formQuestionDto: formBatchDto; + + @ApiProperty({ type: editFormBatchDto }) + @IsOptional() + @ValidateNested() + @Type(() => editFormBatchDto) + editFormQuestionDto: editFormBatchDto; } \ No newline at end of file