├── src ├── .eslintignore ├── models │ ├── cassandra │ │ ├── lock │ │ │ ├── index.js │ │ │ └── lockModel.js │ │ └── dialcodes │ │ │ ├── index.js │ │ │ ├── dialcode_imagesModel.js │ │ │ └── dialcode_batchModel.js │ ├── courseModel.js │ └── contentModel.js ├── test │ ├── .eslintrc │ ├── helpers │ │ └── configHelperSpec.js │ ├── appSpec.js │ ├── fixtures │ │ └── services │ │ │ ├── emailServiceTestData.js │ │ │ └── questionServiceTestData.js │ ├── service │ │ ├── externalUrlMetaServiceSpec.js │ │ ├── filterServiceSpec.js │ │ └── questionServiceSpec.js │ ├── contentMetaConfig.js │ ├── middlewares │ │ └── proxyMiddlewareSpec.js │ └── metaFilterRouteSpec.js ├── assets │ └── fonts │ │ ├── arial │ │ └── arialbold.ttf │ │ └── calibri │ │ └── CalibriBold.ttf ├── metadata.sh ├── config │ ├── constants.json │ ├── contentProviderApiConfig.json │ └── telemetryEventConfig.json ├── .eslintrc ├── routes │ ├── externalUrlMetaRoute.js │ ├── pluginsRoutes.js │ ├── questionRoutes.js │ ├── healthCheckRoutes.js │ ├── searchRoutes.js │ ├── collaborationRoutes.js │ ├── formRoutes.js │ ├── dataExhaustRoutes.js │ ├── channelRoutes.js │ ├── lockRoutes.js │ ├── frameworkTermRoutes.js │ ├── frameworkCategoryInstanceRoutes.js │ ├── frameworkRoutes.js │ ├── conceptRoutes.js │ ├── courseRoutes.js │ ├── dialCodeRoutes.js │ └── contentRoutes.js ├── service │ ├── filterService.js │ ├── externalUrlMetaService.js │ ├── dialCode │ │ ├── imageService.js │ │ ├── dialCodeServiceHelper.js │ │ └── batchImageService.js │ ├── questionService.js │ ├── dataExhaustService.js │ ├── utilsService.js │ ├── channelService.js │ ├── healthCheckService.js │ ├── frameworkTermService.js │ ├── frameworkCategoryInstanceService.js │ └── formService.js ├── gulpfile.js ├── utils │ ├── colorUtil.js │ ├── uploadUtil.js │ ├── qrCodeUtil.js │ └── cassandraUtil.js ├── helpers │ ├── qrCodeKafkaProducer.js │ ├── configHelper.js │ ├── licenseHelper.js │ └── orgHelper.js ├── middlewares │ └── filter.middleware.js ├── package.json └── contentMetaFilter.js ├── .gitmodules ├── .gitignore ├── Docker.md ├── sonar-project.properties ├── dockerPushToRepo.sh ├── deploy.sh ├── Dockerfile ├── LICENSE ├── .circleci └── config.yml ├── setup.md ├── Jenkinsfile ├── auto_build_deploy └── README.md /src/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | coverage/** -------------------------------------------------------------------------------- /src/models/cassandra/lock/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./lockModel') 3 | ] 4 | -------------------------------------------------------------------------------- /src/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jasmine": true 4 | }, 5 | "plugins": ["jasmine"] 6 | } 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/libs"] 2 | path = src/libs 3 | url = https://github.com/project-sunbird/sunbird-js-utils 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/node_modules/ 2 | /src/npm-debug.log 3 | /src/coverage/ 4 | /src/**/*.log 5 | src/.env 6 | /src/.nyc_output 7 | -------------------------------------------------------------------------------- /src/assets/fonts/arial/arialbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphere/knowledge-mw-service/master/src/assets/fonts/arial/arialbold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/calibri/CalibriBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphere/knowledge-mw-service/master/src/assets/fonts/calibri/CalibriBold.ttf -------------------------------------------------------------------------------- /src/models/cassandra/dialcodes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./dialcode_batchModel'), 3 | require('./dialcode_imagesModel') 4 | ] 5 | -------------------------------------------------------------------------------- /src/metadata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | check=$(cat src/package.json | jq -c '{name: .name , version: .version, org: .author, hubuser: "purplesunbird"}') 3 | echo $check 4 | -------------------------------------------------------------------------------- /Docker.md: -------------------------------------------------------------------------------- 1 | docker build -f Dockerfile.Build . 2 | docker run -it {{Imageid}} 3 | docker cp {{Containerid}}:/opt/content-service.zip . 4 | docker build -f Dockerfile 5 | -------------------------------------------------------------------------------- /src/models/courseModel.js: -------------------------------------------------------------------------------- 1 | module.exports.COURSE = { 2 | 3 | CREATE: { 4 | name: 'required|string', 5 | description: 'required|string' 6 | }, 7 | UPDATE: { 8 | versionKey: 'required|string' 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/config/constants.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgCacheExpiryTime": 1800, 3 | "orgfieldsAllowedToSend": ["email","orgName"], 4 | "orgCacheKeyNamePrefix" : "orgdata-", 5 | "readableLicenseFields": ["name", "url", "description"], 6 | "licenseCacheKeyNamePrefix": "license-", 7 | "licenseCacheExpiryTime": 1800 8 | } -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "globals": { 4 | "globalEkstepProxyBaseUrl": true 5 | }, 6 | "rules": { 7 | "indent": ["error", 2], 8 | "max-len": ["error", 120, 2, {"ignoreUrls": true, "ignoreComments": true}], 9 | "quotes": ["error", "single"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectName=sunbird-content-service 2 | sonar.language=js 3 | sonar.projectKey=project-sunbird_sunbird-content-service 4 | sonar.host.url=https://sonarcloud.io 5 | sonar.exclusions=src/test/**, **/node_modules/** 6 | sonar.javascript.lcov.reportPaths=src/coverage/lcov.info 7 | sonar.organization=project-sunbird 8 | sonar.projectVersion=1.0 9 | -------------------------------------------------------------------------------- /src/models/contentModel.js: -------------------------------------------------------------------------------- 1 | module.exports.CONTENT = { 2 | 3 | CREATE: { 4 | name: 'required|string', 5 | mimeType: 'required|string', 6 | contentType: 'required|string', 7 | createdBy: 'required|string' 8 | }, 9 | 10 | UPDATE: { 11 | versionKey: 'required|string' 12 | }, 13 | 14 | COLLABORATORS: { 15 | collaborators: 'array' 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/routes/externalUrlMetaRoute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: external-url-meta-route.js 3 | * author: Loganathan 4 | * desc: route file for external url meta 5 | */ 6 | 7 | var extUrlMetaService = require('../service/externalUrlMetaService') 8 | 9 | var BASE_URL = '/v1/url' 10 | 11 | module.exports = function (app) { 12 | app.route(BASE_URL + '/fetchmeta') 13 | .post(extUrlMetaService.fetchUrlMetaAPI) 14 | } 15 | -------------------------------------------------------------------------------- /src/models/cassandra/lock/lockModel.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | table_name: 'lock', 3 | fields: { 4 | lockId: 'uuid', 5 | resourceId: 'text', 6 | resourceType: 'text', 7 | resourceInfo: 'text', 8 | createdBy: 'text', 9 | creatorInfo: 'text', 10 | createdOn: 'timestamp', 11 | deviceId: 'text', 12 | expiresAt: 'timestamp' 13 | }, 14 | key: ['resourceId', 'resourceType'], 15 | indexes: ['lockId'] 16 | } 17 | -------------------------------------------------------------------------------- /src/test/helpers/configHelperSpec.js: -------------------------------------------------------------------------------- 1 | var configHelper = require('../../helpers/configHelper') 2 | var orgHelper = require('../../helpers/orgHelper') 3 | 4 | describe('configuration helper methods', function () { 5 | it('should get all the channels', function () { 6 | spyOn(orgHelper, 'getRootOrgs') 7 | configHelper.getAllChannelsFromAPI().then((configStr) => { 8 | expect(configStr.length).toBeGreaterThanOrEqual(0) 9 | }) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /dockerPushToRepo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Build script 3 | # set -o errexit 4 | e () { 5 | echo $( echo ${1} | jq ".${2}" | sed 's/\"//g') 6 | } 7 | m=$(./src/metadata.sh) 8 | 9 | org=$(e "${m}" "org") 10 | hubuser=$(e "${m}" "hubuser") 11 | name=$(e "${m}" "name") 12 | version=$(e "${m}" "version") 13 | 14 | artifactLabel=${ARTIFACT_LABEL:-bronze} 15 | 16 | docker login -u "${hubuser}" -p`cat /home/ops/vault_pass` 17 | docker push ${org}/${name}:${version}-${artifactLabel} 18 | docker logout 19 | -------------------------------------------------------------------------------- /src/service/filterService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * filterService - provides service methods for filters like channel filters 3 | * 4 | * author: Loganathan Shanmugam 5 | * email: loganathan.shanmugam@tarento.com 6 | */ 7 | var configUtil = require('sb-config-util') 8 | 9 | function getMetadataFilterQuery (callback) { 10 | var filterQuery = configUtil.getConfig('META_FILTER_REQUEST_JSON') 11 | callback(null, filterQuery) 12 | } 13 | 14 | module.exports.getMetadataFilterQuery = getMetadataFilterQuery 15 | -------------------------------------------------------------------------------- /src/routes/pluginsRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: plugins-route.js 3 | * author: Harish Kumar G 4 | * desc: route file for plugin search 5 | */ 6 | 7 | var contentService = require('../service/contentService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | 10 | var BASE_URL_V1 = '/v1/plugins' 11 | 12 | module.exports = function (app) { 13 | app.route(BASE_URL_V1 + '/search') 14 | .post(requestMiddleware.createAndValidateRequestBody, 15 | contentService.searchPluginsAPI) 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/questionRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: questionRoutes.js 3 | * author: Harish Kumar Gangula 4 | * desc: route file for questions 5 | */ 6 | 7 | var questionService = require('../service/questionService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | 10 | var BASE_URL_V1 = '/v1/question' 11 | 12 | module.exports = function (app) { 13 | app.route(BASE_URL_V1 + '/list') 14 | .post(requestMiddleware.createAndValidateRequestBody, 15 | questionService.getList) 16 | } 17 | -------------------------------------------------------------------------------- /src/models/cassandra/dialcodes/dialcode_imagesModel.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | table_name: 'dialcode_images', 3 | fields: { 4 | filename: 'text', 5 | created_on: { 6 | type: 'timestamp', 7 | default: {'$db_function': 'toTimestamp(now())'} 8 | }, 9 | dialcode: 'text', 10 | channel: 'text', 11 | publisher: 'text', 12 | config: { 13 | type: 'map', 14 | typeDef: '' 15 | }, 16 | status: 'int', // 0 - not available , 1 - in process , 2 - available 17 | url: 'text' 18 | 19 | }, 20 | key: ['filename'] 21 | } 22 | -------------------------------------------------------------------------------- /src/routes/healthCheckRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: healthCheckRoutes.js 3 | * author: Anuj Gupta 4 | * desc: route file for health check 5 | */ 6 | 7 | var healthService = require('../service/healthCheckService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | 10 | module.exports = function (app) { 11 | app.route('/health') 12 | .get(requestMiddleware.createAndValidateRequestBody, healthService.checkHealth) 13 | 14 | app.route('/service/health') 15 | .get(requestMiddleware.createAndValidateRequestBody, healthService.checkContentServiceHealth) 16 | } 17 | -------------------------------------------------------------------------------- /src/models/cassandra/dialcodes/dialcode_batchModel.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | table_name: 'dialcode_batch', 3 | fields: { 4 | processid: { 5 | type: 'uuid', 6 | default: {'$db_function': 'uuid()'} 7 | }, 8 | channel: 'text', 9 | publisher: 'text', 10 | created_on: { 11 | type: 'timestamp', 12 | default: {'$db_function': 'toTimestamp(now())'} 13 | }, 14 | dialcodes: { 15 | type: 'list', 16 | typeDef: '' 17 | }, 18 | config: { 19 | type: 'map', 20 | typeDef: '' 21 | }, 22 | status: 'int', 23 | url: 'text' 24 | }, 25 | key: ['processid'] 26 | } 27 | -------------------------------------------------------------------------------- /src/test/appSpec.js: -------------------------------------------------------------------------------- 1 | var server = require('../app.js') 2 | var expect = require('chai').expect 3 | var request = require('request') 4 | var host = 'http://localhost:5000' 5 | const nock = require('nock'); 6 | 7 | describe('Check health api', function () { 8 | 9 | before((done) => { 10 | server.start(done) 11 | }) 12 | 13 | after((done) => { 14 | server.close(done) 15 | }) 16 | 17 | it('Check with different methods, it should return status code 200', function (done) { 18 | nock(host).persist().get('/health').reply(200, 'OK'); 19 | request.options({ 20 | url: host + '/health', 21 | json: true 22 | }, function (_error, response, body) { 23 | expect(body).eql('OK') 24 | done() 25 | }) 26 | }) 27 | }) 28 | 29 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Build script 3 | # set -o errexit 4 | e () { 5 | echo $( echo ${1} | jq ".${2}" | sed 's/\"//g') 6 | } 7 | m=$(./src/metadata.sh) 8 | 9 | org=$(e "${m}" "org") 10 | name=$(e "${m}" "name") 11 | version=$(e "${m}" "version") 12 | 13 | artifactLabel=${ARTIFACT_LABEL:-bronze} 14 | env=${ENV:-null} 15 | 16 | echo "artifactLabel: ${artifactLabel}" 17 | echo "env: ${env}" 18 | echo "org: ${org}" 19 | echo "name: ${name}" 20 | echo "version: ${version}" 21 | 22 | ansible-playbook --version 23 | ansible-playbook -i ansible/inventory/dev ansible/deploy.yml --tags "stack-sunbird" --extra-vars "hub_org=${org} image_name=${name} image_tag=${version}-${artifactLabel}" --vault-password-file /run/secrets/vault-pass 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/routes/searchRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: search-route.js 3 | * author: Anuj Gupta 4 | * desc: route file for content 5 | */ 6 | 7 | var contentService = require('../service/contentService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var BASE_URL_V1 = '/v1' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(BASE_URL_V1 + '/search') 17 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 20 | contentService.searchAPI) 21 | } 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM circleci/node:8.11.2-stretch 2 | MAINTAINER "Manojvv" "manojv@ilimi.in" 3 | USER root 4 | COPY src /opt/content/ 5 | WORKDIR /opt/content/ 6 | RUN npm install --unsafe-perm 7 | 8 | FROM node:8.11-slim 9 | MAINTAINER "Manojvv" "manojv@ilimi.in" 10 | RUN sed -i '/jessie-updates/d' /etc/apt/sources.list \ 11 | && apt update && apt install openssl imagemagick -y \ 12 | && apt-get clean \ 13 | && useradd -m sunbird 14 | USER sunbird 15 | ADD ImageMagick-i386-pc-solaris2.11.tar.gz /home/sunbird 16 | ENV GRAPH_HOME "/home/sunbird/ImageMagick-6.9.3" 17 | ENV PATH "$GRAPH_HOME/bin:$PATH" 18 | ENV MAGICK_HOME "/home/sunbird/ImageMagick-6.9.3" 19 | ENV PATH "$MAGICK_HOME/bin:$PATH" 20 | COPY --from=0 --chown=sunbird /opt/content /home/sunbird/mw/content 21 | WORKDIR /home/sunbird/mw/content/ 22 | CMD ["node", "app.js", "&"] 23 | -------------------------------------------------------------------------------- /src/routes/collaborationRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: collaborators-route.js 3 | * author: Sourav Dey 4 | * desc: route file for collaborators 5 | */ 6 | 7 | var collaboratorService = require('../service/collaboratorService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var healthService = require('../service/healthCheckService') 10 | 11 | var BASE_URL = '/v1/content' 12 | var dependentServiceHealth = ['EKSTEP'] 13 | 14 | module.exports = function (app) { 15 | app.route(BASE_URL + '/collaborator/update/:contentId') 16 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 17 | requestMiddleware.gzipCompression(), 18 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 19 | requestMiddleware.apiAccessForCreatorUser, collaboratorService.updateCollaborators) 20 | } 21 | -------------------------------------------------------------------------------- /src/routes/formRoutes.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * file: formRoutes.js 4 | * author: Harish Kumar Gangula 5 | * desc: route file for form service 6 | */ 7 | 8 | var formService = require('../service/formService') 9 | var requestMiddleware = require('../middlewares/request.middleware') 10 | 11 | var BASE_URL = '/v1/data/form' 12 | 13 | module.exports = function (app) { 14 | app.route(BASE_URL + '/read') 15 | .post(requestMiddleware.gzipCompression(), requestMiddleware.createAndValidateRequestBody, 16 | formService.getFormRequest) 17 | app.route(BASE_URL + '/update') 18 | .post(requestMiddleware.gzipCompression(), requestMiddleware.createAndValidateRequestBody, 19 | formService.updateFormRequest) 20 | app.route(BASE_URL + '/create') 21 | .post(requestMiddleware.gzipCompression(), requestMiddleware.createAndValidateRequestBody, 22 | formService.createFormRequest) 23 | } 24 | -------------------------------------------------------------------------------- /src/routes/dataExhaustRoutes.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * file: dataExhaustRoutes.js 4 | * author: Anuj Gupta 5 | * desc: route file for data exhaust service 6 | */ 7 | 8 | var dataExaustService = require('../service/dataExhaustService') 9 | var requestMiddleware = require('../middlewares/request.middleware') 10 | 11 | var BASE_URL = '/v1/dataset/request' 12 | 13 | module.exports = function (app) { 14 | app.route(BASE_URL + '/submit') 15 | .post(requestMiddleware.createAndValidateRequestBody, dataExaustService.submitDataSetRequest) 16 | 17 | app.route(BASE_URL + '/list/:clientKey') 18 | .get(requestMiddleware.createAndValidateRequestBody, dataExaustService.getListOfDataSetRequest) 19 | 20 | app.route(BASE_URL + '/read/:clientKey/:requestId') 21 | .get(requestMiddleware.createAndValidateRequestBody, dataExaustService.getDataSetDetailRequest) 22 | 23 | app.route(BASE_URL + '/:dataSetId/:channelId') 24 | .get(requestMiddleware.createAndValidateRequestBody, dataExaustService.getChannelDataSetRequest) 25 | } 26 | -------------------------------------------------------------------------------- /src/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var rimraf = require('rimraf') 3 | var jasmineNode = require('gulp-jasmine-node') 4 | var istanbul = require('gulp-istanbul') 5 | 6 | var paths = { 7 | scripts: ['middlewares/*.js', 'helpers/*.js', 'models/*.js', 'routes/*.js', 8 | 'service/*.js', 'app.js', '!service/conceptService.js'], 9 | tests: ['test/*.js', 'test/**/*.js'], 10 | coverage: 'coverage' 11 | } 12 | 13 | // Below task is to clean up the coverage reports 14 | gulp.task('clean-coverage-report', function (cb) { 15 | rimraf(paths.coverage, cb) 16 | }) 17 | 18 | // Below task is used setup source files 19 | gulp.task('pre-test-node', function () { 20 | return gulp.src(paths.scripts) 21 | .pipe(istanbul({includeUntested: true})) 22 | .pipe(istanbul.hookRequire()) 23 | }) 24 | 25 | // Below task used to run the test cases 26 | gulp.task('test', ['clean-coverage-report', 'pre-test-node'], function () { 27 | return gulp.src(paths.tests) 28 | .pipe(jasmineNode({ 29 | timeout: 10000 30 | })) 31 | .pipe(istanbul.writeReports({dir: paths.coverage, reporters: ['html', 'text-summary']})) 32 | }) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Project Sunbird 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/routes/channelRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: channel-route.js 3 | * author: Rajath V B 4 | * desc: route file for Channel 5 | */ 6 | 7 | var channelService = require('../service/channelService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var healthService = require('../service/healthCheckService') 10 | 11 | var BASE_URL_V1_channel = '/v1/channel' 12 | var dependentServiceHealth = ['EKSTEP'] 13 | 14 | module.exports = function (app) { 15 | app.route(BASE_URL_V1_channel + '/read/:channelId') 16 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 17 | requestMiddleware.createAndValidateRequestBody, channelService.getChannelValuesById) 18 | 19 | app.route(BASE_URL_V1_channel + '/create') 20 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 21 | requestMiddleware.createAndValidateRequestBody, channelService.ChannelCreate) 22 | 23 | app.route(BASE_URL_V1_channel + '/update/:channelId') 24 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 25 | requestMiddleware.createAndValidateRequestBody, channelService.ChannelUpdate) 26 | } 27 | -------------------------------------------------------------------------------- /src/test/fixtures/services/emailServiceTestData.js: -------------------------------------------------------------------------------- 1 | exports.emailServiceTestData = { 2 | 3 | "PUBLISHED_CONTENT": { 4 | "subject": "Congratulations, your content is live! Content Type: {{Content type}}, Title: {{Content title}}", 5 | "body": "Congratulations! The content that you had submitted has been accepted for publication. It will be available for usage shortly.

