From 97e3a0822513f7c8ad0c170e43b7d1e9a2c50a7a Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Tue, 8 Oct 2024 15:26:17 +0530 Subject: [PATCH 1/9] content schema updated for mechanics --- src/schemas/content.schema.ts | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/schemas/content.schema.ts b/src/schemas/content.schema.ts index ea488b9..eec53e8 100644 --- a/src/schemas/content.schema.ts +++ b/src/schemas/content.schema.ts @@ -1,7 +1,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, now, Mixed } from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; -import { IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsNumber, IsArray, IsObject } from 'class-validator'; @Schema({ collection: 'content' }) export class content { @@ -30,6 +30,41 @@ export class content { @Prop({ required: true }) contentSourceData: [Mixed]; + @Prop({ required: false, type: Array }) + @IsOptional() + @IsArray() + mechnics_data: [ + { + mechnics_id: string; + options: [ + { + text: string; + audio_url: string; + image_url: string; + isAns: boolean; + side: string; + } + ]; + hints: { + text: string; + audio_url: string; + image_url: string; + }; + time_limit: number; + correctness: { + "50%": [String], + } + } + ]; + + @Prop({ type: Object, required: false }) + @IsOptional() + @IsObject() + level_complexity: { + level: string; + level_competency: string; + }; + @Prop({ type: String, required: false }) @IsOptional() @IsString() From 1fdd48ca69549d3ac9ace5a3db7bf5bf1fe251b5 Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Thu, 10 Oct 2024 16:58:28 +0530 Subject: [PATCH 2/9] API changes in getcontent for mechanics feature --- src/controllers/content.controller.ts | 284 +++++++++++++++++++++++++- src/schemas/content.schema.ts | 11 +- src/services/content.service.ts | 33 +++ 3 files changed, 315 insertions(+), 13 deletions(-) diff --git a/src/controllers/content.controller.ts b/src/controllers/content.controller.ts index f5f96b4..1b83053 100644 --- a/src/controllers/content.controller.ts +++ b/src/controllers/content.controller.ts @@ -888,16 +888,29 @@ export class contentController { async getContent(@Res() response: FastifyReply, @Body() queryData: any) { try { const Batch: any = queryData.limit || 5; - const contentCollection = await this.contentService.search( - queryData.tokenArr, - queryData.language, - queryData.contentType, - parseInt(Batch), - queryData.tags, - queryData.cLevel, - queryData.complexityLevel, - queryData.graphemesMappedObj, - ); + + let contentCollection; + + if(queryData.mechanics_id === undefined){ + contentCollection = await this.contentService.search( + queryData.tokenArr, + queryData.language, + queryData.contentType, + parseInt(Batch), + queryData.tags, + queryData.cLevel, + queryData.complexityLevel, + queryData.graphemesMappedObj, + ); + }else{ + contentCollection = await this.contentService.getMechanicsContentData( + queryData.contentType, + queryData.mechanics_id, + parseInt(Batch), + queryData.language + ); + } + return response.status(HttpStatus.CREATED).send({ status: 'success', data: contentCollection, @@ -1231,4 +1244,255 @@ export class contentController { deleted, }); } + + @ApiExcludeEndpoint(true) + @ApiBody({ + description: 'Request body parameters for get content', + required: true, + schema: { + type: 'object', + properties: { + tokenArr: { + type: 'array', + description: 'Array of tokens', + items: { + type: 'string', + example: 'c' + } + }, + language: { + type: 'string', + description: 'Language code', + example: 'en' + }, + contentType: { + type: 'string', + description: 'Type of content', + example: 'Word' + }, + limit: { + type: 'number', + description: 'Limit on the number of items', + example: 5 + }, + cLevel: { + type: 'string', + description: 'Content level', + example: 'L2' + }, + complexityLevel: { + type: 'array', + description: 'Array of complexity levels', + items: { + type: 'string', + example: 'C1' + } + }, + graphemesMappedObj: { + type: 'object', + description: 'Object mapping graphemes to their representations', + additionalProperties: { + type: 'array', + items: { + type: 'string', + example: 'ch' + } + }, + example: { + "c": ["ch"], + "o": ["o"], + "a": ["a"], + "v": ["v", "ve"], + "w": ["w", "wh"], + "æ": ["a", "ai", "au"], + "n": ["n"], + "i": ["i"], + "θ": ["th"] + } + } + } + } + }) + @ApiResponse({ + status: 200, + description: 'Successful response', + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'success' }, + data: { + type: 'object', + properties: { + wordsArr: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { type: 'string', example: '660f9545367a62b3902dd58b' }, + contentId: { type: 'string', example: 'f8dd7c97-53f7-4676-b597-4a52aaface5c' }, + collectionId: { type: 'string', example: '6a519951-8635-4d89-821a-d3eb60f6e1ec' }, + name: { type: 'string', example: 'L2_new_3' }, + contentType: { type: 'string', example: 'Word' }, + contentSourceData: { + type: 'array', + items: { + type: 'object', + properties: { + language: { type: 'string', example: 'en' }, + audioUrl: { type: 'string', example: '' }, + text: { type: 'string', example: 'five' }, + phonemes: { + type: 'array', + items: { type: 'string', example: 'f' } + }, + wordCount: { type: 'number', example: 1 }, + wordFrequency: { + type: 'object', + additionalProperties: { type: 'number', example: 1 } + }, + syllableCount: { type: 'number', example: 4 }, + syllableCountMap: { + type: 'object', + additionalProperties: { type: 'number', example: 4 } + }, + syllableCountArray: { + type: 'array', + items: { + type: 'object', + properties: { + k: { type: 'string', example: 'five' }, + v: { type: 'number', example: 4 } + } + } + } + } + } + }, + status: { type: 'string', example: 'live' }, + publisher: { type: 'string', example: 'ekstep' }, + language: { type: 'string', example: 'en' }, + contentIndex: { type: 'number', example: 141 }, + tags: { + type: 'array', + items: { type: 'string' } + }, + createdAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, + updatedAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, + __v: { type: 'number', example: 0 }, + matchedChar: { + type: 'array', + items: { type: 'string', example: 'v' } + } + } + } + }, + contentForToken: { + type: 'object', + additionalProperties: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { type: 'string', example: '660f9545367a62b3902dd58b' }, + contentId: { type: 'string', example: 'f8dd7c97-53f7-4676-b597-4a52aaface5c' }, + collectionId: { type: 'string', example: '6a519951-8635-4d89-821a-d3eb60f6e1ec' }, + name: { type: 'string', example: 'L2_new_3' }, + contentType: { type: 'string', example: 'Word' }, + contentSourceData: { + type: 'array', + items: { + type: 'object', + properties: { + language: { type: 'string', example: 'en' }, + audioUrl: { type: 'string', example: '' }, + text: { type: 'string', example: 'five' }, + phonemes: { + type: 'array', + items: { type: 'string', example: 'f' } + }, + wordCount: { type: 'number', example: 1 }, + wordFrequency: { + type: 'object', + additionalProperties: { type: 'number', example: 1 } + }, + syllableCount: { type: 'number', example: 4 }, + syllableCountMap: { + type: 'object', + additionalProperties: { type: 'number', example: 4 } + }, + syllableCountArray: { + type: 'array', + items: { + type: 'object', + properties: { + k: { type: 'string', example: 'five' }, + v: { type: 'number', example: 4 } + } + } + } + } + } + }, + status: { type: 'string', example: 'live' }, + publisher: { type: 'string', example: 'ekstep' }, + language: { type: 'string', example: 'en' }, + contentIndex: { type: 'number', example: 141 }, + tags: { + type: 'array', + items: { type: 'string' } + }, + createdAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, + updatedAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, + __v: { type: 'number', example: 0 }, + matchedChar: { + type: 'array', + items: { type: 'string', example: 'v' } + } + } + } + } + } + } + } + } + } + }) + @ApiResponse({ + status: 500, + description: 'Error while fetching data from the content table', + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'error' }, + msg: { type: 'string', example: 'Server error - error message' }, + }, + }, + }) + @ApiOperation({ + summary: 'Get all data from the content table' + }) + @Get('/getMeachnicsContent') + async getMechanicsContent( + @Res() response: FastifyReply, + @Query('contentType') contentType: string,@Query('mechanics_id') mechanics_id: string, @Query() { contentlimit = 5 }, @Query('language') language: string + ) { + try { + const Batch: any = contentlimit || 5; + const contentCollection = await this.contentService.getMechanicsContentData( + contentType, + mechanics_id, + Batch, + language + ); + return response.status(HttpStatus.CREATED).send({ + status: 200, + contentCollection, + }); + } catch (error) { + return response.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ + status: 500, + message: 'Server error - ' + error, + }); + } + } } diff --git a/src/schemas/content.schema.ts b/src/schemas/content.schema.ts index eec53e8..e8fb272 100644 --- a/src/schemas/content.schema.ts +++ b/src/schemas/content.schema.ts @@ -33,9 +33,14 @@ export class content { @Prop({ required: false, type: Array }) @IsOptional() @IsArray() - mechnics_data: [ + mechanics_data: [ { - mechnics_id: string; + mechanics_id: string; + language:string; + jumbled_text: string; + text: string; + audio_url: string; + image_url: string; options: [ { text: string; @@ -52,7 +57,7 @@ export class content { }; time_limit: number; correctness: { - "50%": [String], + "50%": [string], } } ]; diff --git a/src/services/content.service.ts b/src/services/content.service.ts index 0600017..6c2462b 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -1621,4 +1621,37 @@ export class contentService { return []; } } + + async getMechanicsContentData( + contentType, + mechanics_id, + limit = 5, + language + ) { + + const wordsArr = await this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language} + } + } + }, + { $sample: { size: limit } }, + ]); + + wordsArr.map((content) => { + const { mechanics_data } = content; + const mechanicData = mechanics_data.find( + (mechanic) => {return mechanic.mechanics_id === mechanics_id} + ); + content.mechanics_data = []; + content.mechanics_data.push(mechanicData); + return content; + }); + + return { wordsArr: wordsArr }; + } } From e6cbb8a981cb5e0809b2adedae3652374c40068d Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Thu, 10 Oct 2024 17:06:35 +0530 Subject: [PATCH 3/9] Changed dev pipeline to 1.3-dev branch --- .github/workflows/Dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Dev.yml b/.github/workflows/Dev.yml index 5a2ac92..9956409 100644 --- a/.github/workflows/Dev.yml +++ b/.github/workflows/Dev.yml @@ -2,7 +2,7 @@ name: DEV DEPLOYMENT on: push: - branches: [ all-1.1-dev ] + branches: [ all-1.3-dev ] jobs: build: From 9e9d5eb3feca17c71d940c85725f3f6982f1f01f Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Mon, 14 Oct 2024 12:55:10 +0530 Subject: [PATCH 4/9] All staging workflow added to 1.3 --- .github/workflows/staging.yml | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/staging.yml diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml new file mode 100644 index 0000000..c536ca7 --- /dev/null +++ b/.github/workflows/staging.yml @@ -0,0 +1,71 @@ +name: STAGING DEPLOYMENT + +on: + push: + branches: [ all-1.3-staging ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME_DEVOPS }} + password: ${{ secrets.DOCKERHUB_TOKEN_DEVOPS }} + + - + name: Load .env from GitHub Secret + run: echo "${{ secrets.MY_ENV_VARS_STAGING }}" > .env + + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ${{ secrets.CONTAINER_REGISTRY_STAGING }}:${{ secrets.IMAGE_TAG }} + env: + MY_ENV_VARS: ${{ secrets.MY_ENV_VARS_STAGING }} + deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [build] + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Deploy Stack + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: ${{ secrets.PORT }} + script: | + + ssh -i ~/.ssh/all_staging_key.pem ${{ secrets.USERNAME_STAGING }}@${{ secrets.HOST_STAGING }} 'bash -s' << 'EOF' + + docker login + + docker container stop ${{ secrets.CONTAINER_NAME }} + + docker rm ${{ secrets.CONTAINER_NAME }} + + docker rmi ${{ secrets.CONTAINER_REGISTRY_STAGING }}:${{ secrets.IMAGE_TAG }} + + docker pull ${{ secrets.CONTAINER_REGISTRY_STAGING }}:${{ secrets.IMAGE_TAG }} + + cd /home/all-staging-user/all-services/all-content-service + + sudo docker-compose up -d --force-recreate --no-deps + + + EOF + + \ No newline at end of file From ef4d80618c9e0e6c4042d3017c75d2d55fe02e7b Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Wed, 16 Oct 2024 12:53:32 +0530 Subject: [PATCH 5/9] level competency added as param for content API --- src/controllers/content.controller.ts | 254 +------------------------- src/services/content.service.ts | 126 ++++++++++--- 2 files changed, 104 insertions(+), 276 deletions(-) diff --git a/src/controllers/content.controller.ts b/src/controllers/content.controller.ts index 1b83053..e35e111 100644 --- a/src/controllers/content.controller.ts +++ b/src/controllers/content.controller.ts @@ -907,7 +907,8 @@ export class contentController { queryData.contentType, queryData.mechanics_id, parseInt(Batch), - queryData.language + queryData.language, + queryData.level_competency ); } @@ -1244,255 +1245,4 @@ export class contentController { deleted, }); } - - @ApiExcludeEndpoint(true) - @ApiBody({ - description: 'Request body parameters for get content', - required: true, - schema: { - type: 'object', - properties: { - tokenArr: { - type: 'array', - description: 'Array of tokens', - items: { - type: 'string', - example: 'c' - } - }, - language: { - type: 'string', - description: 'Language code', - example: 'en' - }, - contentType: { - type: 'string', - description: 'Type of content', - example: 'Word' - }, - limit: { - type: 'number', - description: 'Limit on the number of items', - example: 5 - }, - cLevel: { - type: 'string', - description: 'Content level', - example: 'L2' - }, - complexityLevel: { - type: 'array', - description: 'Array of complexity levels', - items: { - type: 'string', - example: 'C1' - } - }, - graphemesMappedObj: { - type: 'object', - description: 'Object mapping graphemes to their representations', - additionalProperties: { - type: 'array', - items: { - type: 'string', - example: 'ch' - } - }, - example: { - "c": ["ch"], - "o": ["o"], - "a": ["a"], - "v": ["v", "ve"], - "w": ["w", "wh"], - "æ": ["a", "ai", "au"], - "n": ["n"], - "i": ["i"], - "θ": ["th"] - } - } - } - } - }) - @ApiResponse({ - status: 200, - description: 'Successful response', - schema: { - type: 'object', - properties: { - status: { type: 'string', example: 'success' }, - data: { - type: 'object', - properties: { - wordsArr: { - type: 'array', - items: { - type: 'object', - properties: { - _id: { type: 'string', example: '660f9545367a62b3902dd58b' }, - contentId: { type: 'string', example: 'f8dd7c97-53f7-4676-b597-4a52aaface5c' }, - collectionId: { type: 'string', example: '6a519951-8635-4d89-821a-d3eb60f6e1ec' }, - name: { type: 'string', example: 'L2_new_3' }, - contentType: { type: 'string', example: 'Word' }, - contentSourceData: { - type: 'array', - items: { - type: 'object', - properties: { - language: { type: 'string', example: 'en' }, - audioUrl: { type: 'string', example: '' }, - text: { type: 'string', example: 'five' }, - phonemes: { - type: 'array', - items: { type: 'string', example: 'f' } - }, - wordCount: { type: 'number', example: 1 }, - wordFrequency: { - type: 'object', - additionalProperties: { type: 'number', example: 1 } - }, - syllableCount: { type: 'number', example: 4 }, - syllableCountMap: { - type: 'object', - additionalProperties: { type: 'number', example: 4 } - }, - syllableCountArray: { - type: 'array', - items: { - type: 'object', - properties: { - k: { type: 'string', example: 'five' }, - v: { type: 'number', example: 4 } - } - } - } - } - } - }, - status: { type: 'string', example: 'live' }, - publisher: { type: 'string', example: 'ekstep' }, - language: { type: 'string', example: 'en' }, - contentIndex: { type: 'number', example: 141 }, - tags: { - type: 'array', - items: { type: 'string' } - }, - createdAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, - updatedAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, - __v: { type: 'number', example: 0 }, - matchedChar: { - type: 'array', - items: { type: 'string', example: 'v' } - } - } - } - }, - contentForToken: { - type: 'object', - additionalProperties: { - type: 'array', - items: { - type: 'object', - properties: { - _id: { type: 'string', example: '660f9545367a62b3902dd58b' }, - contentId: { type: 'string', example: 'f8dd7c97-53f7-4676-b597-4a52aaface5c' }, - collectionId: { type: 'string', example: '6a519951-8635-4d89-821a-d3eb60f6e1ec' }, - name: { type: 'string', example: 'L2_new_3' }, - contentType: { type: 'string', example: 'Word' }, - contentSourceData: { - type: 'array', - items: { - type: 'object', - properties: { - language: { type: 'string', example: 'en' }, - audioUrl: { type: 'string', example: '' }, - text: { type: 'string', example: 'five' }, - phonemes: { - type: 'array', - items: { type: 'string', example: 'f' } - }, - wordCount: { type: 'number', example: 1 }, - wordFrequency: { - type: 'object', - additionalProperties: { type: 'number', example: 1 } - }, - syllableCount: { type: 'number', example: 4 }, - syllableCountMap: { - type: 'object', - additionalProperties: { type: 'number', example: 4 } - }, - syllableCountArray: { - type: 'array', - items: { - type: 'object', - properties: { - k: { type: 'string', example: 'five' }, - v: { type: 'number', example: 4 } - } - } - } - } - } - }, - status: { type: 'string', example: 'live' }, - publisher: { type: 'string', example: 'ekstep' }, - language: { type: 'string', example: 'en' }, - contentIndex: { type: 'number', example: 141 }, - tags: { - type: 'array', - items: { type: 'string' } - }, - createdAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, - updatedAt: { type: 'string', example: '2024-04-05T05:45:55.335Z' }, - __v: { type: 'number', example: 0 }, - matchedChar: { - type: 'array', - items: { type: 'string', example: 'v' } - } - } - } - } - } - } - } - } - } - }) - @ApiResponse({ - status: 500, - description: 'Error while fetching data from the content table', - schema: { - type: 'object', - properties: { - status: { type: 'string', example: 'error' }, - msg: { type: 'string', example: 'Server error - error message' }, - }, - }, - }) - @ApiOperation({ - summary: 'Get all data from the content table' - }) - @Get('/getMeachnicsContent') - async getMechanicsContent( - @Res() response: FastifyReply, - @Query('contentType') contentType: string,@Query('mechanics_id') mechanics_id: string, @Query() { contentlimit = 5 }, @Query('language') language: string - ) { - try { - const Batch: any = contentlimit || 5; - const contentCollection = await this.contentService.getMechanicsContentData( - contentType, - mechanics_id, - Batch, - language - ); - return response.status(HttpStatus.CREATED).send({ - status: 200, - contentCollection, - }); - } catch (error) { - return response.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ - status: 500, - message: 'Server error - ' + error, - }); - } - } } diff --git a/src/services/content.service.ts b/src/services/content.service.ts index 6c2462b..2d888cf 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -1626,32 +1626,110 @@ export class contentService { contentType, mechanics_id, limit = 5, - language + language, + levelCompetencyArr ) { - - const wordsArr = await this.content.aggregate([ - { - $match: { - contentType: contentType, - language: language, - mechanics_data: { - $elemMatch: { mechanics_id: mechanics_id, language: language} - } + let queries = []; + const numLevels = levelCompetencyArr.length; + + // Calculate split limit (e.g., for limit 5, splitLimit would be 3 for L1.1 and 2 for L1.2) + const splitLimit = Math.ceil(limit / numLevels); + + if (numLevels > 0 && levelCompetencyArr !== undefined) { + levelCompetencyArr.forEach((levelCompetency, levelCompetencyIndex) => { + if (levelCompetencyIndex === 0) { + queries.push( + this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": levelCompetency + } + }, + { $sample: { size: splitLimit } } // Fetch for the first level + ]) + ); + } else { + queries.push( + this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": levelCompetency + } + }, + { $sample: { size: splitLimit - 1 } } // Fetch fewer items for other levels + ]) + ); } - }, - { $sample: { size: limit } }, - ]); - - wordsArr.map((content) => { - const { mechanics_data } = content; - const mechanicData = mechanics_data.find( - (mechanic) => {return mechanic.mechanics_id === mechanics_id} - ); - content.mechanics_data = []; - content.mechanics_data.push(mechanicData); - return content; + }); + } + + let results = []; + + // Execute all queries and combine the results + const queryResults = await Promise.all(queries); + + // Merge the results from different level competencies + queryResults.forEach((queryResult) => { + results = [...results, ...queryResult]; }); - - return { wordsArr: wordsArr }; + + // Ensure total results don't exceed the limit + if (results.length > limit) { + results = results.slice(0, limit); + } + + // If results are less than the limit, fetch additional content from any level + if (results.length < limit) { + const remainingLimit = limit - results.length; + const additionalContent = await this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": { $exists: true } + } + }, + { $sample: { size: remainingLimit } } + ]); + + results = [...results, ...additionalContent]; + } + + // If results are still less than the limit, fetch content without level_competency + if (results.length < limit) { + const remainingLimit = limit - results.length; + const fallbackContent = await this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": { $exists: false } + } + }, + { $sample: { size: remainingLimit } } + ]); + + results = [...results, ...fallbackContent]; + } + + // Return the final result ensuring the limit is respected + return { wordsArr: results.slice(0, limit) }; } + } From 3d84da68bb3cdc9aeb87ca6add15c75b2ecaef97 Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Wed, 16 Oct 2024 15:54:22 +0530 Subject: [PATCH 6/9] fixed issue which provide all data for present mechanics --- src/services/content.service.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/services/content.service.ts b/src/services/content.service.ts index 2d888cf..7189907 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -1728,8 +1728,20 @@ export class contentService { results = [...results, ...fallbackContent]; } - // Return the final result ensuring the limit is respected - return { wordsArr: results.slice(0, limit) }; + let wordsArr = results.slice(0, limit); + + + wordsArr.map((content) => { + const { mechanics_data } = content; + const mechanicData = mechanics_data.find( + (mechanic) => {return mechanic.mechanics_id === mechanics_id} + ); + content.mechanics_data = []; + content.mechanics_data.push(mechanicData); + return content; + }); + + return { wordsArr: wordsArr }; } } From 59cdbb862239e7179eb048b8f5bda5df5683003e Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Wed, 16 Oct 2024 23:40:40 +0530 Subject: [PATCH 7/9] content limit handled properly for second level compentency or more than 2 --- src/services/content.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/content.service.ts b/src/services/content.service.ts index 7189907..4cdd5fc 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -1654,6 +1654,7 @@ export class contentService { ]) ); } else { + let handleLimit = limit % 2 queries.push( this.content.aggregate([ { @@ -1666,7 +1667,7 @@ export class contentService { "level_complexity.level_competency": levelCompetency } }, - { $sample: { size: splitLimit - 1 } } // Fetch fewer items for other levels + { $sample: { size: splitLimit - handleLimit } } // Fetch fewer items for other levels ]) ); } From 0a324caefd5ee91cda22a5ec6dddaaccb6d72d6b Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Thu, 17 Oct 2024 20:04:13 +0530 Subject: [PATCH 8/9] content randomly gets if content is low --- src/services/content.service.ts | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/services/content.service.ts b/src/services/content.service.ts index 4cdd5fc..a213512 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -773,6 +773,56 @@ export class contentService { }); } + // remove all criteria and get random content with content type + if (contentData.length <= limit) { + let randomContentQuery = { + contentSourceData: { + $elemMatch: { + }, + }, + contentType: contentType + }; + + randomContentQuery.contentSourceData.$elemMatch['language'] = language; + + await this.content + .aggregate([ + { + $addFields: { + contentSourceData: { + $map: { + input: '$contentSourceData', + as: 'elem', + in: { + $mergeObjects: [ + '$$elem', + { + syllableCountArray: { + $objectToArray: '$$elem.syllableCountMap', + }, + }, + ], + }, + }, + }, + }, + }, + { + $match: randomContentQuery, + }, + { $sample: { size: limit - contentData.length } }, + ]) + .exec() + .then((doc) => { + for (const docEle of doc) { + if (contentData.length == 0 || !contentDataSet.has(docEle.contentId)) { + contentDataSet.add(docEle.contentId); + contentData.push(docEle); + } + } + }); + } + for (let contentDataEle of contentData) { const regexMatchBegin = new RegExp( @@ -1200,6 +1250,58 @@ export class contentService { }); } + + + // remove all criteria and get random content with content type + if (contentData.length <= limit) { + let randomContentQuery = { + contentSourceData: { + $elemMatch: { + }, + }, + contentType: contentType + }; + + randomContentQuery.contentSourceData.$elemMatch['language'] = en_config.language_code; + + await this.content + .aggregate([ + { + $addFields: { + contentSourceData: { + $map: { + input: '$contentSourceData', + as: 'elem', + in: { + $mergeObjects: [ + '$$elem', + { + syllableCountArray: { + $objectToArray: '$$elem.syllableCountMap', + }, + }, + ], + }, + }, + }, + }, + }, + { + $match: randomContentQuery, + }, + { $sample: { size: limit - contentData.length } }, + ]) + .exec() + .then((doc) => { + for (const docEle of doc) { + if (contentData.length == 0 || !contentDataSet.has(docEle.contentId)) { + contentDataSet.add(docEle.contentId); + contentData.push(docEle); + } + } + }); + } + // Remove content level // if (contentData.length <= limit) { // delete query.contentSourceData.$elemMatch['$and']; From 8e2558a80b1a26b3d636a2eaeda3b9c4d3e467c2 Mon Sep 17 00:00:00 2001 From: Sudeep Ratnaparkhe Date: Mon, 21 Oct 2024 14:52:16 +0530 Subject: [PATCH 9/9] level competency added as param with tag logic and tags is added as in mechanics logic --- src/controllers/content.controller.ts | 4 +- src/services/content.service.ts | 216 +++++++++++++++++++++++--- 2 files changed, 197 insertions(+), 23 deletions(-) diff --git a/src/controllers/content.controller.ts b/src/controllers/content.controller.ts index e35e111..d058fa1 100644 --- a/src/controllers/content.controller.ts +++ b/src/controllers/content.controller.ts @@ -901,6 +901,7 @@ export class contentController { queryData.cLevel, queryData.complexityLevel, queryData.graphemesMappedObj, + queryData.level_competency ); }else{ contentCollection = await this.contentService.getMechanicsContentData( @@ -908,7 +909,8 @@ export class contentController { queryData.mechanics_id, parseInt(Batch), queryData.language, - queryData.level_competency + queryData.level_competency, + queryData.tags ); } diff --git a/src/services/content.service.ts b/src/services/content.service.ts index a213512..bfcecb8 100644 --- a/src/services/content.service.ts +++ b/src/services/content.service.ts @@ -318,6 +318,7 @@ export class contentService { cLevel, complexityLevel, graphemesMappedObj, + level_competency = [] ): Promise { let nextTokenArr = [] if (tokenArr.length >= (limit * 2)) { @@ -1060,6 +1061,10 @@ export class contentService { query["tags"] = { $all: tags }; } + if (level_competency?.length > 0) { + query["level_complexity.level_competency"] = {$in:level_competency}; + } + const allTokenGraphemes = []; let contentData = []; @@ -1250,7 +1255,99 @@ export class contentService { }); } + // remove all criteria and get random content with level conpetency + if (contentData.length <= limit && level_competency?.length > 0) { + let randomContentQuery = { + contentSourceData: { + $elemMatch: { + }, + }, + contentType: contentType, + "level_complexity.level_competency": {$in:level_competency} + }; + + randomContentQuery.contentSourceData.$elemMatch['language'] = en_config.language_code; + + await this.content + .aggregate([ + { + $addFields: { + contentSourceData: { + $map: { + input: '$contentSourceData', + as: 'elem', + in: { + $mergeObjects: [ + '$$elem', + { + syllableCountArray: { + $objectToArray: '$$elem.syllableCountMap', + }, + }, + ], + }, + }, + }, + }, + }, + { + $match: randomContentQuery, + }, + { $sample: { size: limit - contentData.length } }, + ]) + .exec() + .then((doc) => { + for (const docEle of doc) { + if (contentData.length == 0 || !contentDataSet.has(docEle.contentId)) { + contentDataSet.add(docEle.contentId); + contentData.push(docEle); + } + } + }); + } + + if (contentData.length <= limit) { + if (level_competency?.length > 0) { + query["level_complexity.level_competency"] = { $exists: true } + } + await this.content + .aggregate([ + { + $addFields: { + contentSourceData: { + $map: { + input: '$contentSourceData', + as: 'elem', + in: { + $mergeObjects: [ + '$$elem', + { + syllableCountArray: { + $objectToArray: '$$elem.syllableCountMap', + }, + }, + ], + }, + }, + }, + }, + }, + { + $match: query, + }, + { $sample: { size: limit - contentData.length } }, + ]) + .exec() + .then((doc) => { + for (const docEle of doc) { + if (contentData.length == 0 || !contentDataSet.has(docEle.contentId)) { + contentDataSet.add(docEle.contentId); + contentData.push(docEle); + } + } + }); + } // remove all criteria and get random content with content type if (contentData.length <= limit) { @@ -1729,7 +1826,8 @@ export class contentService { mechanics_id, limit = 5, language, - levelCompetencyArr + levelCompetencyArr, + tags ) { let queries = []; const numLevels = levelCompetencyArr.length; @@ -1740,23 +1838,44 @@ export class contentService { if (numLevels > 0 && levelCompetencyArr !== undefined) { levelCompetencyArr.forEach((levelCompetency, levelCompetencyIndex) => { if (levelCompetencyIndex === 0) { - queries.push( - this.content.aggregate([ - { - $match: { - contentType: contentType, - language: language, - mechanics_data: { - $elemMatch: { mechanics_id: mechanics_id, language: language } - }, - "level_complexity.level_competency": levelCompetency - } - }, - { $sample: { size: splitLimit } } // Fetch for the first level - ]) - ); + if(tags.length > 0){ + queries.push( + this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": levelCompetency, + "tags":{ $all: tags } + } + }, + { $sample: { size: splitLimit } } // Fetch for the first level + ]) + ); + }else{ + queries.push( + this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": levelCompetency + } + }, + { $sample: { size: splitLimit } } // Fetch for the first level + ]) + ); + } } else { - let handleLimit = limit % 2 + let handleLimit = limit % 2; + + if(tags.length > 0){ queries.push( this.content.aggregate([ { @@ -1766,12 +1885,30 @@ export class contentService { mechanics_data: { $elemMatch: { mechanics_id: mechanics_id, language: language } }, - "level_complexity.level_competency": levelCompetency + "level_complexity.level_competency": levelCompetency, + "tags":{ $all: tags } } }, { $sample: { size: splitLimit - handleLimit } } // Fetch fewer items for other levels ]) ); + }else{ + queries.push( + this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": levelCompetency + } + }, + { $sample: { size: splitLimit - handleLimit } } // Fetch fewer items for other levels + ]) + ); + } } }); } @@ -1794,7 +1931,9 @@ export class contentService { // If results are less than the limit, fetch additional content from any level if (results.length < limit) { const remainingLimit = limit - results.length; - const additionalContent = await this.content.aggregate([ + let additionalContent; + if(tags.length > 0){ + additionalContent= await this.content.aggregate([ { $match: { contentType: contentType, @@ -1802,11 +1941,27 @@ export class contentService { mechanics_data: { $elemMatch: { mechanics_id: mechanics_id, language: language } }, - "level_complexity.level_competency": { $exists: true } + "level_complexity.level_competency": { $exists: true }, + "tags":{ $all: tags } } }, { $sample: { size: remainingLimit } } ]); + }else{ + additionalContent = await this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, + mechanics_data: { + $elemMatch: { mechanics_id: mechanics_id, language: language } + }, + "level_complexity.level_competency": { $exists: true } + } + }, + { $sample: { size: remainingLimit } } + ]); + } results = [...results, ...additionalContent]; } @@ -1821,8 +1976,23 @@ export class contentService { language: language, mechanics_data: { $elemMatch: { mechanics_id: mechanics_id, language: language } - }, - "level_complexity.level_competency": { $exists: false } + } + } + }, + { $sample: { size: remainingLimit } } + ]); + + results = [...results, ...fallbackContent]; + } + + // If results are still less than the limit, fetch content without mechanics_data + if (results.length < limit) { + const remainingLimit = limit - results.length; + const fallbackContent = await this.content.aggregate([ + { + $match: { + contentType: contentType, + language: language, } }, { $sample: { size: remainingLimit } } @@ -1836,11 +2006,13 @@ export class contentService { wordsArr.map((content) => { const { mechanics_data } = content; + if(mechanics_data){ const mechanicData = mechanics_data.find( (mechanic) => {return mechanic.mechanics_id === mechanics_id} ); content.mechanics_data = []; content.mechanics_data.push(mechanicData); + } return content; });