Content Type: {{Content type}}
Title: {{Content title}}
", 6 | "template": "publishContent" 7 | }, 8 | 9 | "REQUEST": { 10 | "rspObj": { 11 | "apiId": "api.create.flag", 12 | "path": "create/flag", 13 | "apiVersion": "1.1", 14 | "msgid": "weeqw112312", 15 | "result": {} 16 | }, 17 | "params": { 18 | "contentId": "mock Content ID" 19 | }, 20 | "body": { 21 | "request": {} 22 | }, 23 | get: function () { 24 | return 'mock Channel ID' 25 | }, 26 | }, 27 | "ErrorResponse": { 28 | "responseCode": "RESOURCE_NOT_FOUND" 29 | }, 30 | "SuccessResponse": { 31 | "responseCode": "OK", 32 | "result": { 33 | "content": {} 34 | } 35 | } 36 | 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:12.13.0 6 | parallelism: 2 7 | working_directory: ~/project/src 8 | steps: 9 | - checkout 10 | - run: npm cache clean --force 11 | - run: git submodule update --init --recursive src/libs 12 | - run: mkdir -p opt/content && cp -r src/* opt/content/ 13 | - restore_cache: 14 | key: dependency-cache-{{ checksum "src/package.json" }} 15 | - run: cd opt/content/ && npm install --unsafe-perm 16 | - save_cache: 17 | key: dependency-cache-{{ checksum "src/package.json" }} 18 | paths: ./node_modules 19 | - run: cd opt && zip -r content.zip content 20 | - store_artifacts: 21 | path: opt/content.zip 22 | destination: content.zip 23 | - run: 24 | name: Run test cases 25 | command: 'cd src && npm install --unsafe-perm && npm run coverage' 26 | - run: 27 | name: install sonar scanner 28 | command: sudo npm install -g sonarqube-scanner@2.8.0 29 | - run: 30 | name: Run SonarScan 31 | command: sonar-scanner 32 | 33 | workflows: 34 | version: 2.1 35 | build_and_test: 36 | jobs: 37 | - build 38 | -------------------------------------------------------------------------------- /src/routes/lockRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: lock-route.js 3 | * author: Sourav Dey 4 | * desc: route file for locking resources 5 | */ 6 | 7 | var lockService = require('../service/lockService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var healthService = require('../service/healthCheckService') 10 | 11 | var BASE_URL = '/v1/lock' 12 | var dependentServiceHealth = ['EKSTEP', 'CASSANDRA'] 13 | 14 | module.exports = function (app) { 15 | app.route(BASE_URL + '/create') 16 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 17 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 18 | lockService.createLock) 19 | 20 | app.route(BASE_URL + '/refresh') 21 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 22 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 23 | lockService.refreshLock) 24 | 25 | app.route(BASE_URL + '/retire') 26 | .delete(healthService.checkDependantServiceHealth(dependentServiceHealth), 27 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 28 | lockService.retireLock) 29 | 30 | app.route(BASE_URL + '/list') 31 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 32 | requestMiddleware.createAndValidateRequestBody, 33 | lockService.listLock) 34 | } 35 | -------------------------------------------------------------------------------- /src/test/service/externalUrlMetaServiceSpec.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | var host = 'http://localhost:5000' 3 | var fetchMetaUrl = host + '/url/v1/fetchmeta' 4 | var validUrl = 'http://www.dailymotion.com/video/x27zxy8' 5 | var inValidUrl = 'http://www.dailymotionsss.com/video/x27zxy8' 6 | 7 | describe('externalUrlMeta', function () { 8 | describe('fetch url meta service', function () { 9 | it('should handle failure errors incase of invalid url', function (done) { 10 | request.post({ 11 | headers: {'Content-Type': 'application/json'}, 12 | uri: fetchMetaUrl, 13 | body: { 14 | 'request': { 15 | 'url': inValidUrl 16 | } 17 | }, 18 | json: true 19 | }, function (error, response, body) { 20 | expect(response.statusCode).toBe(500) 21 | expect(error).toBeDefined() 22 | expect(body.params.err).toBeDefined() 23 | expect(body.params.errmsg).toBeDefined() 24 | done() 25 | }) 26 | }) 27 | it('should return valid metdata incase of valid url', function (done) { 28 | request.post({ 29 | headers: {'Content-Type': 'application/json'}, 30 | uri: fetchMetaUrl, 31 | body: { 32 | 'request': { 33 | 'url': validUrl 34 | } 35 | }, 36 | json: true 37 | }, function (error, response, body) { 38 | expect(body.responseCode).toBe('OK') 39 | expect(error).toEqual(null) 40 | expect(body.result).toBeDefined() 41 | done() 42 | }) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /src/utils/colorUtil.js: -------------------------------------------------------------------------------- 1 | function colorUtil () {} 2 | 3 | colorUtil.prototype.cmykTohex = function (cmyk) { 4 | var rgb = this.cmykTorgb(cmyk) 5 | return '#' + this.rgbTohex(rgb) 6 | } 7 | 8 | colorUtil.prototype.cmykTorgb = function (cmyk) { 9 | var cmykSplit = cmyk.split(',') 10 | var c = cmykSplit[0] 11 | var m = cmykSplit[1] 12 | var y = cmykSplit[2] 13 | var k = cmykSplit[3] 14 | 15 | var cmykC = Number(c) 16 | var cmykM = Number(m) 17 | var cmykY = Number(y) 18 | var cmykK = Number(k) 19 | 20 | if (cmykC > 0) { 21 | cmykC = cmykC / 100 22 | } else if (cmykM > 0) { 23 | cmykM = cmykM / 100 24 | } else if (cmykY > 0) { 25 | cmykY = cmykY / 100 26 | } else if (cmykK > 0) { 27 | cmykK = cmykK / 100 28 | } 29 | 30 | var rgbR = 1 - Math.min(1, cmykC * (1 - cmykK) + cmykK) 31 | var rgbG = 1 - Math.min(1, cmykM * (1 - cmykK) + cmykK) 32 | var rgbB = 1 - Math.min(1, cmykY * (1 - cmykK) + cmykK) 33 | 34 | rgbR = Math.round(rgbR * 255) 35 | rgbG = Math.round(rgbG * 255) 36 | rgbB = Math.round(rgbB * 255) 37 | 38 | return (rgbR + ',' + rgbG + ',' + rgbB) 39 | } 40 | 41 | colorUtil.prototype.rgbTohex = function (rgb) { 42 | var rgbSplit = rgb.split(',') 43 | var R = rgbSplit[0] 44 | var G = rgbSplit[1] 45 | var B = rgbSplit[2] 46 | return toHex(R) + toHex(G) + toHex(B) 47 | } 48 | 49 | function toHex (n) { 50 | n = parseInt(n, 10) 51 | if (isNaN(n)) { return '00' } 52 | n = Math.max(0, Math.min(n, 255)) 53 | return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + 54 | '0123456789ABCDEF'.charAt(n % 16) 55 | } 56 | 57 | module.exports = colorUtil 58 | -------------------------------------------------------------------------------- /src/routes/frameworkTermRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: frameworkTerm-route.js 3 | * author: Rajath V B 4 | * desc: route file for Channel 5 | */ 6 | 7 | var frameworkTermService = require('../service/frameworkTermService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var baseUrl = '/v1/framework/term' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(baseUrl + '/read/:categoryID') 17 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, frameworkTermService.getFrameworkTerm) 20 | 21 | app.route(baseUrl + '/search') 22 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 23 | requestMiddleware.gzipCompression(), 24 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 25 | frameworkTermService.frameworkTermSearch) 26 | 27 | app.route(baseUrl + '/create') 28 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 29 | requestMiddleware.gzipCompression(), 30 | requestMiddleware.createAndValidateRequestBody, frameworkTermService.frameworkTermCreate) 31 | 32 | app.route(baseUrl + '/update/:categoryID') 33 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 34 | requestMiddleware.gzipCompression(), 35 | requestMiddleware.createAndValidateRequestBody, frameworkTermService.frameworkTermUpdate) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/uploadUtil.js: -------------------------------------------------------------------------------- 1 | var azure = require('azure-storage') 2 | var logger = require('sb_logger_util_v2') 3 | var path = require('path') 4 | var fs = require('fs') 5 | var fse = require('fs-extra') 6 | var filename = path.basename(__filename) 7 | var blobService = azure.createBlobService(process.env.sunbird_azure_account_name, process.env.sunbird_azure_account_key) 8 | 9 | function UploadUtil(name) { 10 | this.containerName = name || 'dial' 11 | blobService.createContainerIfNotExists(this.containerName, { publicAccessLevel: 'blob' }, function (err) { 12 | if (err) { 13 | logger.error({ msg: 'Unable to create a container in azure', err, additionalInfo: { name: this.containerName } }) 14 | } 15 | }) 16 | } 17 | 18 | UploadUtil.prototype.uploadFile = function uploadFile(destPath, sourcePath, callback) { 19 | blobService.createBlockBlobFromLocalFile(this.containerName || 'dial', destPath, sourcePath, callback) 20 | } 21 | 22 | UploadUtil.prototype.deleteFile = function deleteFile(filePath, callback) { 23 | blobService.deleteBlobIfExists(this.containerName, filePath, callback) 24 | } 25 | 26 | UploadUtil.prototype.downloadFile = function downloadFile(destPath, sourcePath, callback) { 27 | fse.ensureFileSync(destPath) 28 | blobService.getBlobToStream(this.containerName, sourcePath, fs.createWriteStream(destPath), 29 | function (error, result, response) { 30 | if (error) { 31 | logger.error({ 32 | msg: 'Unable to download file from azure', 33 | error, 34 | additionalInfo: { name: this.containerName, sourcePath } 35 | }) 36 | } 37 | callback(error, destPath) 38 | }) 39 | } 40 | 41 | module.exports = UploadUtil 42 | -------------------------------------------------------------------------------- /src/test/contentMetaConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | allowedChannels: process.env.sunbird_content_service_whitelisted_channels 3 | ? process.env.sunbird_content_service_whitelisted_channels.split(',') : [], 4 | blackListedChannels: process.env.sunbird_content_service_blacklisted_channels 5 | ? process.env.sunbird_content_service_blacklisted_channels.split(',') : [], 6 | 7 | allowedFramework: process.env.sunbird_content_service_whitelisted_framework 8 | ? process.env.sunbird_content_service_whitelisted_framework.split(',') : [], 9 | blackListedFramework: process.env.sunbird_content_service_blacklisted_framework 10 | ? process.env.sunbird_content_service_blacklisted_framework.split(',') : [], 11 | 12 | allowedMimetype: process.env.sunbird_content_service_whitelisted_mimetype 13 | ? process.env.sunbird_content_service_whitelisted_mimetype.split(',') : [], 14 | blackListedMimetype: process.env.sunbird_content_service_blacklisted_mimetype 15 | ? process.env.sunbird_content_service_blacklisted_mimetype.split(',') : [], 16 | 17 | allowedContenttype: process.env.sunbird_content_service_whitelisted_contenttype 18 | ? process.env.sunbird_content_service_whitelisted_contenttype.split(',') : [], 19 | blackListedContenttype: process.env.sunbird_content_service_blacklisted_contenttype 20 | ? process.env.sunbird_content_service_blacklisted_contenttype.split(',') : [], 21 | 22 | allowedResourcetype: process.env.sunbird_content_service_whitelisted_resourcetype 23 | ? process.env.sunbird_content_service_whitelisted_resourcetype.split(',') : [], 24 | blackListedResourcetype: process.env.sunbird_content_service_blacklisted_resourcetype 25 | ? process.env.sunbird_content_service_blacklisted_resourcetype.split(',') : [] 26 | }) 27 | -------------------------------------------------------------------------------- /src/test/service/filterServiceSpec.js: -------------------------------------------------------------------------------- 1 | var filterService = require('../../service/filterService') 2 | var configUtil = require('../../libs/sb-config-util') 3 | 4 | describe('content meta filter service from config', function () { 5 | it('check for getMetadataFilterQuery method', function () { 6 | spyOn(configUtil, 'getConfig') 7 | filterService.getMetadataFilterQuery(function (obj) {}) 8 | expect(configUtil.getConfig).toHaveBeenCalled() 9 | }) 10 | it('check for whitelisted metafilter set in config', function () { 11 | // var whiteList = ['in.ekstep', '505c7c48ac6dc1edc9b08f21db5a571d', 'b00bc992ef25f1a9a8d63291e20efc8d'] 12 | var whiteList = { 13 | channel: ['b00bc992ef25f1a9a8d63291e20efc8d'], 14 | framework: [ 'NCF' ], 15 | contentType: [ 'Resource' ], 16 | mimeType: [ 'application/vnd.ekstep.content-collection' ], 17 | resourceType: [ 'Learn' ] 18 | } 19 | configUtil.setConfig('META_FILTER_REQUEST_JSON', whiteList) 20 | filterService.getMetadataFilterQuery(function (obj) {}) 21 | expect(configUtil.getConfig('META_FILTER_REQUEST_JSON')).toEqual(whiteList) 22 | }) 23 | it('check for blacklisted metafilter set in config', function () { 24 | var blacklist = { 25 | channel: { ne: ['0124758418460180480', '0124758449502453761'] }, 26 | framework: { ne: [ '01231711180382208027', '012315809814749184151' ] }, 27 | contentType: { ne: [ 'Story' ] }, 28 | mimeType: { ne: [ 'application/vnd.ekstep.h5p-archive' ] }, 29 | resourceType: { ne: [ 'Read' ] } 30 | } 31 | configUtil.setConfig('META_FILTER_REQUEST_JSON', blacklist) 32 | filterService.getMetadataFilterQuery(function (obj) {}) 33 | expect(configUtil.getConfig('META_FILTER_REQUEST_JSON')).toEqual(blacklist) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /setup.md: -------------------------------------------------------------------------------- 1 | #Content Service Setup 2 | 3 | ##Pre Requirements 4 | 1. Node 5 | 2. Install imagemagick, graphicsmagick ref:https://www.npmjs.com/package/gm 6 | 7 | ##Environment Variables: 8 | * sunbird_content_provider_api_base_url: content provider API base url. e.g.: https://qa.ekstep.in or https://api.ekstep.in 9 | * sunbird_content_provider_api_key: API key for the above content provider URL 10 | * sunbird_content_plugin_base_url: Content plugin base url. e.g.: https://qa.ekstep.in or https://community.ekstep.in 11 | * sunbird_keycloak_auth_server_url: Sunbird keycloak auth server url e.g.: https://dev.open-sunbird.org/auth (string) 12 | * sunbird_keycloak_realm: Sunbird keycloak realm e.g.: sunbird (string) 13 | * sunbird_keycloak_client_id: Sunbird keycloak client id e.g: portal (string) 14 | * sunbird_keycloak_public: Sunbird keycloak public e.g.: true (boolean) 15 | * sunbird_cache_store: Sunbird cache store e.g.: memory (string) 16 | * sunbird_cache_ttl: Sunbird cachec time to live e.g.: 1800(number) 17 | * sunbird_image_storage_url 18 | * sunbird_azure_account_name 19 | * sunbird_azure_account_key 20 | * sunbird_dial_code_registry_url eg: staging.open-sunbird.org/dial/ 21 | * sunbird_cassandra_ips e.g : 127.0.0.1,127.0.0.2 22 | * sunbird_cassandra_port e.g: 9042 23 | * sunbird_telemetry_sync_batch_size e.g: 20 24 | * sunbird_learner_service_local_base_url e.g: 'http://learner-service:9000' 25 | 26 | ##Setup Instructions 27 | * Clone the project. 28 | * RUN "git submodule init" followed by "git submodule update" to load submodules to the latest version. 29 | * Run "git submodule foreach git pull origin master" to pull the latest sunbird-js-utils sub module. Rename `master` to specific remote branch name if you want to load from specific branch. 30 | * Change to src folder 31 | * Run `npm install` 32 | * Run `node app.js` 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/routes/frameworkCategoryInstanceRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: frameworkCategoryInstance-route.js 3 | * author: Rajath V B 4 | * desc: route file for Channel 5 | */ 6 | 7 | var frameworkCategoryInstanceService = require('../service/frameworkCategoryInstanceService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var baseUrl = '/v1/framework/category' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(baseUrl + '/read/:categoryID') 17 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, frameworkCategoryInstanceService.getFrameworkCategoryInstance) 20 | 21 | app.route(baseUrl + '/search') 22 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 23 | requestMiddleware.gzipCompression(), 24 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 25 | frameworkCategoryInstanceService.frameworkCategoryInstanceSearch) 26 | 27 | app.route(baseUrl + '/create') 28 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 29 | requestMiddleware.gzipCompression(), 30 | requestMiddleware.createAndValidateRequestBody, 31 | frameworkCategoryInstanceService.frameworkCategoryInstanceCreate) 32 | 33 | app.route(baseUrl + '/update/:categoryID') 34 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 35 | requestMiddleware.gzipCompression(), 36 | requestMiddleware.createAndValidateRequestBody, 37 | frameworkCategoryInstanceService.frameworkCategoryInstanceUpdate) 38 | } 39 | -------------------------------------------------------------------------------- /src/helpers/qrCodeKafkaProducer.js: -------------------------------------------------------------------------------- 1 | const kafka = require('kafka-node') 2 | const fs = require('fs') 3 | const _ = require('lodash') 4 | var logger = require('sb_logger_util_v2') 5 | 6 | const client = new kafka.KafkaClient({ 7 | kafkaHost: process.env.sunbird_kafka_host || 'localhost:9092', 8 | maxAsyncRequests: 100 9 | }) 10 | 11 | const producer = new kafka.HighLevelProducer(client) 12 | producer.on('ready', function () { 13 | console.log('Kafka Producer is connected and ready.') 14 | logger.info({msg: 'Kafka Producer is connected and ready.'}) 15 | }) 16 | 17 | // For this demo we just log producer errors to the console. 18 | producer.on('error', function (error) { 19 | logger.error({msg: 'Error from Kafka producer', error}) 20 | console.error(error) 21 | }) 22 | 23 | const KafkaService = { 24 | sendRecord: (data, callback = () => { }) => { // dialcodes, config, processId, objectId, path 25 | if (_.isEmpty(data)) { 26 | logger.error({msg: 'Data must be provided to send Record', additionalInfo: {data}}) 27 | return callback(new Error('Data must be provided.')) 28 | } 29 | 30 | const event = { 31 | 'eid': 'BE_QR_IMAGE_GENERATOR', 32 | 'processId': data.processId, 33 | 'objectId': data.objectId, // "contentid/channel", 34 | 'dialcodes': data.dialcodes, 35 | 'storage': data.storage, 36 | 'config': data.config 37 | } 38 | 39 | // Create a new payload 40 | const record = [ 41 | { 42 | topic: process.env.sunbird_qrimage_topic, // "sunbirddev.qrimage.request", 43 | messages: JSON.stringify(event), 44 | key: data.objectId 45 | 46 | } 47 | ] 48 | logger.info({msg: 'Kafka record', additionalInfo: {record}}) 49 | // Send record to Kafka and log result/error 50 | producer.send(record, callback) 51 | } 52 | } 53 | 54 | module.exports = KafkaService 55 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | node('build-slave') { 2 | try { 3 | String ANSI_GREEN = "\u001B[32m" 4 | String ANSI_NORMAL = "\u001B[0m" 5 | String ANSI_BOLD = "\u001B[1m" 6 | String ANSI_RED = "\u001B[31m" 7 | String ANSI_YELLOW = "\u001B[33m" 8 | 9 | ansiColor('xterm') { 10 | stage('Checkout') { 11 | if (!env.hub_org) { 12 | println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL) 13 | error 'Please resolve the errors and rerun..' 14 | } 15 | else 16 | println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL) 17 | cleanWs() 18 | checkout scm 19 | commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() 20 | build_tag = sh(script: "echo " + params.github_release_tag.split('/')[-1] + "_" + commit_hash + "_" + env.BUILD_NUMBER, returnStdout: true).trim() 21 | echo "build_tag: " + build_tag 22 | } 23 | 24 | stage('Build') { 25 | env.NODE_ENV = "build" 26 | print "Environment will be : ${env.NODE_ENV}" 27 | sh('git submodule update --init') 28 | // sh('git submodule update --init --recursive --remote') 29 | sh('chmod 777 build.sh') 30 | sh("./build.sh ${build_tag} ${env.NODE_NAME} ${hub_org}") 31 | } 32 | stage('ArchiveArtifacts') { 33 | archiveArtifacts "metadata.json" 34 | currentBuild.description = "${build_tag}" 35 | } 36 | } 37 | 38 | } 39 | catch (err) { 40 | currentBuild.result = "FAILURE" 41 | throw err 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/middlewares/filter.middleware.js: -------------------------------------------------------------------------------- 1 | var filterService = require('../service/filterService') 2 | var utilsService = require('../service/utilsService') 3 | var path = require('path') 4 | var filename = path.basename(__filename) 5 | var logger = require('sb_logger_util_v2') 6 | 7 | function addMetaFilters (req, res, next) { 8 | // If the request body has filter by metaFilter data, continue with the same filter, do not alter the values 9 | // else call the fetchFilterQuery() function to generate the search string for the meta filters 10 | if (req && req.body && req.body.request && req.body.request.filters) { 11 | if (req.body.request.filters.channel === undefined) { 12 | fetchFilterQuery(req, 'channel') 13 | } 14 | if (req.body.request.filters.framework === undefined) { 15 | fetchFilterQuery(req, 'framework') 16 | } 17 | if (req.body.request.filters.contentType === undefined) { 18 | fetchFilterQuery(req, 'contentType') 19 | } 20 | if (req.body.request.filters.mimeType === undefined) { 21 | fetchFilterQuery(req, 'mimeType') 22 | } 23 | if (req.body.request.filters.resourceType === undefined) { 24 | fetchFilterQuery(req, 'resourceType') 25 | } 26 | logger.info({ msg: 'added content meta filter', metaFilters: req.body.request.filters }, req) 27 | next() 28 | } else { 29 | next() 30 | } 31 | } 32 | function fetchFilterQuery (req, filterProperty) { 33 | filterService.getMetadataFilterQuery(function (err, searchJSON) { 34 | if (err) { 35 | logger.error({ msg: 'failed to get fetch filter query', err }) 36 | } else { 37 | for (var key in searchJSON) { 38 | var searchValue = searchJSON[key] 39 | if (key === filterProperty && searchValue !== undefined) { 40 | var finalFilterValue = req.body.request.filters[key] = searchValue 41 | return finalFilterValue 42 | } 43 | } 44 | } 45 | }) 46 | } 47 | 48 | module.exports.addMetaFilters = addMetaFilters 49 | -------------------------------------------------------------------------------- /src/routes/frameworkRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: framework-route.js 3 | * author: Rajath V B 4 | * desc: route file for Channel 5 | */ 6 | 7 | var frameworkService = require('../service/frameworkService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var baseUrl = '/v1/framework' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(baseUrl + '/read/:frameworkId') 17 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, frameworkService.getFrameworkById) 20 | 21 | app.route(baseUrl + '/list') 22 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 23 | requestMiddleware.gzipCompression(), 24 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 25 | frameworkService.frameworklList) 26 | 27 | app.route(baseUrl + '/create') 28 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 29 | requestMiddleware.gzipCompression(), 30 | requestMiddleware.createAndValidateRequestBody, frameworkService.frameworkCreate) 31 | 32 | app.route(baseUrl + '/update/:frameworkId') 33 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 34 | requestMiddleware.gzipCompression(), 35 | requestMiddleware.createAndValidateRequestBody, frameworkService.frameworkUpdate) 36 | 37 | app.route(baseUrl + '/copy/:frameworkId') 38 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 39 | requestMiddleware.gzipCompression(), 40 | requestMiddleware.createAndValidateRequestBody, frameworkService.frameworkCopy) 41 | 42 | app.route(baseUrl + '/publish/:frameworkId') 43 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 44 | requestMiddleware.gzipCompression(), 45 | requestMiddleware.createAndValidateRequestBody, frameworkService.frameworkPublish) 46 | } 47 | -------------------------------------------------------------------------------- /src/service/externalUrlMetaService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To provide external url related helper methods 3 | */ 4 | const urlMetadata = require('url-metadata') 5 | var messageUtils = require('./messageUtil') 6 | var extUrlMessage = messageUtils.EXTERNAL_URL_META 7 | var path = require('path') 8 | var filename = path.basename(__filename) 9 | var responseCode = messageUtils.RESPONSE_CODE 10 | var respUtil = require('response_util') 11 | var utilsService = require('../service/utilsService') 12 | var logger = require('sb_logger_util_v2') 13 | 14 | function fetchUrlMetaAPI (req, response) { 15 | return fetchUrlMeta(req, response) 16 | } 17 | 18 | function fetchUrlMeta (req, response) { 19 | var data = req.body.request 20 | var rspObj = {} 21 | if (!data['url']) { 22 | rspObj.errCode = extUrlMessage.FETCH.MISSING_CODE 23 | rspObj.errMsg = extUrlMessage.FETCH.MISSING_MESSAGE 24 | rspObj.responseCode = responseCode.CLIENT_ERROR 25 | logger.error({ 26 | msg: 'Error due to missing url property in request', 27 | err: { 28 | errCode: rspObj.errCode, 29 | errMsg: rspObj.errMsg, 30 | responseCode: rspObj.responseCode 31 | }, 32 | additionalInfo: {data} 33 | }, req) 34 | return response.status(400).send(respUtil.errorResponse(rspObj)) 35 | } 36 | 37 | // using 'url-metadata'module fetch meta data of given web link and return response 38 | urlMetadata(data.url).then( 39 | function (metadata) { 40 | rspObj.result = metadata 41 | return response.status(200).send(respUtil.successResponse(rspObj)) 42 | }, 43 | function (error) { 44 | rspObj.errCode = error.code || extUrlMessage.FETCH.FAILED_CODE 45 | rspObj.errMsg = extUrlMessage.FETCH.FAILED_MESSAGE 46 | rspObj.responseCode = responseCode.INTERNAL_SERVER_ERROR 47 | logger.error({ 48 | msg: 'Error while fetching meta data of the link', 49 | err: { 50 | error, 51 | errCode: rspObj.errCode, 52 | errMsg: rspObj.errMsg, 53 | responseCode: rspObj.responseCode 54 | }, 55 | additionalInfo: {url: data.url} 56 | }, req) 57 | return response.status(500).send(respUtil.errorResponse(rspObj)) 58 | }) 59 | } 60 | module.exports.fetchUrlMetaAPI = fetchUrlMetaAPI 61 | -------------------------------------------------------------------------------- /auto_build_deploy: -------------------------------------------------------------------------------- 1 | @Library('deploy-conf') _ 2 | node('build-slave') { 3 | try { 4 | String ANSI_GREEN = "\u001B[32m" 5 | String ANSI_NORMAL = "\u001B[0m" 6 | String ANSI_BOLD = "\u001B[1m" 7 | String ANSI_RED = "\u001B[31m" 8 | String ANSI_YELLOW = "\u001B[33m" 9 | 10 | ansiColor('xterm') { 11 | stage('Checkout') { 12 | tag_name = env.JOB_NAME.split("/")[-1] 13 | pre_checks() 14 | if (!env.hub_org) { 15 | println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL) 16 | error 'Please resolve the errors and rerun..' 17 | } 18 | else 19 | println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL) 20 | cleanWs() 21 | def scmVars = checkout scm 22 | checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]] 23 | build_tag = tag_name + "_" + env.BUILD_NUMBER 24 | commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() 25 | artifact_version = tag_name + "_" + commit_hash 26 | echo "build_tag: " + build_tag 27 | 28 | } 29 | 30 | // stage Build 31 | env.NODE_ENV = "build" 32 | print "Environment will be : ${env.NODE_ENV}" 33 | sh('git submodule update --init') 34 | // sh('git submodule update --init --recursive --remote') 35 | sh('chmod 777 build.sh') 36 | sh("./build.sh ${build_tag} ${env.NODE_NAME} ${hub_org}") 37 | 38 | // stage ArchiveArtifacts 39 | archiveArtifacts "metadata.json" 40 | currentBuild.description = "${build_tag}" 41 | 42 | } 43 | currentBuild.result = "SUCCESS" 44 | slack_notify(currentBuild.result, tag_name) 45 | email_notify() 46 | auto_build_deploy() 47 | 48 | } 49 | catch (err) { 50 | currentBuild.result = "FAILURE" 51 | slack_notify(currentBuild.result, tag_name) 52 | email_notify() 53 | throw err 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/utils/qrCodeUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name : qrCodeUtil.js 3 | * @description :: It creates and update the QR code image 4 | * @author :: Hairsh Kumar Gangula 5 | */ 6 | 7 | var QRCode = require('qrcode') 8 | var gm = require('gm').subClass({ imageMagick: true }) 9 | var logger = require('sb_logger_util_v2') 10 | var path = require('path') 11 | var filename = path.basename(__filename) 12 | 13 | function qrCodeUtil() { } 14 | 15 | qrCodeUtil.prototype.mmToPixel = function mmToPixel(data) { 16 | return Math.floor(data * 2.6) 17 | } 18 | 19 | qrCodeUtil.prototype.generate = function generateImage(filePath, text, color, 20 | bgColor, errorCorrectionLevel, margin, size, callback) { 21 | QRCode.toFile(filePath, text, { // dynamic - name should be dial code 22 | color: { 23 | dark: color, 24 | light: bgColor 25 | }, 26 | errorCorrectionLevel: errorCorrectionLevel, 27 | margin: margin, 28 | width: this.mmToPixel(size) 29 | }, function (err) { 30 | if (err) { 31 | logger.error({ 32 | msg: 'QR code generation error', 33 | err, 34 | additionalInfo: 35 | { 36 | filePath, text, color, bgColor, errorCorrectionLevel, margin, size 37 | } 38 | }) 39 | } 40 | callback(err, filePath) 41 | }) 42 | } 43 | 44 | qrCodeUtil.prototype.addTextAndBorder = function addTextAndBorder(filePath, text, border, color, size, callback) { 45 | var tempgm = gm() 46 | if (text) { 47 | tempgm 48 | .in('-extent', this.mmToPixel(size) + 'X' + (this.mmToPixel(size) + 10)) 49 | .in('-fill', color) 50 | .in('-font', path.join(__dirname, './../assets/fonts/arial/arialbold.ttf')) 51 | .drawText(0, 0, text, 'south') 52 | } 53 | tempgm.borderColor(color) 54 | .border(border, border) 55 | .in(filePath) 56 | .write(filePath, function (err) { 57 | if (err) { 58 | logger.error({ msg: 'Unable to add text or border', err }) 59 | } 60 | callback(err, filePath) 61 | }) 62 | } 63 | 64 | qrCodeUtil.prototype.resize = function resize(filePath, width, height, callback) { 65 | gm() 66 | .in('-geometry', this.mmToPixel(width) + 'X' + this.mmToPixel(height) + '!') 67 | .in(filePath) 68 | .write(filePath, function (err) { 69 | if (err) { 70 | logger.error({ msg: 'Unable to resize image', err }) 71 | } 72 | callback(err, filePath) 73 | }) 74 | } 75 | 76 | module.exports = qrCodeUtil 77 | -------------------------------------------------------------------------------- /src/utils/cassandraUtil.js: -------------------------------------------------------------------------------- 1 | var cassandra = require('express-cassandra') 2 | var _ = require('lodash') 3 | var contactPoints = process.env.sunbird_cassandra_urls.split(',') 4 | var cassandraDriver = require('cassandra-driver') 5 | var consistency = getConsistencyLevel(process.env.sunbird_cassandra_consistency_level) 6 | var envReplicationStrategy = process.env.sunbird_cassandra_replication_strategy || 7 | '{"class":"SimpleStrategy","replication_factor":1}' 8 | var replicationStrategy = getReplicationStrategy(envReplicationStrategy) 9 | 10 | var keyspaceConfig = [{ 11 | 'name': 'dialcodes', 12 | 'schemaPath': require('./../models/cassandra/dialcodes') 13 | }, { 14 | 'name': 'lock_db', 15 | 'schemaPath': require('./../models/cassandra/lock') 16 | }] 17 | 18 | var connection = {} 19 | _.forEach(keyspaceConfig, config => { 20 | var models = cassandra.createClient({ 21 | clientOptions: { 22 | contactPoints: contactPoints, 23 | keyspace: config.name, 24 | queryOptions: { consistency: consistency } 25 | }, 26 | ormOptions: { 27 | defaultReplicationStrategy: replicationStrategy, 28 | migration: 'safe' 29 | } 30 | }) 31 | 32 | _.forEach(config.schemaPath, schema => { 33 | models.loadSchema(schema.table_name, schema) 34 | models.instance[schema.table_name].syncDB((err, result) => { 35 | if (err) { 36 | console.log('sync failed for keyspace and table', schema.table_name) 37 | } 38 | }) 39 | connection[config.name] = models 40 | }) 41 | }) 42 | 43 | function getConnections (keyspace) { 44 | return connection[keyspace] 45 | } 46 | 47 | function checkCassandraDBHealth (callback) { 48 | const client = new cassandraDriver.Client({ contactPoints: contactPoints }) 49 | client.connect() 50 | .then(function () { 51 | client.shutdown() 52 | callback(null, true) 53 | }) 54 | .catch(function (err) { 55 | console.log('cassandra err:', err) 56 | client.shutdown() 57 | callback(err, false) 58 | }) 59 | } 60 | 61 | function getConsistencyLevel (consistency) { 62 | let consistencyValue = consistency && _.get(cassandra, `consistencies.${consistency}`) 63 | ? _.get(cassandra, `consistencies.${consistency}`) : cassandra.consistencies.one 64 | return consistencyValue 65 | } 66 | 67 | function getReplicationStrategy (replicationstrategy) { 68 | try { 69 | return JSON.parse(replicationstrategy) 70 | } catch (e) { 71 | console.log('err in getReplicationStrategy', e) 72 | return { 'class': 'SimpleStrategy', 'replication_factor': 1 } 73 | } 74 | } 75 | 76 | module.exports = { getConnections, checkCassandraDBHealth } 77 | -------------------------------------------------------------------------------- /src/service/dialCode/imageService.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var logger = require('sb_logger_util_v2') 3 | var dbModel = require('./../../utils/cassandraUtil').getConnections('dialcodes') 4 | 5 | function ImageService(config) { 6 | this.config = config 7 | } 8 | 9 | ImageService.prototype.getImage = function generateImage(dialcode, channel, publisher, cb) { 10 | var self = this 11 | this.getImgFromDB(dialcode, channel, publisher, function (error, images) { 12 | var image = compareImageConfig(images, self.configToString()) 13 | if (!error && image && image.url) { 14 | cb(null, { url: image.url, 'created': false }) 15 | } else { 16 | cb(error, null) 17 | } 18 | }) 19 | } 20 | 21 | ImageService.prototype.insertImg = function (dialcode, channel, publisher, fileName, callback) { 22 | var image = new dbModel.instance.dialcode_images({ 23 | dialcode: dialcode, 24 | config: this.configToString(), 25 | status: 0, 26 | filename: fileName, 27 | channel: channel, 28 | publisher: publisher 29 | }) 30 | image.save(function (error) { 31 | if (error) { 32 | logger.error({ 33 | msg: 'Unable to insert data to image table', 34 | error, 35 | additionalInfo: { 36 | dialcode, 37 | channel, 38 | publisher 39 | } 40 | }) 41 | callback(error, null) 42 | } else { 43 | callback(error, fileName) 44 | } 45 | }) 46 | } 47 | 48 | ImageService.prototype.getConfig = function () { 49 | return this.config 50 | } 51 | 52 | ImageService.prototype.getImgFromDB = function (dialcode, channel, publisher, callback) { 53 | dbModel.instance.dialcode_images.find( 54 | { 55 | dialcode: dialcode, 56 | channel: channel, 57 | publisher: publisher 58 | }, 59 | { allow_filtering: true }, 60 | function (error, images) { 61 | if (error) { 62 | logger.error({ 63 | msg: 'Unable to query dial code images before creating one', 64 | error, 65 | additionalInfo: { 66 | dialcode: dialcode, 67 | channel: channel, 68 | publisher: publisher 69 | } 70 | }) 71 | } 72 | callback(error, images) 73 | }) 74 | } 75 | 76 | ImageService.prototype.configToString = function () { 77 | return _.mapValues(this.getConfig(), _.method('toString')) 78 | } 79 | 80 | var compareImageConfig = function (images, config) { 81 | var image = null 82 | _.forEach(images, function (img) { 83 | if (_.isEqual(img.config, config)) { 84 | image = img 85 | return false 86 | } 87 | }) 88 | return image 89 | } 90 | module.exports = ImageService 91 | -------------------------------------------------------------------------------- /src/helpers/configHelper.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var filename = path.basename(__filename) 3 | var utilsService = require('../service/utilsService') 4 | var logger = require('sb_logger_util_v2') 5 | var async = require('async') 6 | var _ = require('lodash') 7 | var orgDataHelper = require('./orgHelper') 8 | 9 | /** 10 | * This method gets all channels through 'getRootOrgs' method response 11 | * data asynchronously and return back a promise 12 | * @returns promise 13 | */ 14 | function getAllChannelsFromAPI() { 15 | return new Promise(function (resolve, reject) { 16 | var limit = 200 17 | var offset = 0 18 | var allChannels = [] 19 | var channelReqObj = { 20 | 'request': { 21 | 'filters': { 'isRootOrg': true }, 22 | 'offset': offset, 23 | 'limit': limit 24 | } 25 | } 26 | logger.debug({ msg: 'Request to get all channels from getAllChannelsFrom API', additionalInfo: { channelReqObj } }) 27 | orgDataHelper.getRootOrgs(channelReqObj, function (err, res) { 28 | if (err) { 29 | logger.error({ msg: 'Error while getting root Org info', err, additionalInfo: { channelReqObj } }) 30 | reject(err) 31 | } 32 | const orgCount = res.result.response.count 33 | allChannels = _.without(_.map(res.result.response.content, 'hashTagId'), null) 34 | // if more orgs are there get them iteratively using async 35 | if (limit < orgCount) { 36 | var channelReqArr = [] 37 | while ((offset + limit) < orgCount) { 38 | offset = offset + limit 39 | channelReqObj.request.offset = offset 40 | channelReqArr.push(_.cloneDeep(channelReqObj)) 41 | } 42 | async.map(channelReqArr, orgDataHelper.getRootOrgs, function (err, mappedResArr) { 43 | if (err) { 44 | logger.error({msg: 'Error from getAllChannelsFrom API', err}) 45 | } 46 | /** 47 | * extract hashTagId which represents the channelID from each response 48 | * of responseMap to generate the whitelisted channels array 49 | * */ 50 | _.forEach(mappedResArr, function (item) { 51 | allChannels.push(_.map(item.result.response.content, 'hashTagId')) 52 | }) 53 | allChannels = _.without(_.flatten(allChannels), null) 54 | logger.debug({ msg: 'All channels info from getAllChannelsFrom API', additionalInfo: { allChannels } }) 55 | resolve(allChannels) 56 | }) 57 | } else { 58 | logger.debug({ msg: 'All channels info from getAllChannelsFrom API', additionalInfo: { allChannels } }) 59 | resolve(allChannels) 60 | } 61 | }) 62 | }) 63 | } 64 | 65 | module.exports = { 66 | getAllChannelsFromAPI: getAllChannelsFromAPI 67 | } 68 | -------------------------------------------------------------------------------- /src/service/dialCode/dialCodeServiceHelper.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var _ = require('lodash') 3 | var contentProvider = require('sb_content_provider_util') 4 | var configUtil = require('sb-config-util') 5 | var dialcodeMaxCount = configUtil.getConfig('DIALCODE_GENERATE_MAX_COUNT') 6 | var path = require('path') 7 | var filename = path.basename(__filename) 8 | var messageUtils = require('./../messageUtil') 9 | var responseCode = messageUtils.RESPONSE_CODE 10 | var logger = require('sb_logger_util_v2') 11 | 12 | // Logic to get the dialcodes for request count 13 | 14 | function DialCodeServiceHelper() { } 15 | 16 | DialCodeServiceHelper.prototype.generateDialcodes = function (req, headers, callback) { 17 | this.totalCount = req.request.dialcodes.count > dialcodeMaxCount ? dialcodeMaxCount : req.request.dialcodes.count 18 | var self = this 19 | this.callApi(req, headers, this.totalCount, function (err, res) { 20 | if (err || _.indexOf([responseCode.SUCCESS, responseCode.PARTIAL_SUCCESS], res.responseCode) === -1) { 21 | callback(err, res) 22 | } else if (_.get(res, 'result.count') === self.totalCount) { 23 | callback(err, res) 24 | } else if (_.get(res, 'result.count') < self.totalCount) { 25 | var apiMaxCount = res.result.count 26 | var remainingCount = self.totalCount - apiMaxCount 27 | var totalResCount = res.result.count 28 | var q = async.queue(function (task, cb) { 29 | self.callApi(req, headers, task.count, function (err, resData) { 30 | cb(err, resData) 31 | }) 32 | }, 10) 33 | 34 | q.drain = function () { 35 | logger.info({ msg: 'processed all the requests count', additionalInfo: { totalCount: totalResCount } }) 36 | res.result.count = totalResCount 37 | callback(err, res) 38 | } 39 | 40 | while (remainingCount > 0) { 41 | q.push({ count: remainingCount }, function (err, respData) { 42 | if (err || _.indexOf([responseCode.SUCCESS, responseCode.PARTIAL_SUCCESS], res.responseCode) === -1) { 43 | logger.error({ msg: 'Error during dialCode generation while remainingCount>0', err, response: respData }) 44 | } else { 45 | totalResCount = totalResCount + respData.result.count 46 | res.result.dialcodes = _.merge(res.result.dialcodes, respData.result.dialcodes) 47 | } 48 | }) 49 | remainingCount = remainingCount - apiMaxCount 50 | } 51 | } 52 | }) 53 | } 54 | 55 | DialCodeServiceHelper.prototype.callApi = function (req, headers, count, callback) { 56 | var reqData = _.clone(req) 57 | reqData.request.dialcodes.count = count 58 | contentProvider.generateDialCode(req, headers, callback) 59 | } 60 | 61 | module.exports = new DialCodeServiceHelper() 62 | -------------------------------------------------------------------------------- /src/routes/conceptRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: DomainRoute.js 3 | * author: Anuj Gupta 4 | * desc: route file for user domain and concepts 5 | */ 6 | 7 | var domainService = require('../service/conceptService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | 11 | var BASE_URL_V1 = '/v1' 12 | 13 | module.exports = function (app) { 14 | app.route(BASE_URL_V1 + '/domains') 15 | .get(requestMiddleware.gzipCompression(), 16 | requestMiddleware.createAndValidateRequestBody, domainService.getDomainsAPI) 17 | 18 | app.route(BASE_URL_V1 + '/domains/:domainId') 19 | .get(requestMiddleware.gzipCompression(), 20 | requestMiddleware.createAndValidateRequestBody, domainService.getDomainByIDAPI) 21 | 22 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType') 23 | .get(requestMiddleware.gzipCompression(), 24 | requestMiddleware.createAndValidateRequestBody, domainService.getObjectTypesAPI) 25 | 26 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType/:objectId') 27 | .get(requestMiddleware.gzipCompression(), 28 | requestMiddleware.createAndValidateRequestBody, domainService.getObjectTypeByIDAPI) 29 | 30 | app.route(BASE_URL_V1 + '/concepts/:conceptId') 31 | .get(requestMiddleware.gzipCompression(), 32 | requestMiddleware.createAndValidateRequestBody, domainService.getConceptByIdAPI) 33 | 34 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType/search') 35 | .post(requestMiddleware.gzipCompression(), 36 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 37 | domainService.searchObjectTypeAPI) 38 | 39 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType') 40 | .post(requestMiddleware.gzipCompression(), 41 | requestMiddleware.createAndValidateRequestBody, domainService.createObjectTypeAPI) 42 | 43 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType/:objectId') 44 | .patch(requestMiddleware.gzipCompression(), 45 | requestMiddleware.createAndValidateRequestBody, domainService.updateObjectTypeAPI) 46 | 47 | app.route(BASE_URL_V1 + '/domains/:domainId/:objectType/:objectId/retire') 48 | .post(requestMiddleware.gzipCompression(), 49 | requestMiddleware.createAndValidateRequestBody, domainService.retireObjectTypeAPI) 50 | 51 | app.route(BASE_URL_V1 + '/terms/list') 52 | .get(requestMiddleware.gzipCompression(), 53 | requestMiddleware.createAndValidateRequestBody, domainService.listTermsAPI) 54 | 55 | app.route(BASE_URL_V1 + '/resourcebundles/list') 56 | .get(requestMiddleware.createAndValidateRequestBody, domainService.listResourceBundlesAPI) 57 | 58 | app.route(BASE_URL_V1 + '/ordinals/list') 59 | .get(requestMiddleware.createAndValidateRequestBody, domainService.listOrdinalsAPI) 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sunbird-content-service 2 | 3 | This is the repository for content management micro-service. 4 | 5 | The code in this repository is licensed under MIT unless otherwise noted. Please see the [LICENSE](https://github.com/project-sunbird/sunbird-content-service/blob/master/LICENSE) file for details. 6 | 7 | ## Pre Requirements 8 | 1. Node 9 | 2. Install imagemagick, graphicsmagick ref:https://www.npmjs.com/package/gm 10 | 11 | ## Environment Variables: 12 | * sunbird_content_provider_api_base_url: content provider API base url. e.g.: https://qa.ekstep.in or https://api.ekstep.in 13 | * sunbird_content_repo_api_key: API key for the content provider URL 14 | * sunbird_search_service_api_key: API key for the search provider URL 15 | * sunbird_dial_repo_api_key : API key for the dial URL 16 | * sunbird_plugin_repo_api_key: API key for the plugin URL 17 | * sunbird_data_service_api_key: API key for the data service URL 18 | * sunbird_content_service_log_level : sets the log level e.g debug , info etc. 19 | * sunbird_language_service_api_key: API key for the language service 20 | * sunbird_default_channel: Default channel. e.g. sunbird (string) 21 | * sunbird_content_plugin_base_url: Content plugin base url. e.g.: https://qa.ekstep.in or https://community.ekstep.in 22 | * sunbird_environment: e.g : sunbird.env (string) 23 | * sunbird_instance : e.g : sunbird.ins(string) 24 | * sunbird_keycloak_auth_server_url: Sunbird keycloak auth server url e.g.: https://dev.open-sunbird.org/auth (string) 25 | * sunbird_keycloak_realm: Sunbird keycloak realm e.g.: sunbird (string) 26 | * sunbird_keycloak_client_id: Sunbird keycloak client id e.g: portal (string) 27 | * sunbird_keycloak_public: Sunbird keycloak public e.g.: true (boolean) 28 | * sunbird_cache_store: Sunbird cache store e.g.: memory (string) 29 | * sunbird_cache_ttl: Sunbird cachec time to live e.g.: 1800(number) 30 | * sunbird_image_storage_url 31 | * sunbird_azure_account_name 32 | * sunbird_azure_account_key 33 | * sunbird_dial_code_registry_url eg: staging.open-sunbird.org/dial/ 34 | * sunbird_cassandra_ips e.g : 127.0.0.1,127.0.0.2 35 | * sunbird_cassandra_port e.g: 9042 36 | * sunbird_telemetry_sync_batch_size e.g: 20 37 | * sunbird_learner_service_local_base_url e.g: 'http://learner-service:9000' 38 | * sunbird_content_service_local_base_url e.g: 'http://localhost:5000' 39 | * sunbird_content_upload_data_limit: Content upload data limit e.g.: 50mb (string) 40 | 41 | ## Setup Instructions 42 | * Clone the project.eg .(git clone --recursive url) 43 | * Run "git submodule init" 44 | * Run "git submodule update" 45 | * Run "git submodule update --init --recursive" to pull the latest sunbird-js-utils sub module (if you want to pull from specific branch, "git submodule foreach git pull origin 'branch_name'" ) 46 | * Change to src folder 47 | * Run `npm install` 48 | * Run `node app.js` 49 | 50 | ## Testing 51 | * Run "npm run test" to run test cases 52 | * Run "npm run coverage" to run test cases with coverage report 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/routes/courseRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: course-route.js 3 | * author: Anuj Gupta 4 | * desc: route file for course 5 | */ 6 | 7 | var courseService = require('../service/courseService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var BASE_URL = '/v1/course' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(BASE_URL + '/search') 17 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 20 | courseService.searchCourseAPI) 21 | 22 | app.route(BASE_URL + '/create') 23 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 24 | requestMiddleware.gzipCompression(), 25 | requestMiddleware.createAndValidateRequestBody, courseService.createCourseAPI) 26 | 27 | app.route(BASE_URL + '/update/:courseId') 28 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 29 | requestMiddleware.gzipCompression(), 30 | requestMiddleware.createAndValidateRequestBody, courseService.updateCourseAPI) 31 | 32 | app.route(BASE_URL + '/review/:courseId') 33 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 34 | requestMiddleware.gzipCompression(), 35 | requestMiddleware.createAndValidateRequestBody, courseService.reviewCourseAPI) 36 | 37 | app.route(BASE_URL + '/publish/:courseId') 38 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 39 | requestMiddleware.gzipCompression(), 40 | requestMiddleware.createAndValidateRequestBody, courseService.publishCourseAPI) 41 | 42 | app.route(BASE_URL + '/read/:courseId') 43 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 44 | requestMiddleware.gzipCompression(), 45 | requestMiddleware.createAndValidateRequestBody, courseService.getCourseAPI) 46 | 47 | app.route(BASE_URL + '/read/mycourse/:createdBy') 48 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 49 | requestMiddleware.gzipCompression(), 50 | requestMiddleware.createAndValidateRequestBody, courseService.getMyCourseAPI) 51 | 52 | app.route(BASE_URL + '/hierarchy/:courseId') 53 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 54 | requestMiddleware.gzipCompression(), 55 | requestMiddleware.createAndValidateRequestBody, courseService.getCourseHierarchyAPI) 56 | 57 | app.route(BASE_URL + '/hierarchy/update') 58 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 59 | requestMiddleware.gzipCompression(), 60 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 61 | requestMiddleware.hierarchyUpdateApiAccess, courseService.updateCourseHierarchyAPI) 62 | } 63 | -------------------------------------------------------------------------------- /src/test/middlewares/proxyMiddlewareSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name : proxyMIddlewareSpec.js 3 | * @description :: Responsible for test proxy.middleware.js 4 | * @author :: Anuj Gupta 5 | */ 6 | 7 | var request = require('request'); 8 | var host = "http://localhost:5000"; 9 | 10 | describe("Check elstep api for content player", function() { 11 | 12 | it("Should check api/* route", function(done) { 13 | request.get({ 14 | headers: {'Content-Type': 'application/json'}, 15 | uri: host + '/api/content', 16 | json: true 17 | }, function (error, response, body) { 18 | done(); 19 | }); 20 | }); 21 | 22 | it("Should check content-plugins/* route", function(done) { 23 | request.get({ 24 | headers: {'Content-Type': 'application/json'}, 25 | uri: host + '/content-plugins/content', 26 | json: true 27 | }, function (error, response, body) { 28 | expect(response.statusCode).toBe(403); 29 | done(); 30 | }); 31 | }); 32 | 33 | it("Should check plugins/* route", function(done) { 34 | request.get({ 35 | headers: {'Content-Type': 'application/json'}, 36 | uri: host + '/plugins/content', 37 | json: true 38 | }, function (error, response, body) { 39 | expect(response.statusCode).toBe(403); 40 | done(); 41 | }); 42 | }); 43 | 44 | it("Should check /assets/public/* route", function(done) { 45 | request.get({ 46 | headers: {'Content-Type': 'application/json'}, 47 | uri: host + '/assets/public/content', 48 | json: true 49 | }, function (error, response, body) { 50 | expect(response.statusCode).toBe(403); 51 | done(); 52 | }); 53 | }); 54 | 55 | it("Should check /content/preview/* route", function(done) { 56 | request.get({ 57 | headers: {'Content-Type': 'application/json'}, 58 | uri: host + '/content/preview/content', 59 | json: true 60 | }, function (error, response, body) { 61 | expect(response.statusCode).toBe(403); 62 | done(); 63 | }); 64 | }); 65 | 66 | it("Should check /action/* route", function(done) { 67 | request.get({ 68 | headers: {'Content-Type': 'application/json'}, 69 | uri: host + '/action/content', 70 | json: true 71 | }, function (error, response, body) { 72 | expect(response.statusCode).toBe(404); 73 | done(); 74 | }); 75 | }); 76 | 77 | it("Should check v1/telemetry route", function(done) { 78 | request.get({ 79 | headers: {'Content-Type': 'application/json'}, 80 | uri: host + '/v1/telemetry', 81 | json: true 82 | }, function (error, response, body) { 83 | expect(response.statusCode).toBe(404); 84 | done(); 85 | }); 86 | }); 87 | }); -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "content-service", 3 | "version": "1.14.0", 4 | "description": "This service used to perform CRUD operations on content", 5 | "main": "app.js", 6 | "scripts": { 7 | "lint": "eslint --ignore-path .eslintignore .", 8 | "validate": "npm ls", 9 | "precommit": "lint-staged", 10 | "preinstall": "npm install ./libs/sb-config-util && npm install ./libs/sb-http-util && npm install ./libs/sb_content_provider_util && npm install ./libs/sb_logger_util && npm install ./libs/sb_logger_util_v2 && npm install ./libs/sb_req_validator_util && npm install ./libs/response_util && npm install ./libs/sb_api_interceptor && npm install ./libs/sb_cache_manager && npm install ./libs/sb_telemetry_util", 11 | "test": "mocha test/**/*Spec.js", 12 | "coverage": "cross-env sunbird_environment=test sunbird_instance=sunbird nyc --reporter=lcov mocha --timeout 10000 test/**/*Spec.js --exit" 13 | }, 14 | "lint-staged": { 15 | "*.js": "eslint" 16 | }, 17 | "author": "sunbird", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@project-sunbird/telemetry-sdk": "0.0.9", 21 | "async": "^2.4.0", 22 | "azure-storage": "2.7.0", 23 | "body-parser": "^1.14.2", 24 | "cassandra-driver": "^3.4.1", 25 | "compression": "^1.7.4", 26 | "date-fns": "^2.14.0", 27 | "dateformat": "^4.0.0", 28 | "express": "^4.13.3", 29 | "express-cassandra": "2.1.0", 30 | "express-http-proxy": "^1.0.3", 31 | "fs": "0.0.2", 32 | "fs-extra": "5.0.0", 33 | "gm": "1.23.1", 34 | "https": "^1.0.0", 35 | "joi": "^14.3.0", 36 | "jsonwebtoken": "8.2.0", 37 | "kafka-node": "3.0.1", 38 | "lodash": "4.17.4", 39 | "method-override": "^2.3.5", 40 | "multiparty": "^4.1.3", 41 | "node-cron": "1.2.1", 42 | "qrcode": "1.2.0", 43 | "randomstring": "^1.1.5", 44 | "response_util": "file:libs/response_util", 45 | "sb-config-util": "file:libs/sb-config-util", 46 | "sb-http-util": "file:libs/sb-http-util", 47 | "sb_api_interceptor": "file:libs/sb_api_interceptor", 48 | "sb_cache_manager": "file:libs/sb_cache_manager", 49 | "sb_content_provider_util": "file:libs/sb_content_provider_util", 50 | "sb_logger_util": "file:libs/sb_logger_util", 51 | "sb_logger_util_v2": "file:libs/sb_logger_util_v2", 52 | "sb_req_validator_util": "file:libs/sb_req_validator_util", 53 | "sb_telemetry_util": "file:libs/sb_telemetry_util", 54 | "string-to-stream": "1.1.1", 55 | "underscore": "^1.8.3", 56 | "url-metadata": "^2.1.7", 57 | "uuid": "^3.0.1", 58 | "zip-folder": "1.0.0" 59 | }, 60 | "devDependencies": { 61 | "chai": "^4.2.0", 62 | "chai-as-promised": "^7.1.1", 63 | "cross-env": "^7.0.3", 64 | "dotenv": "^8.2.0", 65 | "eslint": "4.15.0", 66 | "eslint-config-standard": "11.0.0-beta.0", 67 | "eslint-plugin-import": "2.8.0", 68 | "eslint-plugin-jasmine": "^2.9.1", 69 | "eslint-plugin-node": "5.2.1", 70 | "eslint-plugin-promise": "3.6.0", 71 | "eslint-plugin-standard": "3.0.1", 72 | "gulp": "^3.9.1", 73 | "gulp-istanbul": "^1.1.2", 74 | "gulp-jasmine-node": "^1.0.7", 75 | "husky": "^0.14.3", 76 | "istanbul": "^0.4.5", 77 | "jasmine-node": "^1.14.5", 78 | "lint-staged": "^6.0.1", 79 | "mocha": "^6.0.2", 80 | "nock": "^13.0.8", 81 | "node-mocks-http": "^1.7.0", 82 | "nyc": "^13.3.0", 83 | "rimraf": "2.6.2", 84 | "sinon": "^7.3.1" 85 | }, 86 | "standard": { 87 | "env": { 88 | "node": true, 89 | "jasmine": true 90 | }, 91 | "ignore": [ 92 | "./../Dockerfile" 93 | ], 94 | "globals": [ 95 | "globalEkstepProxyBaseUrl" 96 | ], 97 | "rules": { 98 | "indent": [ 99 | "error", 100 | 2 101 | ], 102 | "max-len": [ 103 | "error", 104 | 120, 105 | 2, 106 | { 107 | "ignoreUrls": true, 108 | "ignoreComments": true 109 | } 110 | ], 111 | "quotes": [ 112 | "error", 113 | "single" 114 | ] 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/config/contentProviderApiConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "API": { 3 | "CREATE_CONTENT_URI": "/content/v3/create", 4 | "SEARCH_CONTENT_URI": "/content/v3/search", 5 | "SEARCH_URI": "/v3/search", 6 | "UPDATE_CONTENT_URI": "/content/v3/update", 7 | "GET_CONTENT_URI": "/content/v3/read", 8 | "REVIEW_CONTENT_URI": "/content/v3/review", 9 | "PUBLISH_CONTENT_URI": "/content/v3/publish", 10 | "LIST_CONTENT_URI": "/content/v3/list", 11 | "RETIRE_CONTENT_URI": "/content/v3/retire", 12 | "UPLOAD_CONTENT_URI": "/content/v3/upload", 13 | "HIERARCHY_CONTENT_URI": "/content/v3/hierarchy", 14 | "REJECT_CONTENT_URI": "/content/v3/reject", 15 | "FLAG_CONTENT_URI": "/content/v3/flag", 16 | "ACCEPT_FLAG_CONTENT_URI": "/content/v3/flag/accept", 17 | "REJECT_FLAG_CONTENT_URI": "/content/v3/flag/reject", 18 | "UPLOAD_MEDIA_URI": "/language/v1/language/dictionary/word/media/upload", 19 | "GET_DOMAIN_URI": "/learning/v2/domains", 20 | "GET_CONCEPT_URI": "/learning/v2/concepts", 21 | "LIST_TERMS_URI": "/domain/v3/terms/list", 22 | "LIST_RESOURCE_BUNDLES_URI": "/meta/v3/resourcebundles/list", 23 | "LIST_ORDINALS_URI": "/meta/v3/ordinals/list", 24 | "CONTENT_UPLOAD_URL_URI": "/content/v3/upload/url", 25 | "CONTENT_HIERARCHY_UPDATE_URI": "/content/v3/hierarchy/update", 26 | "HEALTH_CHECK": "/health", 27 | "UNLISTED_PUBLISH_CONTENT_URI": "/content/v3/unlisted/publish", 28 | "GENERATE_DIALCODE_URI": "/dialcode/v3/generate", 29 | "GET_DIALCODE_URI": "/dialcode/v3/read", 30 | "UPDATE_DIALCODE_URI": "/dialcode/v3/update", 31 | "GET_DIALCODEV4_URI": "/dialcode/v4/read", 32 | "UPDATE_DIALCODEV4_URI": "/dialcode/v4/update", 33 | "LIST_DIALCODE_URI": "/dialcode/v3/list", 34 | "CONTENT_LINK_DIALCODE_URI": "/content/v3/dialcode/link", 35 | "SEARCH_DIALCODE_URI": "/dialcode/v3/search", 36 | "PUBLISH_DIALCODE_URI": "/dialcode/v3/publish", 37 | "CREATE_PUBLISHER_URI": "/dialcode/v3/publisher/create", 38 | "GET_PUBLISHER_URI": "/dialcode/v3/publisher/read", 39 | "UPDATE_PUBLISHER_URI": "/dialcode/v3/publisher/update", 40 | "UPDATE_COLLABORATOR": "/content/v1/collaborator/update", 41 | "RESERVE_DIALCODE": "/content/v3/dialcode/reserve", 42 | "RELEASE_DIALCODE": "/content/v3/dialcode/release", 43 | 44 | "CHANNEL_URI": "/channel/v3/read", 45 | "CHANNEL_LIST_URI": "/channel/v3/list", 46 | "CHANNEL_SEARCH_URI": "/channel/v3/search", 47 | "CHANNEL_CREATE_URI": "/channel/v3/create", 48 | "CHANNEL_UPDATE_URI": "/channel/v3/update", 49 | 50 | "FRAMEWORK_URI": "/framework/v3/read", 51 | "FRAMEWORK_LIST_URI": "/framework/v3/list", 52 | "FRAMEWORK_CREATE_URI": "/framework/v3/create", 53 | "FRAMEWORK_UPDATE_URI": "/framework/v3/update", 54 | "FRAMEWORK_COPY_URI": "/framework/v3/copy", 55 | "FRAMEWORK_PUBLISH_URI":"/framework/v3/publish", 56 | 57 | "FRAMEWORK_TERM_URI": "/framework/v3/term/read", 58 | "FRAMEWORK_TERM_SEARCH_URI": "/framework/v3/term/search", 59 | "FRAMEWORK_TERM_CREATE_URI": "/framework/v3/term/create", 60 | "FRAMEWORK_TERM_UPDATE_URI": "/framework/v3/term/update", 61 | 62 | "FRAMEWORK_CATEGORY_INSTANCE_URI": "/framework/v3/category/read", 63 | "FRAMEWORK_CATEGORY_INSTANCE_SEARCH_URI": "/framework/v3/category/search", 64 | "FRAMEWORK_CATEGORY_INSTANCE_CREATE_URI": "/framework/v3/category/create", 65 | "FRAMEWORK_CATEGORY_INSTANCE_UPDATE_URI": "/framework/v3/category/update", 66 | 67 | "TELEMETRY": "v1/telemetry", 68 | 69 | 70 | "SUBMIT_DATA_EXHAUST_URI": "/data/v3/dataset/request/submit", 71 | "LIST_DATA_EXHAUST_URI": "/data/v3/dataset/request/list", 72 | "READ_DATA_EXHAUST_URI": "/data/v3/dataset/request/read", 73 | "CHANNEL_DATA_EXHAUST_URI": "/data/v3/dataset/request", 74 | "SYSTEM_UPDATE_CONTENT_URI": "/system/v3/content/update", 75 | "COPY_CONTENT_URI": "/content/v3/copy", 76 | "PLUGINS_SEARCH_URI": "/v3/search", 77 | 78 | "CREATE_LOCK": "/lock/v1/create", 79 | "REFRESH_LOCK": "/lock/v1/refresh", 80 | "RETIRE_LOCK": "/lock/v1/retire", 81 | "LIST_LOCK": "/lock/v1/list" 82 | } 83 | } -------------------------------------------------------------------------------- /src/service/questionService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : questionService.js 3 | * @author: Harish Kumar Gangula 4 | * @desc : controller file for questions 5 | */ 6 | 7 | var respUtil = require('response_util') 8 | var contentProviderUtil = require('sb_content_provider_util') 9 | var logger = require('sb_logger_util_v2') 10 | var messageUtils = require('./messageUtil') 11 | var utilsService = require('./utilsService') 12 | var _ = require('lodash') 13 | var responseCode = messageUtils.RESPONSE_CODE 14 | const questionAllFields = "name,code,description,mimeType,primaryCategory,additionalCategories,visibility,copyright,license,lockKey,assets,audience,author,owner,attributions,consumerId,contentEncoding,contentDisposition,appIcon,publishCheckList,publishComment,compatibilityLevel,status,prevState,prevStatus,lastStatusChangedOn,keywords,pkgVersion,version,versionKey,language,channel,framework,subject,medium,board,gradeLevel,topic,boardIds,gradeLevelIds,subjectIds,mediumIds,topicsIds,targetFWIds,targetBoardIds,targetGradeLevelIds,targetSubjectIds,targetMediumIds,targetTopicIds,createdOn,createdFor,createdBy,lastUpdatedOn,lastUpdatedBy,lastSubmittedOn,lastSubmittedBy,publisher,lastPublishedOn,lastPublishedBy,publishError,reviewError,body,editorState,answer,solutions,instructions,hints,media,responseDeclaration,interactions,qType,scoringMode,qumlVersion,timeLimit,maxScore,showTimer,showFeedback,showSolutions,interactionTypes,templateId,bloomsLevel,feedback,responseProcessing,templateDeclaration,dailySummaryReportEnabled,allowAnonymousAccess,termsAndConditions,expectedDuration,completionCriteria,collaborators,semanticVersion,schemaVersion" 15 | 16 | /** 17 | * This function helps to get all domain from ekstep 18 | * @param {Object} req 19 | * @param {Object} response 20 | */ 21 | 22 | function readQuestion(identifier, query, headers) { 23 | return (new Promise((resolve, reject) => { 24 | contentProviderUtil.readQuestion(identifier, query, headers, (error, res) => { 25 | if (error) { 26 | reject(error) 27 | } else { 28 | if (res.responseCode !== responseCode.SUCCESS) { 29 | reject(res) 30 | } else { 31 | resolve(res) 32 | } 33 | } 34 | }) 35 | })) 36 | } 37 | 38 | 39 | function getList(req, response) { 40 | delete req.headers['accept-encoding'] 41 | logger.debug({ msg: 'questionService.getList() called' }, req) 42 | let data = {} 43 | let rspObj = req.rspObj 44 | data.body = req.body 45 | req.query.fields = req.query.fields || questionAllFields 46 | data.query = req.query 47 | 48 | let questionIds = _.get(data, 'body.request.search.identifier'); 49 | if (_.isEmpty(questionIds) || !_.isArray(questionIds)) { 50 | rspObj.responseCode = responseCode.CLIENT_ERROR 51 | rspObj.errMsg = 'Either identifier is missing or it is not list type'; 52 | logger.error({ 53 | msg: 'Either identifier is missing or it is not list type', 54 | additionalInfo: { data }, 55 | err: { responseCode: rspObj.responseCode } 56 | }, req) 57 | return response.status(400).send(respUtil.errorResponse(rspObj)) 58 | } 59 | const questionsLimit = parseInt(process.env.questions_list_limit, 10) || 20; 60 | questionIds = _.take(questionIds, questionsLimit); 61 | 62 | logger.debug({ msg: 'Request to get questions by ids ', additionalInfo: { questionIds } }, req) 63 | 64 | const questionsRequestPromises = questionIds.map(id => { 65 | return readQuestion(id, req.query, req.headers) 66 | }) 67 | 68 | Promise.all(questionsRequestPromises).then(questionResponses => { 69 | rspObj.result = {questions: []}; 70 | rspObj.result.questions = questionResponses.map(questionResponse => { 71 | return _.get(questionResponse, 'result.question') 72 | }); 73 | rspObj.result.count = questionResponses.length; 74 | logger.debug({ msg: 'questions details', additionalInfo: { questionResponses } }, req); 75 | return response.status(200).send(respUtil.successResponse(rspObj)); 76 | }).catch(err => { 77 | rspObj.responseCode = _.get(err, 'responseCode') || responseCode.SERVER_ERROR 78 | logger.error({ msg: 'Getting error fetching questions by ids ', additionalInfo: { questionIds }, err: { err, responseCode: rspObj.responseCode } }, req) 79 | var httpStatus = err && err.statusCode >= 100 && err.statusCode < 600 ? err.statusCode : 500 80 | rspObj.result = err && err.result ? err.result : {} 81 | rspObj = utilsService.getErrorResponse(rspObj, err) 82 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 83 | }); 84 | } 85 | 86 | 87 | 88 | module.exports.getList = getList 89 | 90 | -------------------------------------------------------------------------------- /src/test/service/questionServiceSpec.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var expect = require('chai').expect; 3 | var start = require('./../../app.js').start; 4 | require('chai').use(require("chai-as-promised")); 5 | const nock = require('nock'); 6 | var questionServiceTestData = require('../fixtures/services/questionServiceTestData').questionServiceListAPIDATA; 7 | var host = "http://localhost:5000"; 8 | var base_url = host + "/v1/question"; 9 | 10 | 11 | describe.only('Question service', function () { 12 | 13 | before((done) => { 14 | start(done) 15 | }) 16 | 17 | it('should return error when identifer is not provided', function (done) { 18 | var requestData = questionServiceTestData.CLIENT_ERROR_REQUEST; 19 | request.post({ 20 | headers: { 'Content-Type': 'application/json' }, 21 | uri: base_url + '/list', 22 | body: { 23 | "request": requestData 24 | }, 25 | json: true 26 | }, function (error, response, body) { 27 | console.log(error) 28 | expect(response.statusCode).eql(400); 29 | expect(body).to.not.undefined; 30 | expect(body.responseCode).eql("CLIENT_ERROR"); 31 | expect(body.result).to.not.undefined;; 32 | done(); 33 | }); 34 | }); 35 | 36 | it('should return success with data when proper request is sent', function (done) { 37 | const identifer = 'do_1131687689003827201864'; 38 | nock('http://assessment-service:9000') 39 | .persist() 40 | .get(`/question/v4/read/${identifer}`) 41 | .query({fields: "name,code,description,mimeType,primaryCategory,additionalCategories,visibility,copyright,license,lockKey,assets,audience,author,owner,attributions,consumerId,contentEncoding,contentDisposition,appIcon,publishCheckList,publishComment,compatibilityLevel,status,prevState,prevStatus,lastStatusChangedOn,keywords,pkgVersion,version,versionKey,language,channel,framework,subject,medium,board,gradeLevel,topic,boardIds,gradeLevelIds,subjectIds,mediumIds,topicsIds,targetFWIds,targetBoardIds,targetGradeLevelIds,targetSubjectIds,targetMediumIds,targetTopicIds,createdOn,createdFor,createdBy,lastUpdatedOn,lastUpdatedBy,lastSubmittedOn,lastSubmittedBy,publisher,lastPublishedOn,lastPublishedBy,publishError,reviewError,body,editorState,answer,solutions,instructions,hints,media,responseDeclaration,interactions,qType,scoringMode,qumlVersion,timeLimit,maxScore,showTimer,showFeedback,showSolutions,interactionTypes,templateId,bloomsLevel,feedback,responseProcessing,templateDeclaration,dailySummaryReportEnabled,allowAnonymousAccess,termsAndConditions,expectedDuration,completionCriteria,collaborators,semanticVersion,schemaVersion"}) 42 | .reply(200, { "id": "api.question.read", "ver": "3.0", "ts": "2021-02-26T09:29:35ZZ", "params": { "resmsgid": "4fe78618-77fe-4061-a4ff-6eb1cc423f5e", "msgid": null, "err": null, "status": "successful", "errmsg": null }, "responseCode": "OK", "result": { "question": { "ownershipType": ["createdBy"], "code": "org.sunbird.ccG6ru", "credentials": { "enabled": "No" }, "channel": "in.ekstep", "language": ["English"], "mimeType": "application/pdf", "idealScreenSize": "normal", "createdOn": "2020-12-09T12:08:54.913+0000", "objectType": "Content", "primaryCategory": "Explanation Content", "contentDisposition": "inline", "lastUpdatedOn": "2020-12-09T12:08:54.913+0000", "contentEncoding": "identity", "contentType": "Resource", "dialcodeRequired": "No", "identifier": "do_1131687689003827201864", "lastStatusChangedOn": "2020-12-09T12:08:54.913+0000", "audience": ["Student"], "os": ["All"], "visibility": "Default", "consumerId": "7411b6bd-89f3-40ec-98d1-229dc64ce77d", "mediaType": "content", "osId": "org.ekstep.quiz.app", "languageCode": ["en"], "version": 2, "versionKey": "1607515734913", "license": "CC BY 4.0", "idealScreenDensity": "hdpi", "framework": "NCF", "createdBy": "874ed8a5-782e-4f6c-8f36-e0288455901e", "compatibilityLevel": 1, "name": "API DOCUMENTATION CONTENT", "status": "Draft" } } }) 43 | 44 | var requestData = questionServiceTestData.REQUEST; 45 | request.post({ 46 | headers: { 'Content-Type': 'application/json' }, 47 | uri: base_url + '/list', 48 | body: requestData, 49 | json: true 50 | }, function (error, response, body) { 51 | console.log(body) 52 | expect(response.statusCode).eql(200); 53 | expect(body).to.not.undefined; 54 | expect(body.responseCode).eql("OK"); 55 | expect(body.result).to.not.undefined;; 56 | done(); 57 | }); 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /src/routes/dialCodeRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: dialCodeRoutes.js 3 | * author: Anuj Gupta 4 | * desc: route file for dial codes 5 | */ 6 | 7 | var dialCodeService = require('../service/dialCodeService.js') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var healthService = require('../service/healthCheckService') 10 | 11 | var BASE_URL = '/v1/dialcode' 12 | var BASE_URL_V2 = '/v2/dialcode' 13 | var dependentServiceHealth = ['EKSTEP', 'CASSANDRA'] 14 | 15 | module.exports = function (app) { 16 | app.route(BASE_URL + '/generate') 17 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.gzipCompression(), 19 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 20 | dialCodeService.generateDialCodeAPI) 21 | 22 | app.route(BASE_URL + '/list') 23 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 24 | requestMiddleware.gzipCompression(), 25 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 26 | dialCodeService.dialCodeListAPI) 27 | 28 | app.route(BASE_URL + '/read') 29 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 30 | requestMiddleware.gzipCompression(), 31 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 32 | dialCodeService.getDialCodeAPI) 33 | 34 | app.route(BASE_URL_V2 + '/read/:dialCodeId') 35 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 36 | requestMiddleware.createAndValidateRequestBody, dialCodeService.getDialCodeV2API) 37 | 38 | app.route(BASE_URL + '/update/:dialCodeId') 39 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 40 | requestMiddleware.gzipCompression(), 41 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 42 | dialCodeService.updateDialCodeAPI) 43 | 44 | app.route(BASE_URL_V2 + '/update/:dialCodeId') 45 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 46 | requestMiddleware.gzipCompression(), 47 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 48 | dialCodeService.updateDialCodeV2API) 49 | 50 | app.route(BASE_URL + '/content/link') 51 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 52 | requestMiddleware.gzipCompression(), 53 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 54 | dialCodeService.contentLinkDialCodeAPI) 55 | 56 | app.route(BASE_URL + '/search') 57 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 58 | requestMiddleware.gzipCompression(), 59 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 60 | dialCodeService.searchDialCodeAPI) 61 | 62 | app.route(BASE_URL + '/publish/:dialCodeId') 63 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 64 | requestMiddleware.gzipCompression(), 65 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 66 | dialCodeService.publishDialCodeAPI) 67 | 68 | app.route(BASE_URL + '/publisher/create') 69 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 70 | requestMiddleware.gzipCompression(), 71 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 72 | dialCodeService.createPublisherAPI) 73 | 74 | app.route(BASE_URL + '/publisher/read/:publisherId') 75 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 76 | requestMiddleware.gzipCompression(), 77 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 78 | dialCodeService.getPublisherAPI) 79 | 80 | app.route(BASE_URL + '/publisher/update/:publisherId') 81 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 82 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.checkChannelID, 83 | dialCodeService.updatePublisherAPI) 84 | 85 | app.route(BASE_URL + '/process/status/:processId') 86 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 87 | requestMiddleware.gzipCompression(), 88 | requestMiddleware.createAndValidateRequestBody, dialCodeService.getProcessIdStatusAPI) 89 | 90 | app.route(BASE_URL + '/reserve/:contentId') 91 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 92 | requestMiddleware.gzipCompression(), 93 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 94 | dialCodeService.reserveDialCode) 95 | 96 | app.route(BASE_URL + '/release/:contentId') 97 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 98 | requestMiddleware.gzipCompression(), 99 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 100 | dialCodeService.releaseDialCode) 101 | } 102 | -------------------------------------------------------------------------------- /src/service/dialCode/batchImageService.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var ColorUtil = require('./../../utils/colorUtil') 3 | var colorConvert = new ColorUtil() 4 | var dbModel = require('./../../utils/cassandraUtil').getConnections('dialcodes') 5 | var messageUtils = require('./../messageUtil') 6 | var respUtil = require('response_util') 7 | var logger = require('sb_logger_util_v2') 8 | var path = require('path') 9 | var filename = path.basename(__filename) 10 | var dialCodeMessage = messageUtils.DIALCODE 11 | var responseCode = messageUtils.RESPONSE_CODE 12 | var errorCorrectionLevels = ['L', 'M', 'Q', 'H'] 13 | var Telemetry = require('sb_telemetry_util') 14 | var telemetry = new Telemetry() 15 | var batchModelProperties = ['processid', 'dialcodes', 'config', 'status', 'channel', 'publisher'] 16 | var KafkaService = require('./../../helpers/qrCodeKafkaProducer.js') 17 | 18 | const defaultConfig = { 19 | "errorCorrectionLevel": "H", 20 | "pixelsPerBlock": 2, 21 | "qrCodeMargin": 3, 22 | "textFontName": "Verdana", 23 | "textFontSize": 11, 24 | "textCharacterSpacing": 0.1, 25 | "imageFormat": "png", 26 | "colourModel": "Grayscale", 27 | "imageBorderSize": 1 28 | } 29 | 30 | function BatchImageService(config) { 31 | this.config = _.merge(defaultConfig, config); 32 | } 33 | 34 | BatchImageService.prototype.createRequest = function (data, channel, publisher, rspObj, callback) { 35 | var processId = dbModel.uuid() 36 | var dialcodes = _.map(data.dialcodes, 'text'); 37 | // Below line added for ignore eslint camel case issue. 38 | /* eslint new-cap: ["error", { "newIsCap": false }] */ 39 | var batch = new dbModel.instance.dialcode_batch({ 40 | processid: processId, 41 | dialcodes: dialcodes, 42 | config: this.configToString(), 43 | status: 0, 44 | channel: channel, 45 | publisher: publisher 46 | }) 47 | 48 | // Generate audit event 49 | var telemetryData = Object.assign({}, rspObj.telemetryData) 50 | const auditEventData = telemetry.auditEventData(batchModelProperties, 'Create', 'NA') 51 | const cdata = [{ id: dialcodes.toString(), type: 'dialCode' }, { id: publisher, type: 'publisher' }] 52 | if (telemetryData.context) { 53 | telemetryData.context.cdata = cdata 54 | } 55 | telemetryData.edata = auditEventData 56 | telemetry.audit(telemetryData) 57 | 58 | batch.save(function (error) { 59 | if (error) { 60 | logger.error({ msg: 'Error while inserting record', error }) 61 | callback(error, null) 62 | } else { 63 | data.processId = processId; 64 | KafkaService.sendRecord(data, function (err, res) { 65 | if (err) { 66 | logger.error({ msg: 'Error while sending record to kafka', err, additionalInfo: { data } }) 67 | callback(err, null) 68 | } else { 69 | callback(null, processId) 70 | } 71 | }) 72 | //TODO: Send to Kafka 73 | 74 | } 75 | }) 76 | } 77 | 78 | BatchImageService.prototype.getStatus = function (rspObj, processId) { 79 | return new Promise(function (resolve, reject) { 80 | try { 81 | var processUUId = dbModel.uuidFromString(processId) 82 | } catch (e) { 83 | console.log('err', e) 84 | rspObj.errCode = dialCodeMessage.PROCESS.NOTFOUND_CODE 85 | rspObj.errMsg = dialCodeMessage.PROCESS.NOTFOUND_MESSAGE 86 | rspObj.responseCode = responseCode.RESOURCE_NOT_FOUND 87 | logger.error({ 88 | msg: 'Requested process id not found', 89 | err: { 90 | error: e, 91 | errCode: rspObj.errCode, 92 | errMsg: rspObj.errMsg, 93 | responseCode: rspObj.responseCode 94 | }, 95 | additionalInfo: {processId} 96 | }) 97 | reject(new Error(JSON.stringify({ code: 404, data: respUtil.errorResponse(rspObj) }))) 98 | } 99 | dbModel.instance.dialcode_batch.findOne({ processid: processUUId }, function (err, batch) { 100 | if (err) { 101 | rspObj.errCode = dialCodeMessage.PROCESS.FAILED_CODE 102 | rspObj.errMsg = dialCodeMessage.PROCESS.FAILED_MESSAGE 103 | rspObj.responseCode = responseCode.SERVER_ERROR 104 | logger.error({ 105 | msg: 'Unable to get the process info', 106 | err: { 107 | err, 108 | errCode: rspObj.errCode, 109 | errMsg: rspObj.errMsg, 110 | responseCode: rspObj.responseCode 111 | }, 112 | additionalInfo: {processId: processUUId} 113 | }) 114 | reject(new Error(JSON.stringify({ code: 500, data: respUtil.errorResponse(rspObj) }))) 115 | } else if (!batch) { 116 | rspObj.errCode = dialCodeMessage.PROCESS.NOTFOUND_CODE 117 | rspObj.errMsg = dialCodeMessage.PROCESS.NOTFOUND_MESSAGE 118 | rspObj.responseCode = responseCode.RESOURCE_NOT_FOUND 119 | logger.error({ 120 | msg: 'missing batch with given process id', 121 | err: { 122 | errCode: rspObj.errCode, 123 | errMsg: rspObj.errMsg, 124 | responseCode: rspObj.responseCode 125 | }, 126 | additionalInfo: {processId: processUUId, batch} 127 | }) 128 | reject(new Error(JSON.stringify({ code: 404, data: respUtil.errorResponse(rspObj) }))) 129 | } else { 130 | if (batch.status !== 2) { 131 | rspObj.result.status = dialCodeMessage.PROCESS.INPROGRESS_MESSAGE 132 | resolve({ code: 200, data: respUtil.successResponse(rspObj) }) 133 | } else { 134 | rspObj.result.status = dialCodeMessage.PROCESS.COMPLETED 135 | rspObj.result.url = batch.url 136 | resolve({ code: 200, data: respUtil.successResponse(rspObj) }) 137 | } 138 | } 139 | }) 140 | }) 141 | } 142 | 143 | BatchImageService.prototype.configToString = function () { 144 | return _.mapValues(this.config, _.method('toString')) 145 | } 146 | 147 | 148 | module.exports = BatchImageService 149 | -------------------------------------------------------------------------------- /src/routes/contentRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: content-route.js 3 | * author: Anuj Gupta 4 | * desc: route file for content 5 | */ 6 | 7 | var contentService = require('../service/contentService') 8 | var requestMiddleware = require('../middlewares/request.middleware') 9 | var filterMiddleware = require('../middlewares/filter.middleware') 10 | var healthService = require('../service/healthCheckService') 11 | 12 | var BASE_URL = '/v1/content' 13 | var dependentServiceHealth = ['EKSTEP'] 14 | 15 | module.exports = function (app) { 16 | app.route(BASE_URL + '/search') 17 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 18 | requestMiddleware.createAndValidateRequestBody, filterMiddleware.addMetaFilters, 19 | contentService.searchContentAPI) 20 | 21 | app.route(BASE_URL + '/create') 22 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 23 | requestMiddleware.gzipCompression(), 24 | requestMiddleware.createAndValidateRequestBody, contentService.createContentAPI) 25 | 26 | app.route(BASE_URL + '/update/:contentId') 27 | .patch(healthService.checkDependantServiceHealth(dependentServiceHealth), 28 | requestMiddleware.gzipCompression(), 29 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 30 | requestMiddleware.apiAccessForCreatorUser, contentService.updateContentAPI) 31 | 32 | app.route(BASE_URL + '/upload/:contentId') 33 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 34 | requestMiddleware.gzipCompression(), 35 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 36 | requestMiddleware.apiAccessForCreatorUser, contentService.uploadContentAPI) 37 | 38 | app.route(BASE_URL + '/review/:contentId') 39 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 40 | requestMiddleware.gzipCompression(), 41 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 42 | requestMiddleware.apiAccessForCreatorUser, contentService.reviewContentAPI) 43 | 44 | app.route(BASE_URL + '/publish/:contentId') 45 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 46 | requestMiddleware.gzipCompression(), 47 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 48 | requestMiddleware.apiAccessForReviewerUser, contentService.publishContentAPI) 49 | 50 | app.route(BASE_URL + '/retire') 51 | .delete(healthService.checkDependantServiceHealth(dependentServiceHealth), 52 | requestMiddleware.gzipCompression(), 53 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 54 | contentService.retireContentAPI) 55 | 56 | app.route(BASE_URL + '/reject/:contentId') 57 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 58 | requestMiddleware.gzipCompression(), 59 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 60 | requestMiddleware.apiAccessForReviewerUser, contentService.rejectContentAPI) 61 | 62 | app.route(BASE_URL + '/read/:contentId') 63 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 64 | requestMiddleware.createAndValidateRequestBody, contentService.getContentAPI) 65 | 66 | app.route(BASE_URL + '/read/mycontent/:createdBy') 67 | .get(healthService.checkDependantServiceHealth(dependentServiceHealth), 68 | requestMiddleware.gzipCompression(), 69 | requestMiddleware.createAndValidateRequestBody, contentService.getMyContentAPI) 70 | 71 | app.route(BASE_URL + '/flag/:contentId') 72 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 73 | requestMiddleware.createAndValidateRequestBody, contentService.flagContentAPI) 74 | 75 | app.route(BASE_URL + '/flag/accept/:contentId') 76 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 77 | requestMiddleware.gzipCompression(), 78 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 79 | requestMiddleware.apiAccessForReviewerUser, contentService.acceptFlagContentAPI) 80 | 81 | app.route(BASE_URL + '/flag/reject/:contentId') 82 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 83 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 84 | requestMiddleware.apiAccessForReviewerUser, contentService.rejectFlagContentAPI) 85 | 86 | app.route(BASE_URL + '/upload/url/:contentId') 87 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 88 | requestMiddleware.gzipCompression(), 89 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 90 | requestMiddleware.apiAccessForCreatorUser, contentService.uploadContentUrlAPI) 91 | 92 | app.route(BASE_URL + '/badge/assign/:contentId') 93 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 94 | requestMiddleware.gzipCompression(), 95 | requestMiddleware.createAndValidateRequestBody, contentService.assignBadgeAPI) 96 | 97 | app.route(BASE_URL + '/badge/revoke/:contentId') 98 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 99 | requestMiddleware.gzipCompression(), 100 | requestMiddleware.createAndValidateRequestBody, contentService.revokeBadgeAPI) 101 | 102 | app.route(BASE_URL + '/copy/:contentId') 103 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 104 | requestMiddleware.gzipCompression(), 105 | requestMiddleware.createAndValidateRequestBody, requestMiddleware.validateToken, 106 | contentService.copyContentAPI) 107 | 108 | app.route(BASE_URL + '/getContentLockValidation') 109 | .post(healthService.checkDependantServiceHealth(dependentServiceHealth), 110 | requestMiddleware.createAndValidateRequestBody, contentService.validateContentLock) 111 | } 112 | -------------------------------------------------------------------------------- /src/contentMetaFilter.js: -------------------------------------------------------------------------------- 1 | var utilsService = require('./service/utilsService') 2 | var LOG = require('sb_logger_util') 3 | var path = require('path') 4 | var filename = path.basename(__filename) 5 | var _ = require('lodash') 6 | var configHelper = require('./helpers/configHelper.js') 7 | var cron = require('node-cron') 8 | var channelRefreshCronStr = process.env.sunbird_content_service_channel_refresh_cron 9 | var ischannelRefreshEnabled = false 10 | // Function to generate the Config Array 11 | 12 | var generateConfigString = ( async function(metaFiltersArray) { 13 | var configArray = {} 14 | _.forOwn(metaFiltersArray, function (value, key) { 15 | var allowedMetadata = value[0] 16 | var blackListedMetadata = value[1] 17 | if (key === 'channel' && _.includes(allowedMetadata, '$.instance.all')) { 18 | if (channelRefreshCronStr && !ischannelRefreshEnabled) { 19 | setChannelRefreshTask() 20 | ischannelRefreshEnabled = true 21 | } 22 | LOG.info(utilsService.getLoggerData({}, 'INFO', 23 | filename, 'generateConfigString', 'allowed channels', allowedMetadata)) 24 | var allChannels = await (configHelper.getAllChannelsFromAPI()) 25 | allowedMetadata = _.pull(allowedMetadata, '$.instance.all').concat(allChannels) 26 | LOG.info(utilsService.getLoggerData({}, 'INFO', 27 | filename, 'generateConfigString', 'all whitelisted channels count', allowedMetadata.length)) 28 | configArray[key] = getconfigStringFromMeta(allowedMetadata, blackListedMetadata) 29 | 30 | } else { 31 | LOG.info(utilsService.getLoggerData({}, 'INFO', 32 | filename, 'generateConfigString', 'allowed metadata', allowedMetadata)) 33 | configArray[key] = getconfigStringFromMeta(allowedMetadata, blackListedMetadata) 34 | } 35 | }) 36 | LOG.info(utilsService.getLoggerData({}, 'INFO', 37 | filename, 'generateConfigString', 'config array', configArray)) 38 | return configArray 39 | }) 40 | 41 | /** 42 | * This function generates the config string for given allowed and blacklisted channels 43 | * @param allowedMetadata array of metadata item to be allowed in filters 44 | * @param blackListedMetadata array of metadata item to be blacklisted or ignored 45 | * @returns Js object or array which contains the allowed whitelisted meta items 46 | */ 47 | function getconfigStringFromMeta (allowedMetadata, blackListedMetadata) { 48 | var configString = {} 49 | if ((allowedMetadata && allowedMetadata.length > 0) && (blackListedMetadata && blackListedMetadata.length > 0)) { 50 | configString = _.difference(allowedMetadata, blackListedMetadata) 51 | } else if (allowedMetadata && allowedMetadata.length > 0) { 52 | configString = allowedMetadata 53 | } else if (blackListedMetadata && blackListedMetadata.length > 0) { 54 | configString = { 'ne': blackListedMetadata } 55 | } 56 | LOG.info(utilsService.getLoggerData({}, 'INFO', 57 | filename, 'getconfigStringFromMeta', 'config string', configString)) 58 | return configString 59 | } 60 | 61 | // function to generate the search filter and return JSON Object 62 | function getMetaFilterConfig () { 63 | LOG.info(utilsService.getLoggerData({}, 'INFO', 64 | filename, 'getMetaFilterConfig', 'environment info', process.env)) 65 | var allowedChannels = process.env.sunbird_content_service_whitelisted_channels 66 | ? process.env.sunbird_content_service_whitelisted_channels.split(',') : [] 67 | var blackListedChannels = process.env.sunbird_content_service_blacklisted_channels 68 | ? process.env.sunbird_content_service_blacklisted_channels.split(',') : [] 69 | var allowedFramework = process.env.sunbird_content_service_whitelisted_framework 70 | ? process.env.sunbird_content_service_whitelisted_framework.split(',') : [] 71 | var blackListedFramework = process.env.sunbird_content_service_blacklisted_framework 72 | ? process.env.sunbird_content_service_blacklisted_framework.split(',') : [] 73 | var allowedMimetype = process.env.sunbird_content_service_whitelisted_mimetype 74 | ? process.env.sunbird_content_service_whitelisted_mimetype.split(',') : [] 75 | var blackListedMimetype = process.env.sunbird_content_service_blacklisted_mimetype 76 | ? process.env.sunbird_content_service_blacklisted_mimetype.split(',') : [] 77 | var allowedContenttype = process.env.sunbird_content_service_whitelisted_contenttype 78 | ? process.env.sunbird_content_service_whitelisted_contenttype.split(',') : [] 79 | var blackListedContenttype = process.env.sunbird_content_service_blacklisted_contenttype 80 | ? process.env.sunbird_content_service_blacklisted_contenttype.split(',') : [] 81 | var allowedResourcetype = process.env.sunbird_content_service_whitelisted_resourcetype 82 | ? process.env.sunbird_content_service_whitelisted_resourcetype.split(',') : [] 83 | var blackListedResourcetype = process.env.sunbird_content_service_blacklisted_resourcetype 84 | ? process.env.sunbird_content_service_blacklisted_resourcetype.split(',') : [] 85 | 86 | // Check if the Filter Configservice data is defined, if yes, create Object with it 87 | const filterConfigService = null 88 | if (filterConfigService === null) { 89 | // generate JSON and return the configArray 90 | var metaFiltersArray = { 91 | 'channel': [allowedChannels, blackListedChannels], 92 | 'framework': [allowedFramework, blackListedFramework], 93 | 'mimeType': [allowedMimetype, blackListedMimetype], 94 | 'contentType': [allowedContenttype, blackListedContenttype], 95 | 'resourceType': [allowedResourcetype, blackListedResourcetype] 96 | } 97 | return generateConfigString(metaFiltersArray) 98 | } else { 99 | // return getFilterJSONfromConfigService() 100 | return getFilterJSONfromConfigService() 101 | } 102 | } 103 | 104 | function getFilterJSONfromConfigService () { 105 | // Generate JSON from Config Service and return the filter Object else throw errors 106 | throw new Error('Config service is unavailable') 107 | } 108 | 109 | /** 110 | * This function executes the scheduler cron job to refresh the whitelisted 111 | * channels based given cron interval string 'channelRefreshCronStr' 112 | */ 113 | function setChannelRefreshTask () { 114 | cron.schedule(channelRefreshCronStr, function () { 115 | LOG.info(utilsService.getLoggerData({}, 'INFO', 116 | filename, 'setChannelRefreshTask', 'running scheduler task', channelRefreshCronStr)) 117 | getMetaFilterConfig() 118 | }) 119 | } 120 | 121 | module.exports.getMetaFilterConfig = getMetaFilterConfig 122 | -------------------------------------------------------------------------------- /src/service/dataExhaustService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : dataExhaustService.js 3 | * @author: Anuj Gupta 4 | * @desc : controller file for handle data exhaust service. 5 | */ 6 | 7 | var async = require('async') 8 | var path = require('path') 9 | var respUtil = require('response_util') 10 | var contentProvider = require('sb_content_provider_util') 11 | var logger = require('sb_logger_util_v2') 12 | var messageUtils = require('./messageUtil') 13 | var utilsService = require('../service/utilsService') 14 | 15 | var filename = path.basename(__filename) 16 | var dataSetMessages = messageUtils.DATASET 17 | var responseCode = messageUtils.RESPONSE_CODE 18 | 19 | /** 20 | * This controller function helps to submit data set request 21 | * @param {object} req 22 | * @param {object} response 23 | */ 24 | function submitDataSetRequest(req, response) { 25 | var data = req.body 26 | var rspObj = req.rspObj 27 | 28 | async.waterfall([ 29 | 30 | function (CBW) { 31 | logger.debug({ msg: 'Request to content provider to submit data exhaust' }, req) 32 | contentProvider.submitDataSetRequest(data, req.headers, function (err, res) { 33 | if (err || res.responseCode !== responseCode.SUCCESS) { 34 | logger.error({ msg: 'Getting error from content provider while submitting dataset request', additionalInfo: { data }, err }, req) 35 | rspObj.result = res && res.result ? res.result : {} 36 | rspObj = utilsService.getErrorResponse(rspObj, res, dataSetMessages.SUBMIT) 37 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 38 | } else { 39 | CBW(null, res) 40 | } 41 | }) 42 | }, 43 | function (res) { 44 | rspObj.result = res.result 45 | logger.debug({ msg: 'submitDataSetRequest API result', additionalInfo: { result: rspObj.result } }, req) 46 | return response.status(200).send(respUtil.successResponse(rspObj)) 47 | } 48 | ]) 49 | } 50 | 51 | /** 52 | * This constructor function helps to get list of data sets. 53 | * @param {object} req 54 | * @param {object} response 55 | */ 56 | function getListOfDataSetRequest(req, response) { 57 | var query = req.query 58 | var rspObj = req.rspObj 59 | var clientKey = req.params.clientKey 60 | 61 | async.waterfall([ 62 | 63 | function (CBW) { 64 | logger.debug({ 65 | msg: 'Request to content provider to get list of dataset', 66 | additionalInfo: { 67 | query: query, 68 | clientKey: clientKey 69 | } 70 | }, req) 71 | 72 | contentProvider.getListOfDataSetRequest(query, clientKey, req.headers, function (err, res) { 73 | if (err || res.responseCode !== responseCode.SUCCESS) { 74 | logger.error({ msg: 'Getting error from content provider while getting list of dataset', additionalInfo: { query }, err }, req) 75 | rspObj.result = res && res.result ? res.result : {} 76 | rspObj = utilsService.getErrorResponse(rspObj, res, dataSetMessages.LIST) 77 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 78 | } else { 79 | CBW(null, res) 80 | } 81 | }) 82 | }, 83 | function (res) { 84 | rspObj.result = res.result 85 | logger.debug({ msg: 'getListOfDataSetRequest API result', additionalInfo: { result: rspObj.result } }, req) 86 | return response.status(200).send(respUtil.successResponse(rspObj)) 87 | } 88 | ]) 89 | } 90 | 91 | /** 92 | * This constructor function helps to get detail of data set. 93 | * @param {object} req 94 | * @param {object} response 95 | */ 96 | function getDataSetDetailRequest(req, response) { 97 | var rspObj = req.rspObj 98 | var clientKey = req.params.clientKey 99 | var requestId = req.params.requestId 100 | 101 | async.waterfall([ 102 | 103 | function (CBW) { 104 | logger.debug({ 105 | msg: 'Request to content provider to get detail of dataset', 106 | additionalInfo: { 107 | clientKey: clientKey, 108 | requestId: requestId 109 | } 110 | }, req) 111 | contentProvider.getDataSetDetailRequest(clientKey, requestId, req.headers, function (err, res) { 112 | if (err || res.responseCode !== responseCode.SUCCESS) { 113 | logger.error({ msg: 'Getting error from content provider while getting detail of dataset', err }, req) 114 | rspObj.result = res && res.result ? res.result : {} 115 | rspObj = utilsService.getErrorResponse(rspObj, res, dataSetMessages.READ) 116 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 117 | } else { 118 | CBW(null, res) 119 | } 120 | }) 121 | }, 122 | function (res) { 123 | rspObj.result = res.result 124 | logger.debug({ msg: 'getDataSetDetailRequest API result', additionalInfo: { result: rspObj.result } }, req) 125 | return response.status(200).send(respUtil.successResponse(rspObj)) 126 | } 127 | ]) 128 | } 129 | 130 | /** 131 | * This constructor function helps to get channel data set. 132 | * @param {object} req 133 | * @param {object} response 134 | */ 135 | function getChannelDataSetRequest(req, response) { 136 | var query = req.query 137 | var rspObj = req.rspObj 138 | var dataSetId = req.params.dataSetId 139 | var channelId = req.params.channelId 140 | 141 | async.waterfall([ 142 | 143 | function (CBW) { 144 | logger.debug({ 145 | msg: 'Request to content provider to get channel dataset', 146 | additionalInfo: { 147 | query: query, 148 | dataSetId: dataSetId, 149 | channelId: channelId 150 | } 151 | }, req) 152 | contentProvider.getChannelDataSetRequest(query, dataSetId, channelId, req.headers, function (err, res) { 153 | if (err || res.responseCode !== responseCode.SUCCESS) { 154 | logger.error({ msg: 'Getting error from content provider to get channel dataset', additionalInfo: { query }, err }, req) 155 | rspObj.result = res && res.result ? res.result : {} 156 | rspObj = utilsService.getErrorResponse(rspObj, res, dataSetMessages.CHANNEL) 157 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 158 | } else { 159 | CBW(null, res) 160 | } 161 | }) 162 | }, 163 | function (res) { 164 | rspObj.result = res.result 165 | logger.debug({ msg: 'getChannelDataSetRequest API result', additionalInfo: { result: rspObj.result } }, req) 166 | return response.status(200).send(respUtil.successResponse(rspObj)) 167 | } 168 | ]) 169 | } 170 | 171 | module.exports.submitDataSetRequest = submitDataSetRequest 172 | module.exports.getListOfDataSetRequest = getListOfDataSetRequest 173 | module.exports.getDataSetDetailRequest = getDataSetDetailRequest 174 | module.exports.getChannelDataSetRequest = getChannelDataSetRequest 175 | -------------------------------------------------------------------------------- /src/service/utilsService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name : utilsService.js 3 | * @description :: Responsible for handle utility function 4 | * @author :: Anuj Gupta 5 | */ 6 | 7 | var API_CONFIG = require('../config/telemetryEventConfig.json').API 8 | var messageUtils = require('../service/messageUtil') 9 | const jwt = require('jsonwebtoken') 10 | var responseCode = messageUtils.RESPONSE_CODE 11 | var _ = require('lodash') 12 | var configUtil = require('sb-config-util') 13 | 14 | /** 15 | * this function helps to create apiId for error and success response 16 | * @param {String} path 17 | * @returns {getAppIDForRESP.appId|String} 18 | */ 19 | function getAppIDForRESP (path) { 20 | var arr = path.split(':')[0].split('/').filter(function (n) { 21 | return n !== '' 22 | }) 23 | var appId 24 | if (arr.length === 1) { 25 | appId = 'api.' + arr[arr.length - 1] 26 | } else { 27 | appId = 'api.' + arr[arr.length - 2] + '.' + arr[arr.length - 1] 28 | } 29 | return appId 30 | } 31 | 32 | function getLoggerData (rspObj, level, file, method, message, data, stacktrace) { 33 | var newDataObj = {} 34 | if (data && data.headers && data.headers.telemetryData) { 35 | newDataObj = JSON.parse(JSON.stringify(data)) 36 | delete newDataObj.headers['telemetryData'] 37 | } else { 38 | newDataObj = data 39 | } 40 | var dataObj = { 41 | 'eid': 'BE_LOG', 42 | 'did': rspObj.did, 43 | 'ets': Date.now(), 44 | 'ver': '1.0', 45 | 'mid': rspObj.msgid, 46 | 'context': { 47 | 'pdata': { 48 | 'id': rspObj.apiId, 49 | 'ver': rspObj.apiVersion 50 | } 51 | }, 52 | 'edata': { 53 | 'eks': { 54 | 'level': level, 55 | 'class': file, 56 | 'method': method, 57 | 'message': message, 58 | 'data': newDataObj, 59 | 'stacktrace': stacktrace 60 | } 61 | } 62 | } 63 | 64 | return dataObj 65 | } 66 | 67 | function getPerfLoggerData (rspObj, level, file, method, message, data, stacktrace) { 68 | var dataObj = { 69 | 'eid': 'PERF_LOG', 70 | 'ets': Date.now(), 71 | 'ver': '1.0', 72 | 'mid': rspObj.msgid, 73 | 'context': { 74 | 'pdata': { 75 | 'id': rspObj.apiId, 76 | 'ver': rspObj.apiVersion 77 | } 78 | }, 79 | 'edata': { 80 | 'eks': { 81 | 'level': level, 82 | 'class': file, 83 | 'method': method, 84 | 'message': message, 85 | 'data': data, 86 | 'stacktrace': stacktrace 87 | } 88 | } 89 | } 90 | 91 | return dataObj 92 | } 93 | 94 | /** 95 | * This function is used to params data for log event 96 | * @param {*} data 97 | */ 98 | function getParamsDataForLogEvent (data) { 99 | const url = getApiUrl(data.path) 100 | const params = [ 101 | {'rid': data.msgid}, 102 | {'title': API_CONFIG[url] && API_CONFIG[url].title}, 103 | {'category': API_CONFIG[url] && API_CONFIG[url].category}, 104 | {'url': url}, 105 | {'method': data.method} 106 | ] 107 | return params 108 | } 109 | 110 | /** 111 | * This function is used to get route of a API 112 | * @param {String} url 113 | */ 114 | function getApiUrl (url) { 115 | if (!url) { 116 | return '' 117 | } 118 | const uriArray = url.split('/:') 119 | const uriArray1 = uriArray[0].split('/') 120 | const mainUrl = uriArray1.splice(2, uriArray1.length) 121 | return mainUrl.join('/') 122 | } 123 | 124 | /** 125 | * This function is used to get context data for telemetry 126 | * @param {Object} req 127 | */ 128 | function getTelemetryContextData (req) { 129 | const url = getApiUrl(req.body.path) 130 | var context = { 131 | channel: req.get('X-Channel-Id') || configUtil.getConfig('DEFAULT_CHANNEL'), 132 | env: API_CONFIG[url] && API_CONFIG[url].env, 133 | did: req.get('X-Device-ID') || '' 134 | } 135 | return context 136 | } 137 | 138 | /** 139 | * This function is used to update context data for telemetry 140 | * @param {Object} req 141 | */ 142 | function updateContextData (oldData, newData) { 143 | let contextData = {} 144 | contextData.channel = newData.channel || oldData.channel 145 | contextData.env = newData.env || oldData.env 146 | contextData.cdata = newData.cdata || oldData.cdata 147 | contextData.rollup = newData.rollup || oldData.rollup 148 | return JSON.parse(JSON.stringify(contextData)) 149 | } 150 | 151 | /** 152 | * This function is used to get actor data for telemetry 153 | * @param {Object} req 154 | */ 155 | function getTelemetryActorData (req) { 156 | var actor = {} 157 | if (req.rspObj && req.rspObj.userId) { 158 | actor.id = _.toString(req.rspObj.userId) 159 | actor.type = 'user' 160 | } else if (req && req['headers'] && req['headers'] && req['headers']['x-authenticated-user-token']) { 161 | var payload = jwt.decode(req['headers']['x-authenticated-user-token']) 162 | var userId = payload['sub'].split(':') 163 | actor.id = userId[userId.length - 1] 164 | actor.type = 'user' 165 | } else { 166 | actor.id = _.toString(req.headers['x-consumer-id']) 167 | actor.type = req.headers['x-consumer-username'] 168 | } 169 | if (!actor['id'] || actor['id'] === '') { 170 | actor.id = _.toString(process.pid) 171 | actor.type = 'service' 172 | } 173 | 174 | return actor 175 | } 176 | 177 | /** 178 | * Return object data for telemetry envelop 179 | */ 180 | function getObjectData (id, type, ver, rollup) { 181 | return { 182 | id: id, 183 | type: type, 184 | ver: ver, 185 | rollup: rollup 186 | } 187 | } 188 | 189 | /** 190 | * This function helps to get http status code for response 191 | * @param {object} res 192 | */ 193 | function getHttpStatus (res) { 194 | return res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 195 | } 196 | 197 | /** 198 | * This function helps to get error response code 199 | * @param {object} rspObj 200 | * @param {object} serverRsp 201 | * @param {object} msgObject 202 | */ 203 | function getErrorResponse (rspObj, serverRsp, msgObject) { 204 | rspObj.errCode = _.get(serverRsp, 'params.err') ? serverRsp.params.err 205 | : (msgObject && msgObject['FAILED_CODE'] ? msgObject['FAILED_CODE'] : null) 206 | rspObj.errMsg = _.get(serverRsp, 'params.errmsg') ? serverRsp.params.errmsg 207 | : (msgObject && msgObject['FAILED_MESSAGE'] ? msgObject['FAILED_MESSAGE'] : null) 208 | rspObj.responseCode = _.get(serverRsp, 'responseCode') ? serverRsp.responseCode : responseCode['SERVER_ERROR'] 209 | return rspObj 210 | } 211 | 212 | module.exports.getLoggerData = getLoggerData 213 | module.exports.getPerfLoggerData = getPerfLoggerData 214 | module.exports.getAppIDForRESP = getAppIDForRESP 215 | module.exports.getParamsDataForLogEvent = getParamsDataForLogEvent 216 | module.exports.getTelemetryContextData = getTelemetryContextData 217 | module.exports.getTelemetryActorData = getTelemetryActorData 218 | module.exports.getObjectData = getObjectData 219 | module.exports.updateContextData = updateContextData 220 | module.exports.getHttpStatus = getHttpStatus 221 | module.exports.getErrorResponse = getErrorResponse 222 | -------------------------------------------------------------------------------- /src/service/channelService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : channelService.js 3 | * @author: Rajath V B 4 | * @desc : controller file for handle domain and concepts. 5 | */ 6 | 7 | var async = require('async') 8 | var path = require('path') 9 | var respUtil = require('response_util') 10 | var ekStepUtil = require('sb_content_provider_util') 11 | var logger = require('sb_logger_util_v2') 12 | var messageUtils = require('./messageUtil') 13 | var utilsService = require('../service/utilsService') 14 | var _ = require('lodash') 15 | var filename = path.basename(__filename) 16 | var responseCode = messageUtils.RESPONSE_CODE 17 | 18 | /** 19 | * This function helps to get all domain from ekstep 20 | * @param {Object} req 21 | * @param {Object} response 22 | */ 23 | 24 | function getChannelValuesById (req, response) { 25 | logger.debug({ msg: 'channelService.getChannelValuesById() called' }, req) 26 | var data = {} 27 | var rspObj = req.rspObj 28 | data.body = req.body 29 | data.channelId = req.params.channelId 30 | if (rspObj.telemetryData) { 31 | rspObj.telemetryData.object = utilsService.getObjectData(data.channelId, 'channel', '', {}) 32 | } 33 | 34 | if (!data.channelId) { 35 | rspObj.responseCode = responseCode.CLIENT_ERROR 36 | logger.error({ 37 | msg: 'Error due to required channel Id is missing', 38 | additionalInfo: { data }, 39 | err: { responseCode: rspObj.responseCode } 40 | }, req) 41 | return response.status(400).send(respUtil.errorResponse(rspObj)) 42 | } 43 | 44 | async.waterfall([ 45 | 46 | function (CBW) { 47 | logger.debug({ msg: 'Request to get channel details by id ', additionalInfo: { channelId: _.get(data, 'channelId') } }, req) 48 | ekStepUtil.getChannelValuesById(data.channelId, req.headers, function (err, res) { 49 | if (err || res.responseCode !== responseCode.SUCCESS) { 50 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 51 | logger.error({ msg: 'Getting error from ekstep while fetching channel by id ', additionalInfo: { channelId: data.channelId }, err: { err, responseCode: rspObj.responseCode } }, req) 52 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 53 | rspObj.result = res && res.result ? res.result : {} 54 | rspObj = utilsService.getErrorResponse(rspObj, res) 55 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 56 | } else { 57 | CBW(null, res) 58 | } 59 | }) 60 | }, 61 | 62 | function (res) { 63 | rspObj.result = res.result 64 | logger.debug({ msg: 'channel details', additionalInfo: { result: rspObj.result } }, req) 65 | return response.status(200).send(respUtil.successResponse(rspObj)) 66 | } 67 | ]) 68 | } 69 | 70 | function ChannelCreate (req, response) { 71 | logger.debug({ msg: 'channelService.ChannelCreate() called' }, req) 72 | var rspObj = req.rspObj 73 | var data = req.body 74 | if (!data) { 75 | rspObj.responseCode = responseCode.CLIENT_ERROR 76 | logger.error({ 77 | msg: 'Error due to required request body is missing', 78 | additionalInfo: { data }, 79 | err: { responseCode: rspObj.responseCode } 80 | }, req) 81 | return response.status(400).send(respUtil.errorResponse(rspObj)) 82 | } 83 | 84 | var ekStepReqData = { 85 | request: data.request 86 | } 87 | 88 | async.waterfall([ 89 | 90 | function (CBW) { 91 | logger.debug({ msg: 'Request to create channel', additionalInfo: { ekStepReqData } }, req) 92 | ekStepUtil.ChannelCreate(ekStepReqData, req.headers, function (err, res) { 93 | if (err || res.responseCode !== responseCode.SUCCESS) { 94 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 95 | logger.error({ msg: 'Getting error from ekstep while creating channel', additionalInfo: { ekStepReqData }, err: { err, responseCode: rspObj.responseCode } }, req) 96 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 97 | rspObj.result = res && res.result ? res.result : {} 98 | rspObj = utilsService.getErrorResponse(rspObj, res) 99 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 100 | } else { 101 | CBW(null, res) 102 | } 103 | }) 104 | }, 105 | 106 | function (res) { 107 | rspObj.result = res.result 108 | logger.debug({ msg: 'channel created', additionalInfo: { result: rspObj.result } }, req) 109 | return response.status(200).send(respUtil.successResponse(rspObj)) 110 | } 111 | ]) 112 | } 113 | 114 | function ChannelUpdate (req, response) { 115 | logger.debug({ msg: 'channelService.ChannelUpdate() called' }, req) 116 | var rspObj = req.rspObj 117 | var data = req.body 118 | data.channelId = req.params.channelId 119 | // Adding telemetry object data 120 | if (rspObj.telemetryData) { 121 | rspObj.telemetryData.object = utilsService.getObjectData(data.channelId, 'channel', '', {}) 122 | } 123 | if (!data) { 124 | rspObj.responseCode = responseCode.CLIENT_ERROR 125 | logger.error({ 126 | msg: 'Error due to required request body is missing', 127 | additionalInfo: { data }, 128 | err: { responseCode: rspObj.responseCode } 129 | }, req) 130 | return response.status(400).send(respUtil.errorResponse(rspObj)) 131 | } 132 | 133 | var ekStepReqData = { 134 | request: data.request 135 | } 136 | 137 | async.waterfall([ 138 | 139 | function (CBW) { 140 | logger.debug({ msg: 'Request to update channel', additionalInfo: { channelId: _.get(data, 'channelId'), ekStepReqData } }, req) 141 | ekStepUtil.ChannelUpdate(ekStepReqData, data.channelId, req.headers, function (err, res) { 142 | if (err || res.responseCode !== responseCode.SUCCESS) { 143 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 144 | logger.error({ msg: 'Getting error from ekstep while updating channel', additionalInfo: { ekStepReqData }, err: { err, responseCode: rspObj.responseCode } }, req) 145 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 146 | rspObj.result = res && res.result ? res.result : {} 147 | rspObj = utilsService.getErrorResponse(rspObj, res) 148 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 149 | } else { 150 | CBW(null, res) 151 | } 152 | }) 153 | }, 154 | 155 | function (res) { 156 | rspObj.result = res.result 157 | logger.debug({ msg: 'channel updated', additionalInfo: { result: rspObj.result } }, req) 158 | return response.status(200).send(respUtil.successResponse(rspObj)) 159 | } 160 | ]) 161 | } 162 | 163 | module.exports.getChannelValuesById = getChannelValuesById 164 | module.exports.ChannelCreate = ChannelCreate 165 | module.exports.ChannelUpdate = ChannelUpdate 166 | -------------------------------------------------------------------------------- /src/helpers/licenseHelper.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash') 2 | let configData = require('../config/constants') 3 | let async = require('async') 4 | let logger = require('sb_logger_util_v2') 5 | let CacheManager = require('sb_cache_manager') 6 | let cacheManager = new CacheManager({}) 7 | const contentProvider = require('sb_content_provider_util') 8 | 9 | /** 10 | * This function loops each object from the input and includes license details in it 11 | * @param inputdata is req object and res object 12 | * @param cb there will be no error callback , always returns success 13 | */ 14 | function includeLicenseDetails (req, res, cb) { 15 | if (_.get(req, 'query.licenseDetails') && _.get(res, 'result.content')) { 16 | let inputfields = req.query.licenseDetails.split(',') 17 | let fieldsToPopulate = configData.readableLicenseFields.filter(eachfield => inputfields.includes(eachfield)) 18 | let inputContentIsArray = _.isArray(res.result.content) 19 | let contents = inputContentIsArray ? res.result.content : [res.result.content] 20 | if (_.size(fieldsToPopulate) && _.size(contents)) { 21 | populateLicenseDetailsByName(contents, fieldsToPopulate, function 22 | (err, contentWithLicenseDetails) { 23 | if (!err) { 24 | res.result.content = inputContentIsArray ? contentWithLicenseDetails : contentWithLicenseDetails[0] 25 | } 26 | return cb(null, res) 27 | }) 28 | } else { 29 | return cb(null, res) 30 | } 31 | } else { 32 | return cb(null, res) 33 | } 34 | } 35 | /** 36 | * This function loops each object from the input and maps channel id with hasTagId from licenseDetails and prepares licenseDetails obj for each obj in the array 37 | * @param inputdata is array of objects, it might be content or course 38 | * @param cb callback after success or error 39 | */ 40 | function populateLicenseDetailsByName (contents, inputfields, cb) { 41 | let licenseDetails = [] 42 | let licenseSearchQuery = { 43 | 'request': { 44 | 'filters': { 45 | 'objectType': 'License' 46 | }, 47 | 'limit': 100 48 | } 49 | } 50 | let tryFromCache = true 51 | async.waterfall([ 52 | // intially fetch all the licenses till the default limit 53 | function (CBW) { 54 | getLicenseFromCache(licenseSearchQuery, tryFromCache, contents, function (err, licenseData) { 55 | if (!err && licenseData) { 56 | licenseDetails = licenseData 57 | return CBW() 58 | } else { 59 | logger.error({ 60 | msg: 'Error while getting license from cache memory', 61 | err, 62 | additionalInfo: { licenseSearchQuery, tryFromCache, contents } 63 | }) 64 | return cb(null, contents) 65 | } 66 | }) 67 | }, 68 | // mapping channel with licenseDetails in contents 69 | function (CBW) { 70 | let licenseDetailsWithKey = _.keyBy(licenseDetails, 'name') 71 | _.forEach(contents, (content, index) => { 72 | if (content.license) { 73 | let licenseDetail = licenseDetailsWithKey[content.license] 74 | contents[index].licenseDetails = licenseDetail ? _.pick(licenseDetail, inputfields) : {} 75 | } 76 | }) 77 | return cb(null, contents) 78 | } 79 | ]) 80 | } 81 | 82 | /** 83 | * This function tries to get the license from cache if not exits fetches from search api and sets to cache 84 | * @param requestObj is filter query that is sent to fetch composite-search api call, tryfromcache is a boolean flag, 85 | inputdata is array of contents that needs license data 86 | * @param CBW callback after success or error 87 | */ 88 | function getLicenseFromCache (licenseSearchquery, tryfromcache, inputdata, cb) { 89 | async.waterfall([ 90 | function (CBW) { 91 | if (tryfromcache) { 92 | let keyNames = getKeyNames(inputdata) 93 | cacheManager.mget(keyNames, function (err, cacheresponse) { 94 | let cachedata = _.compact(cacheresponse) 95 | if (!err && _.size(cachedata) > 0) { 96 | logger.debug({msg: 'license details - cache.', additionalInfo: {keys: keyNames, cache: cachedata}}) 97 | return cb(null, cachedata) 98 | } else { 99 | if (err) { 100 | logger.error({ msg: 'getLicenseFromCache failed', err }) 101 | } 102 | CBW() 103 | } 104 | }) 105 | } else { 106 | CBW() 107 | } 108 | }, 109 | function (CBW) { 110 | getLicense(licenseSearchquery, function (err, res) { 111 | if (err) { 112 | logger.error({ msg: 'Error while fetching license', err, additionalInfo: { licenseSearchquery } }) 113 | return cb(err) 114 | } else { 115 | if (_.get(res, 'result') && _.get(res.result, 'license')) { 116 | let cacheinputdata = prepareCacheDataToInsert(res.result.license) 117 | insertDataToCache(cacheinputdata) 118 | return cb(null, res.result.license) 119 | } else { 120 | return cb(null, []) 121 | } 122 | } 123 | }, true) 124 | } 125 | ]) 126 | } 127 | 128 | /** 129 | * This function executes the composite-search API to get all licenses 130 | * @param requestObj js object which contains the search request with filters,offset,limit,query etc 131 | * @param cb callback after success or error 132 | */ 133 | function getLicense (requestObj, cb, noExitOnError) { 134 | logger.debug({ msg: 'getLicense called', additionalInfo: { requestObj } }) 135 | contentProvider.compositeSearch(requestObj, {}, (err, res) => { 136 | if (!err) { 137 | return cb(err, res) 138 | } else { 139 | logger.error({ 140 | msg: 'Error from content provider while getting license details', 141 | err, 142 | additionalInfo: { requestObj } 143 | }) 144 | if (!noExitOnError) { 145 | logger.fatal({ 146 | msg: 'Exiting due to error from content provider while getting license details', 147 | err, 148 | additionalInfo: { requestObj, noExitOnError } 149 | }) 150 | process.exit(1) 151 | } 152 | } 153 | }) 154 | } 155 | 156 | function insertDataToCache (cacheinputdata) { 157 | cacheManager.mset({ data: cacheinputdata, ttl: configData.licenseCacheExpiryTime }, function (err, data) { 158 | if (err) { 159 | logger.error({ msg: 'Caching all License data failed', err, additionalInfo: { data: cacheinputdata } }) 160 | } else { 161 | logger.debug({ msg: 'Caching all License data successful', additionalInfo: { data: cacheinputdata } }) 162 | } 163 | }) 164 | } 165 | 166 | // Prepares the cache keys to fetch the data from license cache 167 | function getKeyNames (contentMaps) { 168 | let keyNames = [] 169 | _.forEach(contentMaps, function (contentMap) { 170 | if (contentMap.license) { 171 | let keyname = configData.licenseCacheKeyNamePrefix + contentMap.license 172 | keyNames.push(keyname) 173 | } 174 | }) 175 | return _.uniq(keyNames) 176 | } 177 | 178 | // prepares the set data for inserting in cache 179 | function prepareCacheDataToInsert (searchObjects) { 180 | let cacheKeyValuePairs = [] 181 | _.forEach(searchObjects, function (object) { 182 | if (object.name) { 183 | let keyname = configData.licenseCacheKeyNamePrefix + object.name 184 | cacheKeyValuePairs.push(keyname) 185 | cacheKeyValuePairs.push(object) 186 | } 187 | }) 188 | return cacheKeyValuePairs 189 | } 190 | module.exports = { 191 | includeLicenseDetails: includeLicenseDetails, 192 | getLicense: getLicense, 193 | getLicenseFromCache: getLicenseFromCache 194 | } 195 | -------------------------------------------------------------------------------- /src/service/healthCheckService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name : healthCheckService.js 3 | * @description :: Responsible for check content service health 4 | * @author :: Anuj Gupta 5 | */ 6 | 7 | var async = require('async') 8 | var contentProvider = require('sb_content_provider_util') 9 | var respUtil = require('response_util') 10 | var path = require('path') 11 | var logger = require('sb_logger_util_v2') 12 | var configUtil = require('sb-config-util') 13 | 14 | var messageUtils = require('./messageUtil') 15 | var utilsService = require('./utilsService') 16 | var cassandraUtils = require('../utils/cassandraUtil') 17 | var filename = path.basename(__filename) 18 | 19 | var hcMessages = messageUtils.HEALTH_CHECK 20 | var uuidV1 = require('uuid/v1') 21 | var dateFormat = require('dateformat') 22 | var checksArrayObj = [] 23 | 24 | // Function return to get health check object 25 | function getHealthCheckObj (name, healthy, err, errMsg) { 26 | return { 27 | name: name, 28 | healthy: healthy, 29 | err: err, 30 | errmsg: errMsg 31 | } 32 | }; 33 | 34 | // Function help to get health check response 35 | function getHealthCheckResp (rsp, healthy, checksArrayObj) { 36 | rsp.result = {} 37 | rsp.result.name = messageUtils.SERVICE.NAME 38 | rsp.result.version = messageUtils.API_VERSION.V1 39 | rsp.result.healthy = healthy 40 | rsp.result.check = checksArrayObj 41 | return rsp 42 | } 43 | 44 | /** 45 | * This function is helps to check health of all dependencies 46 | * @param {Object} req 47 | * @param {Object} response 48 | */ 49 | function checkHealth (req, response) { 50 | var rspObj = req.rspObj 51 | checksArrayObj = [] 52 | var isEkStepHealthy 53 | var isLSHealthy 54 | var isDbConnected 55 | async.parallel([ 56 | function (CB) { 57 | cassandraUtils.checkCassandraDBHealth(function (err, res) { 58 | if (err || res === false) { 59 | isDbConnected = false 60 | logger.error({msg: 'CASSANDRA_DB_HEALTH_STATUS is False', err}, req) 61 | configUtil.setConfig('CASSANDRA_DB_HEALTH_STATUS', 'false') 62 | checksArrayObj.push(getHealthCheckObj(hcMessages.CASSANDRA_DB.NAME, isDbConnected, 63 | hcMessages.CASSANDRA_DB.FAILED_CODE, hcMessages.CASSANDRA_DB.FAILED_MESSAGE)) 64 | } else { 65 | isDbConnected = true 66 | logger.info({msg: 'CASSANDRA_DB_HEALTH_STATUS is True'}, req) 67 | configUtil.setConfig('CASSANDRA_DB_HEALTH_STATUS', 'true') 68 | checksArrayObj.push(getHealthCheckObj(hcMessages.CASSANDRA_DB.NAME, isDbConnected, '', '')) 69 | } 70 | CB() 71 | }) 72 | }, 73 | function (CB) { 74 | contentProvider.ekStepHealthCheck(function (err, res) { 75 | if (err) { 76 | isEkStepHealthy = false 77 | logger.error({msg: 'EKSTEP_HEALTH_STATUS is False', err}, req) 78 | configUtil.setConfig('EKSTEP_HEALTH_STATUS', 'false') 79 | checksArrayObj.push(getHealthCheckObj(hcMessages.EK_STEP.NAME, isEkStepHealthy, 80 | hcMessages.EK_STEP.FAILED_CODE, hcMessages.EK_STEP.FAILED_MESSAGE)) 81 | } else if (res && res.result && res.result.healthy) { 82 | isEkStepHealthy = true 83 | logger.info({msg: 'EKSTEP_HEALTH_STATUS is True'}, req) 84 | configUtil.setConfig('EKSTEP_HEALTH_STATUS', 'true') 85 | checksArrayObj.push(getHealthCheckObj(hcMessages.EK_STEP.NAME, isEkStepHealthy, '', '')) 86 | } else { 87 | isEkStepHealthy = false 88 | logger.error({msg: 'EKSTEP_HEALTH_STATUS is False'}, req) 89 | configUtil.setConfig('EKSTEP_HEALTH_STATUS', 'false') 90 | checksArrayObj.push(getHealthCheckObj(hcMessages.EK_STEP.NAME, isEkStepHealthy, 91 | hcMessages.EK_STEP.FAILED_CODE, hcMessages.EK_STEP.FAILED_MESSAGE)) 92 | } 93 | CB() 94 | }) 95 | }, 96 | function (CB) { 97 | contentProvider.learnerServiceHealthCheck(function (err, res) { 98 | if (err) { 99 | isLSHealthy = false 100 | logger.error({msg: 'LEARNER_SERVICE_HEALTH_STATUS is False', err}, req) 101 | configUtil.setConfig('LEARNER_SERVICE_HEALTH_STATUS', 'false') 102 | checksArrayObj.push(getHealthCheckObj(hcMessages.LEARNER_SERVICE.NAME, 103 | isLSHealthy, hcMessages.LEARNER_SERVICE.FAILED_CODE, hcMessages.LEARNER_SERVICE.FAILED_MESSAGE)) 104 | } else if (res && res.result && res.result.response && res.result.response.healthy) { 105 | isLSHealthy = true 106 | logger.info({msg: 'LEARNER_SERVICE_HEALTH_STATUS is True'}, req) 107 | configUtil.setConfig('LEARNER_SERVICE_HEALTH_STATUS', 'true') 108 | checksArrayObj.push(getHealthCheckObj(hcMessages.LEARNER_SERVICE.NAME, isLSHealthy, '', '')) 109 | } else { 110 | isLSHealthy = false 111 | logger.error({msg: 'LEARNER_SERVICE_HEALTH_STATUS is False'}, req) 112 | configUtil.setConfig('LEARNER_SERVICE_HEALTH_STATUS', 'false') 113 | checksArrayObj.push(getHealthCheckObj(hcMessages.LEARNER_SERVICE.NAME, 114 | isLSHealthy, hcMessages.LEARNER_SERVICE.FAILED_CODE, hcMessages.LEARNER_SERVICE.FAILED_MESSAGE)) 115 | } 116 | CB() 117 | }) 118 | } 119 | ], function () { 120 | var rsp = respUtil.successResponse(rspObj) 121 | if (isEkStepHealthy && isLSHealthy && isDbConnected) { 122 | logger.info({msg: 'Content Service is Healthy', additionalInfo: {healthStatus: checksArrayObj}}, req) 123 | return response.status(200).send(getHealthCheckResp(rsp, true, checksArrayObj)) 124 | } else { 125 | logger.error({msg: 'Content Service is not healthy', additionalInfo: {healthStatus: checksArrayObj}}, req) 126 | return response.status(200).send(getHealthCheckResp(rsp, false, checksArrayObj)) 127 | } 128 | }) 129 | } 130 | 131 | /** 132 | * This function helps to check health for content service and returns 200 on success 133 | * @param {Object} req 134 | * @param {Object} response 135 | */ 136 | function checkContentServiceHealth (req, response) { 137 | var rspObj = req.rspObj 138 | var rsp = respUtil.successResponse(rspObj) 139 | return response.status(200).send(getHealthCheckResp(rsp, true)) 140 | } 141 | 142 | /** 143 | * This function helps to check health of all dependency services in content service and returns 503 error if any service is down. This is controlled by a global variable 144 | * @param {Array} dependancyServices 145 | */ 146 | function checkDependantServiceHealth (dependancyServices) { 147 | return function (req, res, next) { 148 | if (configUtil.getConfig('CONTENT_SERVICE_HEALTH_CHECK_ENABLED') === 'false') { 149 | next() 150 | } else { 151 | var heathyServiceCount = 0 152 | dependancyServices.forEach(service => { 153 | if (service === 'LEARNER' && configUtil.getConfig('LEARNER_SERVICE_HEALTH_STATUS') === 'true') { 154 | heathyServiceCount++ 155 | } else if (service === 'EKSTEP' && configUtil.getConfig('EKSTEP_HEALTH_STATUS') === 'true') { 156 | heathyServiceCount++ 157 | } else if (service === 'CASSANDRA' && configUtil.getConfig('CASSANDRA_DB_HEALTH_STATUS') === 'true') { 158 | heathyServiceCount++ 159 | } 160 | }) 161 | 162 | if (dependancyServices.length !== heathyServiceCount) { 163 | res.status(503) 164 | res.send({ 165 | 'id': 'api.error', 166 | 'ver': '1.0', 167 | 'ts': dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss:lo'), 168 | 'params': { 169 | 'resmsgid': uuidV1(), 170 | 'msgid': null, 171 | 'status': 'failed', 172 | 'err': 'SERVICE_UNAVAILABLE', 173 | 'errmsg': 'Service is unavailable' 174 | }, 175 | 'responseCode': 'SERVICE_UNAVAILABLE', 176 | 'result': { check: checksArrayObj } 177 | }) 178 | res.end() 179 | } else { 180 | next() 181 | } 182 | } 183 | } 184 | } 185 | 186 | module.exports.checkHealth = checkHealth 187 | module.exports.checkContentServiceHealth = checkContentServiceHealth 188 | module.exports.checkDependantServiceHealth = checkDependantServiceHealth 189 | -------------------------------------------------------------------------------- /src/config/telemetryEventConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "pdata": { 3 | "id": "sunbird.content-service", 4 | "ver": "1.0", 5 | "pid": "sunbird-content-service" 6 | }, 7 | "API": { 8 | "content/search": { 9 | "title": "Content search api", 10 | "category": "contentsearch", 11 | "env": "content" 12 | }, 13 | "content/create": { 14 | "title": "Content create api", 15 | "category": "contentcreate", 16 | "env": "content" 17 | }, 18 | "content/update": { 19 | "title": "Content update api", 20 | "category": "contentupdate", 21 | "env": "content" 22 | }, 23 | "content/upload": { 24 | "title": "Content upload api", 25 | "category": "contentupload", 26 | "env": "content" 27 | }, 28 | "content/review": { 29 | "title": "Content review api", 30 | "category": "contentreview", 31 | "env": "content" 32 | }, 33 | "content/publish": { 34 | "title": "Content publish api", 35 | "category": "contentpublish", 36 | "env": "content" 37 | }, 38 | "content/retire": { 39 | "title": "Content retire api", 40 | "category": "contentretire", 41 | "env": "content" 42 | }, 43 | "content/reject": { 44 | "title": "Content reject api", 45 | "category": "contentreject", 46 | "env": "content" 47 | }, 48 | "content/read": { 49 | "title": "Content read api", 50 | "category": "contentread", 51 | "env": "content" 52 | }, 53 | "content/read/mycontent": { 54 | "title": "Content read api", 55 | "category": "contentread", 56 | "env": "content" 57 | }, 58 | "content/flag": { 59 | "title": "Content flag api", 60 | "category": "contentflag", 61 | "env": "content" 62 | }, 63 | "content/flag/accept": { 64 | "title": "Content flag accept api", 65 | "category": "contentflagaccept", 66 | "env": "content" 67 | }, 68 | "content/flag/reject": { 69 | "title": "Content flag reject api", 70 | "category": "contentflagreject", 71 | "env": "content" 72 | }, 73 | "content/upload/url": { 74 | "title": "Content upload url api", 75 | "category": "contentuploadurl", 76 | "env": "content" 77 | }, 78 | "course/search": { 79 | "title": "Course search api", 80 | "category": "coursesearch", 81 | "env": "content" 82 | }, 83 | "course/create": { 84 | "title": "Course create api", 85 | "category": "coursecreate", 86 | "env": "content" 87 | }, 88 | "course/update": { 89 | "title": "Course update api", 90 | "category": "courseupdate", 91 | "env": "content" 92 | }, 93 | "course/review": { 94 | "title": "Course review api", 95 | "category": "coursereview", 96 | "env": "content" 97 | }, 98 | "course/publish": { 99 | "title": "Course publish api", 100 | "category": "coursepublish", 101 | "env": "content" 102 | }, 103 | "course/read": { 104 | "title": "Course read api", 105 | "category": "courseread", 106 | "env": "content" 107 | }, 108 | "course/read/mycourse": { 109 | "title": "Course read api", 110 | "category": "courseread", 111 | "env": "content" 112 | }, 113 | "course/hierarchy": { 114 | "title": "Course hierarchy api", 115 | "category": "coursehierarchy" 116 | }, 117 | "search": { 118 | "title": "Search api", 119 | "category": "search", 120 | "env": "content" 121 | }, 122 | "dialcode/generate": { 123 | "title": "Dialcode generate api", 124 | "category": "dialcodegenerate", 125 | "env": "dialcode" 126 | }, 127 | "dialcode/list": { 128 | "title": "Dialcode list api", 129 | "category": "dialcodelist", 130 | "env": "dialcode" 131 | }, 132 | "dialcode/read": { 133 | "title": "Dialcode read api", 134 | "category": "dialcoderead", 135 | "env": "dialcode" 136 | }, 137 | "dialcode/update": { 138 | "title": "Dialcode update api", 139 | "category": "dialcodeupdate", 140 | "env": "dialcode" 141 | }, 142 | "dialcode/content/link": { 143 | "title": "Dialcode content link api", 144 | "category": "dialcodecontentlink", 145 | "env": "dialcode" 146 | }, 147 | "dialcode/search": { 148 | "title": "Dialcode search api", 149 | "category": "dialcodesearch", 150 | "env": "dialcode" 151 | }, 152 | "dialcode/publish": { 153 | "title": "Dialcode publish api", 154 | "category": "dialcodepublish", 155 | "env": "dialcode" 156 | }, 157 | "dialcode/publisher/create": { 158 | "title": "Publisher create api", 159 | "category": "publishercreate", 160 | "env": "dialcode" 161 | }, 162 | "dialcode/publisher/read": { 163 | "title": "Publisher read api", 164 | "category": "publishergenerate", 165 | "env": "dialcode" 166 | }, 167 | "dialcode/publisher/update": { 168 | "title": "Publisher update api", 169 | "category": "publisherupdate", 170 | "env": "dialcode" 171 | }, 172 | "dialcode/process/status": { 173 | "title": "Qr code process status api", 174 | "category": "processstatus", 175 | "env": "dialcode" 176 | }, 177 | "channel/read": { 178 | "title": "Channel read api", 179 | "category": "channelread", 180 | "env": "channel" 181 | }, 182 | "channel/list": { 183 | "title": "Channel list api", 184 | "category": "channellist", 185 | "env": "channel" 186 | }, 187 | "channel/search": { 188 | "title": "Channel search api", 189 | "category": "channelsearch", 190 | "env": "channel" 191 | }, 192 | "channel/create": { 193 | "title": "Channel create api", 194 | "category": "channelcreate", 195 | "env": "channel" 196 | }, 197 | "channel/update": { 198 | "title": "Channel update api", 199 | "category": "channelupdate", 200 | "env": "channel" 201 | }, 202 | "framework/category/read": { 203 | "title": "Framework category read api", 204 | "category": "frameworkcategoryread", 205 | "env": "framework" 206 | }, 207 | "framework/category/search": { 208 | "title": "Framework category search api", 209 | "category": "frameworkcategorysearch", 210 | "env": "framework" 211 | }, 212 | "framework/category/create": { 213 | "title": "Framework category create api", 214 | "category": "frameworkcategorycreate", 215 | "env": "framework" 216 | }, 217 | "framework/category/update": { 218 | "title": "Framework category update api", 219 | "category": "frameworkcategoryupdate", 220 | "env": "framework" 221 | }, 222 | "framework/read": { 223 | "title": "Framework read api", 224 | "category": "frameworkread", 225 | "env": "framework" 226 | }, 227 | "framework/list": { 228 | "title": "Framework list api", 229 | "category": "frameworklist", 230 | "env": "framework" 231 | }, 232 | "framework/create": { 233 | "title": "Framework create api", 234 | "category": "frameworkcreate", 235 | "env": "framework" 236 | }, 237 | "framework/update": { 238 | "title": "Framework update api", 239 | "category": "frameworkupdate", 240 | "env": "framework" 241 | }, 242 | "framework/copy": { 243 | "title": "Framework copy api", 244 | "category": "frameworkcopy", 245 | "env": "framework" 246 | }, 247 | "framework/term/read": { 248 | "title": "Framework term read api", 249 | "category": "frameworktermread", 250 | "env": "framework" 251 | }, 252 | "framework/term/search": { 253 | "title": "Framework term search api", 254 | "category": "frameworktermsearch", 255 | "env": "framework" 256 | }, 257 | "framework/term/create": { 258 | "title": "Framework term create api", 259 | "category": "frameworktermcreate", 260 | "env": "framework" 261 | }, 262 | "framework/term/update": { 263 | "title": "Framework term update api", 264 | "category": "frameworktermupdate", 265 | "env": "framework" 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /src/helpers/orgHelper.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var filename = path.basename(__filename) 3 | var utilsService = require('../service/utilsService') 4 | var logger = require('sb_logger_util_v2') 5 | const contentProvider = require('sb_content_provider_util') 6 | var _ = require('lodash') 7 | var CacheManager = require('sb_cache_manager') 8 | var cacheManager = new CacheManager({}) 9 | var configData = require('../config/constants') 10 | var async = require('async') 11 | 12 | /** 13 | * This function executes the org search lms API to get all orgs 14 | * @param requestObj js object which contains the search request with filters,offset,limit,query etc 15 | * @param cb callback after success or error 16 | */ 17 | function getRootOrgs(requestObj, cb, noExitOnError) { 18 | logger.debug({ msg: 'getRootOrgs() called', additionalInfo: { requestObj } }) 19 | contentProvider.getAllRootOrgs(requestObj, (err, res) => { 20 | if (!err) { 21 | return cb(err, res) 22 | } else { 23 | logger.error({ 24 | msg: 'Error from content provider while getting all root orgs details', 25 | err, 26 | additionalInfo: { requestObj } 27 | }) 28 | if (!noExitOnError) { 29 | logger.fatal({ 30 | msg: 'Exiting due to error from content provider while getting all root orgs details', 31 | err, 32 | additionalInfo: { requestObj, noExitOnError } 33 | }) 34 | process.exit(1) 35 | } 36 | } 37 | }) 38 | } 39 | 40 | /** 41 | * This function tries to get the orgdetails from cache if not exits fetches from api and sets to cache 42 | * @param requestObj is filter query that is sent to fetch org api call, tryfromcache is a boolean flag, 43 | inputdata is array of contents that needs org data 44 | * @param CBW callback after success or error 45 | */ 46 | function getRootOrgsFromCache(orgfetchquery, tryfromcache, inputdata, cb) { 47 | async.waterfall([ 48 | function (CBW) { 49 | if (tryfromcache) { 50 | var keyNames = getKeyNames(inputdata) 51 | cacheManager.mget(keyNames, function (err, cacheresponse) { 52 | var cachedata = _.compact(cacheresponse) 53 | if (!err && _.size(cachedata) > 0) { 54 | return cb(null, cachedata) 55 | } else { 56 | if (err) { 57 | logger.error({ msg: 'getRootOrgsFromCache failed', err }) 58 | } 59 | CBW() 60 | } 61 | }) 62 | } else { 63 | CBW() 64 | } 65 | }, 66 | function (CBW) { 67 | getRootOrgs(orgfetchquery, function (err, res) { 68 | if (err) { 69 | logger.error({ msg: 'Error while fetching rootOrgs', err, additionalInfo: { orgfetchquery } }) 70 | return cb(err) 71 | } else { 72 | if (_.get(res, 'result.response') && _.get(res.result, 'response.content')) { 73 | var cacheinputdata = prepareCacheDataToInsert(res.result.response.content) 74 | insertDataToCache(cacheinputdata) 75 | return cb(null, res.result.response.content) 76 | } else { 77 | return cb(null, []) 78 | } 79 | } 80 | }, true) 81 | } 82 | ]) 83 | } 84 | 85 | function insertDataToCache(cacheinputdata) { 86 | cacheManager.mset({ data: cacheinputdata, ttl: configData.orgCacheExpiryTime }, function (err, data) { 87 | if (err) { 88 | logger.error({ msg: 'Caching allRootOrgs data failed', err, additionalInfo: { data: cacheinputdata } }) 89 | } else { 90 | logger.debug({ msg: 'Caching allRootOrgs data successful', additionalInfo: { data: cacheinputdata } }) 91 | } 92 | }) 93 | } 94 | 95 | /** 96 | * This function loops each object from the input and maps channel id with hasTagId from orgdetails and prepares orgDetails obj for each obj in the array 97 | * @param inputdata is array of objects, it might be content or course 98 | * @param cb callback after success or error 99 | */ 100 | function populateOrgDetailsByHasTag(contents, inputfields, cb) { 101 | var orgDetails = [] 102 | var orgFetchQuery = { 103 | 'request': { 104 | 'filters': { 'isRootOrg': true } 105 | } 106 | } 107 | var tryFromCache = true 108 | async.waterfall([ 109 | // intially fetch all the orgs till the default limit 110 | function (CBW) { 111 | getRootOrgsFromCache(orgFetchQuery, tryFromCache, contents, function (err, orgdata) { 112 | if (!err && orgdata) { 113 | orgDetails = orgdata 114 | return CBW() 115 | } else { 116 | logger.error({ 117 | msg: 'Error while getting rootOrgs from cache memory', 118 | err, 119 | additionalInfo: { orgFetchQuery, tryFromCache, contents } 120 | }) 121 | return cb(null, contents) 122 | } 123 | }) 124 | }, 125 | // fetch the orgs which are not fetched from initial api call 126 | function (CBW) { 127 | var inputHashTagIds = _.uniq(_.map(contents, 'channel')) 128 | var fetchedhashTagIds = _.uniq(_.map(orgDetails, 'hashTagId')) 129 | // diff of channels which doesnt exists in inital fetch 130 | var hasTagIdsNeedToFetch = _.difference(inputHashTagIds, fetchedhashTagIds) 131 | orgFetchQuery.request.filters.hashTagId = hasTagIdsNeedToFetch 132 | if (hasTagIdsNeedToFetch.length) { 133 | // fetch directly from api , as hashTagIdsNeedToFetch are the data which are not found from first api query 134 | tryFromCache = false 135 | getRootOrgsFromCache(orgFetchQuery, tryFromCache, contents, function (err, orgdata) { 136 | if (!err && orgdata) { 137 | orgDetails = _.concat(orgDetails, orgdata) 138 | return CBW() 139 | } else { 140 | logger.error({ 141 | msg: 'Error while getting rootOrgs from cache memory', 142 | err, 143 | additionalInfo: { orgFetchQuery, tryFromCache, contents } 144 | }) 145 | return cb(null, contents) 146 | } 147 | }) 148 | } else { 149 | CBW() 150 | } 151 | }, 152 | // mapping channel with orgdetails in contents 153 | function (CBW) { 154 | var orgDetailsWithKey = _.keyBy(orgDetails, 'hashTagId') 155 | _.forEach(contents, (eachcontent, index) => { 156 | if (eachcontent.channel) { 157 | var eachorgdetail = orgDetailsWithKey[eachcontent.channel] 158 | contents[index].orgDetails = eachorgdetail ? _.pick(eachorgdetail, inputfields) : {} 159 | } 160 | }) 161 | return cb(null, contents) 162 | } 163 | ]) 164 | } 165 | 166 | /** 167 | * This function loops each object from the input and includes org details in it 168 | * @param inputdata is req object and res object 169 | * @param cb there will be no error callback , always returns success 170 | */ 171 | function includeOrgDetails(req, res, cb) { 172 | if (_.get(req, 'query.orgdetails') && _.get(res, 'result.content')) { 173 | var inputfields = req.query.orgdetails.split(',') 174 | var fieldsToPopulate = configData.orgfieldsAllowedToSend.filter(eachfield => inputfields.includes(eachfield)) 175 | var inputContentIsArray = _.isArray(res.result.content) 176 | // contents need to send as array bec populateOrgDetailsByHasTag expects data as array 177 | var contents = inputContentIsArray ? res.result.content : [res.result.content] 178 | if (_.size(fieldsToPopulate) && _.size(contents)) { 179 | populateOrgDetailsByHasTag(contents, fieldsToPopulate, function 180 | (err, contentwithorgdetails) { 181 | if (!err) { 182 | res.result.content = inputContentIsArray ? contentwithorgdetails : contentwithorgdetails[0] 183 | } 184 | return cb(null, res) 185 | }) 186 | } else { 187 | return cb(null, res) 188 | } 189 | } else { 190 | return cb(null, res) 191 | } 192 | } 193 | 194 | // prepares the set data for inserting in cache 195 | function prepareCacheDataToInsert(data) { 196 | var cacheKeyValuePairs = [] 197 | _.forEach(data, function (eachdata) { 198 | if (eachdata.hashTagId) { 199 | var keyname = configData.orgCacheKeyNamePrefix + eachdata.hashTagId 200 | cacheKeyValuePairs.push(keyname) 201 | cacheKeyValuePairs.push(eachdata) 202 | } 203 | }) 204 | return cacheKeyValuePairs 205 | } 206 | 207 | // prepares the get data for fetching from cache 208 | function getKeyNames(data) { 209 | var keyNames = [] 210 | _.forEach(data, function (eachdata) { 211 | if (eachdata.channel) { 212 | var keyname = configData.orgCacheKeyNamePrefix + eachdata.channel 213 | keyNames.push(keyname) 214 | } 215 | }) 216 | return _.uniq(keyNames) 217 | } 218 | 219 | module.exports = { 220 | getRootOrgs: getRootOrgs, 221 | includeOrgDetails: includeOrgDetails, 222 | populateOrgDetailsByHasTag: populateOrgDetailsByHasTag 223 | } 224 | -------------------------------------------------------------------------------- /src/service/frameworkTermService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : frameworkTermService.js 3 | * @author: Rajath V B 4 | * @desc : controller file for handle domain and concepts. 5 | */ 6 | 7 | var async = require('async') 8 | var path = require('path') 9 | var respUtil = require('response_util') 10 | var ekStepUtil = require('sb_content_provider_util') 11 | var logger = require('sb_logger_util_v2') 12 | var messageUtils = require('./messageUtil') 13 | var utilsService = require('../service/utilsService') 14 | 15 | var filename = path.basename(__filename) 16 | var responseCode = messageUtils.RESPONSE_CODE 17 | 18 | /** 19 | * This function helps to get all domain from ekstep 20 | * @param {Object} req 21 | * @param {Object} response 22 | */ 23 | 24 | function getFrameworkTerm (req, response) { 25 | var data = {} 26 | var rspObj = req.rspObj 27 | 28 | data.body = req.body 29 | data.category = req.params.categoryID 30 | data.queryParams = req.query 31 | // Adding telemetry object data 32 | if (rspObj.telemetryData) { 33 | rspObj.telemetryData.object = utilsService.getObjectData(data.category, 'frameworkTerm', '', {}) 34 | } 35 | 36 | if (!data.queryParams) { 37 | rspObj.responseCode = responseCode.CLIENT_ERROR 38 | logger.error({ 39 | msg: 'Error due to missing query Parameters', 40 | err: {responseCode: rspObj.responseCode}, 41 | additionalInfo: { data } 42 | }, req) 43 | return response.status(400).send(respUtil.errorResponse(rspObj)) 44 | } 45 | 46 | async.waterfall([ 47 | 48 | function (CBW) { 49 | logger.debug({ msg: 'Request to get Framework Terms', additionalInfo: { data } }, req) 50 | ekStepUtil.getFrameworkTerm(data.queryParams, data.category, req.headers, function (err, res) { 51 | if (err || res.responseCode !== responseCode.SUCCESS) { 52 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 53 | logger.error({ 54 | msg: 'Error while fetching framework terms from ekstep', 55 | err: { 56 | err, 57 | responseCode: rspObj.responseCode 58 | }, 59 | additionalInfo: {data} 60 | }, req) 61 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 62 | rspObj.result = res && res.result ? res.result : {} 63 | rspObj = utilsService.getErrorResponse(rspObj, res) 64 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 65 | } else { 66 | CBW(null, res) 67 | } 68 | }) 69 | }, 70 | 71 | function (res) { 72 | rspObj.result = res.result 73 | return response.status(200).send(respUtil.successResponse(rspObj)) 74 | } 75 | ]) 76 | } 77 | 78 | function frameworkTermSearch (req, response) { 79 | var rspObj = req.rspObj 80 | var data = req.body 81 | data.queryParams = req.query 82 | if (!data) { 83 | rspObj.responseCode = responseCode.CLIENT_ERROR 84 | logger.error({ 85 | msg: 'Error due to missing query Parameters or request body', 86 | err: {responseCode: rspObj.responseCode}, 87 | additionalInfo: { data } 88 | }, req) 89 | return response.status(400).send(respUtil.errorResponse(rspObj)) 90 | } 91 | 92 | var ekStepReqData = { 93 | request: data.request 94 | } 95 | 96 | async.waterfall([ 97 | 98 | function (CBW) { 99 | logger.debug({ msg: 'Request to search Framework Terms', additionalInfo: { data } }, req) 100 | ekStepUtil.frameworkTermSearch(ekStepReqData, data.queryParams, req.headers, function (err, res) { 101 | if (err || res.responseCode !== responseCode.SUCCESS) { 102 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 103 | logger.error({ 104 | msg: 'Error while searching framework terms from ekstep', 105 | err: { 106 | err, 107 | responseCode: rspObj.responseCode 108 | }, 109 | additionalInfo: {data} 110 | }, req) 111 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 112 | rspObj.result = res && res.result ? res.result : {} 113 | rspObj = utilsService.getErrorResponse(rspObj, res) 114 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 115 | } else { 116 | CBW(null, res) 117 | } 118 | }) 119 | }, 120 | 121 | function (res) { 122 | rspObj.result = res.result 123 | return response.status(200).send(respUtil.successResponse(rspObj)) 124 | } 125 | ]) 126 | } 127 | 128 | function frameworkTermCreate (req, response) { 129 | var rspObj = req.rspObj 130 | var data = req.body 131 | data.queryParams = req.query 132 | if (!data) { 133 | rspObj.responseCode = responseCode.CLIENT_ERROR 134 | logger.error({ 135 | msg: 'Error due to missing query Parameters or request body', 136 | err: {responseCode: rspObj.responseCode}, 137 | additionalInfo: { data } 138 | }, req) 139 | return response.status(400).send(respUtil.errorResponse(rspObj)) 140 | } 141 | 142 | var ekStepReqData = { 143 | request: data.request 144 | } 145 | 146 | async.waterfall([ 147 | 148 | function (CBW) { 149 | logger.debug({ msg: 'Request to create Framework Terms', additionalInfo: { data } }, req) 150 | ekStepUtil.frameworkTermCreate(ekStepReqData, data.queryParams, req.headers, function (err, res) { 151 | if (err || res.responseCode !== responseCode.SUCCESS) { 152 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 153 | logger.error({ 154 | msg: 'Error while creating framework terms from ekstep', 155 | err: { 156 | err, 157 | responseCode: rspObj.responseCode 158 | }, 159 | additionalInfo: {data} 160 | }, req) 161 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 162 | rspObj.result = res && res.result ? res.result : {} 163 | rspObj = utilsService.getErrorResponse(rspObj, res) 164 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 165 | } else { 166 | CBW(null, res) 167 | } 168 | }) 169 | }, 170 | 171 | function (res) { 172 | rspObj.result = res.result 173 | return response.status(200).send(respUtil.successResponse(rspObj)) 174 | } 175 | ]) 176 | } 177 | 178 | function frameworkTermUpdate (req, response) { 179 | var rspObj = req.rspObj 180 | var data = req.body 181 | data.queryParams = req.query 182 | data.category = req.params.categoryID 183 | // Adding telemetry object data 184 | if (rspObj.telemetryData) { 185 | rspObj.telemetryData.object = utilsService.getObjectData(data.category, 'frameworkTerm', '', {}) 186 | } 187 | 188 | if (!data) { 189 | rspObj.responseCode = responseCode.CLIENT_ERROR 190 | logger.error({ 191 | msg: 'Error due to missing query Parameters or request body', 192 | err: {responseCode: rspObj.responseCode}, 193 | additionalInfo: { data } 194 | }, req) 195 | return response.status(400).send(respUtil.errorResponse(rspObj)) 196 | } 197 | 198 | var ekStepReqData = { 199 | request: data.request 200 | } 201 | 202 | async.waterfall([ 203 | 204 | function (CBW) { 205 | logger.debug({ msg: 'Request to update Framework Terms', additionalInfo: { data } }, req) 206 | ekStepUtil.frameworkTermUpdate(ekStepReqData, data.queryParams, data.category, req.headers, function (err, res) { 207 | if (err || res.responseCode !== responseCode.SUCCESS) { 208 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 209 | logger.error({ 210 | msg: 'Error while updating framework terms from ekstep', 211 | err: { 212 | err, 213 | responseCode: rspObj.responseCode 214 | }, 215 | additionalInfo: {data} 216 | }, req) 217 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 218 | rspObj.result = res && res.result ? res.result : {} 219 | rspObj = utilsService.getErrorResponse(rspObj, res) 220 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 221 | } else { 222 | CBW(null, res) 223 | } 224 | }) 225 | }, 226 | 227 | function (res) { 228 | rspObj.result = res.result 229 | return response.status(200).send(respUtil.successResponse(rspObj)) 230 | } 231 | ]) 232 | } 233 | 234 | module.exports.getFrameworkTerm = getFrameworkTerm 235 | module.exports.frameworkTermSearch = frameworkTermSearch 236 | module.exports.frameworkTermCreate = frameworkTermCreate 237 | module.exports.frameworkTermUpdate = frameworkTermUpdate 238 | -------------------------------------------------------------------------------- /src/service/frameworkCategoryInstanceService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : frameworkCategoryInstanceService.js 3 | * @author: Rajath V B 4 | * @desc : controller file for handle domain and concepts. 5 | */ 6 | 7 | var async = require('async') 8 | var path = require('path') 9 | var respUtil = require('response_util') 10 | var ekStepUtil = require('sb_content_provider_util') 11 | var logger = require('sb_logger_util_v2') 12 | var messageUtils = require('./messageUtil') 13 | var utilsService = require('../service/utilsService') 14 | 15 | var filename = path.basename(__filename) 16 | var responseCode = messageUtils.RESPONSE_CODE 17 | 18 | /** 19 | * This function helps to get all domain from ekstep 20 | * @param {Object} req 21 | * @param {Object} response 22 | */ 23 | 24 | function getFrameworkCategoryInstance (req, response) { 25 | var data = {} 26 | var rspObj = req.rspObj 27 | data.body = req.body 28 | data.category = req.params.categoryID 29 | data.queryParams = req.query 30 | if (rspObj.telemetryData) { 31 | rspObj.telemetryData.object = utilsService.getObjectData(data.category, 'category', '', {}) 32 | } 33 | 34 | if (!data.queryParams) { 35 | rspObj.responseCode = responseCode.CLIENT_ERROR 36 | logger.error({ 37 | msg: 'Error due to missing request body or query Parameters or categoryId', 38 | err: {responseCode: rspObj.responseCode}, 39 | additionalInfo: { data } 40 | }, req) 41 | return response.status(400).send(respUtil.errorResponse(rspObj)) 42 | } 43 | 44 | async.waterfall([ 45 | 46 | function (CBW) { 47 | logger.debug({ msg: 'Request to get Framework Category instance', additionalInfo: { data } }, req) 48 | ekStepUtil.getFrameworkCategoryInstance(data.queryParams, data.category, req.headers, function (err, res) { 49 | if (err || res.responseCode !== responseCode.SUCCESS) { 50 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 51 | logger.error({ 52 | msg: 'Error while fetching framework category instance from ekstep', 53 | err: { 54 | err, 55 | responseCode: rspObj.responseCode 56 | }, 57 | additionalInfo: {data} 58 | }, req) 59 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 60 | rspObj.result = res && res.result ? res.result : {} 61 | rspObj = utilsService.getErrorResponse(rspObj, res) 62 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 63 | } else { 64 | CBW(null, res) 65 | } 66 | }) 67 | }, 68 | 69 | function (res) { 70 | rspObj.result = res.result 71 | return response.status(200).send(respUtil.successResponse(rspObj)) 72 | } 73 | ]) 74 | } 75 | 76 | function frameworkCategoryInstanceSearch (req, response) { 77 | var rspObj = req.rspObj 78 | var data = req.body 79 | data.queryParams = req.query 80 | if (!data) { 81 | rspObj.responseCode = responseCode.CLIENT_ERROR 82 | logger.error({ 83 | msg: 'Error due to missing request body or query params', 84 | err: {responseCode: rspObj.responseCode}, 85 | additionalInfo: { data } 86 | }, req) 87 | return response.status(400).send(respUtil.errorResponse(rspObj)) 88 | } 89 | 90 | var ekStepReqData = { 91 | request: data.request 92 | } 93 | 94 | async.waterfall([ 95 | 96 | function (CBW) { 97 | logger.debug({ msg: 'Request to search Framework Category instance', additionalInfo: { data } }, req) 98 | ekStepUtil.frameworkCategoryInstanceSearch(ekStepReqData, data.queryParams, req.headers, function (err, res) { 99 | if (err || res.responseCode !== responseCode.SUCCESS) { 100 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 101 | logger.error({ 102 | msg: 'Error while searching framework category instance from ekstep', 103 | err: { 104 | err, 105 | responseCode: rspObj.responseCode 106 | }, 107 | additionalInfo: {data} 108 | }, req) 109 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 110 | rspObj = utilsService.getErrorResponse(rspObj, res) 111 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 112 | } else { 113 | CBW(null, res) 114 | } 115 | }) 116 | }, 117 | 118 | function (res) { 119 | rspObj.result = res.result 120 | return response.status(200).send(respUtil.successResponse(rspObj)) 121 | } 122 | ]) 123 | } 124 | 125 | function frameworkCategoryInstanceCreate (req, response) { 126 | var rspObj = req.rspObj 127 | var data = req.body 128 | data.queryParams = req.query 129 | if (!data) { 130 | rspObj.responseCode = responseCode.CLIENT_ERROR 131 | logger.error({ 132 | msg: 'Error due to missing request body or query params', 133 | err: {responseCode: rspObj.responseCode}, 134 | additionalInfo: { data } 135 | }, req) 136 | return response.status(400).send(respUtil.errorResponse(rspObj)) 137 | } 138 | 139 | var ekStepReqData = { 140 | request: data.request 141 | } 142 | 143 | async.waterfall([ 144 | 145 | function (CBW) { 146 | logger.debug({ msg: 'Request to create Framework Category instance', additionalInfo: { data } }, req) 147 | ekStepUtil.frameworkCategoryInstanceCreate(ekStepReqData, data.queryParams, req.headers, function (err, res) { 148 | if (err || res.responseCode !== responseCode.SUCCESS) { 149 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 150 | logger.error({ 151 | msg: 'Error while creating framework category instance from ekstep', 152 | err: { 153 | err, 154 | responseCode: rspObj.responseCode 155 | }, 156 | additionalInfo: {data} 157 | }, req) 158 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 159 | rspObj = utilsService.getErrorResponse(rspObj, res) 160 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 161 | } else { 162 | CBW(null, res) 163 | } 164 | }) 165 | }, 166 | 167 | function (res) { 168 | rspObj.result = res.result 169 | return response.status(200).send(respUtil.successResponse(rspObj)) 170 | } 171 | ]) 172 | } 173 | 174 | function frameworkCategoryInstanceUpdate (req, response) { 175 | var rspObj = req.rspObj 176 | var data = req.body 177 | data.queryParams = req.query 178 | data.category = req.params.categoryID 179 | if (rspObj.telemetryData) { 180 | rspObj.telemetryData.object = utilsService.getObjectData(data.category, 'category', '', {}) 181 | } 182 | if (!data) { 183 | rspObj.responseCode = responseCode.CLIENT_ERROR 184 | logger.error({ 185 | msg: 'Error due to missing request body or query params or categoryId', 186 | err: {responseCode: rspObj.responseCode}, 187 | additionalInfo: { data } 188 | }, req) 189 | return response.status(400).send(respUtil.errorResponse(rspObj)) 190 | } 191 | 192 | var ekStepReqData = { 193 | request: data.request 194 | } 195 | 196 | async.waterfall([ 197 | 198 | function (CBW) { 199 | logger.debug({ msg: 'Request to update Framework Category instance', additionalInfo: { data } }, req) 200 | ekStepUtil.frameworkCategoryInstanceUpdate(ekStepReqData, data.queryParams, data.category, req.headers, 201 | function (err, res) { 202 | if (err || res.responseCode !== responseCode.SUCCESS) { 203 | rspObj.responseCode = res && res.responseCode ? res.responseCode : responseCode.SERVER_ERROR 204 | logger.error({ 205 | msg: 'Error while updating framework category instance from ekstep', 206 | err: { 207 | err, 208 | responseCode: rspObj.responseCode 209 | }, 210 | additionalInfo: {data} 211 | }, req) 212 | var httpStatus = res && res.statusCode >= 100 && res.statusCode < 600 ? res.statusCode : 500 213 | rspObj = utilsService.getErrorResponse(rspObj, res) 214 | return response.status(httpStatus).send(respUtil.errorResponse(rspObj)) 215 | } else { 216 | CBW(null, res) 217 | } 218 | }) 219 | }, 220 | 221 | function (res) { 222 | rspObj.result = res.result 223 | return response.status(200).send(respUtil.successResponse(rspObj)) 224 | } 225 | ]) 226 | } 227 | 228 | module.exports.getFrameworkCategoryInstance = getFrameworkCategoryInstance 229 | module.exports.frameworkCategoryInstanceSearch = frameworkCategoryInstanceSearch 230 | module.exports.frameworkCategoryInstanceCreate = frameworkCategoryInstanceCreate 231 | module.exports.frameworkCategoryInstanceUpdate = frameworkCategoryInstanceUpdate 232 | -------------------------------------------------------------------------------- /src/test/metaFilterRouteSpec.js: -------------------------------------------------------------------------------- 1 | // const express = require('express') 2 | var configUtil = require('../libs/sb-config-util') 3 | var _ = require('lodash') 4 | 5 | const metaFilterRoutes = [ 6 | 'content/search', 7 | 'course/search', 8 | 'framework/category/search', 9 | 'framework/term/search', 10 | 'search', 11 | 'framework/list' 12 | ] 13 | 14 | const nonFilterRoutes = [ 15 | 'user/inbox', 16 | 'tenant/info/sunbird' 17 | ] 18 | 19 | var filterMiddleware = require('../middlewares/filter.middleware') 20 | var httpMocks = require('node-mocks-http') 21 | 22 | var baseUrl = 'http://localhost:5000/v1/' 23 | var async = require('async') 24 | var contentMetaConfig = require('./contentMetaConfig') 25 | 26 | function generateConfigString (metaFiltersArray) { 27 | var configArray = {} 28 | _.forOwn(metaFiltersArray, function (value, key) { 29 | const allowedMetadata = value[0] 30 | const blackListedMetadata = value[1] 31 | if ((allowedMetadata && allowedMetadata.length > 0) && (blackListedMetadata && blackListedMetadata.length > 0)) { 32 | configArray[key] = _.difference(allowedMetadata, blackListedMetadata) 33 | } else if (allowedMetadata && allowedMetadata.length > 0) { 34 | configArray[key] = allowedMetadata 35 | } else if (blackListedMetadata && blackListedMetadata.length > 0) { 36 | configArray[key] = { 'ne': blackListedMetadata } 37 | } 38 | }) 39 | return configArray 40 | } 41 | var metaFiltersArray = { 42 | 'channel': [contentMetaConfig.allowedChannels, contentMetaConfig.blackListedChannels], 43 | 'framework': [contentMetaConfig.allowedFramework, contentMetaConfig.blacklistedFramework], 44 | 'mimeType': [contentMetaConfig.allowedMimetype, contentMetaConfig.blackListedMimetype], 45 | 'contentType': [contentMetaConfig.allowedContenttype, contentMetaConfig.blackListedContenttype], 46 | 'resourceType': [contentMetaConfig.allowedResourcetype, contentMetaConfig.blackListedResourcetype] 47 | } 48 | 49 | var generateConfigArray = generateConfigString(metaFiltersArray) 50 | describe('Check for all required route to call the AddMetaFilter', function () { 51 | async.forEach(metaFilterRoutes, function (route, callback) { 52 | describe('Composite search services', function () { 53 | var req, res 54 | var body = { 55 | 'request': { 56 | 'query': 'Test', 57 | 'filters': {} 58 | } 59 | } 60 | 61 | beforeEach(function (done) { 62 | req = httpMocks.createRequest({ 63 | method: 'POST', 64 | uri: baseUrl + route, 65 | body: body 66 | }) 67 | 68 | res = httpMocks.createResponse() 69 | 70 | done() // call done so that the next test can run 71 | }) 72 | it('check for filter in config with route', function () { 73 | filterMiddleware.addMetaFilters(req, res, function next () { 74 | expect(req.body.request.filters).toBeDefined() 75 | }) 76 | }) 77 | it('check for filter in config with value ' + route, function () { 78 | const allwhiteListedFilterQuery = { 79 | channel: ['b00bc992ef25f1a9a8d63291e20efc8d'], 80 | framework: [ 'NCF' ], 81 | contentType: [ 'Resource' ], 82 | mimeType: [ 'application/vnd.ekstep.content-collection' ], 83 | resourceType: [ 'Learn' ] 84 | } 85 | configUtil.setConfig('META_FILTER_REQUEST_JSON', allwhiteListedFilterQuery) 86 | 87 | filterMiddleware.addMetaFilters(req, res, function next () { 88 | var filterQuery = generateConfigArray 89 | // channels 90 | if (contentMetaConfig.allowedChannels && (contentMetaConfig.allowedChannels).length > 0 && 91 | contentMetaConfig.blackListedChannels && (contentMetaConfig.blackListedChannels).length > 0) { 92 | expect(req.body.request.filters.channel).toEqual(filterQuery.channel) 93 | } else if (contentMetaConfig.allowedChannels && (contentMetaConfig.allowedChannels).length > 0) { 94 | expect(req.body.request.filters.channel).toEqual(contentMetaConfig.allowedChannels) 95 | } else if (contentMetaConfig.blackListedChannels && (contentMetaConfig.blackListedChannels).length > 0) { 96 | expect(req.body.request.filters.channel).toEqual(contentMetaConfig.blackListedChannels) 97 | } 98 | 99 | // framework 100 | if (contentMetaConfig.allowedFramework && (contentMetaConfig.allowedFramework).length > 0 && 101 | contentMetaConfig.blackListedFramework && (contentMetaConfig.blackListedFramework).length > 0) { 102 | expect(req.body.request.filters.framework).toEqual(filterQuery.framework) 103 | } else if (contentMetaConfig.allowedFramework && (contentMetaConfig.allowedFramework).length > 0) { 104 | expect(req.body.request.filters.framework).toEqual(contentMetaConfig.allowedFramework) 105 | } else if (contentMetaConfig.blackListedFramework && (contentMetaConfig.blackListedFramework).length > 0) { 106 | expect(req.body.request.filters.framework).toEqual(contentMetaConfig.blackListedFramework) 107 | } 108 | 109 | // contentType 110 | if (contentMetaConfig.allowedContenttype && contentMetaConfig.blackListedContenttype) { 111 | expect(req.body.request.filters.contentType).toEqual(filterQuery.contentType) 112 | } else if (contentMetaConfig.allowedContenttype && (contentMetaConfig.allowedContenttype).length > 0) { 113 | expect(req.body.request.filters.contentType).toEqual(contentMetaConfig.allowedContenttype) 114 | } else if (contentMetaConfig.blackListedContenttype && 115 | (contentMetaConfig.blackListedContenttype).length > 0) { 116 | expect(req.body.request.filters.contentType).toEqual(contentMetaConfig.blackListedContenttype) 117 | } 118 | 119 | // mimeType 120 | if (contentMetaConfig.allowedMimetype && contentMetaConfig.blackListedMimetype) { 121 | expect(req.body.request.filters.mimeType).toEqual(filterQuery.mimeType) 122 | } else if ((contentMetaConfig.allowedMimetype && (contentMetaConfig.allowedMimetype).length > 0)) { 123 | expect(req.body.request.filters.mimeType).toEqual(contentMetaConfig.allowedMimetype) 124 | } else if (contentMetaConfig.blackListedMimetype && (contentMetaConfig.blackListedMimetype).length > 0) { 125 | expect(req.body.request.filters.mimeType).toEqual(contentMetaConfig.blackListedMimetype) 126 | } 127 | 128 | // resourceType 129 | if (contentMetaConfig.allowedResourcetype && contentMetaConfig.blackListedResourcetype) { 130 | expect(req.body.request.filters.resourceType).toEqual(filterQuery.resourceType) 131 | } else if (contentMetaConfig.allowedResourcetype && (contentMetaConfig.allowedResourcetype).length > 0) { 132 | expect(req.body.request.filters.resourceType).toEqual(contentMetaConfig.allowedResourcetype) 133 | } else if (contentMetaConfig.blackListedResourcetype && 134 | (contentMetaConfig.blackListedResourcetype).length > 0) { 135 | expect(req.body.request.filters.resourceType).toEqual(contentMetaConfig.blackListedResourcetype) 136 | } 137 | }) 138 | }) 139 | }) 140 | }) 141 | }) 142 | 143 | describe('Check for routes not to call the AddMetaFilter', function () { 144 | // it('if framework filter calls the route, addMetaFilter should not be called ', function () { 145 | async.forEach(nonFilterRoutes, function (route, callback) { 146 | describe('Composite search services for non filters', function (done) { 147 | var req 148 | var body = { 149 | 'request': { 150 | 'query': 'Test', 151 | 'filters': {} 152 | } 153 | } 154 | beforeEach(function (done) { 155 | req = httpMocks.createRequest({ 156 | method: 'POST', 157 | uri: baseUrl + route, 158 | body: body 159 | }) 160 | 161 | done() 162 | }) 163 | it('if framework filter calls the route, addMetaFilter should not be called ' + route, function () { 164 | const allwhiteListedFilterQuery = { 165 | channel: ['b00bc992ef25f1a9a8d63291e20efc8d'], 166 | framework: [ 'NCF' ], 167 | contentType: [ 'Resource' ], 168 | mimeType: [ 'application/vnd.ekstep.content-collection' ], 169 | resourceType: [ 'Learn' ] 170 | } 171 | configUtil.setConfig('META_FILTER_REQUEST_JSON', allwhiteListedFilterQuery) 172 | expect(!_.includes(metaFilterRoutes, route)).toBeTruthy() 173 | expect(req.body.request.filters).toEqual({}) 174 | }) 175 | }) 176 | }) 177 | }) 178 | describe('Check for routes not to call the AddMetaFilter', function () { 179 | async.forEach(nonFilterRoutes, function (route, callback) { 180 | describe('Composite search services for non filters', function (done) { 181 | var req 182 | var body = { 183 | 'request': { 184 | 'query': 'Test', 185 | 'filters': {} 186 | } 187 | } 188 | beforeEach(function (done) { 189 | req = httpMocks.createRequest({ 190 | method: 'POST', 191 | uri: baseUrl + route, 192 | body: body 193 | }) 194 | 195 | done() 196 | }) 197 | it('if framework filter calls the route, addMetaFilter should not be called ' + route, function () { 198 | const allwhiteListedFilterQuery = { 199 | channel: ['b00bc992ef25f1a9a8d63291e20efc8d'], 200 | framework: [ 'NCF' ], 201 | contentType: [ 'Resource' ], 202 | mimeType: [ 'application/vnd.ekstep.content-collection' ], 203 | resourceType: [ 'Learn' ] 204 | } 205 | configUtil.setConfig('META_FILTER_REQUEST_JSON', allwhiteListedFilterQuery) 206 | expect(!_.includes(metaFilterRoutes, route)).toBeTruthy() 207 | expect(req.body.request.filters).toEqual({}) 208 | }) 209 | }) 210 | }) 211 | }) 212 | -------------------------------------------------------------------------------- /src/test/fixtures/services/questionServiceTestData.js: -------------------------------------------------------------------------------- 1 | exports.questionServiceListAPIDATA = { 2 | 3 | "CLIENT_ERROR_REQUEST": { 4 | "request": { 5 | "search": { 6 | "identifier": "do_1131687689003827201864" 7 | } 8 | } 9 | }, 10 | 11 | "CLIENT_ERROR_RESPONSE": { 12 | "id": "api.question.list", 13 | "ver": "1.0", 14 | "ts": "2021-02-25T10:18:52.606Z", 15 | "params": { 16 | "resmsgid": "dbf285e0-7752-11eb-8966-8167c5617ed4", 17 | "msgid": null, 18 | "status": "failed", 19 | "errmsg": "Either identifier is missing or it is not list type" 20 | }, 21 | "responseCode": "CLIENT_ERROR", 22 | "result": {} 23 | }, 24 | 25 | 26 | "REQUEST": { 27 | "request": { 28 | "search": { 29 | "identifier": [ 30 | "do_1131687689003827201864", 31 | "do_1131687689003827201864", 32 | "do_1131687689003827201864" 33 | ] 34 | } 35 | } 36 | }, 37 | 38 | "RESPONSE": { 39 | "id": "api.question.list", 40 | "ver": "1.0", 41 | "ts": "2021-02-25T10:12:04.589Z", 42 | "params": { 43 | "resmsgid": "e8c011d0-7751-11eb-8006-47189fc7c888", 44 | "msgid": "e58be8e0-7751-11eb-8e0f-ef89a30b0d98", 45 | "status": "successful", 46 | "err": null, 47 | "errmsg": null 48 | }, 49 | "responseCode": "OK", 50 | "result": { 51 | "questions": [ 52 | { 53 | "ownershipType": [ 54 | "createdBy" 55 | ], 56 | "code": "org.sunbird.ccG6ru", 57 | "credentials": { 58 | "enabled": "No" 59 | }, 60 | "channel": "in.ekstep", 61 | "language": [ 62 | "English" 63 | ], 64 | "mimeType": "application/pdf", 65 | "idealScreenSize": "normal", 66 | "createdOn": "2020-12-09T12:08:54.913+0000", 67 | "objectType": "Content", 68 | "primaryCategory": "Explanation Content", 69 | "contentDisposition": "inline", 70 | "lastUpdatedOn": "2020-12-09T12:08:54.913+0000", 71 | "contentEncoding": "identity", 72 | "contentType": "Resource", 73 | "dialcodeRequired": "No", 74 | "identifier": "do_1131687689003827201864", 75 | "lastStatusChangedOn": "2020-12-09T12:08:54.913+0000", 76 | "audience": [ 77 | "Student" 78 | ], 79 | "os": [ 80 | "All" 81 | ], 82 | "visibility": "Default", 83 | "consumerId": "7411b6bd-89f3-40ec-98d1-229dc64ce77d", 84 | "mediaType": "content", 85 | "osId": "org.ekstep.quiz.app", 86 | "languageCode": [ 87 | "en" 88 | ], 89 | "version": 2, 90 | "versionKey": "1607515734913", 91 | "license": "CC BY 4.0", 92 | "idealScreenDensity": "hdpi", 93 | "framework": "NCF", 94 | "createdBy": "874ed8a5-782e-4f6c-8f36-e0288455901e", 95 | "compatibilityLevel": 1, 96 | "name": "API DOCUMENTATION CONTENT", 97 | "status": "Draft" 98 | }, 99 | { 100 | "ownershipType": [ 101 | "createdBy" 102 | ], 103 | "code": "org.sunbird.ccG6ru", 104 | "credentials": { 105 | "enabled": "No" 106 | }, 107 | "channel": "in.ekstep", 108 | "language": [ 109 | "English" 110 | ], 111 | "mimeType": "application/pdf", 112 | "idealScreenSize": "normal", 113 | "createdOn": "2020-12-09T12:08:54.913+0000", 114 | "objectType": "Content", 115 | "primaryCategory": "Explanation Content", 116 | "contentDisposition": "inline", 117 | "lastUpdatedOn": "2020-12-09T12:08:54.913+0000", 118 | "contentEncoding": "identity", 119 | "contentType": "Resource", 120 | "dialcodeRequired": "No", 121 | "identifier": "do_1131687689003827201864", 122 | "lastStatusChangedOn": "2020-12-09T12:08:54.913+0000", 123 | "audience": [ 124 | "Student" 125 | ], 126 | "os": [ 127 | "All" 128 | ], 129 | "visibility": "Default", 130 | "consumerId": "7411b6bd-89f3-40ec-98d1-229dc64ce77d", 131 | "mediaType": "content", 132 | "osId": "org.ekstep.quiz.app", 133 | "languageCode": [ 134 | "en" 135 | ], 136 | "version": 2, 137 | "versionKey": "1607515734913", 138 | "license": "CC BY 4.0", 139 | "idealScreenDensity": "hdpi", 140 | "framework": "NCF", 141 | "createdBy": "874ed8a5-782e-4f6c-8f36-e0288455901e", 142 | "compatibilityLevel": 1, 143 | "name": "API DOCUMENTATION CONTENT", 144 | "status": "Draft" 145 | }, 146 | { 147 | "ownershipType": [ 148 | "createdBy" 149 | ], 150 | "code": "org.sunbird.ccG6ru", 151 | "credentials": { 152 | "enabled": "No" 153 | }, 154 | "channel": "in.ekstep", 155 | "language": [ 156 | "English" 157 | ], 158 | "mimeType": "application/pdf", 159 | "idealScreenSize": "normal", 160 | "createdOn": "2020-12-09T12:08:54.913+0000", 161 | "objectType": "Content", 162 | "primaryCategory": "Explanation Content", 163 | "contentDisposition": "inline", 164 | "lastUpdatedOn": "2020-12-09T12:08:54.913+0000", 165 | "contentEncoding": "identity", 166 | "contentType": "Resource", 167 | "dialcodeRequired": "No", 168 | "identifier": "do_1131687689003827201864", 169 | "lastStatusChangedOn": "2020-12-09T12:08:54.913+0000", 170 | "audience": [ 171 | "Student" 172 | ], 173 | "os": [ 174 | "All" 175 | ], 176 | "visibility": "Default", 177 | "consumerId": "7411b6bd-89f3-40ec-98d1-229dc64ce77d", 178 | "mediaType": "content", 179 | "osId": "org.ekstep.quiz.app", 180 | "languageCode": [ 181 | "en" 182 | ], 183 | "version": 2, 184 | "versionKey": "1607515734913", 185 | "license": "CC BY 4.0", 186 | "idealScreenDensity": "hdpi", 187 | "framework": "NCF", 188 | "createdBy": "874ed8a5-782e-4f6c-8f36-e0288455901e", 189 | "compatibilityLevel": 1, 190 | "name": "API DOCUMENTATION CONTENT", 191 | "status": "Draft" 192 | } 193 | ], 194 | "count": 3 195 | } 196 | }, 197 | 198 | "QUESTION_RESPONSE": { 199 | "id": "api.question.read", 200 | "ver": "3.0", 201 | "ts": "2021-02-26T09:29:35ZZ", 202 | "params": { 203 | "resmsgid": "4fe78618-77fe-4061-a4ff-6eb1cc423f5e", 204 | "msgid": null, 205 | "err": null, 206 | "status": "successful", 207 | "errmsg": null 208 | }, 209 | "responseCode": "OK", 210 | "result": { 211 | "question": { 212 | "ownershipType": [ 213 | "createdBy" 214 | ], 215 | "code": "org.sunbird.ccG6ru", 216 | "credentials": { 217 | "enabled": "No" 218 | }, 219 | "channel": "in.ekstep", 220 | "language": [ 221 | "English" 222 | ], 223 | "mimeType": "application/pdf", 224 | "idealScreenSize": "normal", 225 | "createdOn": "2020-12-09T12:08:54.913+0000", 226 | "objectType": "Content", 227 | "primaryCategory": "Explanation Content", 228 | "contentDisposition": "inline", 229 | "lastUpdatedOn": "2020-12-09T12:08:54.913+0000", 230 | "contentEncoding": "identity", 231 | "contentType": "Resource", 232 | "dialcodeRequired": "No", 233 | "identifier": "do_1131687689003827201864", 234 | "lastStatusChangedOn": "2020-12-09T12:08:54.913+0000", 235 | "audience": [ 236 | "Student" 237 | ], 238 | "os": [ 239 | "All" 240 | ], 241 | "visibility": "Default", 242 | "consumerId": "7411b6bd-89f3-40ec-98d1-229dc64ce77d", 243 | "mediaType": "content", 244 | "osId": "org.ekstep.quiz.app", 245 | "languageCode": [ 246 | "en" 247 | ], 248 | "version": 2, 249 | "versionKey": "1607515734913", 250 | "license": "CC BY 4.0", 251 | "idealScreenDensity": "hdpi", 252 | "framework": "NCF", 253 | "createdBy": "874ed8a5-782e-4f6c-8f36-e0288455901e", 254 | "compatibilityLevel": 1, 255 | "name": "API DOCUMENTATION CONTENT", 256 | "status": "Draft" 257 | } 258 | } 259 | } 260 | 261 | }; 262 | -------------------------------------------------------------------------------- /src/service/formService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file : formService.js 3 | * @author: Harish Kumar Gangula 4 | * @desc : controller file for handle form service. 5 | */ 6 | 7 | var async = require('async') 8 | var path = require('path') 9 | var respUtil = require('response_util') 10 | var contentProvider = require('sb_content_provider_util') 11 | var logger = require('sb_logger_util_v2') 12 | var messageUtils = require('./messageUtil') 13 | var utilsService = require('../service/utilsService') 14 | 15 | var filename = path.basename(__filename) 16 | var formMessages = messageUtils.FORM 17 | var responseCode = messageUtils.RESPONSE_CODE 18 | 19 | /** 20 | * This controller function helps to get form data 21 | * @param {object} req 22 | * @param {object} response 23 | */ 24 | function getForm (req, response) { 25 | var data = req.body 26 | var rspObj = req.rspObj 27 | 28 | if (!data.request || !data.request.type || !data.request.subType || !data.request.action) { 29 | rspObj.errCode = formMessages.READ.MISSING_CODE 30 | rspObj.errMsg = formMessages.READ.MISSING_MESSAGE 31 | rspObj.responseCode = responseCode.CLIENT_ERROR 32 | logger.error({ 33 | msg: 'Error due to missing request or request type or request subtype or request action', 34 | err: { 35 | errCode: rspObj.errCode, 36 | errMsg: rspObj.errMsg, 37 | responseCode: rspObj.responseCode 38 | }, 39 | additionalInfo: { data } 40 | }, req) 41 | return response.status(400).send(respUtil.errorResponse(rspObj)) 42 | } 43 | 44 | async.waterfall([ 45 | 46 | function (CBW) { 47 | logger.debug({ msg: 'Request to content provider to get Form data', additionalInfo: { data } }, req) 48 | var key = data.request.type.toLowerCase() + '.' + data.request.subType.toLowerCase() + 49 | '.' + data.request.action.toLowerCase() 50 | var requestData = { 51 | 'request': { 52 | 'rootOrgId': data.request.rootOrgId || '*', 53 | 'keys': [key] 54 | } 55 | } 56 | contentProvider.learnerServiceGetForm(requestData, req.headers, function (err, res) { 57 | if (err || res.responseCode !== responseCode.SUCCESS) { 58 | logger.error({ 59 | msg: 'Error from content provider while fetching form data from learner service', 60 | err, 61 | additionalInfo: { requestData } 62 | }, req) 63 | rspObj.result = res && res.result ? res.result : {} 64 | rspObj = utilsService.getErrorResponse(rspObj, res, formMessages.READ) 65 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 66 | } else { 67 | var responseData = res 68 | try { 69 | var formData = JSON.parse(res.result.tenantPreference[0].data) 70 | responseData = { 71 | 'result': { 72 | 'form': data.request 73 | } 74 | } 75 | responseData.result.form.data = formData[data.request.framework] || formData['default'] 76 | } catch (error) { 77 | logger.error({ msg: 'Error while parsing response data', error }, req) 78 | } 79 | 80 | CBW(null, responseData) 81 | } 82 | }) 83 | }, 84 | function (res) { 85 | rspObj.result = res.result 86 | return response.status(200).send(respUtil.successResponse(rspObj)) 87 | } 88 | ]) 89 | } 90 | 91 | /** 92 | * This controller function helps to update form data 93 | * @param {object} req 94 | * @param {object} response 95 | */ 96 | function updateForm (req, response) { 97 | var data = req.body 98 | var rspObj = req.rspObj 99 | 100 | if (!data.request || 101 | !data.request.type || 102 | !data.request.subType || 103 | !data.request.action || 104 | !data.request.data) { 105 | rspObj.errCode = formMessages.UPDATE.MISSING_CODE 106 | rspObj.errMsg = formMessages.UPDATE.MISSING_MESSAGE 107 | rspObj.responseCode = responseCode.CLIENT_ERROR 108 | logger.error({ 109 | msg: 'Error due to missing request or request type or request subtype or request action or request data', 110 | err: { 111 | errCode: rspObj.errCode, 112 | errMsg: rspObj.errMsg, 113 | responseCode: rspObj.responseCode 114 | }, 115 | additionalInfo: { data } 116 | }, req) 117 | return response.status(400).send(respUtil.errorResponse(rspObj)) 118 | } 119 | 120 | async.waterfall([ 121 | function (CBW) { 122 | logger.debug({ msg: 'Request to content provider to update Form data', additionalInfo: { data } }, req) 123 | var key = data.request.type.toLowerCase() + '.' + data.request.subType.toLowerCase() + 124 | '.' + data.request.action.toLowerCase() 125 | var requestData = { 126 | 'request': { 127 | 'rootOrgId': data.request.rootOrgId || '*', 128 | 'keys': [key] 129 | } 130 | } 131 | contentProvider.learnerServiceGetForm(requestData, req.headers, function (err, res) { 132 | if (err || res.responseCode !== responseCode.SUCCESS) { 133 | logger.error({ 134 | msg: 'Error from content provider while fetching form data from learner service', 135 | err, 136 | additionalInfo: { requestData } 137 | }, req) 138 | rspObj.result = res && res.result ? res.result : {} 139 | rspObj = utilsService.getErrorResponse(rspObj, res, formMessages.READ) 140 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 141 | } else { 142 | CBW(null, res.result.tenantPreference) 143 | } 144 | }) 145 | }, 146 | function (responseData, CBW) { 147 | var key = data.request.type.toLowerCase() + '.' + data.request.subType.toLowerCase() + 148 | '.' + data.request.action.toLowerCase() 149 | var requestData = { 150 | 'request': { 151 | 'rootOrgId': data.request.rootOrgId || '*', 152 | 'tenantPreference': [ 153 | { 154 | 'key': key, 155 | 'data': {} 156 | } 157 | ] 158 | } 159 | } 160 | var formData = {} 161 | try { 162 | formData = responseData[0]['data'] 163 | ? JSON.parse(responseData[0]['data']) : {} 164 | var frameworkKey = data.request.framework || 'default' 165 | formData[frameworkKey] = data.request.data 166 | } catch (error) { 167 | rspObj.errCode = formMessages.UPDATE.FAILED_CODE 168 | rspObj.errMsg = formMessages.UPDATE.FAILED_MESSAGE 169 | rspObj.responseCode = responseCode.CLIENT_ERROR 170 | logger.error({ 171 | msg: 'Error while parsing response data', 172 | err: { 173 | error, 174 | errCode: rspObj.errCode, 175 | errMsg: rspObj.errMsg, 176 | responseCode: rspObj.responseCode 177 | } 178 | }, req) 179 | return response.status(400).send(respUtil.errorResponse(rspObj)) 180 | } 181 | 182 | requestData.request.tenantPreference[0].data = JSON.stringify(formData) 183 | contentProvider.learnerServiceUpdateForm(requestData, req.headers, function (err, res) { 184 | if (err || res.responseCode !== responseCode.SUCCESS) { 185 | logger.error({ 186 | msg: 'Error from content provider while updating form in learner service', 187 | err, 188 | additionalInfo: { requestData } 189 | }, req) 190 | rspObj.result = res && res.result ? res.result : {} 191 | rspObj = utilsService.getErrorResponse(rspObj, res, formMessages.UPDATE) 192 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 193 | } else { 194 | CBW(null, res) 195 | } 196 | }) 197 | }, 198 | function (res) { 199 | rspObj.result = res.result 200 | return response.status(200).send(respUtil.successResponse(rspObj)) 201 | } 202 | ]) 203 | } 204 | 205 | /** 206 | * This controller function helps to create form data 207 | * @param {object} req 208 | * @param {object} response 209 | */ 210 | function createForm (req, response) { 211 | var data = req.body 212 | var rspObj = req.rspObj 213 | 214 | if (!data.request || 215 | !data.request.type || 216 | !data.request.subType || 217 | !data.request.action || 218 | !data.request.data) { 219 | rspObj.errCode = formMessages.CREATE.MISSING_CODE 220 | rspObj.errMsg = formMessages.CREATE.MISSING_MESSAGE 221 | rspObj.responseCode = responseCode.CLIENT_ERROR 222 | logger.error({ 223 | msg: 'Error due to missing request or request type or request subtype or request action or request data', 224 | err: { 225 | errCode: rspObj.errCode, 226 | errMsg: rspObj.errMsg, 227 | responseCode: rspObj.responseCode 228 | }, 229 | additionalInfo: { data } 230 | }, req) 231 | return response.status(400).send(respUtil.errorResponse(rspObj)) 232 | } 233 | 234 | async.waterfall([ 235 | 236 | function (CBW) { 237 | var key = data.request.type.toLowerCase() + '.' + data.request.subType.toLowerCase() + 238 | '.' + data.request.action.toLowerCase() 239 | var requestData = { 240 | 'request': { 241 | 'rootOrgId': data.request.rootOrgId || '*', 242 | 'tenantPreference': [ 243 | { 244 | 'key': key, 245 | 'data': {} 246 | } 247 | ] 248 | } 249 | } 250 | var frameworkKey = data.request.framework || 'default' 251 | requestData.request.tenantPreference[0].data[frameworkKey] = data.request.data 252 | requestData.request.tenantPreference[0].data = JSON.stringify(requestData.request.tenantPreference[0].data) 253 | logger.debug({ msg: 'Request to content provider to create form', additionalInfo: { requestData } }, req) 254 | contentProvider.learnerServiceCreateForm(requestData, req.headers, function (err, res) { 255 | if (err || res.responseCode !== responseCode.SUCCESS) { 256 | rspObj.result = res && res.result ? res.result : {} 257 | logger.error({ 258 | msg: 'Error from content provider while creating form in learner service', 259 | err, 260 | additionalInfo: { requestData } 261 | }, req) 262 | rspObj = utilsService.getErrorResponse(rspObj, res, formMessages.CREATE) 263 | return response.status(utilsService.getHttpStatus(res)).send(respUtil.errorResponse(rspObj)) 264 | } else { 265 | CBW(null, res) 266 | } 267 | }) 268 | }, 269 | function (res) { 270 | rspObj.result = res.result 271 | return response.status(200).send(respUtil.successResponse(rspObj)) 272 | } 273 | ]) 274 | } 275 | 276 | module.exports.getFormRequest = getForm 277 | module.exports.updateFormRequest = updateForm 278 | module.exports.createFormRequest = createForm 279 | --------------------------------------------------------------------------------