diff --git a/.env.sample b/.env.sample index e8d6d82c..42893888 100644 --- a/.env.sample +++ b/.env.sample @@ -26,4 +26,27 @@ KAFKA_URL = "172.31.0.4:9092" KAFKA_GROUP_ID = "projects" // Kafka group id # SUBMISSION TOPIC -SUBMISSION_TOPIC = "dev.sl.projects.submissions" // Kafka topic name for pushing projects submissions \ No newline at end of file +SUBMISSION_TOPIC = "dev.sl.projects.submissions" // Kafka topic name for pushing projects submissions +PROJECT_SUBMISSION_TOPIC = "dev.sl.projects.submissions" // project submission topic + +# SUNBIRD LOCATION AND USER READ +USER_SERVICE_URL = "http://user-service:3000" // service used for user profile read location search are using this base url + +#service name +SERVICE_NAME = ml-project-service // ml-project service name + +# sunbird-rc service +CERTIFICATE_SERVICE_URL = http://registry-service:8081 // sunbird-RC registry service URL + +PROJECT_CERTIFICATE_ON_OFF = "ON/OFF" // Project certificate enable or disable flag + +USER_DELETE_ON_OFF = ON/OFF // enable/disable user delete flow + +USER_DELETE_TOPIC = {{env_name}}.delete.user // Topic name for user delete event consumer + +ID = ID = {{env_name}}.{{sunbird_instance}}.ml.core.service // ID of service + +TELEMETRY_ON_OFF = ON/OFF // telemetry service on off + +TELEMETRY_TOPIC = {{env_name}}.telemetry.raw // Topic name for telemetry + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..bb5dc77f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ + +# Description +These recommendations are intended to promote code quality and team communication during software development. They cover a variety of topics, including ensuring that pull requests are submitted to the correct branch, documenting new methods, preserving consistency across many services, and avoiding typical blunders like accessing APIs or DB queries within loops. Sensitive data should not be uploaded, and changes to environment variables or database models should be executed consistently. Teams may work more effectively and develop higher-quality software by adhering to these standards. + + +## Type of change +Please choose appropriate options. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Enhancement (additive changes to improve performance) +- [ ] This change requires a documentation update + +## Checklist + +- [ ] It's critical to avoid making needless file modifications in contributions, such as adding new lines, console logs, or additional spaces, to guarantee cleaner and more efficient code. Furthermore, eliminating unnecessary imports from a file might enhance code readability and efficiency. +- [ ] Ensure that the pull request is assigned to the right base branch and that the development branch name contains the JIRA Task Id. Furthermore, each commit message should include the JIRA Task Id in the manner "ED-100: message". +- [ ] Only update packages if it is mentioned and authorized in the design document, and make sure that you have the required permissions. +- [ ] Avoid making API and database queries inside a loop as it can lead to performance issues and slow down the system. +- [ ] When calling another function inside a given function, add comments explaining the purpose and meaning of the passed arguments and expected return values. +- [ ] If adding a blank argument in a function, add a comment explaining the reason for the blank argument. +- [ ] Before submitting a pull request, do a self-review of your code to ensure there are no conflicts with the base branch and all comments have been addressed. +- [ ] Before merging a pull request, it's important to have other team members review it to catch any potential errors or issues +- [ ] To maintain code integrity, it's important to remove all related changes when removing code during a code review. +- [ ] If new constants, endpoints, or utility functions are introduced, it is important to check if they already exist in the service to avoid any duplication. +- [ ] Whenever a new environment variable is added to a service, it's important to ensure that the necessary changes are made to related files such as ".env.sample" and "envVariables.js" to maintain consistency and avoid errors. Additionally, the new environment variable should be added to the devops repository to ensure that it is properly documented and accessible to the team. +- [ ] When adding a new function to a service, it is important to document it with relevant information such as the name, parameters, and return value in a consistent format across all services. Additionally, if there are any changes to the API response, ensure that the documentation in the controllers is updated accordingly. +- [ ] Write a clear and concise commit message that describes the changes made. +- [ ] Maintain consistent function signature and code across all services when adding a function to multiple services. Implement changes to database models in all services that use the same model. +- [ ] Use only let and const. Do not use var. +- [ ] Make common functions for repetitive code blocks. +- [ ] Avoid uploading sensitive information such as secret tokens or passwords in pull requests to ensure data security. +- [ ] Maintain consistent indentation and spacing throughout the code. + + diff --git a/.gitignore b/.gitignore index ede208e0..588ab0e1 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ config/credentials/* *.DS_Store package-lock.json +keycloak-public-keys/ + +.dcignore \ No newline at end of file diff --git a/DBSchema/certificateTemplates.json b/DBSchema/certificateTemplates.json new file mode 100644 index 00000000..9d5c9bfd --- /dev/null +++ b/DBSchema/certificateTemplates.json @@ -0,0 +1,72 @@ +{ + "_id" : "637cb340261b7c0008253fa6", + "status" : "active", + "deleted" : false, + "criteria" : { + "validationText" : "Complete validation message", + "expression" : "C1&&C2&&C3", + "conditions" : { + "C1" : { + "validationText" : "Project Should be submitted within program Enddate", + "expression" : "C1&&C2", + "conditions" : { + "C1" : { + "scope" : "project", + "key" : "status", + "operator" : "==", + "value" : "submitted" + }, + "C2" : { + "scope" : "project", + "key" : "completedDate", + "operator" : "<", + "value" : "15-08-2022" + } + } + }, + "C2" : { + "validationText" : "Evidence project level validation", + "expression" : "C1", + "conditions" : { + "C1" : { + "scope" : "project", + "key" : "attachments", + "function" : "count", + "filter" : { + "key" : "type", + "value" : "all" + }, + "operator" : ">", + "value" : 1 + } + } + }, + "C3" : { + "validationText" : "Evidence task level validation", + "expression" : "C1", + "conditions" : { + "C1" : { + "scope" : "task", + "key" : "attachments", + "function" : "count", + "filter" : { + "key" : "type", + "value" : "all" + }, + "operator" : ">", + "value" : 1 + } + } + } + } + }, + "issuer" : { + "name" : "Gujarat" + }, + "solutionId" : "5ff9dc1b9259097d48017bbe", + "programId" : "605083ba09b7bd61555580fb", + "updatedAt" : "2022-11-22T11:37:37.495+0000", + "createdAt" : "2022-11-22T11:32:16.639+0000", + "__v" : 0, + "templateUrl" : "certificateTemplates/637cb340261b7c0008253fa6/ba9aa220-ff1b-4717-b6ea-ace55f04fc16_22-10-2022-1669117057492.svg" +} \ No newline at end of file diff --git a/DBSchema/programUsers.json b/DBSchema/programUsers.json new file mode 100644 index 00000000..4f830432 --- /dev/null +++ b/DBSchema/programUsers.json @@ -0,0 +1,16 @@ +{ + "_id": "645358eb6144f31d8e8fcef4", + "programId": "5f75b90454670074deacf087", + "userId": "f5591238-63c8-4716-a1c9-27c18a32a4d4", + "__v": 0, + "createdAt": "2023-05-04T07:04:11.874Z", + "resourcesStarted": false, + "updatedAt": "2023-05-04T07:04:11.874Z", + "userProfile": null, + "userRoleInformation": { + "role": "HM", + "state": "db331a8c-b9e2-45f8-b3c0-7ec1e826b6df", + "district": "1dcbc362-ec4c-4559-9081-e0c2864c2931", + "school": "c5726207-4f9f-4f45-91f1-3e9e8e84d824" + } +} \ No newline at end of file diff --git a/DBSchema/programs.json b/DBSchema/programs.json new file mode 100644 index 00000000..dc262202 --- /dev/null +++ b/DBSchema/programs.json @@ -0,0 +1,90 @@ +[ + { + "_id":"5f34e44681871d939950bca6", + "resourceType":[ + "Program" + ], + "language":[ + "English" + ], + "keywords":[ + "keywords 1", + "keywords 2" + ], + "concepts":[ + + ], + "createdFor":[ + "01235953109336064029450" + ], + "components":[ + "5f34e44681871d939950bca7" + ], + "isAPrivateProgram":false, + "rootOrganisations":[ + "01235953109336064029450" + ], + "deleted":false, + "externalId":"TN-Program-1597301830708", + "name":"TN-Program", + "description":"TN01-Mantra4Change-APSWREIS School Leader Feedback", + "owner":"140558b9-7df4-4993-be3c-31eb8b9ca368", + "createdBy":"140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy":"140558b9-7df4-4993-be3c-31eb8b9ca368", + "status":"active", + "imageCompression":{ + "quality":10 + }, + "updatedAt":"2020-08-13T06:57:10.710+0000", + "createdAt":"2020-08-13T06:57:10.710+0000", + "__v":0 + }, + { + "_id":"5f35044f19377eecddb06921", + "resourceType":[ + "Program" + ], + "language":[ + "English" + ], + "keywords":[ + "keywords 1", + "keywords 2" + ], + "concepts":[ + + ], + "createdFor":[ + "0123221617357783046602" + ], + "components":[ + "5f35044f19377eecddb06922", + "5f3bc2d86ba5e3ecd7a42231", + "5f3bc2f16ba5e3ecd7a42233", + "5f3bc30a6ba5e3ecd7a42235", + "5f3bc31d6ba5e3ecd7a42237", + "5f3bc3306ba5e3ecd7a42239", + "5f3bc3456ba5e3ecd7a4223b", + "5f3bc38219377eecddb0695a", + "5f9f097cd1b9b71dbfed5f46" + ], + "isAPrivateProgram":true, + "rootOrganisations":[ + "0123221617357783046602" + ], + "deleted":false, + "externalId":"Test Program-1597310031178", + "name":"Test Program", + "description":"Test - Mantra4Change-APSWREIS School Leader Feedback", + "owner":"86d2d978-5b20-4453-8a76-82b5a4c728c9", + "createdBy":"86d2d978-5b20-4453-8a76-82b5a4c728c9", + "updatedBy":"86d2d978-5b20-4453-8a76-82b5a4c728c9", + "status":"active", + "imageCompression":{ + "quality":10 + }, + "updatedAt":"2020-08-13T09:13:51.180+0000", + "createdAt":"2020-08-13T09:13:51.180+0000", + "__v":0 + } + ] \ No newline at end of file diff --git a/DBSchema/projectTemplateTasks.json b/DBSchema/projectTemplateTasks.json new file mode 100644 index 00000000..65ab966f --- /dev/null +++ b/DBSchema/projectTemplateTasks.json @@ -0,0 +1,46 @@ +[ + { + "_id": "5fd244911233354b094f16e4", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6fa0", + "name": "Start planning the fest or cultural programme.", + "externalId": "IMP-3147bk-TASK5", + "description": "", + "updatedAt": "2020-12-10T15:53:53.016+0000", + "createdAt": "2020-12-10T15:53:53.016+0000", + "__v": 0, + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147bk" + }, + { + "_id": "5fd244911233354b094f16e5", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6fa0", + "name": "Find trainers who can volunteer their time to help students practise.", + "externalId": "IMP-3147bk-TASK6", + "description": "", + "updatedAt": "2020-12-10T15:53:53.019+0000", + "createdAt": "2020-12-10T15:53:53.019+0000", + "__v": 0, + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147bk" + } +] diff --git a/DBSchema/projectTemplates.json b/DBSchema/projectTemplates.json new file mode 100644 index 00000000..009de5eb --- /dev/null +++ b/DBSchema/projectTemplates.json @@ -0,0 +1,65 @@ +[ + { + "_id": "5fcf9954546a92494f2e587c", + "title": "test", + "externalId": "5bd5c4a6-0ccc-41fe-9633-d61ed5378a77", + "categories": [], + "duration": { + "value": "1W", + "label": "1 Week" + }, + "difficultyLevel": { + "value": "B", + "label": "Basic" + }, + "description": "test", + "concepts": [""], + "keywords": [""], + "status": "published", + "isDeleted": false, + "recommendedFor": [], + "tasks": [], + "createdAt": "2020-12-08T15:18:44+00:00", + "updatedAt": "2022-11-25T05:48:13.665+0000", + "createdBy": "SYSTEM", + "updatedBy": null, + "learningResources": [], + "isReusable": true, + "entityType": [], + "taskSequence": [], + "metaInformation": { + "supportingDocuments": [""], + "primaryAudience": [""] + } + }, + { + "_id": "5fcf9954546a92494f2e587f", + "title": "Vidyagama facilitation project", + "externalId": "da9f31a1-dc9d-4665-9af4-d023258dc558", + "categories": [], + "duration": { + "value": "1W", + "label": "1 Week" + }, + "difficultyLevel": { + "value": "B", + "label": "Basic" + }, + "description": "Support HMs and teachers by identifying common good practices and challenges with respect to Vidyagama", + "concepts": [], + "keywords": [""], + "status": "published", + "isDeleted": false, + "recommendedFor": [], + "tasks": ["5fcf9954546a92494f2e587d", "5fcf9954546a92494f2e587e"], + "createdAt": "2020-12-08T15:18:44+00:00", + "updatedAt": "2020-12-08T15:18:44+00:00", + "createdBy": "SYSTEM", + "updatedBy": "SYSTEM", + "learningResources": [], + "isReusable": true, + "entityType": [], + "taskSequence": ["5fcf9954546a92494f2e587d", "5fcf9954546a92494f2e587e"], + "metaInformation": {} + } +] diff --git a/DBSchema/projects.json b/DBSchema/projects.json new file mode 100644 index 00000000..5049d2a9 --- /dev/null +++ b/DBSchema/projects.json @@ -0,0 +1,2060 @@ +[ + { + "_id": "6017ce2ed0993d433a596de0", + "userId": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "userRole": "CHM", + "createdFor": ["0126796199493140480", "01275629649735680040708"], + "status": "started", + "isDeleted": false, + "categories": [ + { + "_id": "5fcfa9a2457d6055e33843f2", + "externalId": "community", + "name": "Community" + } + ], + "createdBy": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "tasks": [ + { + "_id": "e4b85120-b2c6-41ce-9a04-e3c05ba2a9d7", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "f742154a-793c-45d6-b61e-c49a65b1047a", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "e4b85120-b2c6-41ce-9a04-e3c05ba2a9d7", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Look for videos and case studies which capture the parent mela or parent meeting conducted in different schools. Focus on the ideas being used and themes being selected.", + "externalId": "IMP-3147aa-TASK7", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.476+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d5", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Look for samples of parent mela/ excitement building parent meeting from different schools", + "externalId": "IMP-3147aa-TASK1-1611320352803", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.460+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + }, + { + "_id": "9bf13931-a814-4f5f-bae9-f5eba9085913", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "d833bae3-0788-4d66-82aa-4543b0c5fa57", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "9bf13931-a814-4f5f-bae9-f5eba9085913", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "-Meeting with teachers to discuss the importance and role of parent mela in improving community engagement in the school.", + "externalId": "IMP-3147aa-TASK8", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.481+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + }, + { + "_id": "9ff85ddf-3c04-41c8-b5bb-8be77955b760", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "9bf13931-a814-4f5f-bae9-f5eba9085913", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "-Discuss stories from other schools.", + "externalId": "IMP-3147aa-TASK13", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.509+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + }, + { + "_id": "f6d32bed-f497-4378-95c3-db89901e2b24", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "9bf13931-a814-4f5f-bae9-f5eba9085913", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "- Form a committee among teachers to plan and facilitate the mela along with the school leader.", + "externalId": "IMP-3147aa-TASK14", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.521+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Form a parent mela committee among the teachers", + "externalId": "IMP-3147aa-TASK2-1611320352805", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.463+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.173+0000" + }, + { + "_id": "954511f2-d7ab-4e6d-acde-a9fa9c76768e", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "95a157d9-47bd-4095-ae1e-cc367deed3c6", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "954511f2-d7ab-4e6d-acde-a9fa9c76768e", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Finalize the themes and activities for the mela. This could be taken borrowed from other schools or decided by the teachers through brainstorming. \nFinalize the dates for the same", + "externalId": "IMP-3147aa-TASK9", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.487+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d7", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Planning for the mela", + "externalId": "IMP-3147aa-TASK3-1611320352808", + "description": "", + "updatedAt": "2021-02-01T09:47:26.173+0000", + "createdAt": "2020-12-10T15:53:31.465+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + }, + { + "_id": "2160c6bd-8a19-4a9a-a426-72e50cb9e6dd", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "a3e89f70-ce2d-4665-9ad6-b850595779f0", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "2160c6bd-8a19-4a9a-a426-72e50cb9e6dd", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Communicate the event with parents and invite them to the mela", + "externalId": "IMP-3147aa-TASK10", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.493+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d8", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Invite parents", + "externalId": "IMP-3147aa-TASK4-1611320352811", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.468+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + }, + { + "_id": "be7e0147-566f-481c-9d15-9e2eb60ec7e5", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "d563818f-fd71-4119-b24d-5a122b98f3d4", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "be7e0147-566f-481c-9d15-9e2eb60ec7e5", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Do necessary arrangements for smooth functioning of the event and to make it attractive and interesting to parents", + "externalId": "IMP-3147aa-TASK11", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.498+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d9", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Make the arrangements", + "externalId": "IMP-3147aa-TASK5-1611320352814", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.471+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + }, + { + "_id": "ad68fca1-1e37-45a0-b7cc-11e93fb2c23a", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "e1d2f43f-1f0a-4167-b27b-104baf530d9f", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "ad68fca1-1e37-45a0-b7cc-11e93fb2c23a", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Make sure you collect feedback from parents after the event and reflect among the teachers to see the areas of improvement for next year.", + "externalId": "IMP-3147aa-TASK12", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.503+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15da", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Collect feedback after the event", + "externalId": "IMP-3147aa-TASK6-1611320352816", + "description": "", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-10T15:53:31.473+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000" + }, + { + "_id": "0cb96fa4-054d-48ed-a000-89763e1aad6f", + "createdBy": "e7719630-0457-47ca-a5ce-8190ffb34f13", + "updatedBy": "e7719630-0457-47ca-a5ce-8190ffb34f13", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "observation", + "solutionDetails": { + "_id": "600b21227ea68a7ed9278868", + "isReusable": false, + "externalId": "AP-TEST-PROGRAM-3.6.5-OBS-IMP-PROJECT-2-BEO", + "name": "AP-TEST-PROGRAM-3.6.5-OBS-IMP-PROJECT-2-BEO", + "programId": "600ab53cc7de076e6f993724", + "type": "observation", + "subType": "block", + "allowMultipleAssessemts": false, + "isRubricDriven": false, + "criteriaLevelReport": "", + "scoringSystem": "", + "minNoOfSubmissionsRequired": 1 + }, + "name": "Observation task for BEO", + "externalId": "OT-3.6.5-BEO", + "description": "Observation task for BEO", + "updatedAt": "2021-02-01T09:47:26.174+0000", + "createdAt": "2020-12-01T17:06:56.999+0000", + "isDeletable": false, + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-01T09:47:26.174+0000", + "observationInformation": { + "entityId": "d8a34179-d925-4bca-a320-3d8c7cbcffa4", + "programId": "600ab53cc7de076e6f993724", + "solutionId": "600b21227ea68a7ed9278868" + } + } + ], + "updatedBy": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "rootOrganisations": ["0126796199493140480"], + "learningResources": [ + { + "name": "Examprep_10EM_ps_cha1_Q3", + "id": "do_31268582767737241615189", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31268582767737241615189" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q2", + "id": "do_31269107959395942417491", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269107959395942417491" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "description": "Come See Our School!- Parent Mela", + "title": "Come See Our School!- Parent Mela", + "metaInformation": { + "rationale": "", + "primaryAudience": ["Community"], + "goal": "Organizing the Parent Mela in the school in order to make better community reach", + "duration": "At the end of every quarter", + "successIndicators": "", + "risks": "", + "approaches": "" + }, + "updatedAt": "2023-07-12T12:01:15.790+0000", + "createdAt": "2021-02-01T09:47:26.161+0000", + "__v": 0, + "solutionId": "600acbe7c7de076e6f9950a4", + "solutionExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "programId": "600ab53cc7de076e6f993724", + "programExternalId": "AP-TEST-PROGRAM-3.6.5", + "projectTemplateId": "600acc20a0cc3e4909f91f68", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "taskReport": { + "total": 7, + "notStarted": 7 + }, + "isAPrivateProgram": false, + "programInformation": { + "_id": "600ab53cc7de076e6f993724", + "externalId": "AP-TEST-PROGRAM-3.6.5", + "description": "AP Test program for release 3.6.5", + "name": "AP-TEST-PROGRAM-3.6.5" + }, + "solutionInformation": { + "_id": "600acbe7c7de076e6f9950a4", + "externalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-BEO", + "description": "Description of Come See Our School!- Parent Mela", + "name": "Come See Our School!- Parent Mela" + }, + "entityInformation": { + "externalId": "282316", + "name": "B.N.KANDRIGA", + "district": "D_AP-D013", + "districtId": "5fd098e2e049735a86b748b8", + "state": "AP", + "_id": "d8a34179-d925-4bca-a320-3d8c7cbcffa4", + "entityType": "block", + "entityTypeId": "5f32d8228e0dc8312404056b", + "registryDetails": { + "locationId": "d8a34179-d925-4bca-a320-3d8c7cbcffa4", + "code": "282316", + "lastUpdatedAt": "2021-01-27T13:12:17.009Z" + }, + "hierarchy": [ + { + "code": "2823", + "name": "Chittoor", + "id": "b5c35cfc-6c1e-4266-94ef-a425c43c7f4e", + "type": "district", + "parentId": "bc75cc99-9205-463e-a722-5326857838f8" + }, + { + "code": "28", + "name": "Andhra Pradesh", + "id": "bc75cc99-9205-463e-a722-5326857838f8", + "type": "state" + } + ] + }, + "entityId": "d8a34179-d925-4bca-a320-3d8c7cbcffa4", + "lastDownloadedAt": "2021-02-01T09:47:26.180+0000", + "appInformation": { + "appName": "DIKSHA", + "appVersion": "86" + }, + "hasAcceptedTAndC": false, + "userProfile": { + "webPages": null, + "maskedPhone": null, + "tcStatus": null, + "loginId": null, + "subject": null, + "channel": "dikshapreprodcustodian", + "profileUserTypes": [ + { + "type": "administrator", + "subType": "chm" + } + ], + "language": null, + "updatedDate": "2021-02-03 07:43:13:773+0000", + "password": null, + "managedBy": null, + "flagsValue": 2, + "id": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "recoveryEmail": "", + "identifier": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "thumbnail": null, + "profileVisibility": null, + "updatedBy": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "accesscode": null, + "externalIds": [], + "registryId": null, + "roleList": [ + { + "name": "Book Creator", + "id": "BOOK_CREATOR" + }, + { + "name": "Membership Management", + "id": "MEMBERSHIP_MANAGEMENT" + }, + { + "name": "Flag Reviewer", + "id": "FLAG_REVIEWER" + }, + { + "name": "Report Viewer", + "id": "REPORT_VIEWER" + }, + { + "name": "Program Manager", + "id": "PROGRAM_MANAGER" + }, + { + "name": "Program Designer", + "id": "PROGRAM_DESIGNER" + }, + { + "name": "System Administration", + "id": "SYSTEM_ADMINISTRATION" + }, + { + "name": "Content Curation", + "id": "CONTENT_CURATION" + }, + { + "name": "Book Reviewer", + "id": "BOOK_REVIEWER" + }, + { + "name": "Content Creator", + "id": "CONTENT_CREATOR" + }, + { + "name": "Org Management", + "id": "ORG_MANAGEMENT" + }, + { + "name": "Course Admin", + "id": "COURSE_ADMIN" + }, + { + "name": "Org Moderator", + "id": "ORG_MODERATOR" + }, + { + "name": "Public", + "id": "PUBLIC" + }, + { + "name": "Admin", + "id": "ADMIN" + }, + { + "name": "Course Mentor", + "id": "COURSE_MENTOR" + }, + { + "name": "Content Reviewer", + "id": "CONTENT_REVIEWER" + }, + { + "name": "Report Admin", + "id": "REPORT_ADMIN" + }, + { + "name": "Org Admin", + "id": "ORG_ADMIN" + } + ], + "rootOrgId": "0126796199493140480", + "prevUsedEmail": "", + "firstName": "smith2", + "tncAcceptedOn": 1610945494371.0, + "allTncAccepted": {}, + "profileDetails": null, + "phone": "", + "dob": null, + "grade": null, + "currentLoginTime": null, + "userType": "administrator", + "status": 1, + "lastName": "", + "tncLatestVersion": "v12", + "gender": null, + "prevUsedPhone": "", + "stateValidated": false, + "encEmail": "dcXWh0a/IoZzf0BBo5YDesOQpg7YRsDDKJgzfiiKU1h5HUNPlEdrbYgQ6JBFa90WEd2tj0l4QKD9\nk+2W3uL5PE9KY9CSOe31SGfXFLfHPU+S1cV3v0vYkBPcrmgb2jY3T6a+wzaAmCWueMEdPmZuRg==", + "isDeleted": false, + "organisations": [ + { + "organisationId": "0126796199493140480", + "approvedBy": null, + "channel": "dikshapreprodcustodian", + "updatedDate": null, + "approvaldate": null, + "isSystemUpload": false, + "isDeleted": false, + "id": "0131968698566574080", + "isApproved": null, + "orgjoindate": "2021-01-18 04:50:58:065+0000", + "isSelfDeclaration": true, + "updatedBy": null, + "orgName": "Staging Custodian Organization", + "addedByName": null, + "addedBy": null, + "associationType": 2, + "locationIds": [ + "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + ], + "orgLocation": [ + { + "type": "state", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + }, + { + "type": "district", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + } + ], + "externalId": "101010", + "userId": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "isSchool": false, + "hashTagId": "0126796199493140480", + "isSSO": false, + "isRejected": null, + "locations": [ + { + "code": "29", + "name": "Karnataka", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "type": "state", + "parentId": null + }, + { + "code": "2901", + "name": "BELAGAVI", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141", + "type": "district", + "parentId": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + } + ], + "position": null, + "orgLeftDate": null + } + ], + "provider": null, + "countryCode": null, + "tncLatestVersionUrl": "https://sunbirdstagingpublic.blob.core.windows.net/termsandcondtions/terms-and-conditions-v12.html", + "maskedEmail": "sm****@yopmail.com", + "tempPassword": null, + "email": "sm****@yopmail.com", + "rootOrg": { + "dateTime": null, + "preferredLanguage": null, + "keys": {}, + "organisationSubType": null, + "channel": "dikshapreprodcustodian", + "approvedBy": null, + "description": "Pre-prod Custodian Organization", + "updatedDate": "2022-02-18 09:50:42:752+0000", + "organisationType": 5, + "addressId": null, + "orgType": null, + "isTenant": true, + "provider": null, + "locationId": null, + "orgCode": null, + "theme": null, + "id": "0126796199493140480", + "communityId": null, + "isApproved": null, + "isBoard": true, + "email": null, + "slug": "dikshapreprodcustodian", + "isSSOEnabled": null, + "thumbnail": null, + "orgName": "Staging Custodian Organization", + "updatedBy": null, + "locationIds": [ + "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + ], + "externalId": "101010", + "orgLocation": [ + { + "type": "state", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + }, + { + "type": "district", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + } + ], + "isRootOrg": true, + "rootOrgId": "0126796199493140480", + "imgUrl": null, + "approvedDate": null, + "homeUrl": null, + "orgTypeId": null, + "isDefault": null, + "createdDate": "2019-01-18 09:48:13:428+0000", + "createdBy": "system", + "parentOrgId": null, + "hashTagId": "0126796199493140480", + "noOfMembers": null, + "status": 1 + }, + "phoneVerified": true, + "profileSummary": null, + "tcUpdatedDate": null, + "userLocations": [ + { + "code": "2823", + "name": "CHITTOOR", + "id": "b5c35cfc-6c1e-4266-94ef-a425c43c7f4e", + "type": "district", + "parentId": "bc75cc99-9205-463e-a722-5326857838f8" + }, + { + "code": "28", + "name": "Andhra Pradesh", + "id": "bc75cc99-9205-463e-a722-5326857838f8", + "type": "state", + "parentId": null + }, + { + "code": "282316", + "name": "B.N.KANDRIGA", + "id": "d8a34179-d925-4bca-a320-3d8c7cbcffa4", + "type": "block", + "parentId": "b5c35cfc-6c1e-4266-94ef-a425c43c7f4e" + } + ], + "recoveryPhone": "******1076", + "avatar": null, + "userName": "smith2", + "userId": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "userSubType": "chm", + "declarations": [ + { + "persona": "default", + "errorType": null, + "orgId": "12765997661519872117", + "status": "SUBMITTED", + "info": { + "declared-email": "smith2@yopmail.com", + "declared-ext-id": "123", + "declared-school-name": "A.P.T.W.E.M..R SCHOOL", + "declared-school-udise-code": "28231601515" + } + }, + { + "persona": "default", + "errorType": null, + "orgId": "do_213199093633138688144", + "status": null, + "info": {} + } + ], + "promptTnC": true, + "emailVerified": true, + "lastLoginTime": 0, + "createdDate": "2021-01-18 04:50:57:268+0000", + "framework": { + "board": ["IGOT-Health"], + "gradeLevel": ["Doctors", "Nurses"], + "id": ["igot_health"], + "medium": ["English", "Hindi"] + }, + "createdBy": null, + "profileUserType": { + "type": "administrator", + "subType": "chm" + }, + "encPhone": "", + "location": null, + "tncAcceptedVersion": "3.5.0" + } + }, + { + "_id": "6018ef65879c09505d77fc3d", + "userId": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "userRole": "DEO", + "createdFor": ["0126796199493140480", "01275630154475110432564"], + "status": "started", + "isDeleted": false, + "categories": [ + { + "_id": "5fcfa9a2457d6055e33843f0", + "name": "Students", + "externalId": "students" + }, + { + "_id": "5fcfa9a2457d6055e33843f1", + "name": "Infrastructure", + "externalId": "infrastructure" + }, + { + "_id": "5fcfa9a2457d6055e33843f2", + "name": "Community", + "externalId": "community" + } + ], + "createdBy": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "tasks": [ + { + "_id": "61b9c72a-731b-4970-b715-0c1e928a86fb", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "9cecd670-d1e9-46c9-9987-d2540953edc4", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "61b9c72a-731b-4970-b715-0c1e928a86fb", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Look for videos and case studies which capture the parent mela or parent meeting conducted in different schools. Focus on the ideas being used and themes being selected.", + "externalId": "IMP-3147aa-TASK7", + "description": "", + "updatedAt": "2021-02-02T06:21:25.075+0000", + "createdAt": "2020-12-10T15:53:31.476+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d5", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.075+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Look for samples of parent mela/ excitement building parent meeting from different schools", + "externalId": "IMP-3147aa-TASK1-1611320417509", + "description": "", + "updatedAt": "2021-02-02T06:21:25.075+0000", + "createdAt": "2020-12-10T15:53:31.460+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.075+0000" + }, + { + "_id": "a2cad0dd-5c7b-4848-af70-155da9277744", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "985b1846-302d-4988-ab0e-a78acb310b26", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "a2cad0dd-5c7b-4848-af70-155da9277744", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "-Meeting with teachers to discuss the importance and role of parent mela in improving community engagement in the school.", + "externalId": "IMP-3147aa-TASK8", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.481+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "e746fd95-6cbe-415c-bfce-e4de82d0b879", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "a2cad0dd-5c7b-4848-af70-155da9277744", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "-Discuss stories from other schools.", + "externalId": "IMP-3147aa-TASK13", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.509+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "aaf8bf9e-31d0-4b83-aac6-aaaeebc75b49", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "a2cad0dd-5c7b-4848-af70-155da9277744", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "- Form a committee among teachers to plan and facilitate the mela along with the school leader.", + "externalId": "IMP-3147aa-TASK14", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.521+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d6", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Form a parent mela committee among the teachers", + "externalId": "IMP-3147aa-TASK2-1611320417513", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.463+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "0577f2c4-93f2-49a9-94d7-e4dcee92bf01", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "eacbca31-eebe-40e0-88dd-07069f16c5ec", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "0577f2c4-93f2-49a9-94d7-e4dcee92bf01", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Finalize the themes and activities for the mela. This could be taken borrowed from other schools or decided by the teachers through brainstorming. \nFinalize the dates for the same", + "externalId": "IMP-3147aa-TASK9", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.487+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d7", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Planning for the mela", + "externalId": "IMP-3147aa-TASK3-1611320417515", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.465+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "dc310831-7c65-4afc-9c8b-1a7dd63c08ad", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "6eb049b6-f1dd-49a4-8130-ea839415784c", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "dc310831-7c65-4afc-9c8b-1a7dd63c08ad", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Communicate the event with parents and invite them to the mela", + "externalId": "IMP-3147aa-TASK10", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.493+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d8", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Invite parents", + "externalId": "IMP-3147aa-TASK4-1611320417519", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.468+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "c166d100-fc88-40bf-a96d-07f9c95e55c9", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "428a8310-b22f-4780-998a-aa0fc4f05fed", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "c166d100-fc88-40bf-a96d-07f9c95e55c9", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Do necessary arrangements for smooth functioning of the event and to make it attractive and interesting to parents", + "externalId": "IMP-3147aa-TASK11", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.498+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15d9", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Make the arrangements", + "externalId": "IMP-3147aa-TASK5-1611320417521", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.471+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "912e969e-82dc-4081-920a-828189b75dd6", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "isDeletable": true, + "taskSequence": [], + "children": [ + { + "_id": "6c0e3c7f-003c-4771-b199-6a2ddcd96c28", + "createdBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "updatedBy": "140558b9-7df4-4993-be3c-31eb8b9ca368", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "912e969e-82dc-4081-920a-828189b75dd6", + "value": "started" + } + ], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "simple", + "projectTemplateId": "5fd21654e4d17b4af8aa6f7c", + "name": "Make sure you collect feedback from parents after the event and reflect among the teachers to see the areas of improvement for next year.", + "externalId": "IMP-3147aa-TASK12", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.503+0000", + "__v": 0, + "parentId": "5fd2447b1233354b094f15da", + "isDeletable": true, + "projectTemplateExternalId": "IMP-3147aa", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + } + ], + "visibleIf": [], + "hasSubTasks": true, + "learningResources": [ + { + "name": "MOVEMENTS", + "id": "do_3128850456266506241713", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128850456266506241713" + }, + { + "name": "ELECTRICITY", + "id": "do_3128848863878676481659", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_3128848863878676481659" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "type": "content", + "name": "Collect feedback after the event", + "externalId": "IMP-3147aa-TASK6-1611320417523", + "description": "", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-10T15:53:31.473+0000", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000" + }, + { + "_id": "7065398a-20fd-46e7-9012-67a14a38066e", + "createdBy": "e7719630-0457-47ca-a5ce-8190ffb34f13", + "updatedBy": "e7719630-0457-47ca-a5ce-8190ffb34f13", + "isDeleted": false, + "taskSequence": [], + "children": [], + "visibleIf": [], + "hasSubTasks": false, + "learningResources": [], + "deleted": false, + "type": "observation", + "solutionDetails": { + "_id": "600b21c57ea68a7ed9278873", + "isReusable": false, + "externalId": "AP-TEST-PROGRAM-3.6.5-OBS-IMP-PROJECT-2-DEO", + "name": "AP-TEST-PROGRAM-3.6.5-OBS-IMP-PROJECT-2-DEO", + "programId": "600ab53cc7de076e6f993724", + "type": "observation", + "subType": "district", + "allowMultipleAssessemts": false, + "isRubricDriven": false, + "criteriaLevelReport": "", + "scoringSystem": "", + "minNoOfSubmissionsRequired": 1 + }, + "name": "Observation task for DEO", + "externalId": "OT-3.6.5-DEO", + "description": "Observation task for DEO", + "updatedAt": "2021-02-02T06:21:25.076+0000", + "createdAt": "2020-12-01T17:06:56.999+0000", + "isDeletable": false, + "status": "notStarted", + "isImportedFromLibrary": false, + "syncedAt": "2021-02-02T06:21:25.076+0000", + "observationInformation": { + "entityId": "732b83e7-cf4f-401c-a374-db1d45644b3b", + "programId": "600ab53cc7de076e6f993724", + "solutionId": "600b21c57ea68a7ed9278873", + "observationId": "6033a1f81bfad8731adc6dc7" + }, + "submissions": [] + } + ], + "updatedBy": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "rootOrganisations": ["0126796199493140480"], + "learningResources": [ + { + "name": "Examprep_10EM_ps_cha1_Q3", + "id": "do_31268582767737241615189", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31268582767737241615189" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q2", + "id": "do_31269107959395942417491", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269107959395942417491" + }, + { + "name": "Examprep_10tm_ps_cha 11-Q3", + "id": "do_31269108472948326417493", + "app": "diksha", + "link": "https://staging.sunbirded.org/resources/play/content/do_31269108472948326417493" + } + ], + "deleted": false, + "description": "Come See Our School!- Parent Mela", + "title": "Come See Our School!- Parent Mela", + "metaInformation": { + "rationale": "", + "primaryAudience": ["Community"], + "goal": "Organizing the Parent Mela in the school in order to make better community reach", + "duration": "At the end of every quarter", + "successIndicators": "", + "risks": "", + "approaches": "" + }, + "updatedAt": "2021-09-30T16:07:05.334+0000", + "createdAt": "2021-02-02T06:21:25.056+0000", + "__v": 0, + "solutionId": "600acc42c7de076e6f995147", + "solutionExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "programId": "600ab53cc7de076e6f993724", + "programExternalId": "AP-TEST-PROGRAM-3.6.5", + "projectTemplateId": "600acc61a0cc3e4909f91f80", + "projectTemplateExternalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "taskReport": { + "total": 7, + "notStarted": 7 + }, + "isAPrivateProgram": false, + "programInformation": { + "_id": "600ab53cc7de076e6f993724", + "externalId": "AP-TEST-PROGRAM-3.6.5", + "description": "AP Test program for release 3.6.5", + "name": "AP-TEST-PROGRAM-3.6.5" + }, + "solutionInformation": { + "_id": "600acc42c7de076e6f995147", + "externalId": "AP-TEST-PROGRAM-3.6.5-IMP-PROJECT-1-DEO", + "description": "Description of Come See Our School!- Parent Mela", + "name": "Come See Our School!- Parent Mela" + }, + "appInformation": { + "appName": "DIKSHA", + "appVersion": "86" + }, + "entityInformation": { + "externalId": "2820", + "name": "KADAPA", + "region": "", + "districtId": "2820", + "state": "AP", + "_id": "732b83e7-cf4f-401c-a374-db1d45644b3b", + "entityType": "district", + "entityTypeId": "5f32d8228e0dc8312404056c", + "registryDetails": { + "locationId": "732b83e7-cf4f-401c-a374-db1d45644b3b", + "code": "2820", + "lastUpdatedAt": "2021-01-27T13:08:55.994Z" + }, + "hierarchy": [ + { + "code": "28", + "name": "Andhra Pradesh", + "id": "bc75cc99-9205-463e-a722-5326857838f8", + "type": "state" + } + ] + }, + "entityId": "732b83e7-cf4f-401c-a374-db1d45644b3b", + "lastDownloadedAt": "2021-02-02T06:21:25.085+0000", + "lastSync": "2021-02-11T06:59:24.269+0000", + "syncedAt": "2021-02-11T06:59:24.269+0000", + "hasAcceptedTAndC": false, + "userRoleInformation": { + "district": "aecac7ab-15e4-45c9-ac7b-d716444cd652", + "state": "bc75cc99-9205-463e-a722-5326857838f8", + "block": "f6d40b8a-3ba0-4e3b-b7f5-2586b69122f6", + "school": "28110502802", + "role": "HM" + }, + "userProfile": { + "webPages": null, + "maskedPhone": null, + "tcStatus": null, + "loginId": null, + "subject": null, + "channel": "dikshapreprodcustodian", + "profileUserTypes": [ + { + "subType": "hm", + "type": "administrator" + } + ], + "language": null, + "updatedDate": "2021-04-21 13:06:11:740+0000", + "password": null, + "managedBy": null, + "flagsValue": 2, + "id": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "recoveryEmail": "", + "identifier": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "thumbnail": null, + "profileVisibility": null, + "updatedBy": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "accesscode": null, + "externalIds": [], + "registryId": null, + "roleList": [ + { + "name": "Book Creator", + "id": "BOOK_CREATOR" + }, + { + "name": "Membership Management", + "id": "MEMBERSHIP_MANAGEMENT" + }, + { + "name": "Flag Reviewer", + "id": "FLAG_REVIEWER" + }, + { + "name": "Report Viewer", + "id": "REPORT_VIEWER" + }, + { + "name": "Program Manager", + "id": "PROGRAM_MANAGER" + }, + { + "name": "Program Designer", + "id": "PROGRAM_DESIGNER" + }, + { + "name": "System Administration", + "id": "SYSTEM_ADMINISTRATION" + }, + { + "name": "Content Curation", + "id": "CONTENT_CURATION" + }, + { + "name": "Book Reviewer", + "id": "BOOK_REVIEWER" + }, + { + "name": "Content Creator", + "id": "CONTENT_CREATOR" + }, + { + "name": "Org Management", + "id": "ORG_MANAGEMENT" + }, + { + "name": "Course Admin", + "id": "COURSE_ADMIN" + }, + { + "name": "Org Moderator", + "id": "ORG_MODERATOR" + }, + { + "name": "Public", + "id": "PUBLIC" + }, + { + "name": "Admin", + "id": "ADMIN" + }, + { + "name": "Course Mentor", + "id": "COURSE_MENTOR" + }, + { + "name": "Content Reviewer", + "id": "CONTENT_REVIEWER" + }, + { + "name": "Report Admin", + "id": "REPORT_ADMIN" + }, + { + "name": "Org Admin", + "id": "ORG_ADMIN" + } + ], + "rootOrgId": "0126796199493140480", + "prevUsedEmail": "", + "firstName": "Rejneesh R", + "isMinor": false, + "tncAcceptedOn": 1611404969636.0, + "allTncAccepted": { + "groupsTnc": { + "tncAcceptedOn": "2021-02-16 09:08:46:874+0000", + "version": "3.5.0" + } + }, + "profileDetails": null, + "phone": "", + "dob": "1993-12-31", + "grade": null, + "currentLoginTime": null, + "userType": "administrator", + "status": 1, + "lastName": "", + "tncLatestVersion": "v12", + "gender": null, + "prevUsedPhone": "", + "stateValidated": false, + "encEmail": "1uohqqrxzkWsYjwXZtNnzHSR60U3iie3psyNAEaEQSqQUh0tAAwJU4iP3wEWbvXsLbm2FmQHebSZ\nZtJE4nop9d6nYo0pJK3k3v6YdFSJasNIMf6JTlqlIXMa0/gYdzBBT6a+wzaAmCWueMEdPmZuRg==", + "isDeleted": false, + "organisations": [ + { + "organisationId": "0126796199493140480", + "approvedBy": null, + "channel": "dikshapreprodcustodian", + "updatedDate": null, + "approvaldate": null, + "isSystemUpload": false, + "isDeleted": false, + "id": "01319344873463808077", + "isApproved": null, + "orgjoindate": "2021-01-13 08:47:58:166+0000", + "isSelfDeclaration": true, + "updatedBy": null, + "orgName": "Staging Custodian Organization", + "addedByName": null, + "addedBy": null, + "associationType": 2, + "locationIds": [ + "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + ], + "orgLocation": [ + { + "type": "state", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + }, + { + "type": "district", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + } + ], + "externalId": "101010", + "userId": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "isSchool": false, + "hashTagId": "0126796199493140480", + "isSSO": false, + "isRejected": null, + "locations": [ + { + "code": "29", + "name": "Karnataka", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "type": "state", + "parentId": null + }, + { + "code": "2901", + "name": "BELAGAVI", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141", + "type": "district", + "parentId": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + } + ], + "position": null, + "orgLeftDate": null + }, + { + "organisationId": "012756293600911360616", + "approvedBy": null, + "channel": "apekx", + "updatedDate": null, + "approvaldate": null, + "isSystemUpload": false, + "isDeleted": false, + "id": "01326294083861708862", + "isApproved": false, + "orgjoindate": "2021-04-21 13:06:11:752+0000", + "isSelfDeclaration": true, + "updatedBy": null, + "orgName": "MPPS BATHUVA", + "addedByName": null, + "addedBy": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "associationType": 2, + "locationIds": [ + "bc75cc99-9205-463e-a722-5326857838f8", + "8ac1efe9-0415-4313-89ef-884e1c8eee34", + "cb635528-0c07-49c7-94ff-2e64ccf7c1af" + ], + "orgLocation": [ + { + "type": "state", + "id": "bc75cc99-9205-463e-a722-5326857838f8" + }, + { + "type": "district", + "id": "8ac1efe9-0415-4313-89ef-884e1c8eee34" + }, + { + "type": "block", + "id": "cb635528-0c07-49c7-94ff-2e64ccf7c1af" + } + ], + "externalId": "28110502802", + "userId": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "isSchool": true, + "hashTagId": "012756293600911360616", + "isSSO": false, + "isRejected": false, + "locations": [ + { + "code": "28", + "name": "Andhra Pradesh", + "id": "bc75cc99-9205-463e-a722-5326857838f8", + "type": "state", + "parentId": null + }, + { + "code": "2811", + "name": "SRIKAKULAM", + "id": "8ac1efe9-0415-4313-89ef-884e1c8eee34", + "type": "district", + "parentId": "bc75cc99-9205-463e-a722-5326857838f8" + }, + { + "code": "281105", + "name": "G.SIGADAM", + "id": "cb635528-0c07-49c7-94ff-2e64ccf7c1af", + "type": "block", + "parentId": "8ac1efe9-0415-4313-89ef-884e1c8eee34" + } + ], + "position": null, + "orgLeftDate": null + } + ], + "provider": null, + "countryCode": "+91", + "tncLatestVersionUrl": "https://sunbirdstagingpublic.blob.core.windows.net/termsandcondtions/terms-and-conditions-v12.html", + "maskedEmail": "re******@tunerlabs.com", + "tempPassword": null, + "email": "re******@tunerlabs.com", + "rootOrg": { + "dateTime": null, + "preferredLanguage": null, + "keys": {}, + "organisationSubType": null, + "channel": "dikshapreprodcustodian", + "approvedBy": null, + "description": "Pre-prod Custodian Organization", + "updatedDate": "2022-02-18 09:50:42:752+0000", + "organisationType": 5, + "addressId": null, + "orgType": null, + "isTenant": true, + "provider": null, + "locationId": null, + "orgCode": null, + "theme": null, + "id": "0126796199493140480", + "communityId": null, + "isApproved": null, + "isBoard": true, + "email": null, + "slug": "dikshapreprodcustodian", + "isSSOEnabled": null, + "thumbnail": null, + "orgName": "Staging Custodian Organization", + "updatedBy": null, + "locationIds": [ + "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf", + "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + ], + "externalId": "101010", + "orgLocation": [ + { + "type": "state", + "id": "027f81d8-0a2c-4fc6-96ac-59fe4cea3abf" + }, + { + "type": "district", + "id": "8250d58d-f1a2-4397-bfd3-b2e688ba7141" + } + ], + "isRootOrg": true, + "rootOrgId": "0126796199493140480", + "imgUrl": null, + "approvedDate": null, + "homeUrl": null, + "orgTypeId": null, + "isDefault": null, + "createdDate": "2019-01-18 09:48:13:428+0000", + "createdBy": "system", + "parentOrgId": null, + "hashTagId": "0126796199493140480", + "noOfMembers": null, + "status": 1 + }, + "phoneVerified": true, + "profileSummary": null, + "tcUpdatedDate": null, + "userLocations": [ + { + "code": "2814", + "name": "EAST GODAVARI", + "id": "aecac7ab-15e4-45c9-ac7b-d716444cd652", + "type": "district", + "parentId": "bc75cc99-9205-463e-a722-5326857838f8" + }, + { + "code": "28", + "name": "Andhra Pradesh", + "id": "bc75cc99-9205-463e-a722-5326857838f8", + "type": "state", + "parentId": null + }, + { + "code": "281454", + "name": "AMALAPURAM", + "id": "f6d40b8a-3ba0-4e3b-b7f5-2586b69122f6", + "type": "block", + "parentId": "aecac7ab-15e4-45c9-ac7b-d716444cd652" + }, + { + "code": "28110502802", + "name": "MPPS BATHUVA", + "id": "012756293600911360616", + "type": "school", + "parentId": "" + } + ], + "recoveryPhone": "", + "avatar": null, + "userName": "rejneeshr_pt83", + "userId": "44e4c0a0-e96c-4e3e-9294-33713aebdf70", + "userSubType": "hm", + "declarations": [ + { + "persona": "default", + "errorType": null, + "orgId": "0126796199493140480", + "status": null, + "info": {} + }, + { + "persona": "default", + "errorType": null, + "orgId": "do_2130569914095452161188", + "status": null, + "info": {} + } + ], + "promptTnC": true, + "emailVerified": true, + "lastLoginTime": 0, + "createdDate": "2021-01-13 08:47:58:140+0000", + "framework": { + "board": ["State (Andhra Pradesh"], + "id": ["ap_k-12_1"] + }, + "createdBy": null, + "profileUserType": { + "subType": "hm", + "type": "administrator" + }, + "encPhone": null, + "location": null, + "tncAcceptedVersion": "3.5.0" + } + } +] diff --git a/DBSchema/solutions.json b/DBSchema/solutions.json new file mode 100644 index 00000000..f68a675f --- /dev/null +++ b/DBSchema/solutions.json @@ -0,0 +1,469 @@ +[ + { + "_id" : "5f34ade5585244939f89f8f5", + "resourceType" : [ + "Observations Framework" + ], + "language" : [ + "English" + ], + "keywords" : [ + "Framework", + "Observation" + ], + "concepts" : [ + + ], + "createdFor" : [ + "0123221617357783046602" + ], + "themes" : [ + { + "type" : "theme", + "label" : "theme", + "name" : "Observation Theme", + "externalId" : "OB", + "weightage" : 100, + "criteria" : [ + { + "criteriaId" : "5f34ade5585244939f89f8f2", + "weightage" : 33.333333333333336 + }, + { + "criteriaId" : "5f34ade5585244939f89f8f3", + "weightage" : 33.333333333333336 + }, + { + "criteriaId" : "5f34ade5585244939f89f8f4", + "weightage" : 33.333333333333336 + } + ] + } + ], + "flattenedThemes" : [ + + ], + "entities" : [ + + ], + "registry" : [ + + ], + "isRubricDriven" : false, + "enableQuestionReadOut" : false, + "captureGpsLocationAtQuestionLevel" : false, + "isAPrivateProgram" : false, + "allowMultipleAssessemts" : false, + "isDeleted" : false, + "rootOrganisations" : [ + "0123221617357783046602" + ], + "deleted" : false, + "externalId" : "cbd074fa-dd11-11ea-a3bf-000d3af02677-OBSERVATION-TEMPLATE", + "name" : "MH01-Mantra4Change-APSWREIS School Leader Feedback", + "description" : "Feedback on Mantra4Change engagement with APSWREIS school leaders", + "author" : "19d81ef7-36ce-41fe-ae2d-c8365d977be4", + "levelToScoreMapping" : { + "L1" : { + "points" : 100, + "label" : "Good" + } + }, + "scoringSystem" : null, + "noOfRatingLevels" : 1, + "entityTypeId" : "5f32d8228e0dc83124040567", + "entityType" : "school", + "type" : "observation", + "subType" : "school", + "updatedBy" : "INITIALIZE", + "createdAt" : "2020-08-13T03:05:09.183+0000", + "updatedAt" : "2020-08-13T03:05:09.236+0000", + "frameworkId" : "5f34ade5ec065458d5c9d4ef", + "frameworkExternalId" : "cbd074fa-dd11-11ea-a3bf-000d3af02677", + "isReusable" : true, + "__v" : 0, + "evidenceMethods" : { + "OB" : { + "externalId" : "OB", + "tip" : null, + "name" : "Observation", + "description" : null, + "modeOfCollection" : "onfield", + "canBeNotApplicable" : false, + "notApplicable" : false, + "canBeNotAllowed" : false, + "remarks" : null + } + }, + "isTempObTest" : "observationAutomation", + "sections" : { + "S1" : "Observation Questions" + }, + "status" : "active", + "questionSequenceByEcm" : { + "OB" : { + "S1" : [ + "SLFB001_1597287896354", + "SLFB002_1597287896354", + "SLFB003_1597287896354", + "SLFB004_1597287896354", + "SLFB005_1597287896354", + "SLFB006_1597287896354", + "SLFB007_1597287896354", + "SLFB008_1597287896354", + "SLFB009_1597287896354", + "SLFB010_1597287896354", + "SLFB011_1597287896354", + "SLFB012_1597287896354", + "SLFB013_1597287896354", + "SLFB014_1597287896354", + "SLFB015_1597287896354", + "SLFB016_1597287896354", + "SLFB017_1597287896354" + ] + } + }, + "minNoOfSubmissionsRequired" : 1 + }, + { + "_id" : "5fa278c72dbd4b755b88e637", + "resourceType" : [ + "Survey Solution" + ], + "language" : [ + "English" + ], + "keywords" : [ + "Survey" + ], + "concepts" : [ + + ], + "createdFor" : [ + + ], + "themes" : [ + { + "type" : "theme", + "label" : "theme", + "externalId" : "SF", + "name" : "Survey and Feedback", + "weightage" : 0, + "criteria" : [ + { + "criteriaId" : "5fa278c72dbd4b755b88e636", + "weightage" : 0 + } + ] + } + ], + "flattenedThemes" : [ + + ], + "entities" : [ + + ], + "registry" : [ + + ], + "isRubricDriven" : false, + "enableQuestionReadOut" : false, + "captureGpsLocationAtQuestionLevel" : false, + "isAPrivateProgram" : false, + "allowMultipleAssessemts" : false, + "isDeleted" : false, + "rootOrganisations" : [ + + ], + "deleted" : false, + "type" : "survey", + "subType" : "survey", + "name" : "Diksha test survey ", + "externalId" : "cdcc3246-1e82-11eb-a3bf-000d3af02677-SURVEY-TEMPLATE", + "description" : "Diksha test survey via genie", + "linkTitle" : "", + "linkUrl" : "", + "author" : "86d2d978-5b20-4453-8a76-82b5a4c728c9", + "isReusable" : true, + "status" : "active", + "startDate" : "2020-11-04T09:47:51.233+0000", + "endDate" : "2021-11-04T09:47:51.233+0000", + "sections" : { + "SQ" : "Survey Questions" + }, + "evidenceMethods" : { + "SF" : { + "externalId" : "SF", + "name" : "Survey And Feedback", + "description" : "Survey And Feedback", + "modeOfCollection" : "", + "canBeNotApplicable" : false, + "notApplicable" : false, + "canBeNotAllowed" : false, + "remarks" : "", + "isActive" : true + } + }, + "updatedAt" : "2020-11-04T09:47:51.238+0000", + "createdAt" : "2020-11-04T09:47:51.238+0000", + "__v" : 0, + "minNoOfSubmissionsRequired" : 1 + }, + { + "_id" : "61558fec27e9cd00077530e4", + "scope" : { + "entityType" : "state", + "entityTypeId" : "5f32d8228e0dc8312404056e", + "roles" : [ + { + "_id" : "5f32d8238e0dc831240405a0", + "code" : "HM" + } + ], + "entities" : [ + "bc75cc99-9205-463e-a722-5326857838f8" + ] + }, + "resourceType" : [ + + ], + "language" : [ + + ], + "keywords" : [ + + ], + "concepts" : [ + + ], + "themes" : [ + + ], + "flattenedThemes" : [ + + ], + "entities" : [ + + ], + "registry" : [ + + ], + "isRubricDriven" : false, + "enableQuestionReadOut" : false, + "captureGpsLocationAtQuestionLevel" : false, + "isAPrivateProgram" : false, + "allowMultipleAssessemts" : false, + "isDeleted" : false, + "pageHeading" : "Domains", + "deleted" : false, + "name" : "Copy of Jaga july 14th test course", + "description" : "Test", + "link" : "https://staging.sunbirded.org/explore-course/course/do_2133511258201620481282", + "externalId" : "FD79-copy-of-jaga-july-14th-test-course", + "type" : "course", + "subType" : "course", + "programExternalId" : "PGM-testing_program-4.3", + "isReusable" : false, + "programId" : "61558efa27e9cd00077530b5", + "programName" : "Testing program 4.3", + "programDescription" : "Testing program 4.3", + "status" : "active", + "updatedAt" : "2021-09-30T10:28:13.717+0000", + "createdAt" : "2021-09-30T10:22:36.614+0000", + "__v" : 0, + "updatedBy" : "ec8deeb2-4ded-4fa2-ac48-023ad8298d92", + "minNoOfSubmissionsRequired" : 1 + }, + { + "_id" : "5f75c3879a3faabb16a0845f", + "resourceType" : [ + "Individual Assessment Framework" + ], + "language" : [ + "English" + ], + "keywords" : [ + "Framework", + "Assessment" + ], + "concepts" : [ + + ], + "createdFor" : [ + "0124487522476933120" + ], + "themes" : [ + { + "name" : "Theme 1", + "type" : "theme", + "label" : "theme", + "externalId" : "SL", + "weightage" : 40, + "criteria" : [ + { + "criteriaId" : "5f75c3879a3faabb16a0845c", + "weightage" : 40 + }, + { + "criteriaId" : "5f75c3879a3faabb16a0845d", + "weightage" : 40 + }, + { + "criteriaId" : "5f75c3879a3faabb16a0845a", + "weightage" : 40 + }, + { + "criteriaId" : "5f75c3879a3faabb16a0845b", + "weightage" : 40 + }, + { + "criteriaId" : "5f75c3879a3faabb16a0845e", + "weightage" : 40 + } + ] + } + ], + "flattenedThemes" : [ + + ], + "entities" : [ + + ], + "registry" : [ + + ], + "isRubricDriven" : false, + "enableQuestionReadOut" : false, + "captureGpsLocationAtQuestionLevel" : false, + "isAPrivateProgram" : false, + "allowMultipleAssessemts" : false, + "isDeleted" : false, + "rootOrganisations" : [ + + ], + "deleted" : false, + "subType" : "individual", + "type" : "assessment", + "entityType" : "schoolLeader", + "name" : "KEF - Remote teaching and learning baseline assessment for PL", + "externalId" : "KEF-RTLBS-INDIVIDUAL_ASSESSMENT-TEMPLATE", + "updatedAt" : "2020-10-01T11:54:47.738+0000", + "createdAt" : "2020-10-01T16:01:59.349+0000", + "entityTypeId" : "5f32d8228e0dc8312404056a", + "author" : "a082787f-8f8f-42f2-a706-35457ca6f1fd", + "updatedBy" : "INITIALIZE", + "description" : "This is a type of individual assessment", + "frameworkId" : "5f75b81254670074deacef1c", + "frameworkExternalId" : "KEF-RTLBS-INDIVIDUAL_ASSESSMENT", + "isReusable" : true, + "__v" : 0, + "minNoOfSubmissionsRequired" : 1 + }, + { + "_id" : "5f688f019331bf20ee36b13e", + "resourceType" : [ + "ImprovmentProject Solution" + ], + "language" : [ + "English" + ], + "keywords" : [ + "Framework", + "Improvment Project" + ], + "concepts" : [ + "" + ], + "createdFor" : [ + + ], + "registry" : [ + + ], + "type" : "improvementProject", + "subType" : "improvementProject", + "deleted" : false, + "externalId" : "f46b1ea0-fbfd-11ea-8877-65b250e90278", + "name" : "test", + "description" : "", + "author" : "", + "createdAt" : "2020-09-21T11:31:13.000+0000", + "updatedAt" : "2020-09-21T11:31:13.000+0000", + "frameworkId" : "", + "entityTypeId" : "", + "entityType" : "", + "status" : "Open", + "isDeleted" : false, + "isReusable" : false, + "parentSolutionId" : "", + "baseProjectDetails" : [ + { + "title" : "test", + "externalId" : "5bd5c4a6-0ccc-41fe-9633-d61ed5378a77", + "categories" : [ + + ], + "duration" : { + "value" : "1W", + "label" : "1 Week" + }, + "difficultyLevel" : { + "value" : "B", + "label" : "Basic" + }, + "description" : "test", + "concepts" : [ + "" + ], + "keywords" : [ + "" + ], + "status" : "published", + "isDeleted" : false, + "recommendedFor" : [ + + ], + "tasks" : [ + + ], + "createdAt" : "2020-12-08T15:18:44+00:00", + "updatedAt" : "2020-12-08T15:18:44+00:00", + "createdBy" : "SYSTEM", + "updatedBy" : "SYSTEM", + "learningResources" : [ + + ], + "isReusable" : true, + "entityType" : [ + + ], + "taskSequence" : [ + + ], + "metaInformation" : { + "supportingDocuments" : [ + "" + ], + "primaryAudience" : [ + "" + ] + }, + "_id" : "5fcf9954546a92494f2e587c" + } + ], + "programId" : "5fcf9953546a92494f2e5873", + "roles" : { + "projectManagers" : [ + + ], + "programManagers" : [ + + ], + "collaborators" : [ + "86d2d978-5b20-4453-8a76-82b5a4c728c9" + ] + }, + "__v" : 0, + "minNoOfSubmissionsRequired" : 1 + } +] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 32acaa63..250f5648 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:20 WORKDIR /opt/projects diff --git a/README.md b/README.md index 42a0b404..91dde12f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,134 @@ # ml-projects-service -ML projects Service + +This is ml-projects-services, the managed-learn component responsible for creating projects within the managed learn services. + +# Setup Guide + +## Pre-Requisite + +- Install any IDE in your system(eg: VScode etc..) +- Install nodejs from : https://nodejs.org/en/download/ +- Install mongoDB: https://docs.mongodb.com/manual/installation/ +- Install Robo 3T: ​​https://robomongo.org/ + +Basic understanding of git and github is recommended. + +- https://www.youtube.com/watch?v=RGOj5yH7evk&t=2s +- https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F + +## Setup ml-projects-services + +### Clone the service repository onto your system + +- Create a new folder where you want to clone the repository. +- Navigate to that directory using the terminal. +- Execute the git commands to clone the repository using the provided link from the code tab. + +Git link + + https://github.com/shikshalokam/ml-projects-service.git + +command to clone + + git clone https://github.com/shikshalokam/ml-projects-service.git + +### Create .env file + +Create a file named `.env` and copy the environment-specific data corresponding to that service into the `.env` file. + + # Ml project service configurations file + + APPLICATION_PORT = "3000" // Application port number + APPLICATION_ENV = "development" // Application running environment + + INTERNAL_ACCESS_TOKEN = "Fg*************yr" // Internal access token for accessing internal service APIs + + # DB + MONGODB_URL = "mongodb://localhost:27017/sl-assessment" // Mongodb connection url + + # ML Core Service + ML_CORE_SERVICE_URL = "http://ml-core-service:3000" // ML Core Service URL + + # ML Reports Service + ML_REPORTS_SERVICE_URL = "http://ml-reports-service:3000" // ML Reports Service URL + + # ML Survey Service + ML_SURVEY_SERVICE_URL = "http://ml-survey-service:3000" // ML Survey Service URL + + # OFFLINE VALIDATION + KEYCLOAK_PUBLIC_KEY_PATH = "keycloak-public-keys" // Keycloak public key path + + # KAFKA Configurations + KAFKA_COMMUNICATIONS_ON_OFF = "ON/OFF" // Kafka enable or disable communication flag + KAFKA_URL = "172.31.0.4:9092" // IP address of kafka server with port without HTTP + KAFKA_GROUP_ID = "projects" // Kafka group id + + # SUBMISSION TOPIC + SUBMISSION_TOPIC = "dev.sl.projects.submissions" // Kafka topic name for pushing projects submissions + PROJECT_SUBMISSION_TOPIC = "dev.sl.projects.submissions" // project submission topic + + # SUNBIRD LOCATION AND USER READ + USER_SERVICE_URL = "http://user-service:3000" // service used for user profile read location search are using this base url + + #service name + SERVICE_NAME = ml-project-service // ml-project service name + + # sunbird-rc service + CERTIFICATE_SERVICE_URL = http://registry-service:8081 // sunbird-RC registry service URL + + PROJECT_CERTIFICATE_ON_OFF = "ON/OFF" // Project certificate enable or disable flag + +### Install Dependencies + +To install dependencies from a `package.json` file in Visual Studio Code, you can use the integrated terminal. Here are the steps: + +- Open the integrated terminal by going to View > Terminal or using the shortcut Ctrl+` (backtick). +- In the terminal, navigate to the directory where the package.json file is located. +- Run the command `npm install` or `yarn install`, depending on your preferred package manager. +- The package manager will read the package.json file and install all the dependencies specified in it. +- Wait for the installation process to complete. You should see progress indicators or a success message for each installed dependency. +- Once the installation is finished, the dependencies listed in the package.json file will be installed in a node_modules directory in your project. + +### Setting the keycloak + +- Create a folder on service directory named: `keycloak-public-keys` +- Inside that folder create a file `GRxxx....................xxxxx60fA` + +**for keycloak file please contact Backend Team** + +### Setup Database + +Before proceeding with these steps, ensure that you have MongoDB installed on your computer. For a graphical user interface (GUI) for MongoDB, you can choose to install Robo 3T. + +- Obtain the latest database dump from the backend team. +- Restore the database in your local environment using the following command: + + For Windows/Linux: + `mongorestore ` + + For macOS: `mongorestore -d ` + +Note: Add `` to mongoDB url in `.env` file. + +### DB Schema + +The schema serves as a blueprint for creating and maintaining the database that supports the ML projects services data storage and retrieval operations. + +![ML-Projects Service](https://ml-services-uploads.s3.ap-south-1.amazonaws.com/DBSchema/ML-Project.png) + +[Click here](https://ml-services-uploads.s3.ap-south-1.amazonaws.com/DBSchema/ML-Project.pdf) for DB schema and corresponding examples in a PDF format. + +### Postman Collection + +The ML Projects Service Postman Collection is a comprehensive resource for interacting with the ML Core Service. It includes organized endpoints, detailed documentation, and example workflows, providing a valuable reference for developers. Leverage this collection to enhance productivity and collaboration in ML Services. + +[Click here](https://documenter.getpostman.com/view/7997930/2s946chuaT) + +## IMPORTANT: + +Always work on branches. **Never make changes to master**. + +Creating a branch from master. + +For more information on git you can use : + https://education.github.com/git-cheat-sheet-education.pdf diff --git a/app.js b/app.js index cee24dc9..1fc38b84 100644 --- a/app.js +++ b/app.js @@ -44,12 +44,13 @@ app.use(bodyParser.urlencoded({ limit: "50MB", extended: false })); app.use(express.static("public")); app.all('*', (req, res, next) => { - console.log("-------Request log starts here------------------"); + console.log({"Debugging ML Projects Service": true}); + console.log("<------------Request log starts here------------------>"); console.log("Request URL: ", req.url); - console.log("Request Headers: ", req.headers); - console.log("Request Body: ", req.body); - console.log("Request Files: ", req.files); - console.log("-------Request log ends here------------------"); + console.log("Request Headers: ", JSON.stringify(req.headers)); + console.log("Request Body: ", JSON.stringify(req.body)); + // console.log("Request Files: ", req.files); + console.log("<---------------Request log ends here------------------>"); next(); }); @@ -59,7 +60,6 @@ const router = require("./routes"); //add routing router(app); - //listen to given port app.listen(process.env.APPLICATION_PORT, () => { console.log("Environment : " + process.env.APPLICATION_ENV); diff --git a/auto_build_deploy b/auto_build_deploy new file mode 100644 index 00000000..97b8f690 --- /dev/null +++ b/auto_build_deploy @@ -0,0 +1,55 @@ +@Library('deploy-conf') _ +node('build-slave') { + try { + String ANSI_GREEN = "\u001B[32m" + String ANSI_NORMAL = "\u001B[0m" + String ANSI_BOLD = "\u001B[1m" + String ANSI_RED = "\u001B[31m" + String ANSI_YELLOW = "\u001B[33m" + + ansiColor('xterm') { + timestamps { + stage('Checkout') { + tag_name = env.JOB_NAME.split("/")[-1] + pre_checks() + if (!env.hub_org) { + println(ANSI_BOLD + ANSI_RED + "Uh Oh! Please set a Jenkins environment variable named hub_org with value as registery/sunbidrded" + ANSI_NORMAL) + error 'Please resolve the errors and rerun..' + } else + println(ANSI_BOLD + ANSI_GREEN + "Found environment variable named hub_org with value as: " + hub_org + ANSI_NORMAL) + } + cleanWs() + def scmVars = checkout scm + checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]] + build_tag = tag_name + "_" + env.BUILD_NUMBER + commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() + artifact_version = tag_name + "_" + commit_hash + echo "build_tag: " + build_tag + + // stage Build + env.NODE_ENV = "build" + print "Environment will be : ${env.NODE_ENV}" + sh('git submodule update --init') + sh('git submodule update --init --recursive --remote') + sh('chmod 777 build.sh') + sh("./build.sh ${build_tag} ${env.NODE_NAME} ${hub_org}") + + + // stage ArchiveArtifacts + archiveArtifacts "metadata.json" + currentBuild.description = "${build_tag}" + + } + } + currentBuild.result = "SUCCESS" + slack_notify(currentBuild.result, tag_name) + email_notify() + auto_build_deploy() + } + catch (err) { + currentBuild.result = "FAILURE" + slack_notify(currentBuild.result, tag_name) + email_notify() + throw err + } +} \ No newline at end of file diff --git a/config/db/mongodb.js b/config/db/mongodb.js index 1ba8f648..5a5d2392 100644 --- a/config/db/mongodb.js +++ b/config/db/mongodb.js @@ -68,11 +68,21 @@ const DB = function() { return model; }; + const runCompoundIndex = function(modelName,opts) { + if (opts && opts.length > 0) { + for ( let indexPointer = 0 ; indexPointer < opts.length ; indexPointer++ ) { + let currentIndex = opts[indexPointer]; + db.collection(modelName).createIndex(currentIndex.name, currentIndex.indexType); + } + } + }; + return { database: db, createModel: createModel, ObjectId: ObjectId, - models: db.models + models: db.models, + runCompoundIndex: runCompoundIndex }; }; diff --git a/config/globals.js b/config/globals.js index 02a1341d..c14c6428 100644 --- a/config/globals.js +++ b/config/globals.js @@ -15,6 +15,7 @@ module.exports = function () { global.async = require("async"); global.PROJECT_ROOT_DIRECTORY = path.join(__dirname, '..'); global.MODULES_BASE_PATH = PROJECT_ROOT_DIRECTORY + "/module"; + global.DB_QUERY_BASE_PATH = PROJECT_ROOT_DIRECTORY + "/databaseQueries"; global.GENERICS_FILES_PATH = PROJECT_ROOT_DIRECTORY + "/generics"; global.GENERIC_HELPERS_PATH = GENERICS_FILES_PATH + "/helpers"; global._ = require("lodash"); @@ -75,6 +76,7 @@ module.exports = function () { global.schemas[name] = require(PROJECT_ROOT_DIRECTORY + '/models/' + file); } }); + // All controllers global.controllers = requireAll({ diff --git a/config/kafka.js b/config/kafka.js index 34b7a90f..e2633c96 100644 --- a/config/kafka.js +++ b/config/kafka.js @@ -9,7 +9,9 @@ //dependencies const kafka = require('kafka-node'); const SUBMISSION_TOPIC = process.env.SUBMISSION_TOPIC; - +const CERTIFICATE_TOPIC = process.env.PROJECT_SUBMISSION_TOPIC; +const USER_DELETE_TOPIC = process.env.USER_DELETE_TOPIC; +const USER_DELETE_ON_OFF = process.env.USER_DELETE_ON_OFF /** * Kafka configurations. * @function @@ -44,6 +46,17 @@ const connect = function() { process.env.KAFKA_URL ); + // project certificate details consumer + _sendToKafkaConsumers( + CERTIFICATE_TOPIC, + process.env.KAFKA_URL + ); + + // user Delete Consumer + if(USER_DELETE_ON_OFF !== "OFF") { + _sendToKafkaConsumers(USER_DELETE_TOPIC, process.env.KAFKA_URL); + } + return { kafkaProducer: producer, kafkaClient: client @@ -60,7 +73,7 @@ const connect = function() { */ var _sendToKafkaConsumers = function (topic,host) { - + if (topic && topic != "") { let consumer = new kafka.ConsumerGroup( @@ -73,10 +86,24 @@ var _sendToKafkaConsumers = function (topic,host) { consumer.on('message', async function (message) { + console.log("-------Kafka consumer log starts here------------------"); + console.log("Topic Name: ", topic); + console.log("Message: ", JSON.stringify(message)); + console.log("-------Kafka consumer log ends here------------------"); + if (message && message.topic === SUBMISSION_TOPIC) { submissionsConsumer.messageReceived(message); } + // call projectCertificateConsumer + if (message && message.topic === CERTIFICATE_TOPIC) { + projectCertificateConsumer.messageReceived(message); + } + + // call userDelete consumer + if (message && message.topic === USER_DELETE_TOPIC) { + userDeleteConsumer.messageReceived(message); + } }); @@ -85,6 +112,13 @@ var _sendToKafkaConsumers = function (topic,host) { if(error.topics && error.topics[0] === SUBMISSION_TOPIC) { submissionsConsumer.errorTriggered(error); } + if(error.topics && error.topics[0] === CERTIFICATE_TOPIC) { + projectCertificateConsumer.errorTriggered(error); + } + + if (error.topics && error.topics[0] === USER_DELETE_TOPIC) { + userDeleteConsumer.errorTriggered(error); + } }); diff --git a/controllers/v1/certificateTemplates.js b/controllers/v1/certificateTemplates.js new file mode 100644 index 00000000..c57fa739 --- /dev/null +++ b/controllers/v1/certificateTemplates.js @@ -0,0 +1,16 @@ +/** + * name : certificateTemplates.js + * author : Vishnu + * created-date : 29-Sep-2022 + * Description : Certificate template related information. +*/ + +module.exports = class CertificateTemplates extends Abstract { + constructor() { + super("certificateTemplates"); + } + + static get name() { + return "certificateTemplates"; + } +} \ No newline at end of file diff --git a/controllers/v1/dataPipeline.js b/controllers/v1/dataPipeline.js new file mode 100644 index 00000000..2abd4293 --- /dev/null +++ b/controllers/v1/dataPipeline.js @@ -0,0 +1,67 @@ +/** + * name : dataPipeline.js + * author : Rakesh + * created-date : 20-July-2020 + * Description : DataPipeline related information. + */ + +// Dependencies +const dataPipelineHelper = require(MODULES_BASE_PATH + "/dataPipeline/helper"); + + /** + * DataPipeline + * @class +*/ + +module.exports = class DataPipeline { + + /** + * @apiDefine errorBody + * @apiError {String} status 4XX,5XX + * @apiError {String} message Error + */ + + /** + * @apiDefine successBody + * @apiSuccess {String} status 200 + * @apiSuccess {String} result Data + */ + + static get name() { + return "dataPipeline"; + } + + /** + * Get project details. + * @method + * @name read + * @param {Object} req - request data. + * @param {String} req.params._id - project Id. + * @returns {JSON} project details + */ + + async userProject(req) { + return new Promise(async (resolve, reject) => { + try { + + const projectDetails = await dataPipelineHelper.userProject( + req.params._id + ); + + return resolve({ + status: projectDetails.status, + message: projectDetails.message, + result: projectDetails.data + }); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + +}; \ No newline at end of file diff --git a/controllers/v1/library/categories.js b/controllers/v1/library/categories.js index 6f19b572..b8f6cad3 100644 --- a/controllers/v1/library/categories.js +++ b/controllers/v1/library/categories.js @@ -36,85 +36,6 @@ module.exports = class LibraryCategories extends Abstract { return "projectCategories"; } - /** - * @api {get} /improvement-project/api/v1/library/categories/list - * List of library categories. - * @apiVersion 1.0.0 - * @apiGroup Library Categories - * @apiSampleRequest /improvement-project/api/v1/library/categories/list - * @apiParamExample {json} Response: - { - "message": "Project categories fetched successfully", - "status": 200, - "result": [ - { - "name": "Community", - "type": "community", - "updatedAt": "2020-11-18T16:03:22.563Z", - "projectsCount": 0, - "url": "https://storage.googleapis.com/download/storage/v1/b/sl-dev-storage/o/static%2FprojectCategories%2Fcommunity.png?alt=media" - }, - { - "name": "Education Leader", - "type": "educationLeader", - "updatedAt": "2020-11-18T16:03:22.563Z", - "projectsCount": 0, - "url": "https://storage.googleapis.com/download/storage/v1/b/sl-dev-storage/o/static%2FprojectCategories%2FeducationLeader.png?alt=media" - }, - { - "name": "Infrastructure", - "type": "infrastructure", - "updatedAt": "2020-11-18T16:03:22.563Z", - "projectsCount": 0, - "url": "https://storage.googleapis.com/download/storage/v1/b/sl-dev-storage/o/static%2FprojectCategories%2Finfrastructure.png?alt=media" - }, - { - "name": "Students", - "type": "students", - "updatedAt": "2020-11-18T16:03:22.563Z", - "projectsCount": 0, - "url": "https://storage.googleapis.com/download/storage/v1/b/sl-dev-storage/o/static%2FprojectCategories%2Fstudents.png?alt=media" - }, - { - "name": "Teachers", - "type": "teachers", - "updatedAt": "2020-11-18T16:03:22.563Z", - "projectsCount": 0, - "url": "https://storage.googleapis.com/download/storage/v1/b/sl-dev-storage/o/static%2FprojectCategories%2Fteachers.png?alt=media" - } - ]} - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * List of library categories - * @method - * @name list - * @returns {Array} Library categories. - */ - - async list() { - return new Promise(async (resolve, reject) => { - try { - - let projectCategories = await libraryCategoriesHelper.list(); - - projectCategories.result = projectCategories.data; - - return resolve(projectCategories); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** * @api {get} /improvement-project/api/v1/library/categories/projects/:categoryExternalId?page=:page&limit=:limit&search=:search&sort=:sort * List of library projects. @@ -176,120 +97,4 @@ module.exports = class LibraryCategories extends Abstract { }) } - /** - * @api {get} /improvement-project/api/v1/library/categories/projectDetails/:projectId - * Library projects details. - * @apiVersion 1.0.0 - * @apiGroup Library Categories - * @apiSampleRequest /improvement-project/api/v1/library/categories/projectDetails/5f2ab0a33f623cb3b25c846a - * @apiParamExample {json} Response: - * { - "message": "Successfully fetched projects", - "status": 200, - "result": - { - "_id": "5f2ab0a33f623cb3b25c846a", - "status": "pending", - "createdAt": "2020-06-29T05:38:43.408Z", - "lastDownloadedAt": "2020-06-29T05:38:43.408Z", - "syncedAt": "2020-06-29T05:38:43.408Z", - "isDeleted": false, - "category": [ - { - "_id": "5f102331665bee6a740714e9", - "name": "Students", - "externalId": "students" - } - ], - "createdBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "startedAt": "2020-06-29T05:38:43.408Z", - "completedAt": "2020-06-29T05:38:43.408Z", - "tasks": [ - { - "_id": "5f24404784504944928b10bc", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [ - { - "_id": "5f24404784504944928b10bd", - "createdBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "updatedBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [], - "deleted": false, - "type": "single", - "projectTemplateId": "5f2402f570d11462f8e9a591", - "name": "Sub-Task-4", - "externalId": "sub-task-4", - "description": "Sub Task-4-details", - "updatedAt": "2020-07-31T16:25:01.405Z", - "createdAt": "2020-07-31T16:01:11.286Z", - "__v": 0, - "parentId": "5f24404784504944928b10bc" - } - ], - "deleted": false, - "type": "multiple", - "projectTemplateId": "5f2402f570d11462f8e9a591", - "name": "Task-3", - "externalId": "Task-3", - "description": "Task-3details", - "updatedAt": "2020-07-31T16:25:01.430Z", - "createdAt": "2020-07-31T16:01:11.280Z" - } - ], - "entityInformation": { - "externalId": "1959076", - "name": "Nigam Pratibha Vidyalaya (Girls), Jauna Pur, New Delhi" - }, - "solutionInformation": { - "externalId": "EF-DCPCR-2018-001", - "name": "DCPCR Assessment Framework 2018", - "description": "DCPCR Assessment Framework 2018", - "_id": "5b98fa069f664f7e1ae7498c" - }, - "entityTypeId": "5ce23d633c330302e720e65f", - "programInformation": { - "externalId": "PROGID01", - "name": "DCPCR School Development Index 2018-19", - "description": "DCPCR School Development Index 2018-19" - }, - "title": "Improving Library", - "goal": "Improving Library", - "duration": "1 weeak" - } - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * List of library projects - * @method - * @name projects - * @returns {JSON} returns a list of library projects. - */ - - async projectDetails(req) { - return new Promise(async (resolve, reject) => { - try { - - let libraryProjectDetails = - await libraryCategoriesHelper.projectDetails( - req.params._id, - req.userDetails.userToken - ); - - libraryProjectDetails.result = libraryProjectDetails.data; - return resolve(libraryProjectDetails); - - } catch (error) { - return reject(error); - } - }) - } - }; diff --git a/controllers/v1/programUsers.js b/controllers/v1/programUsers.js new file mode 100644 index 00000000..35d8ef63 --- /dev/null +++ b/controllers/v1/programUsers.js @@ -0,0 +1,22 @@ +/** + * name : programUsers.js + * author : Ankit Shahu + * created-date : 9-Jan-2023 + * Description : PII data related controller. +*/ + +/** + * programUsers + * @class + */ +module.exports = class ProgramUsers extends Abstract { + constructor() { + super("programUsers"); + } + + static get name() { + return "programUsers"; + } + +}; + diff --git a/controllers/v1/programs.js b/controllers/v1/programs.js new file mode 100644 index 00000000..9e89755e --- /dev/null +++ b/controllers/v1/programs.js @@ -0,0 +1,15 @@ +/** + * name : programs.js + * author : vishnu + * created-date : 09-Mar-2022 + * Description : programs related information. + */ +module.exports = class Programs extends Abstract{ + constructor() { + super("programs"); + } + + static get name() { + return "programs"; + } +} \ No newline at end of file diff --git a/controllers/v1/project/templateTasks.js b/controllers/v1/project/templateTasks.js index 50126526..cff6baab 100644 --- a/controllers/v1/project/templateTasks.js +++ b/controllers/v1/project/templateTasks.js @@ -138,4 +138,52 @@ module.exports = class ProjectTemplateTasks extends Abstract { }) } + /** + * @api {post} /improvement-project/api/v1/project/templateTasks/update/:taskId + * Update projects template. + * @apiVersion 1.0.0 + * @apiGroup Project Template Tasks + * @apiSampleRequest /improvement-project/api/v1/project/templateTasks/update/6006b5cca1a95727dbcdf648 + * @apiHeader {String} internal-access-token internal access token + * @apiHeader {String} X-authenticated-user-token Authenticity token + * @apiUse successBody + * @apiUse errorBody + * @apiParamExample {json} Response: + * { + * "status": 200, + "message": "template task updated successfully" + } + */ + + /** + * Update project templates task + * @method + * @name update + * @returns {JSON} returns uploaded project template task. + */ + + async update(req) { + return new Promise(async (resolve, reject) => { + try { + + let projectTemplateTask = await projectTemplateTasksHelper.update( + req.params._id, + req.body, + req.userDetails.id + ); + + projectTemplateTask.result = projectTemplateTask.data; + + return resolve(projectTemplateTask); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + }; diff --git a/controllers/v1/project/templates.js b/controllers/v1/project/templates.js index 903b2f86..1cc19f70 100644 --- a/controllers/v1/project/templates.js +++ b/controllers/v1/project/templates.js @@ -32,6 +32,7 @@ module.exports = class ProjectTemplates extends Abstract { constructor() { super("project-templates"); } + /** * @api {post} /improvement-project/api/v1/project/templates/bulkCreate @@ -666,12 +667,21 @@ module.exports = class ProjectTemplates extends Abstract { async details(req) { return new Promise(async (resolve, reject) => { + try { - + if( !req.params._id && !req.query.link ) { + throw{ + status:HTTP_STATUS_CODE.bad_request.status, + message:CONSTANTS.apiResponses.TEMPLATE_ID_OR_LINK_REQUIRED + } + } + let projectTemplatesDetails = await projectTemplatesHelper.details( - req.params._id, - req.userDetails.userInformation.userId + req.params._id ? req.params._id : "", + req.query.link ? req.query.link : "", + req.userDetails && req.userDetails.userInformation && req.userDetails.userInformation.userId ? req.userDetails.userInformation.userId : "", + req.query.isAPrivateProgram ? req.query.isAPrivateProgram : "" ); projectTemplatesDetails.result = projectTemplatesDetails.data; @@ -687,4 +697,52 @@ module.exports = class ProjectTemplates extends Abstract { }) } + /** + * @api {post} /improvement-project/api/v1/project/templates/update/:templateId + * Update projects template. + * @apiVersion 1.0.0 + * @apiGroup Project Templates + * @apiSampleRequest /improvement-project/api/v1/project/templates/update/6006b5cca1a95727dbcdf648 + * @apiHeader {String} internal-access-token internal access token + * @apiHeader {String} X-authenticated-user-token Authenticity token + * @apiUse successBody + * @apiUse errorBody + * @apiParamExample {json} Response: + * { + * "status": 200, + "message": "template updated successfully" + } + */ + + /** + * Update project templates + * @method + * @name update + * @returns {JSON} returns uploaded project template. + */ + + async update(req) { + return new Promise(async (resolve, reject) => { + try { + + let projectTemplate = await projectTemplatesHelper.update( + req.params._id, + req.body, + req.userDetails.id + ); + + projectTemplate.result = projectTemplate.data; + + return resolve(projectTemplate); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + }; diff --git a/controllers/v1/reports.js b/controllers/v1/reports.js index 1314ae7f..cb59004b 100644 --- a/controllers/v1/reports.js +++ b/controllers/v1/reports.js @@ -78,6 +78,7 @@ module.exports = class Reports { req.query.reportType, req.query.programId ? req.query.programId : "", req.query.requestPdf ? (req.query.requestPdf).toLowerCase() =="true" ? true :false : false, + req.headers['x-app-ver'] ); return resolve({ @@ -95,73 +96,18 @@ module.exports = class Reports { }) } - /** - * @api {get} /improvement-project/api/v1/reports/types - * Get report types. - * @apiVersion 1.0.0 - * @apiGroup Reports - * @apiSampleRequest /improvement-project/api/v1/reports/types - * @apiParamExample {json} Response: - * { - "message": "Report types fetched successfully.", - "status": 200, - "result": [ - { - "message": "Report types fetched successfully.", - "status": 200, - "result": [ - { - "label": "Weekly", - "value": 0 - }, - { - "label": "Monthly", - "value": 1 - }, - { - "label": "Quarterly", - "value": 3 - } - - ] - * } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Get entity types - * @method - * @name types - * @returns {JSON} enity report details. - */ - async types(req) { - return new Promise(async (resolve, reject) => { - try { - - const reportTypes = await reportsHelper.types(); - - return resolve({ - message: reportTypes.message, - result: reportTypes.data - }); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } + /** - * @api {get} /improvement-project/api/v1/reports/getProgramsByEntity/:_id + * @api {post} /improvement-project/api/v1/reports/getProgramsByEntity/:_id * Get programs by entity. * @apiVersion 1.0.0 * @apiGroup Reports - * @apiSampleRequest /improvement-project/api/v1/reports/getProgramsByEntity/5ddf79ff47e9260268c9547a?page=1&limi1=10&search=a + * @apiSampleRequest /improvement-project/api/v1/reports/getProgramsByEntity/5ddf79ff47e9260268c9547a?page=1&limit=10&search=a + * @apiParamExample {json} Request: + { + "role": "HM,DEO", + } * @apiParamExample {json} Response: * { "message": "Programs fetched successfully", diff --git a/controllers/v1/solutions.js b/controllers/v1/solutions.js index 107278ad..186a0e1e 100644 --- a/controllers/v1/solutions.js +++ b/controllers/v1/solutions.js @@ -7,7 +7,14 @@ // Dependencies const solutionsHelper = require(MODULES_BASE_PATH + "/solutions/helper"); -module.exports = class Solutions { +module.exports = class Solutions extends Abstract{ + constructor() { + super("solutions"); + } + + static get name() { + return "solutions"; + } /** * @api {post} /improvement-project/api/v1/solutions/create Create solution @@ -16,8 +23,6 @@ module.exports = class Solutions { * @apiGroup Solutions * @apiParamExample {json} Request-Body: * { - * "createdFor" : ["01305447637218918413"], - * "rootOrganisations" : ["01305447637218918413"], "programExternalId" : "AMAN_TEST_123-1607937244986", "entityType" : "school", "externalId" : "IMPROVEMENT-PROJECT-TEST-SOLUTION", diff --git a/controllers/v1/userProjects.js b/controllers/v1/userProjects.js index 31fd1fbe..fb5c2a02 100644 --- a/controllers/v1/userProjects.js +++ b/controllers/v1/userProjects.js @@ -8,6 +8,7 @@ // Dependencies const csv = require('csvtojson'); const userProjectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); +const projectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); /** * UserProjects @@ -36,114 +37,113 @@ module.exports = class UserProjects extends Abstract { return "userProjects"; } - /** - * @api {get} /improvement-project/api/v1/userProjects/list?updateLastDownloadedAt=:updateLastDownloadedAt - * List of projects. + /** + * @api {post} /improvement-project/api/v1/userProjects/sync/:projectId?lastDownloadedAt=:epochTime + * Sync project. * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/list?updateLastDownloadedAt=true - * @apiParamExample {json} Response: + * @apiSampleRequest /improvement-project/api/v1/userProjects/sync/5f731631e8d7cd3b88ac0659?lastDownloadedAt=0125747659358699520 + * @apiParamExample {json} Request: * { - "message": "Project lists fetched successfully", - "status": 200, - "result": [ + "title": "Project 1", + "description": "Project 1 description", + "tasks": [ { - "_id": "5f2449eb626a540f40817ef5", - "userId": "e97b5582-471c-4649-8401-3cc4249359bb", - "status": "pending", - "createdAt": "2020-06-29T05:38:43.408Z", - "lastDownloadedAt": "2020-06-29T05:38:43.408Z", - "syncedAt": "2020-06-29T05:38:43.408Z", - "isDeleted": false, - "category": [ - "Community" - ], - "createdBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "startedAt": "2020-06-29T05:38:43.408Z", - "completedAt": "2020-06-29T05:38:43.408Z", - "tasks": [ + "_id": "289d9558-b98f-41cf-81d3-92486f114a49", + "name": "Task 1", + "description": "Task 1 description", + "status": "notStarted/inProgress/completed", + "isACustomTask": false, + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single/multiple", + “isDeleted” : false, + “attachments” : [ + { + "name" : "download(2).jpeg", + "type" : "image/jpeg", + "sourcePath" : "projectId/userId/imageName" + } + ], + “remarks” : “Tasks completed”, + “assignee” : “Aman”, + "children": [ { - "_id": "5f24404784504944928b10bc", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [ - { - "_id": "5f24404784504944928b10bd", - "createdBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "updatedBy": "e97b5582-471c-4649-8401-3cc4249359bb", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [], - "deleted": false, - "type": "single", - "projectTemplateId": "5f2402f570d11462f8e9a591", - "name": "Sub-Task-4", - "externalId": "sub-task-4", - "description": "Sub Task-4-details", - "updatedAt": "2020-07-31T16:25:01.405Z", - "createdAt": "2020-07-31T16:01:11.286Z", - "__v": 0, - "parentId": "5f24404784504944928b10bc" - } - ], - "deleted": false, - "type": "multiple", - "projectTemplateId": "5f2402f570d11462f8e9a591", - "name": "Task-3", - "externalId": "Task-3", - "description": "Task-3details", - "updatedAt": "2020-07-31T16:25:01.430Z", - "createdAt": "2020-07-31T16:01:11.280Z" + "_id": "289d9558-b98f-41cf-81d3-92486f114a50", + "name": "Task 2", + "description": "Task 2 description", + "status": "notStarted/inProgress/completed", + "children": [], + "isACustomTask": false, + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single/multiple”, + “isDeleted” : false } - ], - "entityInformation": { - "externalId": "1959076", - "name": "Nigam Pratibha Vidyalaya (Girls), Jauna Pur, New Delhi" - }, - "solutionInformation": { - "externalId": "EF-DCPCR-2018-001", - "name": "DCPCR Assessment Framework 2018", - "description": "DCPCR Assessment Framework 2018", - "_id": "5b98fa069f664f7e1ae7498c" - }, - "entityTypeId": "5ce23d633c330302e720e65f", - "programInformation": { - "externalId": "PROGID01", - "name": "DCPCR School Development Index 2018-19", - "description": "DCPCR School Development Index 2018-19" - }, - "title": "Improving Library", - "goal": "Improving Library", - "duration": "1 weeak" + ] } - ] - } + ], + "programId": "", + "programName": "New Project Program", + "entityId" : “5beaa888af0065f0e0a10515”, + "categories": [ + { + "value": "5f102331665bee6a740714e8", + "label": "teacher" + }, + { + "value": "", + "label": "other" + } + ], + "status": "notStarted/inProgress/completed", + “lastDownloadedAt” : "2020-09-29T09:08:41.667Z", + "payload": { + "_id": "289d9558-b98f-41cf-81d3-92486f114a51" + }} + * @apiParamExample {json} Response: + * { + * "message": "Project updated successfully", + * "status": 200, + * "result" : { + * "programId" : "5fb669f223575a2f0cef3b33" + * } + * } * @apiUse successBody * @apiUse errorBody */ - /** - * List of project + /** + * Sync projects. * @method - * @name list + * @name sync * @param {Object} req - request data. - * @returns {JSON} returns a list of project. + * @param {String} req.params._id - Project id. + * @returns {JSON} Create Self projects. */ - async list(req) { + async sync(req) { return new Promise(async (resolve, reject) => { try { - - let projects = await userProjectsHelper.list( + let createdProject = await userProjectsHelper.sync( + req.params._id, + req.query.lastDownloadedAt, + req.body, req.userDetails.userInformation.userId, - req.query.updateLastDownloadedAt ? req.query.updateLastDownloadedAt : false + req.userDetails.userToken, + req.headers["x-app-id"] ? + req.headers["x-app-id"] : + req.headers.appname ? req.headers.appname : "", + req.headers["x-app-ver"] ? + req.headers["x-app-ver"] : + req.headers.appversion ? req.headers.appversion : "" ); - projects.result = projects.data; + createdProject.result = createdProject.data; - return resolve(projects); + return resolve(createdProject); } catch (error) { return reject({ @@ -155,103 +155,130 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {get} /improvement-project/api/v1/userProjects/metaForm - * Projects metaForm. - * @apiVersion 1.0.0 + /** + * @api {post} /improvement-project/api/v1/userProjects/details/:projectId?programId=:programId&solutionId=:solutionId&templateId=:templateId + * Project Details. + * @apiVersion 2.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/metaForm + * @apiSampleRequest /improvement-project/api/v1/userProjects/details/5f731631e8d7cd3b88ac0659?programId=5f4e538bdf6dd17bab708173&solutionId=5f8688e7d7f86f040b77f460&templateId=IDEAIMP4 + * @apiParamExample {json} Request: + { + "role" : "HM,DEO", + "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", + "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", + "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" + } * @apiParamExample {json} Response: * { - * "message": "Successfully fetched projects metaform", - * "status": 200, - * "result": [ - { - "field": "title", - "label": "Title", - "value": "", - "visible": true, - "editable": true, - "input": "text", - "validation": { - "required": true - } - }, - { - "field": "description", - "label": "Description", - "value": "", - "visible": true, - "editable": true, - "input": "textarea", - "validation": { - "required": true + "message": "Successfully fetched project details", + "status": 200, + "result": { + "_id": "5f97d2f6bf3a3b1c0116c80a", + "status": "notStarted", + "isDeleted": false, + "categories": [ + { + "_id": "5f102331665bee6a740714e8", + "name": "Teachers", + "externalId": "teachers" + }, + { + "name": "newCategory", + "externalId": "", + "_id": "" } - }, - { - "field": "categories", - "label": "Categories", - "value": "", - "visible": true, - "editable": true, - "input": "select", - "options": [ - { - "_id": "5fbb2bcc3e7f4958654e351e", - "label": "Teachers", - "value": "teachers" - }, - { - "_id": "5fbb2bcc3e7f4958654e351f", - "label": "Students", - "value": "students" - }, - { - "_id": "5fbb2bcc3e7f4958654e3520", - "label": "Infrastructure", - "value": "infrastructure" - }, - { - "_id": "5fbb2bcc3e7f4958654e3521", - "label": "Community", - "value": "community" - }, - { - "_id": "5fbb2bcc3e7f4958654e3522", - "label": "Education Leader", - "value": "educationLeader" - }, - { - "_id": "", - "label": "Others", - "value": "others" - } - ], - "validation": { - "required": false + ], + "tasks": [ + { + "_id": "289d9558-b98f-41cf-81d3-92486f114a49", + "name": "Task 1", + "description": "Task 1 description", + "status": "notStarted", + "isACustomTask": false, + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single", + "isDeleted": false, + "attachments": [ + { + "name": "download(2).jpeg", + "type": "image/jpeg", + "sourcePath": "projectId/userId/imageName" + } + ], + "remarks": "Tasks completed", + "assignee": "Aman", + "children": [ + { + "_id": "289d9558-b98f-41cf-81d3-92486f114a50", + "name": "Task 2", + "description": "Task 2 description", + "status": "notStarted", + "children": [], + "isACustomTask": false, + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single", + "isDeleted": false, + "externalId": "task 2", + "isDeleteable": false, + "createdAt": "2020-10-28T05:58:24.907Z", + "updatedAt": "2020-10-28T05:58:24.907Z", + "isImportedFromLibrary": false + } + ], + "externalId": "task 1", + "isDeleteable": false, + "createdAt": "2020-10-28T05:58:24.907Z", + "updatedAt": "2020-10-28T05:58:24.907Z", + "isImportedFromLibrary": false } - }]} - * @apiUse successBody - * @apiUse errorBody + ], + "resources": [], + "deleted": false, + "lastDownloadedAt": "2020-09-29T09:08:41.667Z", + "__v": 0, + "description": "Project 1 description" + } + } + * @apiUse successBody + * @apiUse errorBody */ - /** - * Projects form + /** + * Project details * @method - * @name metaForm + * @name details * @param {Object} req - request data. - * @returns {JSON} Projects form. + * @param {String} req.params._id - Project id. + * @returns {JSON} Create Self projects. */ - async metaForm(req) { + async details(req) { return new Promise(async (resolve, reject) => { try { - const formsData = - await userProjectsHelper.metaForm(req.userDetails.userToken); + let projectDetails = + await userProjectsHelper.detailsV2( + req.params._id ? req.params._id : "", + req.query.solutionId, + req.userDetails.userInformation.userId, + req.userDetails.userToken, + req.body, + req.headers["x-app-id"] ? + req.headers["x-app-id"] : + req.headers.appname ? req.headers.appname : "", + req.headers["x-app-ver"] ? + req.headers["x-app-ver"] : + req.headers.appversion ? req.headers.appversion : "", + req.query.templateId + ); + + projectDetails.result = projectDetails.data; - formsData.result = formsData.data; - return resolve(formsData); + return resolve(projectDetails); } catch (error) { return reject({ @@ -263,38 +290,34 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {get} /improvement-project/api/v1/userProjects/tasksMetaForm - * Projects task meta form. + /** + * @api {post} /improvement-project/api/v1/userProjects/tasksStatus/:projectId + * User Project tasks status * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/tasksMetaForm - * @apiParamExample {json} Response: + * @apiSampleRequest /improvement-project/api/v1/userProjects/tasksStatus/5f731631e8d7cd3b88ac0659 + * @apiParamExample {json} Request: * { - "message": "Successfully fetched projects tasks metaform", + * "taskIds" : [ + "2f2ef6dd-24e9-40ab-a681-3b3167fcd2c6", + "a18ae088-fa11-4ff4-899f-213abefb30f6" + ] + } + * @apiParamExample {json} Response: + { + "message": "Tasks status fetched successfully", "status": 200, "result": [ { - "field": "name", - "label": "Name", - "value": "", - "visible": true, - "editable": true, - "input": "text", - "validation": { - "required": true - } + "type": "assessment", + "status": "started", + "_id": "2f2ef6dd-24e9-40ab-a681-3b3167fcd2c6" }, { - "field": "description", - "label": "Description", - "value": "", - "visible": true, - "editable": true, - "input": "textarea", - "validation": { - "required": true - } + "type": "observation", + "status": "started", + "_id": "a18ae088-fa11-4ff4-899f-213abefb30f6", + "submissionId": "5fbaa71d97ccef111cbb4ee0" } ] } @@ -302,22 +325,27 @@ module.exports = class UserProjects extends Abstract { * @apiUse errorBody */ - /** - * Projects tasks meta form. + /** + * Tasks status * @method - * @name tasksMetaForm + * @name tasksStatus * @param {Object} req - request data. - * @returns {JSON} Projects tasks meta form. + * @param {String} req.params._id - Project id. + * @returns {JSON} status of tasks */ - - async tasksMetaForm() { + + async tasksStatus(req) { return new Promise(async (resolve, reject) => { try { - let formsData = await userProjectsHelper.tasksMetaForm(); + let taskStatus = await userProjectsHelper.tasksStatus( + req.params._id, + req.body.taskIds + ); - formsData.result = formsData.data; - return resolve(formsData); + taskStatus.result = taskStatus.data; + + return resolve(taskStatus); } catch (error) { return reject({ @@ -330,47 +358,185 @@ module.exports = class UserProjects extends Abstract { } /** - * @api {post} /improvement-project/api/v1/userProjects/bulkCreate - * Bulk create user projects. + * @api {post} /improvement-project/api/v1/userProjects/solutionDetails/:projectId?taskId=:taskId + * User project solution details * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiParam {File} projects Mandatory project file of type CSV. - * @apiSampleRequest /improvement-project/api/v1/userProjects/bulkCreate + * @apiSampleRequest /improvement-project/api/v1/userProjects/solutionDetails/5fba54dc5bf46b25a926bee5?taskId=347400e7-8a62-4dad-bc24-af7c5bd70ad1 + * @apiParamExample {json} Request: + { + "role" : "HM,DEO", + "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", + "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", + "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" + } + * @apiParamExample {json} Response: + * { + "message" : "Solutions details fetched successfully", + "status": 200, + "result": { + "entityId": "5beaa888af0065f0e0a10515", + "programId": "5fba54dc2a1f7b172f066597", + "observationId": "5d1a002d2dfd8135bc8e1617", + "solutionId": "5d15b0d7463d3a6961f91749" + “solutionDetails”:{ + "_id" : "60b06e30343385596ef48c25", + "isReusable" : false, + "externalId" : "NEW-TEST-SOLUTION", + "name" : "NEW-TEST-SOLUTION", + "programId" : "600ab53cc7de076e6f993724", + "type" : "observation", + "subType" : "district", + "isRubricDriven" : true, + "criteriaLevelReport" : "", + "allowMultipleAssessemts" : false, + "scoringSystem": "" + } + + } + } * @apiUse successBody * @apiUse errorBody */ /** - * Bulk create user projects. + * Solutions details information. * @method - * @name bulkCreate + * @name status * @param {Object} req - request data. - * @returns {CSV} Assigned projects to user. + * @param {String} req.params._id - Project id. + * @param {String} req.query.taskId - task id. + * @returns {JSON} Solutions details */ - - async bulkCreate(req) { + + async solutionDetails(req) { return new Promise(async (resolve, reject) => { try { - if ( !req.files || !req.files.projects ) { - return resolve( - { - status : HTTP_STATUS_CODE["bad_request"].status, - message : CONSTANTS.apiResponses.PROJECTS_CSV_REQUIRED - } - ) + let solutionDetails = await userProjectsHelper.solutionDetails( + req.userDetails.userToken, + req.params._id, + req.query.taskId, + req.body + ); + + solutionDetails.result = solutionDetails.data; + + return resolve(solutionDetails); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + + /** + * @api {post} /improvement-project/api/v1/userProjects/add + * Add project. + * @apiVersion 1.0.0 + * @apiGroup User Projects + * @apiSampleRequest /improvement-project/api/v1/userProjects/add + * @apiParamExample {json} Request: + * { + "title": "Project 1", + "description": "Project 1 description", + "tasks": [ + { + "_id": "289d9558-b98f-41cf-81d3-92486f114a49", + "name": "Task 1", + "description": "Task 1 description", + "status": "notStarted/inProgress/completed", + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single/multiple", + “isDeleted” : false, + “remarks” : “Tasks completed”, + “assignee” : “Aman”, + "children": [ + { + "_id": "289d9558-b98f-41cf-81d3-92486f114a50", + "name": "Task 2", + "description": "Task 2 description", + "status": "notStarted/inProgress/completed", + "children": [], + "startDate": "2020-09-29T09:08:41.667Z", + "endDate": "2020-09-29T09:08:41.667Z", + "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "type": "single/multiple”, + “isDeleted” : false } + ] + } + ], + "programId": "", + "programName": "New Project Program", + "entityId" : “5beaa888af0065f0e0a10515”, + "categories": [ + { + "value": "5f102331665bee6a740714e8", + "label": "teacher" + }, + { + "value": "", + "label": "other" + } + ], + "status": "notStarted/inProgress/completed", + “lastDownloadedAt” : "2020-09-29T09:08:41.667Z", + "payload": { + "_id": "289d9558-b98f-41cf-81d3-92486f114a51" + }, + "profileInformation" : { + "role" : "HM,DEO", + "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", + "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", + "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" + }} + * @apiParamExample {json} Response: + * { + * "message": "Project created successfully", + * "status": 200, + * "result" : { + * "programId" : "5fb669f223575a2f0cef3b33" + * "projectId" : "5f102331665bee6a740714e8" + * } + * } + * @apiUse successBody + * @apiUse errorBody + */ - const projectsData = await csv().fromString( - req.files.projects.data.toString() - ); + /** + * Add projects. + * @method + * @name add + * @param {Object} req - request data. + * @returns {JSON} Create Self projects. + */ + + async add(req) { + return new Promise(async (resolve, reject) => { + try { - const projects = await userProjectsHelper.bulkCreate( - projectsData, - req.userDetails.userToken + let createdProject = await userProjectsHelper.add( + req.body, + req.userDetails.userInformation.userId, + req.userDetails.userToken, + req.headers["x-app-id"] ? + req.headers["x-app-id"] : + req.headers.appname ? req.headers.appname : "", + req.headers["x-app-ver"] ? + req.headers["x-app-ver"] : + req.headers.appversion ? req.headers.appversion : "" ); - return resolve(projects); + createdProject.result = createdProject.data; + + return resolve(createdProject); } catch (error) { return reject({ @@ -382,173 +548,108 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {post} /improvement-project/api/v1/userProjects/importFromLibrary/:projectTemplateId - * Import project from library. + /** + * @api {get} /improvement-project/api/v1/userProjects/userAssigned?page=:page&limit=:limit&search=:search&filter=:assignedToMe + * List of user assigned project. * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/importFromLibrary/5f5b32cef16777642d51aaf0 - * @apiParamExample {json} Request: - * { - * "programId" : "", - * "programName" : "My Program", - * "rating" : 2 - * } + * @apiSampleRequest /improvement-project/api/v1/userProjects/userAssigned?page=1&limit=10 * @apiParamExample {json} Response: * { - "message": "Successfully fetched projects", + "message": "User project fetched successfully", "status": 200, "result": { - "userId": "01c04166-a65e-4e92-a87b-a9e4194e771d", - "createdFor": [], - "isDeleted": false, - "categories": [ + "data": [ { - "_id": "5f102331665bee6a740714eb", - "externalId": "community", - "name": "Community" + "_id": "6049c282348d1b060c6454b7", + "solutionId": "6049c277f026c305dd471769", + "programId": "6049c275f026c305dd471768", + "name": "TEST TITLE", + "programName": "NEW", + "externalId": "01c04166-a65e-4e92-a87b-a9e4194e771d-1615446645973", + "type": "improvementProject" } ], - "createdBy": "01c04166-a65e-4e92-a87b-a9e4194e771d", - "tasks": [ - { - "_id": "61d6690d-82cb-4db2-8191-8dd945c5e742", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [ - { - "_id": "b5068cef-eefc-4f43-8a29-ab9c2268f451", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [], - "visibleIf": [ - { - "operator": "===", - "_id": "5f72f9998925ec7c60f79a91", - "value": "started" - } - ], - "deleted": false, - "type": "single", - "projectTemplateId": "5f5b32cef16777642d51aaf0", - "name": "Sub task 1", - "externalId": "Sub-task-1", - "description": "Sub-Task-1-Description", - "updatedAt": "2020-09-29T09:08:41.681Z", - "createdAt": "2020-09-29T09:08:41.675Z", - "__v": 0, - "status": "notStarted" - }, - { - "_id": "988ef20f-267f-4bed-9a38-9d7dc6a320e9", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [], - "visibleIf": [ - { - "operator": "===", - "_id": "5f72f9998925ec7c60f79a91", - "value": "started" - } - ], - "deleted": false, - "type": "single", - "projectTemplateId": "5f5b32cef16777642d51aaf0", - "name": "Sub task 2", - "externalId": "Sub-task-2", - "description": "Sub-Task-2-Description", - "updatedAt": "2020-09-29T09:08:41.693Z", - "createdAt": "2020-09-29T09:08:41.689Z", - "__v": 0, - "status": "notStarted" - } - ], - "visibleIf": [], - "deleted": false, - "type": "multiple", - "projectTemplateId": "5f5b32cef16777642d51aaf0", - "name": "Task 1", - "externalId": "task-1", - "description": "Task-1 Description", - "updatedAt": "2020-09-29T09:08:41.691Z", - "createdAt": "2020-09-29T09:08:41.612Z", - "__v": 0, - "status": "notStarted" - }, - { - "_id": "289d9558-b98f-41cf-81d3-92486f114a49", - "isDeleted": false, - "isDeletable": false, - "taskSequence": [], - "children": [], - "visibleIf": [], - "deleted": false, - "type": "single", - "projectTemplateId": "5f5b32cef16777642d51aaf0", - "name": "Task 12", - "externalId": "Task-12", - "description": "Task-1 Description", - "updatedAt": "2020-09-29T09:08:41.667Z", - "createdAt": "2020-09-29T09:08:41.667Z", - "__v": 0, - "status": "notStarted" - } - ], - "updatedBy": "01c04166-a65e-4e92-a87b-a9e4194e771d", - "rootOrganisations": [], - "_id": "5f731d68920a8c3e092e6e4c", - "deleted": false, - "name": "Test-2", - "description": "improving school library", - "status": "notStarted", - "updatedAt": "2020-09-29T11:41:28.656Z", - "createdAt": "2020-09-11T08:18:22.077Z", - "__v": 0, - "solutionInformation": { - "externalId": "01c04166-a65e-4e92-a87b-a9e4194e771d-1601379673400" - }, - "programInformation": { - "externalId": "My Program-1601379673400", - "name": "My Program" - }, - "taskReport": {}, - "entityInformation": {}, - "rationale": "sample", - "primaryAudience": [ - "teachers", - "head master" - ] + "count": 1 }} + + /** + * List of user assigned projects. + * @method + * @name userAssigned + * @param {Object} req - request data. + * @returns {JSON} List of user assigned projects. + */ + + async userAssigned(req) { + return new Promise(async (resolve, reject) => { + try { + + let projects = await userProjectsHelper.userAssigned( + req.userDetails.userInformation.userId, + req.pageSize, + req.pageNo, + req.searchText, + req.query.filter + ); + + projects.result = projects.data; + + return resolve(projects); + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + + /** + * @api {get} /improvement-project/api/v1/userProjects/share/:projectId?tasks=:taskId1,:taskId2 + * Share project and task pdf report. + * @apiVersion 1.0.0 + * @apiGroup User Projects + * @apiSampleRequest /improvement-project/api/v1/userProjects/share/6065ced7e9259b7f0b1f5d66?tasks=4d074de7-7059-4d99-9da9-452b0d32e081 + * @apiParamExample {json} Response: + * { + * "message": "Report generated succesfully", + * "status": 200, + * "result" : { + * "data" : { + * "downloadUrl": "http://localhost:4700/dhiti/api/v1/observations/pdfReportsUrl?id=dG1wLzVhNzZjMTY5LTA5YjAtNGU3Zi04ZmNhLTg0NDc5ZmI2YTNiNC0tODUyOA==" + * } + * } + * } * @apiUse successBody * @apiUse errorBody */ - /** - * Import project from library. + /* + * Share project and task pdf report. * @method - * @name importFromLibrary + * @name share * @param {Object} req - request data. - * @param {String} req.params._id - project Template Id. - * @returns {JSON} import project from library. + * @param {String} req.params._id - projectId + * @returns {JSON} Downloadable pdf url. */ - async importFromLibrary(req) { + async share(req) { return new Promise(async (resolve, reject) => { try { - const createdProject = await userProjectsHelper.importFromLibrary( + let taskIds = req.query.tasks ? req.query.tasks.split(",") : []; + + let report = await userProjectsHelper.share( req.params._id, - req.body, + taskIds, req.userDetails.userToken, - req.userDetails.userInformation.userId + req.headers['x-app-ver'] ); - return resolve({ - message: createdProject.message, - result: createdProject.data + message: report.message, + result: report.data }); } catch (error) { @@ -562,44 +663,57 @@ module.exports = class UserProjects extends Abstract { } /** - * @api {get} /improvement-project/api/v1/userProjects/create - * Create project. + * @api {get} /improvement-project/api/v1/userProjects/importedProjects/:programId * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/create + * @apiGroup Lists of User Imported Projects + * @apiSampleRequest /improvement-project/api/v1/userProjects/importedProjects/60545d541fc23d6d2d44c0c9 * @apiParamExample {json} Response: - * { - "message": "Created user project successfully", + { + "message": "List of imported projects fetched", "status": 200, - "result": { - "_id": "5f97d2f6bf3a3b1c0116c80a", - "lastDownloadedAt": "2020-10-27T07:57:41.003Z" - }} + "result": [ + { + "_id": "60793b80bd49095a19ddeae1", + "description": "", + "title": "Project with learning resources", + "projectTemplateId": "60546a4cb807066d9cddba21", + "programInformation": { + "_id": "60545d541fc23d6d2d44c0c9", + "externalId": "PGM-3542-3.8.0_testing_program-2", + "description": "3.8.0 testing program - 2", + "name": "3.8.0 testing program - 2" + }, + "solutionInformation": { + "_id": "605468721fc23d6d2d44c0cb", + "externalId": "IMP-3542_solution2", + "description": "", + "name": "Project with learning resources" + } + } + ]} * @apiUse successBody * @apiUse errorBody */ - /** - * Create project. - * @method - * @name create - * @param {Object} req - request data. - * @param {String} req.params._id - Project id. - * @returns {JSON} Create project. + /* + * List of user imported projects + * @method + * @name importedProjects + * @returns {JSON} List of imported projects. */ - async create(req) { + async importedProjects(req) { return new Promise(async (resolve, reject) => { try { - let createdProject = await userProjectsHelper.create( + let importedProjects = await userProjectsHelper.importedProjects( req.userDetails.userInformation.userId, - req.userDetails.userToken + req.params._id ? req.params._id : "" ); - createdProject.result = createdProject.data; + importedProjects["result"] = importedProjects["data"]; - return resolve(createdProject); + return resolve(importedProjects); } catch (error) { return reject({ @@ -611,629 +725,227 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {post} /improvement-project/api/v1/userProjects/sync/:projectId?lastDownloadedAt=:epochTime - * Sync project. + /** + * @api {post} /improvement-project/api/v1/userProjects/list + * Lists of projects. + * @apiVersion 0.0.1 + * @apiName Lists of projects. + * @apiGroup Entity Types + * @apiHeader {String} X-authenticated-user-token Authenticity token + * @apiSampleRequest /improvement-project/api/v1/userProjects/list + * @apiUse successBody + * @apiUse errorBody + * @apiParamExample {json} Request-Body: + * { + "query" : { + "code" : "HM" + }, + "projection" : ["_id","code"] + } + * @apiParamExample {json} Response: + * { + * "message": "Project fetched successfully", + * "status": 200, + * "result" : [ + * { + * "_id": "5d15a959e9185967a6d5e8a6", + * "title": "Come See Our School!- Parent Mela" + }] + } + */ + + /** + * Lists of projects. + * @method + * @name list + * @returns {JSON} List projects. + */ + + async list(req) { + return new Promise(async (resolve, reject) => { + try { + + let projects = await userProjectsHelper.list(req.body); + return resolve(projects); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }); + } + + /** + * @api {post} /improvement-project/api/v1/userProjects/importFromLibrary/:projectTemplateId&isATargetedSolution=false + * Import project from library. * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/sync/5f731631e8d7cd3b88ac0659?lastDownloadedAt=0125747659358699520 + * @apiSampleRequest /improvement-project/api/v1/userProjects/importFromLibrary/5f5b32cef16777642d51aaf0 * @apiParamExample {json} Request: * { - "title": "Project 1", - "description": "Project 1 description", - "tasks": [ - { - "_id": "289d9558-b98f-41cf-81d3-92486f114a49", - "name": "Task 1", - "description": "Task 1 description", - "status": "notStarted/inProgress/completed", - "isACustomTask": false, - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", - "type": "single/multiple", - “isDeleted” : false, - “attachments” : [ - { - "name" : "download(2).jpeg", - "type" : "image/jpeg", - "sourcePath" : "projectId/userId/imageName" - } - ], - “remarks” : “Tasks completed”, - “assignee” : “Aman”, - "children": [ - { - "_id": "289d9558-b98f-41cf-81d3-92486f114a50", - "name": "Task 2", - "description": "Task 2 description", - "status": "notStarted/inProgress/completed", - "children": [], - "isACustomTask": false, - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", - "type": "single/multiple”, - “isDeleted” : false - } - ] - } - ], - "programId": "", - "programName": "New Project Program", - "entityId" : “5beaa888af0065f0e0a10515”, - "categories": [ - { - "value": "5f102331665bee6a740714e8", - "label": "teacher" - }, - { - "value": "", - "label": "other" - } - ], - "status": "notStarted/inProgress/completed", - “lastDownloadedAt” : "2020-09-29T09:08:41.667Z", - "payload": { - "_id": "289d9558-b98f-41cf-81d3-92486f114a51" - }} + * "programId" : "", + * "programName" : "My Program", + * "rating" : 2 + * } * @apiParamExample {json} Response: * { - * "message": "Project updated successfully", - * "status": 200, - * "result" : { - * "programId" : "5fb669f223575a2f0cef3b33" - * } - * } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Sync projects. - * @method - * @name sync - * @param {Object} req - request data. - * @param {String} req.params._id - Project id. - * @returns {JSON} Create Self projects. - */ - - async sync(req) { - return new Promise(async (resolve, reject) => { - try { - - let createdProject = await userProjectsHelper.sync( - req.params._id, - req.query.lastDownloadedAt, - req.body, - req.userDetails.userInformation.userId, - req.userDetails.userToken, - req.headers["x-app-id"] ? - req.headers["x-app-id"] : - req.headers.appname ? req.headers.appname : "", - req.headers["x-app-ver"] ? - req.headers["x-app-ver"] : - req.headers.appversion ? req.headers.appversion : "" - ); - - createdProject.result = createdProject.data; - - return resolve(createdProject); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** - * @api {post} /improvement-project/api/v1/userProjects/details/:projectId?programId=:programId&solutionId=:solutionId&templateId=:templateId - * Project Details. - * @apiVersion 2.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/details/5f731631e8d7cd3b88ac0659?programId=5f4e538bdf6dd17bab708173&solutionId=5f8688e7d7f86f040b77f460&templateId=IDEAIMP4 - * @apiParamExample {json} Request: - { - "role" : "HM", - "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", - "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", - "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" - } - * @apiParamExample {json} Response: - * { - "message": "Successfully fetched project details", + "message": "Successfully fetched projects", "status": 200, "result": { - "_id": "5f97d2f6bf3a3b1c0116c80a", - "status": "notStarted", + "userId": "01c04166-a65e-4e92-a87b-a9e4194e771d", "isDeleted": false, "categories": [ { - "_id": "5f102331665bee6a740714e8", - "name": "Teachers", - "externalId": "teachers" - }, - { - "name": "newCategory", - "externalId": "", - "_id": "" + "_id": "5f102331665bee6a740714eb", + "externalId": "community", + "name": "Community" } ], + "createdBy": "01c04166-a65e-4e92-a87b-a9e4194e771d", "tasks": [ { - "_id": "289d9558-b98f-41cf-81d3-92486f114a49", - "name": "Task 1", - "description": "Task 1 description", - "status": "notStarted", - "isACustomTask": false, - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", - "type": "single", + "_id": "61d6690d-82cb-4db2-8191-8dd945c5e742", "isDeleted": false, - "attachments": [ - { - "name": "download(2).jpeg", - "type": "image/jpeg", - "sourcePath": "projectId/userId/imageName" - } - ], - "remarks": "Tasks completed", - "assignee": "Aman", + "isDeletable": false, + "taskSequence": [], "children": [ { - "_id": "289d9558-b98f-41cf-81d3-92486f114a50", - "name": "Task 2", - "description": "Task 2 description", - "status": "notStarted", + "_id": "b5068cef-eefc-4f43-8a29-ab9c2268f451", + "isDeleted": false, + "isDeletable": false, + "taskSequence": [], "children": [], - "isACustomTask": false, - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", + "visibleIf": [ + { + "operator": "===", + "_id": "5f72f9998925ec7c60f79a91", + "value": "started" + } + ], + "deleted": false, "type": "single", + "projectTemplateId": "5f5b32cef16777642d51aaf0", + "name": "Sub task 1", + "externalId": "Sub-task-1", + "description": "Sub-Task-1-Description", + "updatedAt": "2020-09-29T09:08:41.681Z", + "createdAt": "2020-09-29T09:08:41.675Z", + "__v": 0, + "status": "notStarted" + }, + { + "_id": "988ef20f-267f-4bed-9a38-9d7dc6a320e9", "isDeleted": false, - "externalId": "task 2", - "isDeleteable": false, - "createdAt": "2020-10-28T05:58:24.907Z", - "updatedAt": "2020-10-28T05:58:24.907Z", - "isImportedFromLibrary": false + "isDeletable": false, + "taskSequence": [], + "children": [], + "visibleIf": [ + { + "operator": "===", + "_id": "5f72f9998925ec7c60f79a91", + "value": "started" + } + ], + "deleted": false, + "type": "single", + "projectTemplateId": "5f5b32cef16777642d51aaf0", + "name": "Sub task 2", + "externalId": "Sub-task-2", + "description": "Sub-Task-2-Description", + "updatedAt": "2020-09-29T09:08:41.693Z", + "createdAt": "2020-09-29T09:08:41.689Z", + "__v": 0, + "status": "notStarted" } ], - "externalId": "task 1", - "isDeleteable": false, - "createdAt": "2020-10-28T05:58:24.907Z", - "updatedAt": "2020-10-28T05:58:24.907Z", - "isImportedFromLibrary": false + "visibleIf": [], + "deleted": false, + "type": "multiple", + "projectTemplateId": "5f5b32cef16777642d51aaf0", + "name": "Task 1", + "externalId": "task-1", + "description": "Task-1 Description", + "updatedAt": "2020-09-29T09:08:41.691Z", + "createdAt": "2020-09-29T09:08:41.612Z", + "__v": 0, + "status": "notStarted" + }, + { + "_id": "289d9558-b98f-41cf-81d3-92486f114a49", + "isDeleted": false, + "isDeletable": false, + "taskSequence": [], + "children": [], + "visibleIf": [], + "deleted": false, + "type": "single", + "projectTemplateId": "5f5b32cef16777642d51aaf0", + "name": "Task 12", + "externalId": "Task-12", + "description": "Task-1 Description", + "updatedAt": "2020-09-29T09:08:41.667Z", + "createdAt": "2020-09-29T09:08:41.667Z", + "__v": 0, + "status": "notStarted" } ], - "resources": [], + "updatedBy": "01c04166-a65e-4e92-a87b-a9e4194e771d", + "_id": "5f731d68920a8c3e092e6e4c", "deleted": false, - "lastDownloadedAt": "2020-09-29T09:08:41.667Z", - "__v": 0, - "description": "Project 1 description" - } - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Project details - * @method - * @name details - * @param {Object} req - request data. - * @param {String} req.params._id - Project id. - * @returns {JSON} Create Self projects. - */ - - async details(req) { - return new Promise(async (resolve, reject) => { - try { - - let projectDetails = - await userProjectsHelper.detailsV2( - req.params._id ? req.params._id : "", - req.query.solutionId, - req.userDetails.userInformation.userId, - req.userDetails.userToken, - req.body, - req.headers["x-app-id"] ? - req.headers["x-app-id"] : - req.headers.appname ? req.headers.appname : "", - req.headers["x-app-ver"] ? - req.headers["x-app-ver"] : - req.headers.appversion ? req.headers.appversion : "", - req.query.templateId - ); - - projectDetails.result = projectDetails.data; - - return resolve(projectDetails); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** - * @api {post} /improvement-project/api/v1/userProjects/tasksStatus/:projectId - * User Project tasks status - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/tasksStatus/5f731631e8d7cd3b88ac0659 - * @apiParamExample {json} Request: - * { - * "taskIds" : [ - "2f2ef6dd-24e9-40ab-a681-3b3167fcd2c6", - "a18ae088-fa11-4ff4-899f-213abefb30f6" - ] - } - * @apiParamExample {json} Response: - { - "message": "Tasks status fetched successfully", - "status": 200, - "result": [ - { - "type": "assessment", - "status": "started", - "_id": "2f2ef6dd-24e9-40ab-a681-3b3167fcd2c6" - }, - { - "type": "observation", - "status": "started", - "_id": "a18ae088-fa11-4ff4-899f-213abefb30f6", - "submissionId": "5fbaa71d97ccef111cbb4ee0" - } - ] - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Tasks status - * @method - * @name tasksStatus - * @param {Object} req - request data. - * @param {String} req.params._id - Project id. - * @returns {JSON} status of tasks - */ - - async tasksStatus(req) { - return new Promise(async (resolve, reject) => { - try { - - let taskStatus = await userProjectsHelper.tasksStatus( - req.params._id, - req.body.taskIds - ); - - taskStatus.result = taskStatus.data; - - return resolve(taskStatus); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** - * @api {get} /improvement-project/api/v1/userProjects/solutionDetails/:projectId?taskId=:taskId - * User project solution details - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/solutionDetails/5fba54dc5bf46b25a926bee5?taskId=347400e7-8a62-4dad-bc24-af7c5bd70ad1 - * @apiParamExample {json} Response: - * { - "message" : "Solutions details fetched successfully", - "status": 200, - "result": { - "entityId": "5beaa888af0065f0e0a10515", - "programId": "5fba54dc2a1f7b172f066597", - "observationId": "5d1a002d2dfd8135bc8e1617", - "solutionId": "5d15b0d7463d3a6961f91749" - “solutionDetails”:{ - "_id" : "60b06e30343385596ef48c25", - "isReusable" : false, - "externalId" : "NEW-TEST-SOLUTION", - "name" : "NEW-TEST-SOLUTION", - "programId" : "600ab53cc7de076e6f993724", - "type" : "observation", - "subType" : "district", - "isRubricDriven" : true, - "criteriaLevelReport" : "", - "allowMultipleAssessemts" : false, - "scoringSystem": "" - } - - } - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Solutions details information. - * @method - * @name status - * @param {Object} req - request data. - * @param {String} req.params._id - Project id. - * @param {String} req.query.taskId - task id. - * @returns {JSON} Solutions details - */ - - async solutionDetails(req) { - return new Promise(async (resolve, reject) => { - try { - - let solutionDetails = await userProjectsHelper.solutionDetails( - req.userDetails.userToken, - req.params._id, - req.query.taskId - ); - - solutionDetails.result = solutionDetails.data; - - return resolve(solutionDetails); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - - /** - * @api {post} /improvement-project/api/v1/userProjects/bulkCreateByUserRoleAndEntity - * Bulk create user projects by entity and role. - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/bulkCreateByUserRoleAndEntity - * @apiParamExample {json} Request: - * { - * "templateId": "5f2449eb626a540f40817ef5", - * "entityId": "5f2449eb626a540f40817ef5", - * "role": "CRP", - * "programExternalId": "TAF-pgm", - * "solutionExternalId": "TAF-solution" - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * Bulk create user projects by entity and role. - * @method - * @name bulkCreateByUserRoleAndEntity - * @param {Object} req - request data. - * @param {String} req.body.entityId - entityId - * @param {String} req.body.role - role - * @returns {CSV} Assigned projects to user. - */ - - async bulkCreateByUserRoleAndEntity(req) { - return new Promise(async (resolve, reject) => { - try { - - let projects = await userProjectsHelper.bulkCreateByUserRoleAndEntity( - req.body, - req.userDetails.userToken - ); - - return resolve(projects); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** - * @api {post} /improvement-project/api/v1/userProjects/getProject?page=:page&limit=:limit&search=:search&filter=:assignedToMe - * List of User projects and auto targeted. - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/getProject - * @apiParamExample {json} Request: - * { - * "role" : "HM", - "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", - "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", - "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" - } - * @apiParamExample {json} Response: - { - "message": " Targeted projects fetched successfully", - "status": 200, - "result": { - "description": "Manage and track your school Improvement easily by creating tasks and planning timelines.", - "data": [ - { - "_id": "5fd6f3b6062df5269e6532f0", - "description": "h bucks ", - "programId": "5fd6f3b7ab86c4262564b83f", - "solutionId": "5fd6f3b7ab86c4262564b840", - "name": "gjk" - }, - { - "_id": "", - "externalId": "TAMIL-NADU-AUTO-TARGETING-IMPROVEMENT-PROJECT", - "programId": "5ffbf8909259097d48017bbf", - "programName": "Tamil nadu AUTO TARGETING program", - "description": "tamil nadu improvement project testing", - "name": "tamil nadu improvement project testing", - "solutionId": "5ffbf9629259097d48017bc0" - } - ], - "count": 2 - } - } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * List of user projects and targetted ones. - * @method - * @name getProject - * @param {Object} req - request data. - * @returns {JSON} List of user project with targetted ones. - */ - - async getProject(req) { - return new Promise(async (resolve, reject) => { - try { - - let projects = await userProjectsHelper.getProject( - req.body, - req.userDetails.userInformation.userId, - req.userDetails.userToken, - req.pageSize, - req.pageNo, - req.searchText, - req.query.filter - ); - - projects.result = projects.data; - - return resolve(projects); - - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - - /** - * @api {post} /improvement-project/api/v1/userProjects/add - * Add project. - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/add - * @apiParamExample {json} Request: - * { - "title": "Project 1", - "description": "Project 1 description", - "tasks": [ - { - "_id": "289d9558-b98f-41cf-81d3-92486f114a49", - "name": "Task 1", - "description": "Task 1 description", - "status": "notStarted/inProgress/completed", - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", - "type": "single/multiple", - “isDeleted” : false, - “remarks” : “Tasks completed”, - “assignee” : “Aman”, - "children": [ - { - "_id": "289d9558-b98f-41cf-81d3-92486f114a50", - "name": "Task 2", - "description": "Task 2 description", - "status": "notStarted/inProgress/completed", - "children": [], - "startDate": "2020-09-29T09:08:41.667Z", - "endDate": "2020-09-29T09:08:41.667Z", - "lastModifiedAt": "2020-09-29T09:08:41.667Z", - "type": "single/multiple”, - “isDeleted” : false - } - ] - } - ], - "programId": "", - "programName": "New Project Program", - "entityId" : “5beaa888af0065f0e0a10515”, - "categories": [ - { - "value": "5f102331665bee6a740714e8", - "label": "teacher" + "name": "Test-2", + "description": "improving school library", + "status": "notStarted", + "updatedAt": "2020-09-29T11:41:28.656Z", + "createdAt": "2020-09-11T08:18:22.077Z", + "__v": 0, + "solutionInformation": { + "externalId": "01c04166-a65e-4e92-a87b-a9e4194e771d-1601379673400" }, - { - "value": "", - "label": "other" - } - ], - "status": "notStarted/inProgress/completed", - “lastDownloadedAt” : "2020-09-29T09:08:41.667Z", - "payload": { - "_id": "289d9558-b98f-41cf-81d3-92486f114a51" - }, - "profileInformation" : { - "role" : "HM", - "state" : "236f5cff-c9af-4366-b0b6-253a1789766a", - "district" : "1dcbc362-ec4c-4559-9081-e0c2864c2931", - "school" : "c5726207-4f9f-4f45-91f1-3e9e8e84d824" + "programInformation": { + "externalId": "My Program-1601379673400", + "name": "My Program" + }, + "taskReport": {}, + "entityInformation": {}, + "rationale": "sample", + "primaryAudience": [ + "teachers", + "head master" + ] }} - * @apiParamExample {json} Response: - * { - * "message": "Project created successfully", - * "status": 200, - * "result" : { - * "programId" : "5fb669f223575a2f0cef3b33" - * "projectId" : "5f102331665bee6a740714e8" - * } - * } * @apiUse successBody * @apiUse errorBody */ /** - * Add projects. + * Import project from library. * @method - * @name add + * @name importFromLibrary * @param {Object} req - request data. - * @returns {JSON} Create Self projects. + * @param {String} req.params._id - project Template Id. + * @returns {JSON} import project from library. */ - async add(req) { + async importFromLibrary(req) { return new Promise(async (resolve, reject) => { try { - - let createdProject = await userProjectsHelper.add( + + const createdProject = await userProjectsHelper.importFromLibrary( + req.params._id, req.body, - req.userDetails.userInformation.userId, req.userDetails.userToken, - req.headers["x-app-id"] ? - req.headers["x-app-id"] : - req.headers.appname ? req.headers.appname : "", - req.headers["x-app-ver"] ? - req.headers["x-app-ver"] : - req.headers.appversion ? req.headers.appversion : "" + req.userDetails.userInformation.userId, + req.query.isATargetedSolution ? req.query.isATargetedSolution : "" ); - createdProject.result = createdProject.data; - - return resolve(createdProject); + return resolve({ + status: createdProject.status, + message: createdProject.message, + result: createdProject.data + }); } catch (error) { return reject({ @@ -1245,110 +957,54 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {get} /improvement-project/api/v1/userProjects/userAssigned?page=:page&limit=:limit&search=:search&filter=:assignedToMe - * List of user assigned project. + /** + * @api {post} /improvement-project/api/v1/userProjects/certificateCallback + * Project certificate callback * @apiVersion 1.0.0 * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/userAssigned?page=1&limit=10 + * @apiSampleRequest /improvement-project/api/v1/userProjects/certificateCallback + * @apiParamExample {json} Request + * { + "event": "sunbird-rc-create", + "timestamp": 1660145509358, + "data": { + "userId": "anonymous", + "entityType": "ProjectCertificate", + "osid": "ce2244a4-1a17-49a0-a3f9-c151161e70bl", + "transactionId": "1-3a4892d8-2221-4e96-9434-f4b37886126b", + "status": "SUCCESSFUL", + "message": "" + }, + "webhookUrl": "http://ml-project-service:3000/v1/userProjects/certificateCallback" + } * @apiParamExample {json} Response: - * { - "message": "User project fetched successfully", - "status": 200, - "result": { - "data": [ - { - "_id": "6049c282348d1b060c6454b7", - "solutionId": "6049c277f026c305dd471769", - "programId": "6049c275f026c305dd471768", - "name": "TEST TITLE", - "programName": "NEW", - "externalId": "01c04166-a65e-4e92-a87b-a9e4194e771d-1615446645973", - "type": "improvementProject" + /**{ + "message": "Successfully generated project certificate", + "status": 200, + "result": { + "_id": "63446059eeffea2b819f036e" } - ], - "count": 1 - }} - + } /** - * List of user assigned projects. - * @method - * @name userAssigned - * @param {Object} req - request data. - * @returns {JSON} List of user assigned projects. - */ - - async userAssigned(req) { - return new Promise(async (resolve, reject) => { - try { - - let projects = await userProjectsHelper.userAssigned( - req.userDetails.userInformation.userId, - req.pageSize, - req.pageNo, - req.searchText, - req.query.filter - ); - - projects.result = projects.data; - - return resolve(projects); - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error - }); - } - }) - } - /** - * @api {get} /improvement-project/api/v1/userProjects/share/:projectId?tasks=:taskId1,:taskId2 - * Share project and task pdf report. - * @apiVersion 1.0.0 - * @apiGroup User Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/share/6065ced7e9259b7f0b1f5d66?tasks=4d074de7-7059-4d99-9da9-452b0d32e081 - * @apiParamExample {json} Response: - * { - * "message": "Report generated succesfully", - * "status": 200, - * "result" : { - * "data" : { - * "downloadUrl": "http://localhost:4700/dhiti/api/v1/observations/pdfReportsUrl?id=dG1wLzVhNzZjMTY5LTA5YjAtNGU3Zi04ZmNhLTg0NDc5ZmI2YTNiNC0tODUyOA==" - * } - * } - * } - * @apiUse successBody - * @apiUse errorBody + /** + * Project certificate callback. + * @method + * @name certificateCallback + * @param {Object} req - request data. + * @returns {JSON} certificate details. */ - /* - * Share project and task pdf report. - * @method - * @name share - * @param {Object} req - request data. - * @param {String} req.params._id - projectId - * @returns {JSON} Downloadable pdf url. - */ - - async share(req) { - return new Promise(async (resolve, reject) => { + async certificateCallback(req) { + return new Promise(async (resolve, reject) => { try { - - let taskIds = req.query.tasks ? req.query.tasks.split(",") : []; - - let report = await userProjectsHelper.share( - req.params._id, - taskIds, - req.userDetails.userToken - ); - - return resolve({ - message: report.message, - result: report.data - }); - + //console request body to check if callback is coming or not and to check any structural change is there or not + console.log("-------------callback request body------------",JSON.stringify(req.body)) + let certificateDetails = await userProjectsHelper.certificateCallback( req.body.data.transactionId, req.body.data.osid ); + return resolve({ + message: certificateDetails.message, + result: certificateDetails.data + }); } catch (error) { return reject({ status: error.status || HTTP_STATUS_CODE.internal_server_error.status, @@ -1359,59 +1015,135 @@ module.exports = class UserProjects extends Abstract { }) } - /** - * @api {get} /improvement-project/api/v1/userProjects/importedProjects/:programId + /** + * @api {get} /improvement-project/api/v1/userProjects/certificates + * List of user project with certificate * @apiVersion 1.0.0 - * @apiGroup Lists of User Imported Projects - * @apiSampleRequest /improvement-project/api/v1/userProjects/importedProjects/60545d541fc23d6d2d44c0c9 + * @apiGroup User Projects + * @apiSampleRequest /improvement-project/api/v1/userProjects/certificates * @apiParamExample {json} Response: - { - "message": "List of imported projects fetched", - "status": 200, - "result": [ - { - "_id": "60793b80bd49095a19ddeae1", - "description": "", - "title": "Project with learning resources", - "projectTemplateId": "60546a4cb807066d9cddba21", - "programInformation": { - "_id": "60545d541fc23d6d2d44c0c9", - "externalId": "PGM-3542-3.8.0_testing_program-2", - "description": "3.8.0 testing program - 2", - "name": "3.8.0 testing program - 2" - }, - "solutionInformation": { - "_id": "605468721fc23d6d2d44c0cb", - "externalId": "IMP-3542_solution2", - "description": "", - "name": "Project with learning resources" + * { + "message": "User project fetched successfully", + "status": 200, + "result": { + "data": [{ + "_id": "60793b80bd49095a19ddeae1", + "title": "Project with learning resources", + "certificate": { + "osid": "1-21c8ecab-7b8d-40f1-9961-cae7fcb6a5f9", + "status": "active", + "templateId": "600acc42c7de076e6f995147", + "templateUrl": "certificateTemplates/6343bd978f9d8980b7841e85/ba9aa220-ff1b-4717-b6ea-ace55f04fc16_2022-9-10-1665383945769.svg", + "issuedOn": "2020-12-03 13:22:31.988Z" + }, + "status": "submitted" + }, + { + "_id": "6011136a2d25b926974d9ec9", + "title": "Keep Our Schools Alive! (Petition)", + "status": "submitted", + "certificate": { + "eligible": false, + "templateId": "600acc42c7de076e6f995147", + "message": "Not submitted the project the project within program end date" + } + } + ], + "count": 2, + "certificateCount": 1 } } - ]} - * @apiUse successBody - * @apiUse errorBody - */ + /** - /* - * List of user imported projects - * @method - * @name importedProjects - * @returns {JSON} List of imported projects. - */ + /** + * List user project details with certificate + * @method + * @name certificates + * @returns {JSON} User project detaills with certificate + */ - async importedProjects(req) { + async certificates(req) { return new Promise(async (resolve, reject) => { - try { + try { + // fetch projects data of user, whish has certificate on completion + let projectDetails = await userProjectsHelper.certificates( req.userDetails.userInformation.userId ); + return resolve({ + message: projectDetails.message, + result: projectDetails.data + }); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } - let importedProjects = await userProjectsHelper.importedProjects( - req.userDetails.userInformation.userId, - req.params._id ? req.params._id : "" - ); + /** + * @api {post} /improvement-project/api/v1/userProjects/certificateReIssue + * ReIssue project certificate (admin api) + * @apiVersion 1.0.0 + * @apiGroup User Projects + * @apiSampleRequest /improvement-project/api/v1/userProjects/certificateReIssue + * @apiParamExample {json} Response: + /**{ + "message": "Successfully generated project certificate", + "status": 200, + "result": { + "_id": "63446059eeffea2b819f036e" + } + } + /** + * ReIssue project certificate + * @method + * @name certificateReIssue + * @returns {JSON} Reissued details + */ - importedProjects["result"] = importedProjects["data"]; + async certificateReIssue(req) { + return new Promise(async (resolve, reject) => { + try { + // ReIssue certificate of given project : projectId is passed as param + // This console has to be removed- adding to check the Issuer kid value in case rancher doesn't display console while deployment + console.log("+++++CERTIFICATE_ISSUER_KID+++++ : ",CERTIFICATE_ISSUER_KID) + let projectDetails = await userProjectsHelper.certificateReIssue( + req.params._id, + ); + return resolve({ + message: projectDetails.message, + result: projectDetails.data + }); + + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error + }); + } + }) + } + async overview(req) { + return new Promise(async (resolve, reject) => { + try { - return resolve(importedProjects); + let stats = req.query.stats; + if (stats !== undefined) { + stats = UTILS.convertStringToBoolean(stats); + } + let listOfCreatedProjects = await userProjectsHelper.userProjectOverview({ + userId:req.userDetails.userInformation.userId + },['title','description','_id','userId','isAPrivateProgram','createdBy','status','createdAt','deleted'],stats) + + return resolve({ + message: 'success', + result: listOfCreatedProjects + }); + } catch (error) { return reject({ status: error.status || HTTP_STATUS_CODE.internal_server_error.status, @@ -1419,6 +1151,8 @@ module.exports = class UserProjects extends Abstract { errorObject: error }); } - }) + }) } + + }; \ No newline at end of file diff --git a/databaseQueries/certificateTemplates.js b/databaseQueries/certificateTemplates.js new file mode 100644 index 00000000..fa2a2ddc --- /dev/null +++ b/databaseQueries/certificateTemplates.js @@ -0,0 +1,60 @@ +/** + * name : certificateTemplates.js + * author : Vishnu + * created-date : 03-Oct-2022 + * Description : Certificate template helper for DB interactions. + */ + +// Dependencies + +/** + * CertificateTemplates + * @class +*/ + +module.exports= class CertificateTemplates{ + /** + * certificate template details. + * @method + * @name certificateTemplateDocument + * @param {Array} [filterData = "all"] - certificate template filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} certificateTemplates details. + */ + + static certificateTemplateDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + let certificateTemplateDoc = + await database.models.certificateTemplates.find( + queryObject, + projection + ).lean(); + + return resolve(certificateTemplateDoc); + + } catch (error) { + return reject(error); + } + }); + } +} \ No newline at end of file diff --git a/databaseQueries/programUsers.js b/databaseQueries/programUsers.js new file mode 100644 index 00000000..479b8b8e --- /dev/null +++ b/databaseQueries/programUsers.js @@ -0,0 +1,52 @@ +/** + * name : programUsers.js + * author : Ankit Shahu + * created-date : 07-04-2023 + * Description : program users helper for DB interactions. + */ +module.exports = class programUsers { + + /** + * program users details. + * @method + * @name programUsersDocument + * @param {Array} [filterData = "all"] - program users filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} program users details. + */ + + static programUsersDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + + let programJoinedData = await database.models.programUsers + .find(queryObject, projection) + .lean(); + return resolve(programJoinedData); + } catch (error) { + return reject(error); + } + }); + } + +}; diff --git a/databaseQueries/programs.js b/databaseQueries/programs.js new file mode 100644 index 00000000..d160b702 --- /dev/null +++ b/databaseQueries/programs.js @@ -0,0 +1,60 @@ +/** + * name : programs.js + * author : Vishnu + * created-date : 09-Mar-2022 + * Description : program helper for DB interactions. + */ + +// Dependencies + +/** + * Programs + * @class +*/ + + + +module.exports= class Programs{ + /** + * programs details. + * @method + * @name programsDocument + * @param {Array} [filterData = "all"] - programs filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} program details. + */ + + static programsDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + let programsDoc = + await database.models.programs.find( + queryObject, + projection + ).lean(); + return resolve(programsDoc); + } catch (error) { + return reject(error); + } + }); + } +} \ No newline at end of file diff --git a/databaseQueries/projectCategories.js b/databaseQueries/projectCategories.js new file mode 100644 index 00000000..7d4a9354 --- /dev/null +++ b/databaseQueries/projectCategories.js @@ -0,0 +1,98 @@ +/** + * name : projectCategories.js + * author : Priyanka + * created-date : 01-Sep-2021 + * Description : Project categories helper for DB interactions. + */ + +// Dependencies + +/** + * ProjectCategories + * @class +*/ + +module.exports = class ProjectCategories { + + /** + * Library project categories documents. + * @method + * @name categoryDocuments + * @param {Object} [findQuery = "all"] - filtered data. + * @param {Array} [fields = "all"] - projected data. + * @param {Array} [skipFields = "none"] - fields to skip. + * @returns {Array} - Library project categories data. + */ + + static categoryDocuments( + findQuery = "all", + fields = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + + try { + + let queryObject = {}; + + if (findQuery != "all") { + queryObject = findQuery; + } + + let projection = {}; + + if (fields != "all") { + fields.forEach(element => { + projection[element] = 1; + }); + } + + if (skipFields != "none") { + skipFields.forEach(element => { + projection[element] = 0; + }); + } + + let projectCategoriesData = + await database.models.projectCategories.find( + queryObject, + projection + ).lean(); + + return resolve(projectCategoriesData); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * update Many project categories documents. + * @method + * @name updateMany + * @param {Object} [filterQuery] - filtered Query. + * @param {Object} [updateData] - update data. + * @returns {Array} - Library project categories data. + */ + + static updateMany(filterQuery, updateData) { + return new Promise(async (resolve, reject) => { + + try { + + let updatedCategories = + await database.models.projectCategories.updateMany( + filterQuery, + updateData + ); + + return resolve(updatedCategories); + + } catch (error) { + return reject(error); + } + }); + } + +}; diff --git a/databaseQueries/projectTemplateTask.js b/databaseQueries/projectTemplateTask.js new file mode 100644 index 00000000..ea57ba35 --- /dev/null +++ b/databaseQueries/projectTemplateTask.js @@ -0,0 +1,156 @@ +/** + * name : projectTemplateTask.js + * author : Priyanka + * created-date : 01-Sep-2021 + * Description : Project Templates helper for DB interactions. + */ + +// Dependencies + +/** + * ProjectTemplateTask + * @class +*/ + +module.exports = class ProjectTemplateTask { + + /** + * Lists of template tasks. + * @method + * @name taskDocuments + * @param {Array} [filterData = "all"] - template filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} Lists of template. + */ + + static taskDocuments( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + + let templateTasks = + await database.models.projectTemplateTasks.find( + queryObject, + projection + ).lean(); + + return resolve(templateTasks); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Create project template task. + * @method + * @name createTemplateTask + * @param {Object} [templateData] - template task Data. + * @returns {Object} - Project template task data. + */ + + static createTemplateTask(templateData) { + return new Promise(async (resolve, reject) => { + + try { + + let templateTask = await database.models.projectTemplateTasks.create(templateData); + return resolve(templateTask); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update projectTemplateTask document. + * @method + * @name updateTaskDocument + * @param {Object} query - query to find document + * @param {Object} updateObject - fields to update + * @returns {String} - message. + */ + + static updateTaskDocument(query= {}, updateObject= {}) { + return new Promise(async (resolve, reject) => { + try { + + if (Object.keys(query).length == 0) { + throw new Error(CONSTANTS.apiResponses.UPDATE_QUERY_REQUIRED) + } + + if (Object.keys(updateObject).length == 0) { + throw new Error (CONSTANTS.apiResponses.UPDATE_OBJECT_REQUIRED) + } + + let updateResponse = await database.models.projectTemplateTasks.updateOne + ( + query, + updateObject + ) + + if (updateResponse.nModified == 0) { + throw new Error(CONSTANTS.apiResponses.FAILED_TO_UPDATE) + } + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, + data: true + }); + + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: false + }); + } + }); + } + + /** + * Update project templates task documents. + * @method + * @name findOneAndUpdate + * @param {Object} [filterQuery] - filtered Query. + * @param {Object} [updateData] - update data. + * @returns {Object} - Project templates task data. + */ + + static findOneAndUpdate(findQuery,UpdateObject, returnData = {}) { + return new Promise(async (resolve, reject) => { + + try { + + let templateTask = await database.models.projectTemplateTasks.findOneAndUpdate(findQuery,UpdateObject, returnData); + return resolve(templateTask); + + } catch (error) { + return reject(error); + } + }); + } + +}; diff --git a/databaseQueries/projectTemplates.js b/databaseQueries/projectTemplates.js new file mode 100644 index 00000000..cbfdcabf --- /dev/null +++ b/databaseQueries/projectTemplates.js @@ -0,0 +1,176 @@ +/** + * name : projectTemplates.js + * author : Priyanka + * created-date : 01-Sep-2021 + * Description : Project Templates helper for DB interactions. + */ + +// Dependencies + +/** + * ProjectTemplates + * @class +*/ + +module.exports = class ProjectTemplates { + + /** + * Project templates documents. + * @method + * @name getAggregate + * @param {Object} [aggregateData] - aggregate Data. + * @returns {Array} - Project templates data. + */ + + static getAggregate(aggregateData) { + return new Promise(async (resolve, reject) => { + + try { + + let projectTemplatesData = await database.models.projectTemplates.aggregate(aggregateData); + return resolve(projectTemplatesData); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Lists of template. + * @method + * @name templateDocument + * @param {Array} [filterData = "all"] - template filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} Lists of template. + */ + + static templateDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + let templates = + await database.models.projectTemplates.find( + queryObject, + projection + ).lean(); + + return resolve(templates); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Create project templates documents. + * @method + * @name createTemplate + * @param {Object} [templateData] - template Data. + * @returns {Array} - Project templates data. + */ + + static createTemplate(templateData) { + return new Promise(async (resolve, reject) => { + + try { + + let projectTemplate = await database.models.projectTemplates.create(templateData); + return resolve(projectTemplate); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update project templates documents. + * @method + * @name findOneAndUpdate + * @param {Object} [filterQuery] - filtered Query. + * @param {Object} [updateData] - update data. + * @returns {Array} - Project templates data. + */ + + static findOneAndUpdate(findQuery,UpdateObject, returnData = {}) { + return new Promise(async (resolve, reject) => { + + try { + + let projectTemplate = await database.models.projectTemplates.findOneAndUpdate(findQuery,UpdateObject, returnData); + return resolve(projectTemplate); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update projectTemplates document. + * @method + * @name updateProjectTemplateDocument + * @param {Object} query - query to find document + * @param {Object} updateObject - fields to update + * @returns {String} - message. + */ + + static updateProjectTemplateDocument(query= {}, updateObject= {}) { + return new Promise(async (resolve, reject) => { + try { + + if (Object.keys(query).length == 0) { + throw new Error(CONSTANTS.apiResponses.UPDATE_QUERY_REQUIRED) + } + + if (Object.keys(updateObject).length == 0) { + throw new Error (CONSTANTS.apiResponses.UPDATE_OBJECT_REQUIRED) + } + + let updateResponse = await database.models.projectTemplates.updateOne + ( + query, + updateObject + ) + + if (updateResponse.nModified == 0) { + throw new Error(CONSTANTS.apiResponses.FAILED_TO_UPDATE) + } + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, + data: true + }); + + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: false + }); + } + }); + } + +}; diff --git a/databaseQueries/projects.js b/databaseQueries/projects.js new file mode 100644 index 00000000..4c871d76 --- /dev/null +++ b/databaseQueries/projects.js @@ -0,0 +1,184 @@ +/** + * name : projects.js + * author : Priyanka + * created-date : 01-Sep-2021 + * Description : Project categories helper for DB interactions. + */ + +// Dependencies + +/** + * Projects + * @class +*/ + +module.exports = class Projects { + + /** + * Lists of projects document. + * @method + * @name projectDocument + * @param {Array} [filterData = "all"] - project filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} Lists of projects. + */ + + static projectDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + + let projects = + await database.models.projects.find( + queryObject, + projection + ).lean(); + return resolve(projects); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Get Aggregate of Project documents. + * @method + * @name getAggregate + * @param {Object} [aggregateData] - aggregate Data. + * @returns {Array} - Project data. + */ + + static getAggregate(aggregateData) { + return new Promise(async (resolve, reject) => { + + try { + + let projectDocuments = await database.models.projects.aggregate(aggregateData); + return resolve(projectDocuments); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update project documents. + * @method + * @name findOneAndUpdate + * @param {Object} [filterQuery] - filtered Query. + * @param {Object} [updateData] - update data. + * @returns {Object} - Project data. + */ + + static findOneAndUpdate(findQuery,UpdateObject, returnData = {}) { + return new Promise(async (resolve, reject) => { + + try { + + let projectDocument = await database.models.projects.findOneAndUpdate(findQuery,UpdateObject, returnData); + return resolve(projectDocument); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Create project documents. + * @method + * @name createProject + * @param {Object} [projectData] - project Data. + * @returns {Array} - Project data. + */ + + static createProject(projectData) { + return new Promise(async (resolve, reject) => { + + try { + + let projectDocument = await database.models.projects.create(projectData); + return resolve(projectDocument); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update projects + * @method + * @name updateMany + * @param {Object} query + * @param {Object} update + * @param {Object} options + * @returns {JSON} - update projects. + */ + + static updateMany(query, update, options = {}) { + return new Promise(async (resolve, reject) => { + try { + + let updatedProjectCount = await database.models.projects.updateMany( + query, + update, + options + ); + if( updatedProjectCount) { + return resolve(updatedProjectCount); + } + } catch (error) { + return reject(error); + } + }) + } + + /** + * Get Observation document based on filtered data provided. + * @method + * @name countDocuments + * @param {Object} [findQuery = "all"] -filter data. + * @returns {Array} - Count of projects. + */ + + static countDocuments(findQuery = "all",) { + return new Promise(async (resolve, reject) => { + try { + let queryObject = {}; + if (findQuery != "all") { + queryObject = _.merge(queryObject, findQuery); + } + let countDocuments = await database.models.projects + .countDocuments(queryObject) + .lean(); + + return resolve(countDocuments); + } catch (error) { + return reject(error); + } + }); + } + +}; diff --git a/databaseQueries/solutions.js b/databaseQueries/solutions.js new file mode 100644 index 00000000..d8f87302 --- /dev/null +++ b/databaseQueries/solutions.js @@ -0,0 +1,109 @@ +/** + * name : solutions.js + * author : Vishnu + * created-date : 26-Jan-2022 + * Description : solutions helper for DB interactions. + */ + +// Dependencies + +/** + * Solutions + * @class +*/ + + + +module.exports= class Solutions{ + /** + * Solution details. + * @method + * @name solutionsDocument + * @param {Array} [filterData = "all"] - solutions filter query. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} solutions details. + */ + + static solutionsDocument( + filterData = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + let queryObject = (filterData != "all") ? filterData : {}; + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }); + } + let solutionsDoc = + await database.models.solutions.find( + queryObject, + projection + ).lean(); + + return resolve(solutionsDoc); + + } catch (error) { + return reject(error); + } + }); + } + + /** + * Update solution document. + * @method + * @name updateSolutionDocument + * @param {Object} query - query to find document + * @param {Object} updateObject - fields to update + * @returns {String} - message. + */ + + static updateSolutionDocument(query= {}, updateObject= {}) { + return new Promise(async (resolve, reject) => { + try { + + if (Object.keys(query).length == 0) { + throw new Error(messageConstants.apiResponses.UPDATE_QUERY_REQUIRED) + } + + if (Object.keys(updateObject).length == 0) { + throw new Error (messageConstants.apiResponses.UPDATE_OBJECT_REQUIRED) + } + + let updateResponse = await database.models.solutions.updateOne + ( + query, + updateObject + ) + + if (updateResponse.nModified == 0) { + throw new Error(CONSTANTS.apiResponses.FAILED_TO_UPDATE) + } + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, + data: true + }); + + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: false + }); + } + }); + } +} \ No newline at end of file diff --git a/envVariables.js b/envVariables.js index a1600dab..8cc63ba8 100644 --- a/envVariables.js +++ b/envVariables.js @@ -5,102 +5,195 @@ * Description : Required Environment variables . */ -const Log = require("log"); -let log = new Log("debug"); -let table = require("cli-table"); - -let tableData = new table(); - -let enviromentVariables = { - "APPLICATION_PORT" : { - "message" : "Please specify the value for e.g. 4201", - "optional" : false - }, - "APPLICATION_ENV" : { - "message" : "Please specify the value for e.g. local/development/qa/production", - "optional" : false - }, - "MONGODB_URL" : { - "message" : "Required mongodb url", - "optional" : false - }, - "INTERNAL_ACCESS_TOKEN" : { - "message" : "Required internal access token", - "optional" : false + const Log = require("log"); + let log = new Log("debug"); + let table = require("cli-table"); + const certificateService = require(GENERICS_FILES_PATH + "/services/certificate"); + + let tableData = new table(); + + let enviromentVariables = { + "APPLICATION_PORT" : { + "message" : "Please specify the value for e.g. 4201", + "optional" : false + }, + "APPLICATION_ENV" : { + "message" : "Please specify the value for e.g. local/development/qa/production", + "optional" : false + }, + "MONGODB_URL" : { + "message" : "Required mongodb url", + "optional" : false + }, + "INTERNAL_ACCESS_TOKEN" : { + "message" : "Required internal access token", + "optional" : false + }, + "KAFKA_COMMUNICATIONS_ON_OFF" : { + "message" : "Enable/Disable kafka communications", + "optional" : false + }, + "KAFKA_URL" : { + "message" : "Required", + "optional" : false + }, + "USER_SERVICE_URL" : { + "message" : "Required user service base url", + "optional" : false + }, + "SERVICE_NAME" : { + "message" : "current service name", + "optional" : true, + "default" : "ml-projects-service" + }, + "CERTIFICATE_SERVICE_URL" : { + "message" : "certificate service base url", + "optional" : true, + "default" : "http://registry-service:8081", + "requiredIf" : { + "key": "PROJECT_CERTIFICATE_ON_OFF", + "operator" : "EQUALS", + "value" : "ON" + } + }, + "PROJECT_CERTIFICATE_ON_OFF" : { + "message" : "Enable/Disable project certification", + "optional" : false, + "default" : "ON" + }, + "USER_DELETE_ON_OFF": { + message: "Enable/Disable user delete flow", + optional: false, + default : "ON" }, - "KAFKA_COMMUNICATIONS_ON_OFF" : { - "message" : "Enable/Disable kafka communications", - "optional" : false - }, - "KAFKA_URL" : { - "message" : "Required", - "optional" : false - } -} - -let success = true; - -module.exports = function() { - Object.keys(enviromentVariables).forEach(eachEnvironmentVariable=>{ - - let tableObj = { - [eachEnvironmentVariable] : "" - }; - - if( - enviromentVariables[eachEnvironmentVariable].requiredIf - && process.env[enviromentVariables[eachEnvironmentVariable].requiredIf.key] - && process.env[enviromentVariables[eachEnvironmentVariable].requiredIf.key] === enviromentVariables[eachEnvironmentVariable].requiredIf.value - ) { - tableObj[eachEnvironmentVariable].optional = false; + "USER_DELETE_TOPIC": { + message: "Required user delete kafka consumer topic name", + optional: true, + requiredIf : { + key: "USER_DELETE_ON_OFF", + operator: "EQUALS", + value: "ON" } - - if( - !(process.env[eachEnvironmentVariable]) && - !(enviromentVariables[eachEnvironmentVariable].optional) - ) { - - success = false; - - if( - enviromentVariables[eachEnvironmentVariable].default && - enviromentVariables[eachEnvironmentVariable].default != "" - ) { - process.env[eachEnvironmentVariable] = - enviromentVariables[eachEnvironmentVariable].default; - } - - if( - enviromentVariables[eachEnvironmentVariable] && - enviromentVariables[eachEnvironmentVariable].message !== "" - ) { - tableObj[eachEnvironmentVariable] = - enviromentVariables[eachEnvironmentVariable].message; - } else { - tableObj[eachEnvironmentVariable] = "required"; - } - - } else { - - tableObj[eachEnvironmentVariable] = "Passed"; - - if( - enviromentVariables[eachEnvironmentVariable].possibleValues && - !enviromentVariables[eachEnvironmentVariable].possibleValues.includes(process.env[eachEnvironmentVariable]) - ) { - tableObj[eachEnvironmentVariable] = ` Valid values - ${enviromentVariables[eachEnvironmentVariable].possibleValues.join(", ")}`; - } - + }, + "ID": { + message: "Required Service ID", + optional: false, + }, + "TELEMETRY_ON_OFF":{ + message: "Required telemetry on/off status", + optional: false, + default: "ON" + }, + "TELEMETRY_TOPIC": { + message: "Required telemetry topic", + optional: true, + requiredIf : { + key: "TELEMETRY_ON_OFF", + operator: "EQUALS", + value: "ON" } - - tableData.push(tableObj); - }) - - log.info(tableData.toString()); - - return { - success : success - } -} - - + }, + } + + let success = true; + + module.exports = function() { + Object.keys(enviromentVariables).forEach(eachEnvironmentVariable=>{ + + let tableObj = { + [eachEnvironmentVariable] : "PASSED" + }; + + let keyCheckPass = true; + let validRequiredIfOperators = ["EQUALS","NOT_EQUALS"] + + if(enviromentVariables[eachEnvironmentVariable].optional === true + && enviromentVariables[eachEnvironmentVariable].requiredIf + && enviromentVariables[eachEnvironmentVariable].requiredIf.key + && enviromentVariables[eachEnvironmentVariable].requiredIf.key != "" + && enviromentVariables[eachEnvironmentVariable].requiredIf.operator + && validRequiredIfOperators.includes(enviromentVariables[eachEnvironmentVariable].requiredIf.operator) + && enviromentVariables[eachEnvironmentVariable].requiredIf.value + && enviromentVariables[eachEnvironmentVariable].requiredIf.value != "") { + switch (enviromentVariables[eachEnvironmentVariable].requiredIf.operator) { + case "EQUALS": + if(process.env[enviromentVariables[eachEnvironmentVariable].requiredIf.key] === enviromentVariables[eachEnvironmentVariable].requiredIf.value) { + enviromentVariables[eachEnvironmentVariable].optional = false; + } + break; + case "NOT_EQUALS": + if(process.env[enviromentVariables[eachEnvironmentVariable].requiredIf.key] != enviromentVariables[eachEnvironmentVariable].requiredIf.value) { + enviromentVariables[eachEnvironmentVariable].optional = false; + } + break; + default: + break; + } + } + + if(enviromentVariables[eachEnvironmentVariable].optional === false) { + if(!(process.env[eachEnvironmentVariable]) + || process.env[eachEnvironmentVariable] == "") { + success = false; + keyCheckPass = false; + } else if (enviromentVariables[eachEnvironmentVariable].possibleValues + && Array.isArray(enviromentVariables[eachEnvironmentVariable].possibleValues) + && enviromentVariables[eachEnvironmentVariable].possibleValues.length > 0) { + if(!enviromentVariables[eachEnvironmentVariable].possibleValues.includes(process.env[eachEnvironmentVariable])) { + success = false; + keyCheckPass = false; + enviromentVariables[eachEnvironmentVariable].message += ` Valid values - ${enviromentVariables[eachEnvironmentVariable].possibleValues.join(", ")}` + } + } + } + + if((!(process.env[eachEnvironmentVariable]) + || process.env[eachEnvironmentVariable] == "") + && enviromentVariables[eachEnvironmentVariable].default + && enviromentVariables[eachEnvironmentVariable].default != "") { + process.env[eachEnvironmentVariable] = enviromentVariables[eachEnvironmentVariable].default; + success = true; + keyCheckPass = true; + } + + if(!keyCheckPass) { + if(enviromentVariables[eachEnvironmentVariable].message !== "") { + tableObj[eachEnvironmentVariable] = + enviromentVariables[eachEnvironmentVariable].message; + } else { + tableObj[eachEnvironmentVariable] = `FAILED - ${eachEnvironmentVariable} is required`; + } + } + tableData.push(tableObj); + }) + + log.info(tableData.toString()); + getKid(); + return { + success : success + } + } + + async function getKid(){ + if ( process.env.PROJECT_CERTIFICATE_ON_OFF === "ON" ) { + // get certificate issuer kid from sunbird-RC + let kidData = await certificateService.getCertificateIssuerKid(); + if( !kidData.success ) { + console.log("failed to get kid value from registry service : ",kidData) + if( process.env.CERTIFICATE_ISSUER_KID && process.env.CERTIFICATE_ISSUER_KID != "" ) { + global.CERTIFICATE_ISSUER_KID = process.env.CERTIFICATE_ISSUER_KID; + } + // console.log("Server stoped . Failed to set certificate issuer Kid value") + // process.exit(); + } else { + console.log("Kid data fetched successfully : ",kidData.data) + global.CERTIFICATE_ISSUER_KID = kidData.data + } + console.log(JSON.stringify(kidData)) + // global.CERTIFICATE_ISSUER_KID = kidData.data + } + }; + + + + \ No newline at end of file diff --git a/generics/abstract.js b/generics/abstract.js index 5a53eb5d..7c1d9453 100644 --- a/generics/abstract.js +++ b/generics/abstract.js @@ -14,6 +14,10 @@ let Abstract = class Abstract { constructor(schema) { database.createModel(schemas[schema]); + if ( schemas[schema].compoundIndex && schemas[schema].compoundIndex.length > 0 ) { + database.runCompoundIndex(schemas[schema].name,schemas[schema].compoundIndex); + } + } }; diff --git a/generics/constants/api-responses.js b/generics/constants/api-responses.js index 0bc32caf..84d0c7ff 100644 --- a/generics/constants/api-responses.js +++ b/generics/constants/api-responses.js @@ -115,5 +115,27 @@ module.exports = { "COULD_NOT_GENERATE_PDF_REPORT": "Could not generate pdf report", "PROJECT_TEMPLATE_DETAILS_FETCHED": "Successfully fetched project template details", "PROJECT_TEMPLATE_EXISTS_IN_SOLUTION" : "Project templates already exists in solution", - "IMPORTED_PROJECTS_FETCHED" : "List of imported projects fetched" + "IMPORTED_PROJECTS_FETCHED" : "List of imported projects fetched", + "MIN_NO_OF_SUBMISSION_REQUIRED_MIS_MATCHED" : "minNoOfSubmissionsRequired should be 1 because allowMultipleAssessemts is false", + "TASKS_MARKED_AS_ISDELETABLE_FALSE": "Successfully updated the tasks", + "TEMPLATE_TASK_NOT_UPDATED": "Could not updated template task", + "PROJECT_TEMPLATE_UPDATED" : "Successfully updated project template", + "PROJECT_TEMPLATE_TASK_UPDATED" : "Successfully updated project template task", + "TEMPLATE_ID_OR_LINK_REQUIRED" : "TemplateId or Link either one is required", + "TEMPLATE_ID_NOT_FOUND_IN_SOLUTION" : "Could not found templateId in solution", + "FAILED_TO_SYNC_PROJECT_ALREADY_SUBMITTED" : "Failed to sync, Project is already Submitted", + "SOLUTION_ID_AND_USERPROFILE_REQUIRED": "Required solution Id and userProfile", + "PROJECT_WITH_CERTIFICATE_NOT_FOUND": "No certification project found for user", + "PROJECT_CERTIFICATE_GENERATED" : "Successfully generated project certificate", + "TRANSACTION_ID_AND_OSID_REQUIRED" : "Required transactionId and osid", + "PROJECT_CERTIFICATE_GENERATED_ONCE" : "Certificate generated once", + "DOWNLOADABLE_URL_NOT_FOUND" : "Failed to generate downloadable URL", + "CERTIFICATE_TEMPLATE_NOT_FOUND" : "Certificate template details not found", + "CERTIFICATE_GENERATION_FAILED" : "Certificate generation failed", + "NOT_ELIGIBLE_FOR_CERTIFICATE" : "Project is not eligible for certificate", + "ISSUER_KID_NOT_FOUND" : "Failed to fetch certificate issuer kid", + "PROJECT_SUBMITTED_FOR_REISSUE" : "Submitted for project certificate reIssue", + "PROGRAM_JOIN_FAILED" : "Failed to join program", + "FAILED_TO_START_RESOURCE": "There was an error in starting/joining. Please try again after some time.", + "ENTITY_FETCHED": "Entities fetched successfull", }; diff --git a/generics/constants/common.js b/generics/constants/common.js index 83a4c91c..93dfb8c9 100644 --- a/generics/constants/common.js +++ b/generics/constants/common.js @@ -5,36 +5,62 @@ * Description : All common messages. */ -module.exports = { - "ACTIVE_STATUS" : "active", - "PUBLISHED" : "published", - "SUCCESS" : "Success", - "ASSESSMENT" : "assessment", - "CONTENT" : "content", - "IMPROVEMENT_PROJECT" : "improvementProject", - "MULTIPLE" : "multiple", - "OTHERS" : "Others", - "SIMPLE_TASK_TYPE" : "simple", - "NOT_STARTED" : "not started", - "RECENTLY_ADDED_PROJECT" : "currentlyAdded", - "IMPORTANT_PROJECT" : "importantProject", - "COMPLETED_STATUS" : "completed", - "INPROGRESS_STATUS" : "inProgress", - "NOT_STARTED_STATUS" : "notStarted", - "OBSERVATION" : "observation", - "PUBLISHED_STATUS" : "published", - "LEAD_ASSESSOR" : "LEAD_ASSESSOR", - "PROJECT_DESCRIPTION" : "Manage and track your school improvement easily, by creating tasks and planning project timelines.", - "RESOURCE_TYPE" : "Improvement Project Solution", - "ENGLISH_LANGUAGE" : "English", - "KEYWORDS" : "Improvement Project", - "ASSIGN_TO_ME" : "assignedToMe", - "CREATED_BY_ME" : "createdByMe", - "DEFAULT_PAGE_NO" : 1, - "DEFAULT_PAGE_SIZE" : 100, - "OBSERVATION_REFERENCE_KEY" : "observation", - "ALLOW_MULTIPLE_ASSESSMENTS" : "allowMultipleAssessemts", - "IS_RUBRIC_DRIVEN" : "isRubricDriven", - "CRITERIA_LEVEL_REPORT" : "criteriaLevelReport" - + module.exports = { + "ACTIVE_STATUS" : "active", + "PUBLISHED" : "published", + "SUCCESS" : "Success", + "ASSESSMENT" : "assessment", + "CONTENT" : "content", + "IMPROVEMENT_PROJECT" : "improvementProject", + "MULTIPLE" : "multiple", + "OTHERS" : "Others", + "SIMPLE_TASK_TYPE" : "simple", + "NOT_STARTED" : "not started", + "RECENTLY_ADDED_PROJECT" : "currentlyAdded", + "IMPORTANT_PROJECT" : "importantProject", + "COMPLETED_STATUS" : "completed", + "INPROGRESS_STATUS" : "inProgress", + "NOT_STARTED_STATUS" : "notStarted", + "STARTED":"started", + "SUBMITTED_STATUS":"submitted", + "OBSERVATION" : "observation", + "PUBLISHED_STATUS" : "published", + "LEAD_ASSESSOR" : "LEAD_ASSESSOR", + "PROJECT_DESCRIPTION" : "Manage and track your school improvement easily, by creating tasks and planning project timelines.", + "RESOURCE_TYPE" : "Improvement Project Solution", + "ENGLISH_LANGUAGE" : "English", + "KEYWORDS" : "Improvement Project", + "ASSIGN_TO_ME" : "assignedToMe", + "CREATED_BY_ME" : "createdByMe", + "DEFAULT_PAGE_NO" : 1, + "DEFAULT_PAGE_SIZE" : 100, + "OBSERVATION_REFERENCE_KEY" : "observation", + "ALLOW_MULTIPLE_ASSESSMENTS" : "allowMultipleAssessemts", + "IS_RUBRIC_DRIVEN" : "isRubricDriven", + "CRITERIA_LEVEL_REPORT" : "criteriaLevelReport", + "LINK" : "link", + "DEFAULT_SUBMISSION_REQUIRED" : 1, + "TASK_SEQUENCE" : "taskSequence", + "CHILDREN" : "children", + "ATTACHMENT_TYPE_LINK" : "link", + "PROJECT_ATTACHMENT" : "project", + "TASK_ATTACHMENT" : "task", + "DEFAULT_TASK_COMPLETED" : 0, + "IMAGE_DATA_TYPE" : "image/jpeg", + "DISTRICT": "district", + "SERVER_TIME_OUT" : 5000, + "OK" : "OK", + "PROJECT" : "project", + "PROJECT_CERTIFICATE_GENERATED_SUCCESSFULLY" : "Certificate generated successfully", + "TELEMETRY_VERSION": "3.0", + "DELETED_USER": "Deleted User", + "TELEMTRY_EVENT_LOGGER": "TelemetryEventLogger", + "INFO_LEVEL": "INFO", + "DELETE_STATE": "Delete", + "USER_DELETE_TYPE": "DeleteUserStatus", + "AUDIT": "AUDIT", + "DELETE_USER": "delete-user", + "USER": "User", + "USER_DELETE_MODULE": "userDelete", + "OFF" : "OFF", }; diff --git a/generics/constants/endpoints.js b/generics/constants/endpoints.js index cb586fbb..697d8521 100644 --- a/generics/constants/endpoints.js +++ b/generics/constants/endpoints.js @@ -22,7 +22,6 @@ module.exports = { USER_PRIVATE_PROGRAMS : "/v1/users/privatePrograms", UPDATE_SOLUTIONS : "/v1/solutions/updateSolutions", LIST_PROGRAMS : "/v1/programs/list", - GET_USER_ORGANISATIONS : "/v1/users/getUserOrganisationsAndRootOrganisations", PRESIGNED_GCP_URL : "/v1/cloud-services/gcp/preSignedUrls", // Kendra service PRESIGNED_AWS_URL : "/v1/cloud-services/aws/preSignedUrls", // Kendra service PRESIGNED_AZURE_URL : "/v1/cloud-services/azure/preSignedUrls", // Kendra service, @@ -44,5 +43,15 @@ module.exports = { SOLUTION_DETAILS_BASED_ON_ROLE_LOCATION : "/v1/solutions/detailsBasedOnRoleAndLocation", LIST_ENTITIES_BY_LOCATION_IDS : "/v1/entities/listByLocationIds", CREATE_IMPROVEMENT_PROJECT_SOLUTION : "/v1/solutions/create", - PROJECT_AND_TASK_REPORT : "/v1/improvement-project/projectAndTaskReport" + PROJECT_AND_TASK_REPORT : "/v1/improvement-project/projectAndTaskReport", + FILES_DOWNLOADABLE_URL: "/v1/cloud-services/files/getDownloadableUrl", + OBSERVATION_DETAILS : "/v1/observations/details", + USER_READ_V5 : "/v5/user/read", + GET_LOCATION_DATA : "/v1/location/search", + CERTIFICATE_CREATE : "/api/v1/ProjectCertificate", + PROJECT_CERTIFICATE_API_CALLBACK : "/v1/userProjects/certificateCallback", + USER_READ_PRIVATE : "/private/user/v1/read", // !Caution: End point for reading user details without token. Do not use for public work flow + GET_CERTIFICATE_KID : "/api/v1/PublicKey/search", + PROGRAM_JOIN: "/v1/programs/join", + IS_TARGETED_BASED_ON_USER_PROFILE : "/v1/solutions/isTargetedBasedOnUserProfile", }; diff --git a/generics/helpers/utils.js b/generics/helpers/utils.js index 95af57e0..b028e3b1 100644 --- a/generics/helpers/utils.js +++ b/generics/helpers/utils.js @@ -3,9 +3,11 @@ * author : Aman Karki * Date : 13-July-2020 * Description : All utility functions. - */ - - /** +*/ +// Dependencies +const {validate : uuidValidate,v4 : uuidV4} = require('uuid'); +const packageData = require(PROJECT_ROOT_DIRECTORY + "/package.json"); +/** * convert camel case to title case. * @function * @name camelCaseToTitleCase @@ -170,6 +172,293 @@ function epochTime() { return currentDate; } +/** + * Convert Project Status + * @function + * @name convertProjectStatus + * @returns {String} returns converted project status + */ + +function convertProjectStatus(status) { + + let convertedStatus; + + if ( status == CONSTANTS.common.NOT_STARTED_STATUS ) { + convertedStatus = CONSTANTS.common.STARTED; + } else if ( status == CONSTANTS.common.COMPLETED_STATUS ) { + convertedStatus = CONSTANTS.common.SUBMITTED_STATUS; + } else { + convertedStatus = status; + } + + return convertedStatus; +} + +/** + * Revert Project Status For Older App + * @function + * @name revertProjectStatus + * @returns {String} returns reverted project status + */ + + function revertProjectStatus(status) { + + let revertedStatus; + + if ( status == CONSTANTS.common.STARTED ) { + revertedStatus = CONSTANTS.common.NOT_STARTED_STATUS; + } else if ( status == CONSTANTS.common.SUBMITTED_STATUS ) { + revertedStatus = CONSTANTS.common.COMPLETED_STATUS; + } else { + revertedStatus = status; + } + + return revertedStatus; +} + +/** + * revert status or not + * @method + * @name revertStatusorNot + * @param {String} appVersion - app Version. + * @returns {Boolean} - true or false +*/ + +function revertStatusorNot( appVersion ) { + + let versions = ["4.10", "4.11", "4.12" ]; + + let appVer = appVersion.split('.',2).join('.'); + if ( versions.includes(appVer)) { + return false + } else { + + let appVersionNo = Number(appVer); + if ( !isNaN(appVersionNo) && appVersionNo < 4.7 ) { + return true + } else { + return false + } + } + +} + +/** + * check whether string is valid uuid. + * @function + * @name checkValidUUID + * @param {String} uuids + * @returns {Boolean} returns a Boolean value true/false +*/ + +function checkValidUUID(uuids) { + + var validateUUID = true; + if(Array.isArray(uuids)){ + for (var i = 0; uuids.length > i; i++) { + if(!uuidValidate(uuids[i])){ + validateUUID = false + } + } + }else { + validateUUID = uuidValidate(uuids); + } + return validateUUID; +} + +/** + * make dates comparable + * @function + * @name createComparableDates + * @param {String} dateArg1 + * @param {String} dateArg2 + * @returns {Object} - date object +*/ + +function createComparableDates(dateArg1, dateArg2) { + let date1 + if(typeof dateArg1 === "string") { + date1 = new Date(dateArg1.replace( /(\d{2})-(\d{2})-(\d{4})/, "$2/$1/$3")) + } else { + date1 = new Date(dateArg1) + } + + let date2 + if(typeof dateArg2 === "string") { + date2 = new Date(dateArg2.replace( /(\d{2})-(\d{2})-(\d{4})/, "$2/$1/$3")) + } else { + date2 = new Date(dateArg2) + } + + date1.setHours(0) + date1.setMinutes(0) + date1.setSeconds(0) + date2.setHours(0) + date2.setMinutes(0) + date2.setSeconds(0) + return({ + dateOne: date1, + dateTwo: date2 + }) +} + +/** + * count attachments + * @function + * @name noOfElementsInArray + * @param {Object} data - data to count + * @param {Object} filter - filter data + * @returns {Number} - attachment count +*/ + +function noOfElementsInArray(data, filter = {}) { + if ( !filter || !(Object.keys(filter).length > 0) ) { + return data.length; + } + if ( !(data.length > 0) ) { + return 0; + } else { + if ( filter.value == "all" ){ + return data.length; + } else { + let count = 0; + for ( let attachment = 0; attachment < data.length; attachment++ ) { + if ( data[attachment][filter.key] == filter.value ) { + count++ + } + } + return count; + } + } +} + +/** + * validate lhs and rhs using operator passed as String/ Number + * @function + * @name operatorValidation + * @param {Number or String} valueLhs + * @param {Number or String} valueRhs + * @returns {Boolean} - validation result +*/ + +function operatorValidation(valueLhs, valueRhs, operator) { + return new Promise(async (resolve, reject) => { + let result = false; + if (operator == "==" ) { + result = (valueLhs == valueRhs) ? true : false + } else if (operator == "!=" ) { + result = (valueLhs != valueRhs) ? true : false + } else if (operator == ">" ) { + result = (valueLhs > valueRhs) ? true : false + } else if (operator == "<" ) { + result = (valueLhs < valueRhs) ? true : false + } else if (operator == "<=" ) { + result = (valueLhs <= valueRhs) ? true : false + } else if (operator == ">=" ) { + result = (valueLhs >= valueRhs) ? true : false + } + return resolve(result) + }) +} + +/** + * generate uuid + * @function + * @name generateUUId + * @returns {String} returns uuid. + */ + +function generateUUId() { + return uuidV4(); +} + + +/** + * generate skeleton telemetry raw event + * @function + * @name generateTelemetryEventSkeletonStructure + * @returns {Object} returns uuid. + */ + function generateTelemetryEventSkeletonStructure() { + let telemetrySkeleton = { + eid: "", + ets: epochTime(), + ver: CONSTANTS.common.TELEMETRY_VERSION, + mid: generateUUId(), + actor: {}, + context: { + channel: "", + pdata: { + id: process.env.ID, + ver: packageData.version, + }, + env: "", + cdata: [], + rollup: {}, + }, + object: {}, + edata: {}, + }; + return telemetrySkeleton; +} + +/** + * generate telemetry event + * @function + * @name generateTelemetryEvent + * @returns {Object} returns uuid. + */ + function generateTelemetryEvent(rawEvent) { + let telemetryEvent = { + timestamp: new Date(), + msg: JSON.stringify(rawEvent), + lname: "", + tname: "", + level: "", + HOSTNAME: "", + "application.home": "", + }; + return telemetryEvent; +} + + +/** + * check the uuid is valid + * @function + * @name checkIfValidUUID + * @returns {String} returns boolean. +*/ + +function checkIfValidUUID(value) { + const regexExp = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; + return regexExp.test(value); +} + +/** + * filter out location id and code + * @function + * @name filterLocationIdandCode + * @returns {Object} - Object contain locationid and location code array. +*/ + +function filterLocationIdandCode(dataArray) { + let locationIds = []; + let locationCodes = []; + dataArray.forEach(element=>{ + if (this.checkIfValidUUID(element)) { + locationIds.push(element); + } else { + locationCodes.push(element); + } + }); + return ({ + ids : locationIds, + codes : locationCodes + }); +} + + + module.exports = { camelCaseToTitleCase : camelCaseToTitleCase, lowerCase : lowerCase, @@ -179,5 +468,16 @@ module.exports = { convertStringToBoolean : convertStringToBoolean, getAllBooleanDataFromModels : getAllBooleanDataFromModels, epochTime : epochTime, - isValidMongoId : isValidMongoId + isValidMongoId : isValidMongoId, + convertProjectStatus : convertProjectStatus, + revertProjectStatus:revertProjectStatus, + revertStatusorNot:revertStatusorNot, + checkValidUUID : checkValidUUID, + createComparableDates : createComparableDates, + noOfElementsInArray : noOfElementsInArray, + operatorValidation : operatorValidation, + generateTelemetryEventSkeletonStructure : generateTelemetryEventSkeletonStructure, + generateTelemetryEvent : generateTelemetryEvent, + filterLocationIdandCode : filterLocationIdandCode, + checkIfValidUUID : checkIfValidUUID }; diff --git a/generics/http-status-codes/index.js b/generics/http-status-codes/index.js index e105035f..f673f7d8 100644 --- a/generics/http-status-codes/index.js +++ b/generics/http-status-codes/index.js @@ -16,7 +16,8 @@ module.exports = { }, 'ok': { status: 200, - message: "Success" + message: "Success", + code: "OK" }, 'created': { status: 201, diff --git a/generics/kafka/consumers/projectCertificate.js b/generics/kafka/consumers/projectCertificate.js new file mode 100644 index 00000000..66823a75 --- /dev/null +++ b/generics/kafka/consumers/projectCertificate.js @@ -0,0 +1,62 @@ +/** + * name : projectCertificate.js + * author : Vishnu + * created-date : 10-Oct-2022 + * Description : Project certificates submission consumer. +*/ + +//dependencies +const userProjectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); + +/** +* submission consumer message received. +* @function +* @name messageReceived +* @param {String} message - consumer data +* @returns {Promise} return a Promise. +*/ + +var messageReceived = function (message) { + return new Promise(async function (resolve, reject) { + try { + // This consumer is consuming from an old topic : PROJECT_CERTIFICATE_TOPIC, which is no more used by data team. ie) using existig topic instead of creating new one. + let parsedMessage = JSON.parse( message.value ); + if ( parsedMessage.status == CONSTANTS.common.SUBMITTED_STATUS && + parsedMessage.certificate && + Object.keys(parsedMessage.certificate).length > 0 && + !parsedMessage.certificate.eligible + ) { + await userProjectsHelper.generateCertificate( parsedMessage ); + } + return resolve("Message Received"); + } catch (error) { + return reject(error); + } + + }); +}; + +/** +* If message is not received. +* @function +* @name errorTriggered +* @param {Object} error - error object +* @returns {Promise} return a Promise. +*/ + +var errorTriggered = function (error) { + return new Promise(function (resolve, reject) { + + try { + return resolve(error); + } catch (error) { + return reject(error); + } + + }); +}; + +module.exports = { + messageReceived: messageReceived, + errorTriggered: errorTriggered +}; diff --git a/generics/kafka/consumers/submissions.js b/generics/kafka/consumers/submissions.js index 92bc5121..e2411f53 100644 --- a/generics/kafka/consumers/submissions.js +++ b/generics/kafka/consumers/submissions.js @@ -25,16 +25,14 @@ var messageReceived = function (message) { try { let parsedMessage = JSON.parse(message.value); - + let submissionDocument = { - "submissionDetails._id" : parsedMessage._id.toString(), - "submissionDetails.status" : parsedMessage.status, - "submissionDetails.completedDate" : - parsedMessage.submissionDate ? parsedMessage.submissionDate : "" + "_id" : parsedMessage._id.toString(), + "status" : parsedMessage.status, + "completedDate" : parsedMessage.submissionDate ? parsedMessage.submissionDate : "" }; - - await userProjectsHelper.updateTask( + await userProjectsHelper.pushSubmissionToTask( parsedMessage.projectId, parsedMessage.taskId, submissionDocument diff --git a/generics/kafka/consumers/userDelete.js b/generics/kafka/consumers/userDelete.js new file mode 100644 index 00000000..f1aa5a3e --- /dev/null +++ b/generics/kafka/consumers/userDelete.js @@ -0,0 +1,67 @@ +/** + * name : userDMS.js + * author : Ankit + * created-date : 10-Nov-2023 + * Description : user delete event consumer. + */ + +//dependencies +const userProjectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); +/** + * consumer message received. + * @function + * @name messageReceived + * @param {Object} message - consumer data + * { + * highWaterOffset:63 + * key:null + * offset:62 + * partition:0 + * topic:'deleteuser' + * value:'{"eid":"BE_JOB_REQUEST","ets":1619527882745,"mid":"LP.1619527882745.32dc378a-430f-49f6-83b5-bd73b767ad36","actor":{"id":"delete-user","type":"System"},"context":{"channel":"01309282781705830427","pdata":{"id":"org.sunbird.platform","ver":"1.0"},"env":"dev"},"object":{"id":"","type":"User"},"edata":{"organisationId":"0126796199493140480","userId":"a102c136-c6da-4c6c-b6b7-0f0681e1aab9","suggested_users":[{"role":"ORG_ADMIN","users":[""]},{"role":"CONTENT_CREATOR","users":[""]},{"role":"COURSE_MENTOR","users":[""]}],"action":"delete-user","iteration":1}}' + * } + * @returns {Promise} return a Promise. + */ + +var messageReceived = function (message) { + return new Promise(async function (resolve, reject) { + try { + let parsedMessage = JSON.parse(message.value); + if (parsedMessage.edata.action === CONSTANTS.common.DELETE_USER) { + let userDataDeleteStatus = await userProjectsHelper.deleteUserPIIData( + parsedMessage + ); + if (userDataDeleteStatus.success === true) { + return resolve("Message Processed."); + } else { + return resolve("Message Processed."); + } + } + } catch (error) { + return reject(error); + } + }); +}; + +/** + * If message is not received. + * @function + * @name errorTriggered + * @param {Object} error - error object + * @returns {Promise} return a Promise. + */ + +var errorTriggered = function (error) { + return new Promise(function (resolve, reject) { + try { + return resolve(error); + } catch (error) { + return reject(error); + } + }); +}; + +module.exports = { + messageReceived: messageReceived, + errorTriggered: errorTriggered, +}; diff --git a/generics/kafka/producers.js b/generics/kafka/producers.js index ee376c23..605679b5 100644 --- a/generics/kafka/producers.js +++ b/generics/kafka/producers.js @@ -7,6 +7,8 @@ // Dependencies const kafkaCommunicationsOnOff = (!process.env.KAFKA_COMMUNICATIONS_ON_OFF || process.env.KAFKA_COMMUNICATIONS_ON_OFF != "OFF") ? "ON" : "OFF"; +const projectSubmissionTopic = (process.env.PROJECT_SUBMISSION_TOPIC && process.env.PROJECT_SUBMISSION_TOPIC != "OFF") ? process.env.PROJECT_SUBMISSION_TOPIC : "sl-improvement-project-submission-dev"; +const telemetryEventTopic = process.env.TELEMETRY_TOPIC ? process.env.TELEMETRY_TOPIC : "dev.telemetry.raw"; /** * Push improvement projects to kafka. @@ -20,7 +22,7 @@ const pushProjectToKafka = function (message) { try { let kafkaPushStatus = await pushMessageToKafka([{ - topic: process.env.NOTIFICATIONS_TOPIC, + topic: projectSubmissionTopic, messages: JSON.stringify(message) }]); @@ -32,6 +34,31 @@ const pushProjectToKafka = function (message) { }) } + + +/** + * Push message to telemetry. + * @function + * @name pushTelemetryEventToKafka + * @param {Object} message - Message data. + */ + const pushTelemetryEventToKafka = function (message) { + return new Promise(async (resolve, reject) => { + try { + let kafkaPushStatus = await pushMessageToKafka([ + { + topic: telemetryEventTopic, + messages: JSON.stringify(message), + }, + ]); + + return resolve(kafkaPushStatus); + } catch (error) { + return reject(error); + } + }); +}; + /** * Push message to kafka. * @function @@ -46,6 +73,12 @@ const pushMessageToKafka = function(payload) { throw reject("Kafka configuration is not done"); } + console.log("-------Kafka producer log starts here------------------"); + console.log("Topic Name: ", payload[0].topic); + console.log("Message: ", JSON.stringify(payload)); + console.log("-------Kafka producer log ends here------------------"); + + kafkaClient.kafkaProducer.send(payload, (err, data) => { if (err) { return reject("Kafka push to topic "+ payload[0].topic +" failed."); @@ -69,7 +102,9 @@ const pushMessageToKafka = function(payload) { }) } + module.exports = { - pushProjectToKafka : pushProjectToKafka + pushProjectToKafka : pushProjectToKafka, + pushTelemetryEventToKafka : pushTelemetryEventToKafka }; diff --git a/generics/middleware/authenticator.js b/generics/middleware/authenticator.js index f5160731..c7388fc9 100644 --- a/generics/middleware/authenticator.js +++ b/generics/middleware/authenticator.js @@ -49,19 +49,19 @@ module.exports = async function (req, res, next, token = "") { // Allow search endpoints for non-logged in users. let guestAccess = false; - let guestAccessPaths = []; + let guestAccessPaths = ["/dataPipeline/","/templates/details","userProjects/certificateCallback"]; await Promise.all(guestAccessPaths.map(async function (path) { if (req.path.includes(path)) { guestAccess = true; } })); - if(guestAccess==true) { + if( guestAccess == true && !token ) { next(); return; } - let internalAccessApiPaths = ["/templates/bulkCreate"]; + let internalAccessApiPaths = ["/templates/bulkCreate",'/userProjects/overview']; let performInternalAccessTokenCheck = false; await Promise.all(internalAccessApiPaths.map(async function (path) { if (req.path.includes(path)) { @@ -76,6 +76,10 @@ module.exports = async function (req, res, next, token = "") { rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status; return res.status(HTTP_STATUS_CODE["unauthorized"].status).send(respUtil(rspObj)); } + if(!token){ + next(); + return; + } } @@ -97,7 +101,7 @@ module.exports = async function (req, res, next, token = "") { const kid = decoded.header.kid let cert = ""; - let path = keyCloakPublicKeyPath + kid; + let path = keyCloakPublicKeyPath + kid.replace(/\.\.\//g, ''); if (fs.existsSync(path)) { diff --git a/generics/middleware/validator/index.js b/generics/middleware/validator/index.js index c136727a..c06ba317 100644 --- a/generics/middleware/validator/index.js +++ b/generics/middleware/validator/index.js @@ -19,7 +19,7 @@ module.exports = (req, res, next) => { PROJECT_ROOT_DIRECTORY + `/module/${req.params.controller}/validator/${req.params.version}.js`; } - if (fs.existsSync(validatorPath)) require(validatorPath)(req); + if (fs.existsSync(validatorPath)) require(validatorPath.replace(/\.\.\//g, ''))(req); next(); diff --git a/generics/services/certificate.js b/generics/services/certificate.js new file mode 100644 index 00000000..6fd193c6 --- /dev/null +++ b/generics/services/certificate.js @@ -0,0 +1,126 @@ +/** + * name : certificate.js + * author : Vishnu + * Date : 07-Oct-2022 + * Description : Sunbird-RC certificate api. + */ + +//dependencies +const request = require('request'); + +/** + * Project certificate creation + * @function + * @name createCertificate + * @param {Object} bodyData - Body data + * @returns {JSON} - Certificate creation details. +*/ + +const createCertificate = function (bodyData) { + return new Promise(async (resolve, reject) => { + try { + + const ML_PROJECT_URL = `http://${process.env.SERVICE_NAME}:${process.env.APPLICATION_PORT}`; + const callbackUrl = ML_PROJECT_URL + CONSTANTS.endpoints.PROJECT_CERTIFICATE_API_CALLBACK; + let certificateCreateUrl = + process.env.CERTIFICATE_SERVICE_URL + + CONSTANTS.endpoints.CERTIFICATE_CREATE + "?mode=async&callback=" + callbackUrl; + const options = { + headers : { + "content-type": "application/json" + }, + json : bodyData + }; + + console.log("bodyData : ",bodyData) + console.log("certificateCreateUrl : ",certificateCreateUrl) + + request.post(certificateCreateUrl,options,certificateCallback); + + function certificateCallback(err, data) { + console.log("line 39 raw data from RC call :",JSON.stringify(data)); + let result = { + success : true + }; + if (err) { + result.success = false; + console.log("line 45 error from RC call error :",err.message); + } else { + let response = data.body; + console.log("certificate success response: ",JSON.stringify(response)) + if( response.params && response.params.status && response.params.status === "SUCCESSFUL" ) { + result["data"] = response.result; + } else { + result.success = false; + } + } + return resolve(result); + } + + } catch (error) { + console.log("line 58 catch block : ",error.message) + return reject(error); + } + }) +} + +/** + * Project certificate issuer-kid + * @function + * @name getCertificateIssuerKid + * @returns {JSON} - Certificate issuer kid details. +*/ + +const getCertificateIssuerKid = function () { + return new Promise(async (resolve, reject) => { + try { + let issuerKidUrl = + process.env.CERTIFICATE_SERVICE_URL + CONSTANTS.endpoints.GET_CERTIFICATE_KID; + + let bodyData = {"filters": {}}; + + const options = { + headers : { + "Content-Type": "application/json" + }, + json : bodyData + }; + console.log("issuer Kid url : ",issuerKidUrl); + console.log("issuer Kid bodyData : ",JSON.stringify(bodyData)); + request.post(issuerKidUrl,options,getKidCallback); + function getKidCallback(err, data) { + let result = { + success : true + }; + + if (err) { + console.log("KID rc call error : ",err.message) + result.success = false; + } else { + let response = data.body; + console.log("KID success response : ",JSON.stringify(response)) + if( response.length > 0 && response[0].osid && response[0].osid !== "" ) { + result["data"] = response[0].osid; + } else { + result.success = false; + } + } + return resolve(result); + } + setTimeout(function () { + return resolve (result = { + success : false + }); + }, CONSTANTS.common.SERVER_TIME_OUT); + + } catch (error) { + console.log("catch error : ",error.message) + return reject(error); + } + }) +} + +module.exports = { + createCertificate : createCertificate, + getCertificateIssuerKid : getCertificateIssuerKid +} \ No newline at end of file diff --git a/generics/services/kendra.js b/generics/services/core.js similarity index 83% rename from generics/services/kendra.js rename to generics/services/core.js index 9919031e..8a680f8c 100644 --- a/generics/services/kendra.js +++ b/generics/services/core.js @@ -9,7 +9,7 @@ const request = require('request'); const fs = require("fs"); -const KENDRA_URL = process.env.ML_CORE_SERVICE_URL; +const ML_CORE_URL = process.env.ML_CORE_SERVICE_URL; /** * Get downloadable file. @@ -21,7 +21,9 @@ const KENDRA_URL = process.env.ML_CORE_SERVICE_URL; const getDownloadableUrl = function (bodyData) { - let fileDownloadUrl = KENDRA_URL + CONSTANTS.endpoints.FILES_DOWNLOADABLE_URL; + let fileDownloadUrl = ML_CORE_URL + CONSTANTS.endpoints.FILES_DOWNLOADABLE_URL; + + console.log("File Downloadable url is",fileDownloadUrl); return new Promise((resolve, reject) => { try { @@ -33,9 +35,11 @@ const getDownloadableUrl = function (bodyData) { }; if (err) { + console.log("File download error is",err); result.success = false; } else { let response = data.body; + console.log("File downloaded response is",response); if( response.status === HTTP_STATUS_CODE['ok'].status ) { result["data"] = response.result; @@ -79,7 +83,7 @@ const entityTypesDocuments = function ( return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.LIST_ENTITY_TYPES; + const url = ML_CORE_URL + CONSTANTS.endpoints.LIST_ENTITY_TYPES; const options = { headers : { @@ -141,7 +145,7 @@ const rolesDocuments = function ( return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.LIST_USER_ROLES; + const url = ML_CORE_URL + CONSTANTS.endpoints.LIST_USER_ROLES; const options = { headers : { @@ -197,7 +201,7 @@ const formDetails = function ( formName ) { try { const url = - KENDRA_URL + + ML_CORE_URL + CONSTANTS.endpoints.DETAILS_FORM + "/" + formName; const options = { @@ -251,7 +255,7 @@ const entityDocuments = function ( return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.LIST_ENTITIES; + const url = ML_CORE_URL + CONSTANTS.endpoints.LIST_ENTITIES; const options = { headers : { @@ -302,11 +306,15 @@ const entityDocuments = function ( * @returns {JSON} - Create user program and solution. */ -const createUserProgramAndSolution = function ( data,userToken ) { +const createUserProgramAndSolution = function ( data,userToken, createADuplicateSolution = "" ) { return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.CREATE_PROGRAM_AND_SOLUTION; + let url = ML_CORE_URL + CONSTANTS.endpoints.CREATE_PROGRAM_AND_SOLUTION; + + if( createADuplicateSolution == false ) { + url = url + "?createADuplicateSolution=" + true; + } const options = { headers : { @@ -358,7 +366,7 @@ const getProfile = function ( token ) { return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.USER_EXTENSION_GET_PROFILE; + const url = ML_CORE_URL + CONSTANTS.endpoints.USER_EXTENSION_GET_PROFILE; const options = { headers : { @@ -411,7 +419,7 @@ const updateUserProfile = function ( token,updateData ) { return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + + const url = ML_CORE_URL + CONSTANTS.endpoints.USER_EXTENSION_UPDATE_USER_PROFILE; const options = { @@ -458,7 +466,7 @@ const userPrivatePrograms = function ( token ) { return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + + const url = ML_CORE_URL + CONSTANTS.endpoints.USER_PRIVATE_PROGRAMS; const options = { @@ -508,7 +516,7 @@ const getUsersByEntityAndRole = function ( return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.GET_USERS_BY_ENTITY_AND_ROLE + "/" + entityId + "?role=" + role; + const url = ML_CORE_URL + CONSTANTS.endpoints.GET_USERS_BY_ENTITY_AND_ROLE + "/" + entityId + "?role=" + role; const options = { headers : { @@ -560,7 +568,7 @@ const createSolution = function ( bodyData,token ) { return new Promise(async (resolve, reject) => { try { - const url = KENDRA_URL + CONSTANTS.endpoints.CREATE_IMPROVEMENT_PROJECT_SOLUTION; + const url = ML_CORE_URL + CONSTANTS.endpoints.CREATE_IMPROVEMENT_PROJECT_SOLUTION; const options = { headers : { @@ -616,7 +624,7 @@ const solutionBasedOnRoleAndLocation = function ( token,bodyData,typeAndSubType, try { let url = - KENDRA_URL + CONSTANTS.endpoints.SOLUTION_BASED_ON_ROLE_LOCATION+ "?type=" + typeAndSubType + "&subType=" + typeAndSubType; + ML_CORE_URL + CONSTANTS.endpoints.SOLUTION_BASED_ON_ROLE_LOCATION+ "?type=" + typeAndSubType + "&subType=" + typeAndSubType; if( searchText !== "" ) { url = url + "&search=" + searchText; @@ -674,9 +682,8 @@ const solutionBasedOnRoleAndLocation = function ( token,bodyData,typeAndSubType, const solutionDetailsBasedOnRoleAndLocation = function ( token,bodyData,solutionId ) { return new Promise(async (resolve, reject) => { try { - const url = - KENDRA_URL + CONSTANTS.endpoints.SOLUTION_DETAILS_BASED_ON_ROLE_LOCATION + "/" + solutionId; + ML_CORE_URL + CONSTANTS.endpoints.SOLUTION_DETAILS_BASED_ON_ROLE_LOCATION + "/" + solutionId; const options = { headers : { @@ -718,36 +725,90 @@ const solutionDetailsBasedOnRoleAndLocation = function ( token,bodyData,solution } /** - * Update solution + * program Join Api. * @function - * @name getUserOrganisationsAndRootOrganisations - * @param {String} token - Logged in user token. - * @param {String} userId - User id. - * @returns {JSON} - Update solutions. + * @name joinProgram + * @param {String} userToken - User token. + * @param {Object} bodyData - Requested body data. + * @param {String} programId - program id. + * @returns {JSON} - Program Join Status. */ - -const getUserOrganisationsAndRootOrganisations = function ( token,userId = "" ) { +const joinProgram = function (programId,bodyData,userToken) { return new Promise(async (resolve, reject) => { try { + + const url = + ML_CORE_URL + CONSTANTS.endpoints.PROGRAM_JOIN + "/" + programId; + const options = { + headers : { + "content-type": "application/json", + "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, + "x-authenticated-user-token" : userToken, + }, + + }; - let url = - KENDRA_URL + - CONSTANTS.endpoints.GET_USER_ORGANISATIONS; + if ( bodyData.appVersion !== "" ) { + options.headers.appversion = bodyData.appVersion; + delete bodyData.appVersion + } + + if ( bodyData.appName !== "" ) { + options.headers.appname = bodyData.appName; + delete bodyData.appName + } + + + options.json = bodyData + request.post(url,options,kendraCallback); + + function kendraCallback(err, data) { + + let result = { + success : true + }; + + if (err) { + result.success = false; + } else { + + let response = data.body; + if( response.status === HTTP_STATUS_CODE['ok'].status ) { + result["data"] = response.result; + } else { + result.success = false; + } + } - if( userId !== "" ) { - url = url + "/" + userId; + return resolve(result); } + } catch (error) { + return reject(error); + } + }) +} + + +const checkIfSolutionIsTargetedForUserProfile = function ( token,bodyData,solutionId ) { + return new Promise(async (resolve, reject) => { + try { + + const url = + ML_CORE_URL + CONSTANTS.endpoints.IS_TARGETED_BASED_ON_USER_PROFILE + "/" + solutionId; + const options = { headers : { "content-type": "application/json", + "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, "x-authenticated-user-token" : token - } + }, + json : bodyData }; - request.post(url,options,kendraCallback); + request.post(url,options,verifyTargetedSolutionCallback); - function kendraCallback(err, data) { + function verifyTargetedSolutionCallback(err, data) { let result = { success : true @@ -756,14 +817,13 @@ const getUserOrganisationsAndRootOrganisations = function ( token,userId = "" ) if (err) { result.success = false; } else { - - let response = JSON.parse(data.body); + + let response = data.body; if( response.status === HTTP_STATUS_CODE['ok'].status ) { result["data"] = response.result; } else { result.success = false; } - } return resolve(result); @@ -774,7 +834,6 @@ const getUserOrganisationsAndRootOrganisations = function ( token,userId = "" ) } }) } - module.exports = { entityTypesDocuments : entityTypesDocuments, rolesDocuments : rolesDocuments, @@ -788,7 +847,8 @@ module.exports = { createSolution: createSolution, solutionBasedOnRoleAndLocation : solutionBasedOnRoleAndLocation, solutionDetailsBasedOnRoleAndLocation : solutionDetailsBasedOnRoleAndLocation, - getDownloadableUrl : getDownloadableUrl, - getUserOrganisationsAndRootOrganisations : getUserOrganisationsAndRootOrganisations + getDownloadableUrl : getDownloadableUrl, + joinProgram: joinProgram, + checkIfSolutionIsTargetedForUserProfile:checkIfSolutionIsTargetedForUserProfile }; diff --git a/generics/services/dhiti.js b/generics/services/report.js similarity index 95% rename from generics/services/dhiti.js rename to generics/services/report.js index 62c72af3..75e58b9a 100644 --- a/generics/services/dhiti.js +++ b/generics/services/report.js @@ -1,5 +1,5 @@ /** - * name : dhiti.js + * name : report.js * author : Rakesh Kumar * Date : 10-Nov-2020 * Description : All dhiti service related information. @@ -30,7 +30,7 @@ const viewFullReport = function (token,input) { headers : { "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, "content-type": "application/json", - "x-auth-token": token + "x-authenticated-user-token": token }, json : input }; @@ -76,7 +76,7 @@ const entityReport = function (token,input) { headers : { "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, "content-type": "application/json", - "x-auth-token": token + "x-authenticated-user-token": token }, json : input }; @@ -123,7 +123,7 @@ const projectAndTaskReport = function (token, input, projectPdf) { headers : { "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, "content-type": "application/json", - "x-auth-token": token + "x-authenticated-user-token": token }, json : input }; diff --git a/generics/services/assessment.js b/generics/services/survey.js similarity index 97% rename from generics/services/assessment.js rename to generics/services/survey.js index 09398446..bb2f1011 100644 --- a/generics/services/assessment.js +++ b/generics/services/survey.js @@ -1,5 +1,5 @@ /** - * name : assessment.js + * name : survey.js * author : Aman Jung Karki * Date : 21-Nov-2020 * Description : Assessment service related information. @@ -319,7 +319,7 @@ const observationDetails = function (token,observationId) { let url = ASSESSMENT_URL + CONSTANTS.endpoints.OBSERVATION_DETAILS + "/" + observationId; - + const options = { headers : { "content-type": "application/json", @@ -340,7 +340,7 @@ const observationDetails = function (token,observationId) { result.success = false; } else { - let response = data.body; + let response = JSON.parse(data.body); if( response.status === HTTP_STATUS_CODE['ok'].status ) { result["data"] = response.result; } else { @@ -348,6 +348,7 @@ const observationDetails = function (token,observationId) { } } + return resolve(result); } @@ -374,7 +375,7 @@ const listSolutions = function (solutionIds) { const options = { headers : { "content-type": "application/json", - "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, + "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN }, json : { solutionIds : solutionIds @@ -393,7 +394,6 @@ const listSolutions = function (solutionIds) { result.success = false; } else { let response = data.body; - if( response.status === HTTP_STATUS_CODE['ok'].status ) { result["data"] = response.result; } else { @@ -479,7 +479,7 @@ const updateSolution = function ( token,updateData,solutionExternalId ) { * @returns {JSON} - Create observation. */ -const createObservation = function (token,solutionId,data) { +const createObservation = function (token,solutionId,data, userRoleAndProfileInformation = {}) { return new Promise(async (resolve, reject) => { try { @@ -487,7 +487,7 @@ const createObservation = function (token,solutionId,data) { ASSESSMENT_URL + CONSTANTS.endpoints.CREATE_OBSERVATIONS + "?solutionId=" + solutionId; - const options = { + let options = { headers : { "content-type": "application/json", "internal-access-token": process.env.INTERNAL_ACCESS_TOKEN, @@ -497,6 +497,10 @@ const createObservation = function (token,solutionId,data) { data : data } }; + + if ( userRoleAndProfileInformation && Object.keys(userRoleAndProfileInformation).length > 0){ + options.json.userRoleAndProfileInformation = userRoleAndProfileInformation; + } request.post(createdObservationUrl,options,assessmentCallback); @@ -518,6 +522,7 @@ const createObservation = function (token,solutionId,data) { } } + return resolve(result); } @@ -565,7 +570,6 @@ const listProgramsBasedOnIds = function ( programIds ) { } else { let response = data.body; - if( response.status === HTTP_STATUS_CODE['ok'].status ) { result["data"] = response.result; } else { @@ -723,7 +727,8 @@ const listEntitiesByLocationIds = function ( token,locationIds ) { let response = data.body; - if( response.status === HTTP_STATUS_CODE['ok'].status ) { + + if( response.status === HTTP_STATUS_CODE['ok'].status && response.result ) { result["data"] = response.result; } else { result.success = false; diff --git a/generics/services/users.js b/generics/services/users.js new file mode 100644 index 00000000..66bb5a9f --- /dev/null +++ b/generics/services/users.js @@ -0,0 +1,244 @@ +/** + * name : users.js + * author : Vishnu + * Date : 07-April-2022 + * Description : All users related api call. + */ + +//dependencies +const request = require('request'); +const userServiceUrl = process.env.USER_SERVICE_URL; + +const profile = function ( token,userId = "" ) { + return new Promise(async (resolve, reject) => { + try { + + let url = userServiceUrl + CONSTANTS.endpoints.USER_READ_V5; + + if( userId !== "" ) { + url = url + "/" + userId + "?" + "fields=organisations,roles,locations,declarations,externalIds" + } + + const options = { + headers : { + "content-type": "application/json", + "x-authenticated-user-token" : token + } + }; + + request.get(url,options,userReadCallback); + let result = { + success : true + }; + function userReadCallback(err, data) { + + if (err) { + result.success = false; + } else { + + let response = JSON.parse(data.body); + if( response.responseCode === HTTP_STATUS_CODE['ok'].code ) { + result["data"] = _.omit(response.result, [ + "response.email", + "response.maskedEmail", + "response.maskedPhone", + "response.recoveryEmail", + "response.phone", + "response.prevUsedPhone", + "response.prevUsedEmail", + "response.recoveryPhone", + "response.encEmail", + "response.encPhone", + ]); + } else { + result.success = false; + } + + } + + return resolve(result); + } + setTimeout(function () { + return resolve (result = { + success : false + }); + }, CONSTANTS.common.SERVER_TIME_OUT); + + } catch (error) { + return reject(error); + } + }) +} + + +/** + * + * @function + * @name locationSearch + * @param {object} filterData - location search filter object. + * @param {Boolean} formatResult - format result or not. + * @returns {Promise} returns a promise. +*/ + +const locationSearch = function ( filterData, formatResult = false ) { + return new Promise(async (resolve, reject) => { + try { + + let bodyData={}; + bodyData["request"] = {}; + bodyData["request"]["filters"] = filterData; + const url = + userServiceUrl + CONSTANTS.endpoints.GET_LOCATION_DATA; + + const options = { + headers : { + "content-type": "application/json" + }, + json : bodyData + }; + + request.post(url,options,requestCallback); + + let result = { + success : true + }; + + function requestCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = data.body; + + if( response.responseCode === CONSTANTS.common.OK && + response.result && + response.result.response && + response.result.response.length > 0 + ) { + if ( formatResult ) { + let entityResult =new Array; + response.result.response.map(entityData => { + let data = {}; + data._id = entityData.id; + data.entityType = entityData.type; + data.metaInformation = {}; + data.metaInformation.name = entityData.name; + data.metaInformation.externalId = entityData.code + data.registryDetails = {}; + data.registryDetails.locationId = entityData.id; + data.registryDetails.code = entityData.code; + entityResult.push(data); + }); + result["data"] = entityResult; + result["count"] = response.result.count; + } else { + result["data"] = response.result.response; + result["count"] = response.result.count; + } + + } else { + result.success = false; + } + } + return resolve(result); + } + + setTimeout(function () { + return resolve (result = { + success : false + }); + }, CONSTANTS.common.SERVER_TIME_OUT); + + } catch (error) { + return reject(error); + } + }) +} +/** + * get Parent Entities of an entity. + * @method + * @name getParentEntities + * @param {String} entityId - entity id + * @returns {Array} - parent entities. +*/ + +async function getParentEntities( entityId, iteration = 0, parentEntities ) { + + if ( iteration == 0 ) { + parentEntities = []; + } + + let filterQuery = { + "id" : entityId + }; + + let entityDetails = await locationSearch(filterQuery); + if ( !entityDetails.success ) { + return parentEntities; + } else { + + let entityData = entityDetails.data[0]; + if ( iteration > 0 ) parentEntities.push(entityData); + if ( entityData.parentId ) { + iteration = iteration + 1; + entityId = entityData.parentId; + await getParentEntities(entityId, iteration, parentEntities); + } + } + + return parentEntities; + +} + +/** + * get user profileData without token. + * @method + * @name profileReadPrivate + * @param {String} userId - user Id + * @returns {JSON} - User profile details +*/ +const profileReadPrivate = function (userId) { + return new Promise(async (resolve, reject) => { + try { + // <--- Important : This url endpoint is private do not use it for regular workflows ---> + let url = userServiceUrl + CONSTANTS.endpoints.USER_READ_PRIVATE + "/" + userId; + const options = { + headers : { + "content-type": "application/json" + } + }; + request.get(url,options,userReadCallback); + let result = { + success : true + }; + function userReadCallback(err, data) { + if (err) { + result.success = false; + } else { + + let response = JSON.parse(data.body); + if( response.responseCode === HTTP_STATUS_CODE['ok'].code ) { + result["data"] = response.result; + } else { + result.success = false; + } + + } + return resolve(result); + } + setTimeout(function () { + return resolve (result = { + success : false + }); + }, CONSTANTS.common.SERVER_TIME_OUT); + + } catch (error) { + return reject(error); + } + }) +} +module.exports = { + profile : profile, + locationSearch : locationSearch, + getParentEntities : getParentEntities, + profileReadPrivate : profileReadPrivate +}; diff --git a/healthCheck/mongodb.js b/healthCheck/mongodb.js index 96f7f2f9..3efc6288 100644 --- a/healthCheck/mongodb.js +++ b/healthCheck/mongodb.js @@ -20,6 +20,7 @@ function health_check() { return resolve(false) }); db.once("open", function() { + db.close(function(){}); return resolve(true); }); }) diff --git a/migrations/updateDistrictNameInProjects/Readme.md b/migrations/updateDistrictNameInProjects/Readme.md new file mode 100644 index 00000000..254502d3 --- /dev/null +++ b/migrations/updateDistrictNameInProjects/Readme.md @@ -0,0 +1,83 @@ +## Migrations + +#### Steps to run the script files + +This script is intended to address a particular bug in Diksha Prod, and it is unrelated to the 6.0.0 release. + +Here is the ticket for this issue:- (Click Here)[https://project-sunbird.atlassian.net/browse/ED-3101]. + +In order to execute this migration script, we need to first log in to the pod where the service is running and then proceed with the provided instructions. + +This script is designed to update project documents specifically for cases where there is a mismatch in district names. It will only affect the IDs specified in the script. + +This script is intended to correct inconsistencies in district names within project documents. + +### Step 1: + + Navigate to /opt/projects/migrations/updateDistrictNameInProjects/ + +### Step 2: + +Run the script to update projects. + + node updateDistrictName.js + +#### Validation + +After the script has been executed in the production environment, we can validate the data by utilizing the DBfind API. + +Retrieve the project IDs from `updateDistrictName.json` and include them in the body of the DBfind API request as follows. + +{ + "query": { + "_id": { + "$in": [ + //Add Project Ids here + ] + } + }, + "mongoIdKeys": [ + "_id" + ], + "projection": [ + "userProfile.userLocations" + ], + "limit": 200, + "skip": 0 +} + +After receiving the response, ensure that the userLocations does not include a district named `Gaurela-Pendra-Marvahi` or `Bastar` instead, it should have either `Gourela Pendra Marvahi` and `Baster`. + +### Step 3: + +This script is designed to refresh project documents by incorporating the most recent profile information along with the individual's previous role in the project. + +Run the script to update projects. + + node updateUserProfileDistrictNameMissing.js + +#### Validation + +After the script has been executed in the production environment, we can validate the data by utilizing the DBfind API. + +Retrieve the project IDs from `updateUserProfileDistrictNameMissing.json` and include them in the body of the DBfind API request as follows. + +{ + "query": { + "_id": { + "$in": [ + //Add Project Ids here + ] + } + }, + "mongoIdKeys": [ + "_id" + ], + "projection": [ + "userProfile.userLocations", "userRoleInformation" + ], + "limit": 200, + "skip": 0 +} + +Upon receiving the response from the DBfind API, ensure that `userProfile.userLocations` and `userRoleInformation` contain valid entries for both district names and corresponding IDs. diff --git a/migrations/updateDistrictNameInProjects/updateDistrictName.js b/migrations/updateDistrictNameInProjects/updateDistrictName.js new file mode 100644 index 00000000..5daac1a5 --- /dev/null +++ b/migrations/updateDistrictNameInProjects/updateDistrictName.js @@ -0,0 +1,180 @@ +/** + * name : updateDistrictName.js + * author : Ankit Shahu + * created-date : 10-Nov-2022 + * Description : Update District Name in projects where district name miss matching + */ + +const path = require("path"); +let rootPath = path.join(__dirname, "../../"); +require("dotenv").config({ path: rootPath + "/.env" }); + +let _ = require("lodash"); +let mongoUrl = process.env.MONGODB_URL; +let dbName = mongoUrl.split("/").pop(); +let url = mongoUrl.split(dbName)[0]; +var MongoClient = require("mongodb").MongoClient; +var ObjectId = require("mongodb").ObjectID; + +var fs = require("fs"); +const request = require("request"); + +const userServiceUrl = "http://learner-service:9000"; +const endPoint = "/v1/location/search"; + +(async () => { + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + let updatedProjectIds = []; + + let projectIds = [ + new ObjectId("629739efe95af10009e7e57c"), + new ObjectId("6299eb19e95af10009e8e28f"), + new ObjectId("629a34b3e95af10009e91338"), + new ObjectId("629a3555e95af10009e913a6"), + new ObjectId("62b0416910889b000746d088"), + new ObjectId("62b2a009cd5f3c00077a1c47"), + new ObjectId("62b56b29cd5f3c00077a3282"), + new ObjectId("62b98078cd5f3c00077a4814"), + new ObjectId("63019b518f78ad0007651260"), + new ObjectId("6307813b8f78ad0007652524"), + new ObjectId("62985b30e95af10009e81ea0"), + new ObjectId("62985b345e195a0007d8fff0"), + new ObjectId("62985b365e195a0007d8fff4"), + ]; + + let filedMissmatch = "district"; + + //get project information from db + let projectDocuments = await db + .collection("projects") + .find({ + _id: { $in: projectIds }, + }) + .project({ + _id: 1, + userProfile: 1, + userRoleInformation: 1, + }) + .toArray(); + let updateProjectDocument = []; + + for (let count = 0; count < projectDocuments.length; count++) { + let filterData = { + id: projectDocuments[count].userRoleInformation[filedMissmatch], + }; + let correctDistrictData = await locationSearch(filterData); + let userLocationsWithCorrectData = []; + if (correctDistrictData.success) { + let userLocations = projectDocuments[count].userProfile.userLocations; + userLocationsWithCorrectData = userLocations.filter(function ( + locations + ) { + return ( + locations.id !== + projectDocuments[count].userRoleInformation[filedMissmatch] + ); + }); + userLocationsWithCorrectData.push(...correctDistrictData.data); + } + if (userLocationsWithCorrectData > 0) { + let updateObject = { + updateOne: { + filter: { + _id: projectDocuments[count]._id, + }, + update: { + $set: { + "userProfile.userLocations": userLocationsWithCorrectData, + }, + }, + }, + }; + updateProjectDocument.push(updateObject); + updatedProjectIds.push(projectDocuments[count]._id); + } + } + // will create BulkWrite Query to otimize excution + if (updateProjectDocument > 0) { + await db.collection("projects").bulkWrite(updateProjectDocument); + } + fs.writeFile( + `updateDistrictName.json`, + + JSON.stringify(updatedProjectIds), + + function (err) { + if (err) { + console.error("Crap happens"); + } + } + ); + // used to get location information from learn service + function locationSearch(filterData) { + return new Promise(async (resolve, reject) => { + try { + let bodyData = {}; + bodyData["request"] = {}; + bodyData["request"]["filters"] = filterData; + const url = userServiceUrl + endPoint; + const options = { + headers: { + "content-type": "application/json", + }, + json: bodyData, + }; + + request.post(url, options, requestCallback); + + let result = { + success: true, + }; + + function requestCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = data.body; + if ( + response.responseCode === "OK" && + response.result && + response.result.response && + response.result.response.length > 0 + ) { + let entityResult = new Array(); + response.result.response.map((entityData) => { + let entity = _.omit(entityData, ["identifier"]); + entityResult.push(entity); + }); + result["data"] = entityResult; + result["count"] = response.result.count; + } else { + result.success = false; + } + } + return resolve(result); + } + + setTimeout(function () { + return resolve( + (result = { + success: false, + }) + ); + }, 5000); + } catch (error) { + return reject(error); + } + }); + } + + //write updated project ids to file + + console.log("Updated Project Count : ", updatedProjectIds.length); + console.log("completed"); + connection.close(); + } catch (error) { + console.log(error); + } +})().catch((err) => console.error(err)); diff --git a/migrations/updateDistrictNameInProjects/updateUserProfileDistrictNameMissing.js b/migrations/updateDistrictNameInProjects/updateUserProfileDistrictNameMissing.js new file mode 100644 index 00000000..dfb6a7e8 --- /dev/null +++ b/migrations/updateDistrictNameInProjects/updateUserProfileDistrictNameMissing.js @@ -0,0 +1,183 @@ +/** + * name : updateUserProfileDistrictNameMissing.js + * author : Ankit Shahu + * created-date : 10-Nov-2023 + * Description : update delhi projects where district name is missing + */ + +const path = require("path"); +let rootPath = path.join(__dirname, "../../"); +require("dotenv").config({ path: rootPath + "/.env" }); + +let _ = require("lodash"); +let mongoUrl = process.env.MONGODB_URL; +let dbName = mongoUrl.split("/").pop(); +let url = mongoUrl.split(dbName)[0]; +var MongoClient = require("mongodb").MongoClient; +var ObjectId = require("mongodb").ObjectID; + +var fs = require("fs"); +const request = require("request"); + +const userServiceUrl = "http://learner-service:9000"; +const userReadEndpoint = "/private/user/v1/read"; +(async () => { + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + let updatedProjectIds = []; + let failedToGetProfileProjectIds = []; + + let projectIds = [ + new ObjectId("638ef0d0be39f5000813f984"), + new ObjectId("63b3b13d01da8e0008597faa"), + ]; + + //get project information from db + let projectDocuments = await db + .collection("projects") + .find({ + _id: { $in: projectIds }, + }) + .project({ + _id: 1, + userProfile: 1, + userRoleInformation: 1, + userId: 1, + }) + .toArray(); + + // update Projects With Profile and UserRoleInformation if both are missing + if (projectDocuments.length > 0) { + let updateProjectUserRoleAndProfile = []; + for (let count = 0; count < projectDocuments.length; count++) { + let projectId = projectDocuments[count]._id; + let userId = projectDocuments[count].userId; + + //call profile api to get user profile + let profile = await profileReadPrivate(userId); + + if (profile.success && profile.data && profile.data.response) { + //get userRoleInformation from Resuable function + let userDetailsForProject = + await getUserRoleAndProfileWithUpdatedData( + profile.data.response, + projectDocuments[count].userRoleInformation + ); + let updateObject = { + updateOne: { + filter: { + _id: projectId, + }, + update: { + $set: { + userRoleInformation: + userDetailsForProject.userRoleInformation, + userProfile: userDetailsForProject.profile, + }, + }, + }, + }; + updateProjectUserRoleAndProfile.push(updateObject); + updatedProjectIds.push(projectId); + } else if (!profile.success) { + failedToGetProfileProjectIds.push(projectId); + } + } + // will create BulkWrite Query to otimize excution + if (updateProjectUserRoleAndProfile.length > 0) { + await db + .collection("projects") + .bulkWrite(updateProjectUserRoleAndProfile); + } + } + //write updated project ids to file + fs.writeFile( + `updateUserProfileDistrictNameMissing.json`, + + JSON.stringify({ + updatedProjectIds: updatedProjectIds, + failedToGetProfileProjectIds: failedToGetProfileProjectIds, + }), + + function (err) { + if (err) { + console.error("Crap happens"); + } + } + ); + // this function is used to get userRoleInformation from userProfile + function getUserRoleAndProfileWithUpdatedData( + profile, + userRoleInformation + ) { + for (let counter = 0; counter < profile.userLocations.length; counter++) { + if (profile.userLocations[counter].type === "school") { + userRoleInformation.school = profile.userLocations[counter].code; + } else { + userRoleInformation[profile.userLocations[counter].type] = + profile.userLocations[counter].id; + } + } + if (userRoleInformation.hasOwnProperty("role")) { + //get RoleInformation + let userRoles = userRoleInformation.role.split(","); + profile.profileUserType = new Array(); + for (let j = 0; j < userRoles.length; j++) { + if (userRoles[j].toUpperCase() === "TEACHER") { + // If subRole is teacher + profile.profileUserType.push({ + subType: null, + type: "teacher", + }); + } else { + // If subRole is not teacher + profile.profileUserType.push({ + subType: userRoles[j].toLowerCase(), + type: "administrator", + }); + } + } + } + return { userRoleInformation: userRoleInformation, profile: profile }; + } + + function profileReadPrivate(userId) { + return new Promise(async (resolve, reject) => { + try { + let url = userServiceUrl + userReadEndpoint + "/" + userId; + const options = { + headers: { + "content-type": "application/json", + }, + }; + request.get(url, options, userReadCallback); + let result = { + success: true, + }; + function userReadCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = JSON.parse(data.body); + if (response.responseCode === "OK") { + result["data"] = response.result; + } else { + result.success = false; + } + } + return resolve(result); + } + } catch (error) { + return reject(error); + } + }); + } + + console.log("Updated Project Count : ", updatedProjectIds.length); + console.log("completed"); + connection.close(); + } catch (error) { + console.log(error); + } +})().catch((err) => console.error(err)); diff --git a/migrations/updateProjectDocuments/migratePrivateProjectToPublicProgram.js b/migrations/updateProjectDocuments/migratePrivateProjectToPublicProgram.js new file mode 100644 index 00000000..1a5866df --- /dev/null +++ b/migrations/updateProjectDocuments/migratePrivateProjectToPublicProgram.js @@ -0,0 +1,166 @@ +/** + * name : updatePrivateProgramInProject.js + * author : Ankit Shahu + * created-date : 02-Feb-2023 + * Description : Migration script for update project + */ + + const path = require("path"); + let rootPath = path.join(__dirname, '../../') + require('dotenv').config({ path: rootPath+'/.env' }) + + let _ = require("lodash"); + let mongoUrl = process.env.MONGODB_URL; + let dbName = mongoUrl.split("/").pop(); + let url = mongoUrl.split(dbName)[0]; + var MongoClient = require('mongodb').MongoClient; + var ObjectId = require('mongodb').ObjectID; + + var fs = require('fs'); + + +(async () => { + + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + + let updatedProjectIds = []; + let deletedSolutionIds = []; + let deletedProgramIds = []; + + + + //get all projectss id where user profile is not there. + let projectDocument = await db.collection('projects').find({ + userRoleInformation: {$exists : false}, + isAPrivateProgram: true, + }).project({_id:1,userProfile:1}).toArray(); + + + let chunkOfProjectDocument = _.chunk(projectDocument, 10); + // console.log(chunkOfProjectDocument) + let projectIds; + + for (let pointerToProject = 0; pointerToProject < chunkOfProjectDocument.length; pointerToProject++) { + projectIds = await chunkOfProjectDocument[pointerToProject].map( + projectDoc => { + return projectDoc._id; + } + ); + + + // get project documents from projectss collection in Array + let projectDocuments = await db.collection('projects').find({ + _id: { $in :projectIds } + }).project({_id:1,userProfile:1}).toArray(); + //iterate project documents one by one + for(let counter = 0; counter < projectDocuments.length; counter++) { + + + if(projectDocuments[counter].hasOwnProperty("solutionId") && projectDocuments[counter].isAPrivateProgram){ + // find solution document form solution collection + let solutionDocument = await db.collection('solutions').find({ + _id: projectDocuments[counter].solutionId, + parentSolutionId : {$exists:true}, + isAPrivateProgram : true + }).project({}).toArray({}) + //find program document form program collection + if(solutionDocument.length == 1){ + + // find parent solution document in same collection + let parentSolutionDocument = await db.collection('solutions').find({ + _id: solutionDocument[0].parentSolutionId}).project({}).toArray({}); + //varibale to update project document + let updateProjectDocument = { + "$set" : {} + }; + updateProjectDocument["$set"]["solutionId"] = parentSolutionDocument[0]._id + updateProjectDocument["$set"]["isAPrivateProgram"] = parentSolutionDocument[0].isAPrivateProgram + updateProjectDocument["$set"]["solutionInformation"] = { + name: parentSolutionDocument[0].name, + description: parentSolutionDocument[0].description, + externalId: parentSolutionDocument[0].externalId, + _id: parentSolutionDocument[0]._id, + } + updateProjectDocument["$set"]["solutionExternalId"] = parentSolutionDocument[0].externalId, + updateProjectDocument["$set"]["programId"] = parentSolutionDocument[0].programId, + updateProjectDocument["$set"]["programExternalId"] = parentSolutionDocument[0].programExternalId + updateProjectDocument["$set"]["programInformation"] = { + _id : parentSolutionDocument[0].programId, + name : parentSolutionDocument[0].programName, + externalId : parentSolutionDocument[0].programExternalId, + description : parentSolutionDocument[0].programDescription, + isAPrivateProgram : parentSolutionDocument[0].isAPrivateProgram + } + if(projectDocuments[counter].hasOwnProperty("userProfile")) + { + let userLocations = projectDocuments[counter].userProfile.userLocations + let userRoleInfomration = {} + //get data in userRoleInfomration key + for(let userLocationCounter = 0; userLocationCounter < userLocations.length; userLocationCounter++){ + if(userLocations[userLocationCounter].type !== "school"){ + userRoleInfomration[userLocations[userLocationCounter].type] = userLocations[userLocationCounter].id + }else{ + userRoleInfomration[userLocations[userLocationCounter].type] = userLocations[userLocationCounter].code + } + } + let Roles = "" + for(let roleCounter = 0; roleCounter < projectDocuments[counter].userProfile.profileUserTypes.length; roleCounter++){ + Roles = Roles !== "" ? Roles+"," : Roles + Roles += (projectDocuments[counter].userProfile.profileUserTypes[roleCounter].subType ? projectDocuments[counter].userProfile.profileUserTypes[roleCounter].subType.toUpperCase() : projectDocuments[counter].userProfile.profileUserTypes[roleCounter].type.toUpperCase()) + } + userRoleInfomration.Role = Roles + updateProjectDocument["$set"]["userRoleInformation"] = userRoleInfomration + } + + //push all updated and deleted id in arrays and save in file + updatedProjectIds.push(projectDocuments[counter]._id) + deletedSolutionIds.push(projectDocuments[counter].solutionId) + deletedProgramIds.push(projectDocuments[counter].programId) + + // update project documents + await db.collection('projects').findOneAndUpdate({ + "_id" : projectDocuments[counter]._id + },updateProjectDocument); + + await db.collection('solutions').deleteOne({ + _id: projectDocuments[counter].solutionId + }) + await db.collection('programs').deleteOne({ + _id: projectDocuments[counter].programId + }) + } + } + } + + + + + + + + + //write updated project ids to file + fs.writeFile( + 'updatedProjectIdsAll.json', + + JSON.stringify({updatedProjectIds: updatedProjectIds,deletedProgramIds: deletedProgramIds,deletedSolutionIds: deletedSolutionIds}), + + function (err) { + if (err) { + console.error('Crap happens'); + } + } + ); + } + console.log("Updated Project Count : ", updatedProjectIds.length) + console.log("deleted program Count : ", deletedProgramIds.length) + console.log("deleted solutionId Count : ", deletedSolutionIds.length) + console.log("completed") + connection.close(); + } + catch (error) { + console.log(error) + } +})().catch(err => console.error(err)); \ No newline at end of file diff --git a/migrations/updateProjectDocuments/removeAttachmentsNotUploadedInCloud.js b/migrations/updateProjectDocuments/removeAttachmentsNotUploadedInCloud.js new file mode 100644 index 00000000..b0c29f74 --- /dev/null +++ b/migrations/updateProjectDocuments/removeAttachmentsNotUploadedInCloud.js @@ -0,0 +1,210 @@ +/** + * name : updatePrivateProgramInProject.js + * author : Ankit Shahu + * created-date : 02-Feb-2023 + * Description : Migration script for update project + */ + "use strict"; +const path = require("path"); +let rootPath = path.join(__dirname, '../../') +require('dotenv').config({ path: rootPath+'/.env' }) + +let _ = require("lodash"); +let mongoUrl = process.env.MONGODB_URL; +let dbName = mongoUrl.split("/").pop(); +let url = mongoUrl.split(dbName)[0]; +var MongoClient = require('mongodb').MongoClient; +var ObjectId = require('mongodb').ObjectID; +var request = require('request'); +var fs = require('fs'); +const { at } = require("lodash"); + +const filePathUrl = "https://samikshaprod.blob.core.windows.net/samiksha/"; + + +(async () => { + + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + // check project attachments for isUploaded = false data and remove the attachment object + let collectionDocs = await db.collection('projects').find({ + "$or": [ + { + "attachments.isUploaded": false + }, + { + "tasks.attachments.isUploaded": false + } + ] + }).project({_id:1}).toArray(); + + //varibale to store all projectIds which are updated + let projectIds = []; + collectionDocs.forEach( eachDoc => { + projectIds.push(eachDoc._id); + }) + + let chunkOfProjectIds = _.chunk(projectIds, 10); + let UpdatedProjectId = [] + //loop project chunks + for ( let chunkPointer = 0; chunkPointer < chunkOfProjectIds.length; chunkPointer++ ) { + + //chunk of project ids + let projectId = chunkOfProjectIds[chunkPointer]; + + //loop for chunk of project id in chunk + for ( let projectIdpointer = 0 ; projectIdpointer < projectId.length; projectIdpointer++ ) { + let id = projectId[projectIdpointer]; + + //pull project Data from DB + let pullProjectDataDB = await db.collection('projects').find({ + _id: id + }).project({}).toArray({}) + + //store in varibale to avoid using [0] + const pullProjectData = pullProjectDataDB[0] + + const allTasksBeforeValidation = JSON.stringify(pullProjectData.tasks) + let allAttachmentsBeforeValidation = "" + + //update Object + let updateObject = { + "$set" : {} + } + + //check if project Document has attachments or not if present then checks length of attachment it should be greater than 0 + if( pullProjectData.hasOwnProperty('attachments') && pullProjectData.attachments.length > 0 ) { + allAttachmentsBeforeValidation = JSON.stringify(pullProjectData.attachments) + //assign attachmentPresent object to update variable + updateObject['$set']['attachments'] = await validatedAttachments(pullProjectData.attachments) + } + + //check if project has tasks available and length of tasks array should be greater than 0 + if( pullProjectData.hasOwnProperty('tasks') && pullProjectData.tasks.length > 0){ + + let tasks = pullProjectData.tasks + updateObject['$set']['tasks'] = [...tasks] + + //for loop for each task + for(let taskCounter = 0; taskCounter< tasks.length; taskCounter++){ + + //checks if task object has key attachment present or not + if(tasks[taskCounter].hasOwnProperty("attachments") && tasks[taskCounter].attachments.length>0){ + //assign attachmentPresent array to task attachments + updateObject['$set']['tasks'][taskCounter].attachments = await validatedAttachments((updateObject['$set']['tasks'][taskCounter].attachments)) + } + } + } + + //push project id which is validated + if(JSON.stringify(updateObject["$set"]["tasks"]) != allTasksBeforeValidation || JSON.stringify(updateObject["$set"]["attachments"]) != allAttachmentsBeforeValidation){ + + if(JSON.stringify(updateObject["$set"]["tasks"]) == allTasksBeforeValidation){ + delete updateObject["$set"].tasks + } + if(JSON.stringify(updateObject["$set"]["attachments"]) == allAttachmentsBeforeValidation){ + delete updateObject["$set"].attachments + } + if(updateObject["$set"].hasOwnProperty("attachments") || updateObject["$set"].hasOwnProperty("tasks")){ + UpdatedProjectId.push(id) + //update project with new varibales + await db.collection('projects').updateOne({_id:id},updateObject)} + } + } + + + } + console.log(UpdatedProjectId) + + + + fs.writeFile( + 'updatedProjectWithAttachments.json', + + JSON.stringify({updatedProjectIds: UpdatedProjectId}), + + function (err) { + if (err) { + console.error('Crap happens'); + } + } + ); + + + + async function validatedAttachments(attachments){ + //varibale to store updated attachment objects + let attachmentsPresent = [] + + //for loop for each attachment + for(let attachmentCounter = 0; attachmentCounter < attachments.length; attachmentCounter++){ + + //check if isUploaded key is present or not and isUploaded should be false and checks type of attachment + if(attachments[attachmentCounter].hasOwnProperty("isUploaded") && !attachments[attachmentCounter].isUploaded && attachments[attachmentCounter].type !== "link"){ + + //checks if document is present or not + let documentExist = await getDocumentStatus(attachments[attachmentCounter].sourcePath) + //if present then push object to new array and update isUploaded to true + if(documentExist.success){ + attachments[attachmentCounter].isUploaded = true + attachmentsPresent.push(attachments[attachmentCounter]) + } + }else{ + //if isUploaded is not present then push object to new array and if type is link then also + attachmentsPresent.push(attachments[attachmentCounter]) + } + } + + return attachmentsPresent; + } + + //function to check if document exists + function getDocumentStatus (sourcePath) { + return new Promise(async (resolve, reject) => { + try { + let url = filePathUrl + sourcePath; + const options = { + headers : { + } + }; + request.get(url,options,userReadCallback); + let result = { + success : true + }; + function userReadCallback(err, data) { + if (err) { + result.success = false; + } else { + if( data.statusCode === 200 ) { + result.success = true; + } else { + result.success = false; + } + + } + return resolve(result); + } + setTimeout(function () { + return resolve (result = { + success : false + }); + }, 5000); + + } catch (error) { + return reject(error); + } + }) + } + + + console.log("Updated Projects", UpdatedProjectId.length) + console.log("finished project attachment deletion based on isUploaded:= false, completed...") + connection.close(); + } + catch (error) { + console.log(error) + } +})().catch(err => console.error(err)); + + diff --git a/migrations/updateUserProfileAndMissMatchOfRoleInformation/Readme.md b/migrations/updateUserProfileAndMissMatchOfRoleInformation/Readme.md new file mode 100644 index 00000000..826a1b5c --- /dev/null +++ b/migrations/updateUserProfileAndMissMatchOfRoleInformation/Readme.md @@ -0,0 +1,97 @@ +## Migrations + +#### Steps to run the script files + +This script is intended to address a particular bug in Diksha Prod, and it is unrelated to the 6.0.0 release. + +Here is the ticket for this issue:- (Click Here)[https://project-sunbird.atlassian.net/browse/ED-3101]. + +In order to execute this migration script, we need to first log in to the pod where the service is running and then proceed with the provided instructions. + +This script will update projects in cases where the user profile is missing and there is a mismatch between userProfile and userRoleInformations. + +### Step 1: + + Navigate to /opt/projects/migrations/updateUserProfileAndMissMatchOfRoleInformation/ + +### Step 2: + +Run the script to update projects. + + node updateProjectWithProfileData.js + +### Setp 3: Validations + +upon executing this script we can validate the data using DBfind api + +#### check for userRoleInformation missing in projects + +Open the `updatedProjectIds.json` file and extract the project IDs from `userRoleInformationMissingProjectIds`. Utilize these project IDs in the following DBfind API to retrieve the corresponding projects. + +{ + "query": { + "_id": { + "$in": [ + // Add project ids here + ] + } + }, + "mongoIdKeys": [ + "_id" + ], + "projection": [ + "userRoleInformation" + ], + "limit": 200, + "skip": 0 +} + +After obtaining the response, validate that all projects include userRoleInformation entries. + +#### check for userProfile missing in projects + +Open the `updatedProjectIds.json` file and extract the project IDs from `userProfileMissingProjectIds`. Utilize these project IDs in the following DBfind API to retrieve the corresponding projects. + +{ + "query": { + "_id": { + "$in": [ + // Add project ids here + ] + } + }, + "mongoIdKeys": [ + "_id" + ], + "projection": [ + "userProfile" + ], + "limit": 200, + "skip": 0 +} + +After obtaining the response, validate that all projects include userProfile entries. + +#### check for userRoleInformation and userProfile missing in projects + +Open the `updatedProjectIds.json` file and extract the project IDs from `bothDataMissingProjectIds`. Utilize these project IDs in the following DBfind API to retrieve the corresponding projects. + +{ + "query": { + "_id": { + "$in": [ + // Add project ids here + ] + } + }, + "mongoIdKeys": [ + "_id" + ], + "projection": [ + "userRoleInformation","userProfile" + ], + "limit": 200, + "skip": 0 +} + +After obtaining the response, validate that all projects include both userRoleInformation and userProfile entries. \ No newline at end of file diff --git a/migrations/updateUserProfileAndMissMatchOfRoleInformation/updateProjectWithProfileData.js b/migrations/updateUserProfileAndMissMatchOfRoleInformation/updateProjectWithProfileData.js new file mode 100644 index 00000000..a7a572fa --- /dev/null +++ b/migrations/updateUserProfileAndMissMatchOfRoleInformation/updateProjectWithProfileData.js @@ -0,0 +1,449 @@ +/** + * name : updateProjectWithProfileData.js + * author : Ankit Shahu + * created-date : 10-Nov-2023 + * Description : Migration script for update userProfile in project + */ + +const path = require("path"); +let rootPath = path.join(__dirname, "../../"); +require("dotenv").config({ path: rootPath + "/.env" }); + +let _ = require("lodash"); +let mongoUrl = process.env.MONGODB_URL; +let dbName = mongoUrl.split("/").pop(); +let url = mongoUrl.split(dbName)[0]; +var MongoClient = require("mongodb").MongoClient; +var ObjectId = require("mongodb").ObjectID; + +var fs = require("fs"); +const request = require("request"); + +const userServiceUrl = "http://learner-service:9000"; + +const endPoint = "/v1/location/search"; +const userReadEndpoint = "/private/user/v1/read"; + +(async () => { + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + let updatedProjectIds = { + userRoleInformationMissingProjectIds: [], + userProfileMissingProjectIds: [], + bothDataMissingProjectIds: [], + failedToGetProfileProjectIds: [], + }; + + // get all projects id where user profile is not there. + let projectDocument = await db + .collection("projects") + .find({ userProfile: { $exists: false } }) + .project({ _id: 1 }) + .toArray(); + + //make it in chunks so that we can iterate over + let chunkOfProjectDocument = _.chunk(projectDocument, 100); + let projectIds; + + for ( + let pointerToProject = 0; + pointerToProject < chunkOfProjectDocument.length; + pointerToProject++ + ) { + projectIds = await chunkOfProjectDocument[pointerToProject].map( + (projectDoc) => { + return projectDoc._id; + } + ); + + //get project documents along with userId, userProfile and userRoleInformation + let projectWithIncompleteData = await db + .collection("projects") + .find({ + _id: { $in: projectIds }, + }) + .project({ + _id: 1, + userId: 1, + userProfile: 1, + userRoleInformation: 1, + }) + .toArray(); + + let projectSeggregate = { + userProfileMissing: [], + userRoleInformationMissing: [], + bothDataMissing: [], + }; + + // seggredate project data based on condition + for (let count = 0; count < projectWithIncompleteData.length; count++) { + if ( + !projectWithIncompleteData[count].hasOwnProperty( + "userRoleInformation" + ) && + !projectWithIncompleteData[count].hasOwnProperty("userProfile") + ) { + projectSeggregate.bothDataMissing.push( + projectWithIncompleteData[count] + ); + } else if ( + !projectWithIncompleteData[count].hasOwnProperty( + "userRoleInformation" + ) + ) { + projectSeggregate.userRoleInformationMissing.push( + projectWithIncompleteData[count] + ); + } else if ( + !projectWithIncompleteData[count].hasOwnProperty("userProfile") + ) { + projectSeggregate.userProfileMissing.push( + projectWithIncompleteData[count] + ); + } + } + + //update userRoleInformations if userProfile is present in project + if (projectSeggregate.userRoleInformationMissing.length > 0) { + let updateUserRoleInformationInProjects = []; + for ( + let count = 0; + count < projectSeggregate.userRoleInformationMissing.length; + count++ + ) { + let userRoleInformationForProject = + await getUserRoleInformationFromProfile( + projectSeggregate.userRoleInformationMissing[count].userProfile + ); + let updateProjectWithRoleInformation = { + updateOne: { + filter: { + _id: projectSeggregate.userRoleInformationMissing[count]._id, + }, + update: { + $set: { userRoleInformation: userRoleInformationForProject }, + }, + }, + }; + + updateUserRoleInformationInProjects.push( + updateProjectWithRoleInformation + ); + updatedProjectIds.userRoleInformationMissingProjectIds.push( + projectSeggregate.userRoleInformationMissing[count]._id + ); + } + // will create BulkWrite Query to otimize excution + await db + .collection("projects") + .bulkWrite(updateUserRoleInformationInProjects); + } + + // update Projects With Profile and UserRoleInformation if both are missing + if (projectSeggregate.bothDataMissing.length > 0) { + let updateProjectWithProfileAndUserRoleInformation = []; + for ( + let count = 0; + count < projectSeggregate.bothDataMissing.length; + count++ + ) { + let projectIdWithoutUserProfile = + projectSeggregate.bothDataMissing[count]._id; + let userId = projectSeggregate.bothDataMissing[count].userId; + + //call profile api to get user profile + let profile = await profileReadPrivate(userId); + + if (profile.success && profile.data && profile.data.response) { + //get userRoleInformation from Resuable function + let userRoleInformationForProject = + await getUserRoleInformationFromProfile(profile.data.response); + let updateObject = { + updateOne: { + filter: { + _id: projectIdWithoutUserProfile, + }, + update: { + $set: { + userRoleInformation: userRoleInformationForProject, + userProfile: profile.data.response, + }, + }, + }, + }; + updateProjectWithProfileAndUserRoleInformation.push(updateObject); + updatedProjectIds.bothDataMissingProjectIds.push( + projectIdWithoutUserProfile + ); + } else if (!profile.success) { + updatedProjectIds.failedToGetProfileProjectIds.push( + projectIdWithoutUserProfile + ); + } + } + // will create BulkWrite Query to otimize excution + if (updateProjectWithProfileAndUserRoleInformation.length > 0) { + await db + .collection("projects") + .bulkWrite(updateProjectWithProfileAndUserRoleInformation); + } + } + + // Update Project with user Profile if userRoleInformation is present + if (projectSeggregate.userProfileMissing.length > 0) { + let updateProjectWithUserProfile = []; + for ( + let count = 0; + count < projectSeggregate.userProfileMissing.length; + count++ + ) { + let projectIdWithoutUserProfile = + projectSeggregate.userProfileMissing[count]._id; + let userId = projectSeggregate.userProfileMissing[count].userId; + + //call profile api to get user profile + let profile = await profileReadPrivate(userId); + if (profile.success && profile.data && profile.data.response) { + let userProfile = profile.data.response; + let userRoleInformation = await getUserRoleInformationFromProfile( + userProfile + ); + let bothRoleInformationEqual = _.isEqual( + userRoleInformation, + projectSeggregate.userProfileMissing[count].userRoleInformation + ); + + if (!bothRoleInformationEqual) { + let userRoleInformationkeys = Object.keys( + projectSeggregate.userProfileMissing[count].userRoleInformation + ); + userProfile = _.omit(userProfile, ["userLocations"]); + userProfile.userLocations = []; + for (let i = 0; i < userRoleInformationkeys.length; i++) { + if (userRoleInformationkeys[i].toUpperCase() === "SCHOOL") { + //get school data + let filterData = { + code: projectSeggregate.userProfileMissing[count] + .userRoleInformation[userRoleInformationkeys[i]], + }; + let schoolData = await locationSearch(filterData); + if (schoolData.success) { + userProfile.userLocations.push(...schoolData.data); + } + } else if ( + userRoleInformationkeys[i].toUpperCase() === "ROLE" + ) { + //get RoleInformation + let userRoles = + projectSeggregate.userProfileMissing[ + count + ].userRoleInformation[userRoleInformationkeys[i]].split( + "," + ); + userProfile.profileUserType = new Array(); + for (let j = 0; j < userRoles.length; j++) { + if (userRoles[j].toUpperCase() === "TEACHER") { + // If subRole is teacher + userProfile.profileUserType.push({ + subType: null, + type: "teacher", + }); + } else { + // If subRole is not teacher + userProfile.profileUserType.push({ + subType: userRoles[j].toLowerCase(), + type: "administrator", + }); + } + } + } else { + // get Other RoleInformation data + let filterData = { + id: projectSeggregate.userProfileMissing[count] + .userRoleInformation[userRoleInformationkeys[i]], + }; + let userLocations = await locationSearch(filterData); + if (userLocations.success) { + userProfile.userLocations.push(...userLocations.data); + } + } + } + } + + let updateObject = { + updateOne: { + filter: { + _id: projectIdWithoutUserProfile, + }, + update: { + $set: { + userProfile: userProfile, + }, + }, + }, + }; + + updateProjectWithUserProfile.push(updateObject); + updatedProjectIds.userProfileMissingProjectIds.push( + projectIdWithoutUserProfile + ); + } else if (!profile.success) { + updatedProjectIds.failedToGetProfileProjectIds.push( + projectIdWithoutUserProfile + ); + } + } + // will create BulkWrite Query to otimize excution + if (updateProjectWithUserProfile.length > 0) { + await db + .collection("projects") + .bulkWrite(updateProjectWithUserProfile); + } + } + //write updated project ids to file + fs.writeFile( + `updatedProjectIds.json`, + + JSON.stringify(updatedProjectIds), + + function (err) { + if (err) { + console.error("Crap happens"); + } + } + ); + } + // this function is used to get userRoleInformation from userProfile + function getUserRoleInformationFromProfile(profile) { + let userRoleInformationForProject = {}; + for (let counter = 0; counter < profile.userLocations.length; counter++) { + if (profile.userLocations[counter].type === "school") { + userRoleInformationForProject.school = + profile.userLocations[counter].code; + } else { + userRoleInformationForProject[profile.userLocations[counter].type] = + profile.userLocations[counter].id; + } + } + let Roles = []; + for ( + let counter = 0; + counter < profile.profileUserType.length; + counter++ + ) { + if ( + profile.profileUserType[counter].hasOwnProperty("subType") && + profile.profileUserType[counter].subType !== null + ) { + Roles.push(profile.profileUserType[counter].subType.toUpperCase()); + } else { + Roles.push(profile.profileUserType[counter].type.toUpperCase()); + } + } + userRoleInformationForProject.role = Roles.join(","); + return userRoleInformationForProject; + } + // this function is used to get location data + function locationSearch(filterData) { + return new Promise(async (resolve, reject) => { + try { + let bodyData = {}; + bodyData["request"] = {}; + bodyData["request"]["filters"] = filterData; + const url = userServiceUrl + endPoint; + const options = { + headers: { + "content-type": "application/json", + }, + json: bodyData, + }; + + request.post(url, options, requestCallback); + + let result = { + success: true, + }; + + function requestCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = data.body; + if ( + response.responseCode === "OK" && + response.result && + response.result.response && + response.result.response.length > 0 + ) { + let entityResult = new Array(); + response.result.response.map((entityData) => { + let entity = _.omit(entityData, ["identifier"]); + entityResult.push(entity); + }); + result["data"] = entityResult; + result["count"] = response.result.count; + } else { + result.success = false; + } + } + return resolve(result); + } + + setTimeout(function () { + return resolve( + (result = { + success: false, + }) + ); + }, 5000); + } catch (error) { + return reject(error); + } + }); + } + //this function to get the user profileData from learn service + + function profileReadPrivate(userId) { + return new Promise(async (resolve, reject) => { + try { + // <--- Important : This url endpoint is private do not use it for regular workflows ---> + let url = userServiceUrl + userReadEndpoint + "/" + userId; + const options = { + headers: { + "content-type": "application/json", + }, + }; + request.get(url, options, userReadCallback); + let result = { + success: true, + }; + function userReadCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = JSON.parse(data.body); + if (response.responseCode === "OK") { + result["data"] = response.result; + } else { + result.success = false; + } + } + return resolve(result); + } + } catch (error) { + return reject(error); + } + }); + } + + console.log("Updated Project Count : ", updatedProjectIds.length); + console.log("completed"); + connection.close(); + } catch (error) { + console.log(error); + } +})().catch((err) => console.error(err)); + +let; diff --git a/migrations/userProfileAbsentInProjects/setUserProfileInProjects.js b/migrations/userProfileAbsentInProjects/setUserProfileInProjects.js new file mode 100644 index 00000000..c739d924 --- /dev/null +++ b/migrations/userProfileAbsentInProjects/setUserProfileInProjects.js @@ -0,0 +1,407 @@ +/** + * name : updateUserProfileInProjects.js + * author : Priyanka Pradeep + * created-date : 10-Nov-2022 + * Description : Migration script for update userProfile in project + */ + + const path = require("path"); + let rootPath = path.join(__dirname, '../../') + require('dotenv').config({ path: rootPath+'/.env' }) + + let _ = require("lodash"); + let mongoUrl = process.env.MONGODB_URL; + let dbName = mongoUrl.split("/").pop(); + let url = mongoUrl.split(dbName)[0]; + var MongoClient = require('mongodb').MongoClient; + var ObjectId = require('mongodb').ObjectID; + + var fs = require('fs'); + const request = require('request'); + + const userServiceUrl = "http://learner-service:9000"; + const endPoint = "/v1/location/search"; + const userReadEndpoint = "/private/user/v1/read"; + +(async () => { + + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + + let updatedProjectIds = []; + + //get all projects id where user profile is not there. + let projectDocument = await db.collection('projects').find({ + userRoleInformation: {$exists : true}, + userProfile: {$exists : false}, + }).project({ "_id": 1}).toArray(); + + + let chunkOfProjectDocument = _.chunk(projectDocument, 10); + let projectIds; + + for (let pointerToProject = 0; pointerToProject < chunkOfProjectDocument.length; pointerToProject++) { + + projectIds = await chunkOfProjectDocument[pointerToProject].map( + projectDoc => { + return projectDoc._id; + } + ); + + //get user ids of project without profile + let userIdsWithoutProfile = await db.collection('projects').find({ + _id: { $in : projectIds } + }).project({ + "_id" : 1, + "userId" : 1 + }).toArray(); + + //loop userIds- These user profiles are absent in project + for ( let count = 0; count < userIdsWithoutProfile.length; count++ ) { + + let projectIdWithoutUserProfile = userIdsWithoutProfile[count]._id; + let userId = userIdsWithoutProfile[count].userId; + + //call profile api to get user profile + let profile = await profileReadPrivate(userId); + + // update project with profile + if( profile.success && profile.data && profile.data.response ) { + let updateObject = { + "$set" : {} + }; + updateObject["$set"]["userProfile"] = profile.data.response; + + await db.collection('projects').findOneAndUpdate({ + "_id" : projectIdWithoutUserProfile + },updateObject); + } + + } + + + let projectDocuments = await db.collection('projects').find({ + _id: { $in : projectIds}, + userProfile: {$exists : true} + }).project({ + "_id": 1, + "userRoleInformation" : 1, + "userProfile" : 1 + }).toArray(); + + //loop all projects + for ( let count = 0; count < projectDocuments.length; count++ ) { + + let project = projectDocuments[count]; + let userProfile = project.userProfile; + + + let updateUserProfileRoleInformation = false; // Flag to see if roleInformation i.e. userProfile.profileUserTypes has to be updated based on userRoleInfromation.roles + + if(project.userRoleInformation.role) { // Check if userRoleInformation has role value. + let rolesInUserRoleInformation = project.userRoleInformation.role.split(","); // userRoleInfomration.role can be multiple with comma separated. + + let resetCurrentUserProfileRoles = false; // Flag to reset current userProfile.profileUserTypes i.e. if current role in profile is not at all there in userRoleInformation.roles + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + + // Loop through current roles in userProfile.profileUserTypes + for (let pointerToCurrentProfileUserTypes = 0; pointerToCurrentProfileUserTypes < userProfile.profileUserTypes.length; pointerToCurrentProfileUserTypes++) { + const currentProfileUserType = userProfile.profileUserTypes[pointerToCurrentProfileUserTypes]; + + if(currentProfileUserType.subType && currentProfileUserType.subType !== null) { // If the role has a subType + + // Check if subType exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!project.userRoleInformation.role.toUpperCase().includes(currentProfileUserType.subType.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } else { // If the role subType is null or is not there + + // Check if type exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!project.userRoleInformation.role.toUpperCase().includes(currentProfileUserType.type.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } + } + } + if(resetCurrentUserProfileRoles) { // Reset userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + } + + // Loop through each subRole in userRoleInformation + for (let pointerToRolesInUserInformation = 0; pointerToRolesInUserInformation < rolesInUserRoleInformation.length; pointerToRolesInUserInformation++) { + const subRole = rolesInUserRoleInformation[pointerToRolesInUserInformation]; + + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + if(!_.find(userProfile.profileUserTypes, { 'type': subRole.toLowerCase() }) && !_.find(userProfile.profileUserTypes, { 'subType': subRole.toLowerCase() })) { + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + if(subRole.toUpperCase() === "TEACHER") { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } else { // Make a new entry if userProfile.profileUserTypes is empty or does not exist. + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + if(subRole.toUpperCase() === "TEACHER") { // If subRole is teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } + } + + // Create location only object from userRoleInformation + let userRoleInformationLocationObject = _.omit(project.userRoleInformation, ['role']); + + // All location keys from userRoleInformation + let userRoleInfomrationLocationKeys = Object.keys(userRoleInformationLocationObject); + + let updateUserProfileLocationInformation = false; // Flag to see if userLocations i.e. userProfile.userLocations has to be updated based on userRoleInfromation location values + + // Loop through all location keys. + for (let pointerToUserRoleInfromationLocationKeys = 0; pointerToUserRoleInfromationLocationKeys < userRoleInfomrationLocationKeys.length; pointerToUserRoleInfromationLocationKeys++) { + + const locationType = userRoleInfomrationLocationKeys[pointerToUserRoleInfromationLocationKeys]; // e.g. state, district, school + const locationValue = userRoleInformationLocationObject[locationType]; // Location UUID values or school code. + + // Check if userProfile.userLocations exists and is an array of length > 0 + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + + if(locationType === "school") { // If location type school exist check if same is there in userProfile.userLocations + if(!_.find(userProfile.userLocations, { 'type': "school", 'code': locationValue })) { + updateUserProfileLocationInformation = true; // School does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } else { // Check if location type is there in userProfile.userLocations and has same value as userRoleInformation + if(!_.find(userProfile.userLocations, { 'type': locationType, 'id': locationValue })) { + updateUserProfileLocationInformation = true; // Location does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } + } else { + updateUserProfileLocationInformation = true; + break; + } + } + + + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + if(userProfile.userLocations.length != userRoleInfomrationLocationKeys.length) { + updateUserProfileLocationInformation = true; + } + } + + // If userProfile.userLocations has to be updated, get all values and set in userProfile. + if(updateUserProfileLocationInformation) { + + //update userLocations in userProfile + let locationIds = []; + let locationCodes = []; + let userLocations = new Array; + + userRoleInfomrationLocationKeys.forEach( requestedDataKey => { + if (checkIfValidUUID(userRoleInformationLocationObject[requestedDataKey])) { + locationIds.push(userRoleInformationLocationObject[requestedDataKey]); + } else { + locationCodes.push(userRoleInformationLocationObject[requestedDataKey]); + } + }) + + //query for fetch location using id + if ( locationIds.length > 0 ) { + let locationQuery = { + "id" : locationIds + } + + let entityData = await locationSearch(locationQuery); + if ( entityData.success ) { + userLocations = entityData.data; + } + } + + // query for fetch location using code + if ( locationCodes.length > 0 ) { + let codeQuery = { + "code" : locationCodes + } + + let entityData = await locationSearch(codeQuery); + if ( entityData.success ) { + userLocations = userLocations.concat(entityData.data); + } + } + + if ( userLocations.length > 0 ) { + userProfile["userLocations"] = userLocations; + } + } + + + //update projects if userProfile role or location information is incorrect + if ( updateUserProfileRoleInformation || updateUserProfileLocationInformation ) { + + let updateObject = { + "$set" : {} + }; + if(updateUserProfileRoleInformation) { + updateObject["$set"]["userProfile.profileUserTypes"] = userProfile.profileUserTypes; + updateObject["$set"]["userProfile.userRoleMismatchFoundAndUpdated"] = true; + } + if(updateUserProfileLocationInformation) { + updateObject["$set"]["userProfile.userLocations"] = userProfile.userLocations; + updateObject["$set"]["userProfile.userLocationsMismatchFoundAndUpdated"] = true; + } + + await db.collection('projects').findOneAndUpdate({ + "_id" : project._id + },updateObject); + + updatedProjectIds.push(project._id.toString()); + } + + } + + //write updated project ids to file + fs.writeFile( + 'updatedProjectIds.json', + + JSON.stringify(updatedProjectIds), + + function (err) { + if (err) { + console.error('Crap happens'); + } + } + ); + } + + function locationSearch ( filterData ) { + return new Promise(async (resolve, reject) => { + try { + + let bodyData={}; + bodyData["request"] = {}; + bodyData["request"]["filters"] = filterData; + const url = userServiceUrl + endPoint; + const options = { + headers : { + "content-type": "application/json" + }, + json : bodyData + }; + + request.post(url,options,requestCallback); + + let result = { + success : true + }; + + function requestCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = data.body; + if( response.responseCode === "OK" && + response.result && + response.result.response && + response.result.response.length > 0 + ) { + let entityResult = new Array; + response.result.response.map(entityData => { + let entity = _.omit(entityData, ['identifier']); + entityResult.push(entity); + }) + result["data"] = entityResult; + result["count"] = response.result.count; + } else { + result.success = false; + } + } + return resolve(result); + } + + setTimeout(function () { + return resolve (result = { + success : false + }); + }, 5000); + + } catch (error) { + return reject(error); + } + }) + } + + function profileReadPrivate (userId) { + return new Promise(async (resolve, reject) => { + try { + // <--- Important : This url endpoint is private do not use it for regular workflows ---> + let url = userServiceUrl + userReadEndpoint + "/" + userId; + const options = { + headers : { + "content-type": "application/json" + } + }; + request.get(url,options,userReadCallback); + let result = { + success : true + }; + function userReadCallback(err, data) { + if (err) { + result.success = false; + } else { + + let response = JSON.parse(data.body); + if( response.responseCode === "OK" ) { + result["data"] = response.result; + } else { + result.success = false; + } + + } + return resolve(result); + } + setTimeout(function () { + return resolve (result = { + success : false + }); + }, 5000); + + } catch (error) { + return reject(error); + } + }) + } + + console.log("Updated Project Count : ", updatedProjectIds.length) + console.log("completed") + connection.close(); + } + catch (error) { + console.log(error) + } +})().catch(err => console.error(err)); + +function checkIfValidUUID(value) { + const regexExp = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; + return regexExp.test(value); +} \ No newline at end of file diff --git a/migrations/userProfileAndRoleMismatchInProjects/updateUserProfileInProjects.js b/migrations/userProfileAndRoleMismatchInProjects/updateUserProfileInProjects.js new file mode 100644 index 00000000..6f172436 --- /dev/null +++ b/migrations/userProfileAndRoleMismatchInProjects/updateUserProfileInProjects.js @@ -0,0 +1,331 @@ +/** + * name : updateUserProfileInProjects.js + * author : Priyanka Pradeep + * created-date : 10-Nov-2022 + * Description : Migration script for update userProfile in project + */ + + const path = require("path"); + let rootPath = path.join(__dirname, '../../') + require('dotenv').config({ path: rootPath+'/.env' }) + + let _ = require("lodash"); + let mongoUrl = process.env.MONGODB_URL; + let dbName = mongoUrl.split("/").pop(); + let url = mongoUrl.split(dbName)[0]; + var MongoClient = require('mongodb').MongoClient; + var ObjectId = require('mongodb').ObjectID; + + var fs = require('fs'); + const request = require('request'); + + const userServiceUrl = "http://learner-service:9000"; + const endPoint = "/v1/location/search"; + +(async () => { + + let connection = await MongoClient.connect(url, { useNewUrlParser: true }); + let db = connection.db(dbName); + try { + + let updatedProjectIds = []; + + //get all projects + let projectDocument = await db.collection('projects').find({ + userRoleInformation: {$exists : true}, + userProfile: {$exists : true}, + }).project({ "_id": 1}).toArray(); + + let chunkOfProjectDocument = _.chunk(projectDocument, 10); + let projectIds; + + for (let pointerToProject = 0; pointerToProject < chunkOfProjectDocument.length; pointerToProject++) { + + projectIds = await chunkOfProjectDocument[pointerToProject].map( + projectDoc => { + return projectDoc._id; + } + ); + + let projectDocuments = await db.collection('projects').find({ + _id: { $in : projectIds } + }).project({ + "_id": 1, + "userRoleInformation" : 1, + "userProfile" : 1 + }).toArray(); + + //loop all projects + for ( let count = 0; count < projectDocuments.length; count++ ) { + + let project = projectDocuments[count]; + let userProfile = project.userProfile; + + + let updateUserProfileRoleInformation = false; // Flag to see if roleInformation i.e. userProfile.profileUserTypes has to be updated based on userRoleInfromation.roles + + if(project.userRoleInformation.role) { // Check if userRoleInformation has role value. + let rolesInUserRoleInformation = project.userRoleInformation.role.split(","); // userRoleInfomration.role can be multiple with comma separated. + + let resetCurrentUserProfileRoles = false; // Flag to reset current userProfile.profileUserTypes i.e. if current role in profile is not at all there in userRoleInformation.roles + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + + // Loop through current roles in userProfile.profileUserTypes + for (let pointerToCurrentProfileUserTypes = 0; pointerToCurrentProfileUserTypes < userProfile.profileUserTypes.length; pointerToCurrentProfileUserTypes++) { + const currentProfileUserType = userProfile.profileUserTypes[pointerToCurrentProfileUserTypes]; + + if(currentProfileUserType.subType && currentProfileUserType.subType !== null) { // If the role has a subType + + // Check if subType exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!project.userRoleInformation.role.toUpperCase().includes(currentProfileUserType.subType.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } else { // If the role subType is null or is not there + + // Check if type exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!project.userRoleInformation.role.toUpperCase().includes(currentProfileUserType.type.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } + } + } + if(resetCurrentUserProfileRoles) { // Reset userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + } + + // Loop through each subRole in userRoleInformation + for (let pointerToRolesInUserInformation = 0; pointerToRolesInUserInformation < rolesInUserRoleInformation.length; pointerToRolesInUserInformation++) { + const subRole = rolesInUserRoleInformation[pointerToRolesInUserInformation]; + + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + if(!_.find(userProfile.profileUserTypes, { 'type': subRole.toLowerCase() }) && !_.find(userProfile.profileUserTypes, { 'subType': subRole.toLowerCase() })) { + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + if(subRole.toUpperCase() === "TEACHER") { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } else { // Make a new entry if userProfile.profileUserTypes is empty or does not exist. + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + if(subRole.toUpperCase() === "TEACHER") { // If subRole is teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } + } + + // Create location only object from userRoleInformation + let userRoleInformationLocationObject = _.omit(project.userRoleInformation, ['role']); + + // All location keys from userRoleInformation + let userRoleInfomrationLocationKeys = Object.keys(userRoleInformationLocationObject); + + let updateUserProfileLocationInformation = false; // Flag to see if userLocations i.e. userProfile.userLocations has to be updated based on userRoleInfromation location values + + // Loop through all location keys. + for (let pointerToUserRoleInfromationLocationKeys = 0; pointerToUserRoleInfromationLocationKeys < userRoleInfomrationLocationKeys.length; pointerToUserRoleInfromationLocationKeys++) { + + const locationType = userRoleInfomrationLocationKeys[pointerToUserRoleInfromationLocationKeys]; // e.g. state, district, school + const locationValue = userRoleInformationLocationObject[locationType]; // Location UUID values or school code. + + // Check if userProfile.userLocations exists and is an array of length > 0 + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + + if(locationType === "school") { // If location type school exist check if same is there in userProfile.userLocations + if(!_.find(userProfile.userLocations, { 'type': "school", 'code': locationValue })) { + updateUserProfileLocationInformation = true; // School does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } else { // Check if location type is there in userProfile.userLocations and has same value as userRoleInformation + if(!_.find(userProfile.userLocations, { 'type': locationType, 'id': locationValue })) { + updateUserProfileLocationInformation = true; // Location does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } + } else { + updateUserProfileLocationInformation = true; + break; + } + } + + + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + if(userProfile.userLocations.length != userRoleInfomrationLocationKeys.length) { + updateUserProfileLocationInformation = true; + } + } + + // If userProfile.userLocations has to be updated, get all values and set in userProfile. + if(updateUserProfileLocationInformation) { + + //update userLocations in userProfile + let locationIds = []; + let locationCodes = []; + let userLocations = new Array; + + userRoleInfomrationLocationKeys.forEach( requestedDataKey => { + if (checkIfValidUUID(userRoleInformationLocationObject[requestedDataKey])) { + locationIds.push(userRoleInformationLocationObject[requestedDataKey]); + } else { + locationCodes.push(userRoleInformationLocationObject[requestedDataKey]); + } + }) + + //query for fetch location using id + if ( locationIds.length > 0 ) { + let locationQuery = { + "id" : locationIds + } + + let entityData = await locationSearch(locationQuery); + if ( entityData.success ) { + userLocations = entityData.data; + } + } + + // query for fetch location using code + if ( locationCodes.length > 0 ) { + let codeQuery = { + "code" : locationCodes + } + + let entityData = await locationSearch(codeQuery); + if ( entityData.success ) { + userLocations = userLocations.concat(entityData.data); + } + } + + if ( userLocations.length > 0 ) { + userProfile["userLocations"] = userLocations; + } + } + + + //update projects if userProfile role or location information is incorrect + if ( updateUserProfileRoleInformation || updateUserProfileLocationInformation ) { + + let updateObject = { + "$set" : {} + }; + if(updateUserProfileRoleInformation) { + updateObject["$set"]["userProfile.profileUserTypes"] = userProfile.profileUserTypes; + updateObject["$set"]["userProfile.userRoleMismatchFoundAndUpdated"] = true; + } + if(updateUserProfileLocationInformation) { + updateObject["$set"]["userProfile.userLocations"] = userProfile.userLocations; + updateObject["$set"]["userProfile.userLocationsMismatchFoundAndUpdated"] = true; + } + + await db.collection('projects').findOneAndUpdate({ + "_id" : project._id + },updateObject); + + updatedProjectIds.push(project._id.toString()); + } + + } + + //write updated project ids to file + fs.writeFile( + 'updatedProjectIds.json', + + JSON.stringify(updatedProjectIds), + + function (err) { + if (err) { + console.error('Crap happens'); + } + } + ); + } + + function locationSearch ( filterData ) { + return new Promise(async (resolve, reject) => { + try { + + let bodyData={}; + bodyData["request"] = {}; + bodyData["request"]["filters"] = filterData; + const url = userServiceUrl + endPoint; + const options = { + headers : { + "content-type": "application/json" + }, + json : bodyData + }; + + request.post(url,options,requestCallback); + + let result = { + success : true + }; + + function requestCallback(err, data) { + if (err) { + result.success = false; + } else { + let response = data.body; + if( response.responseCode === "OK" && + response.result && + response.result.response && + response.result.response.length > 0 + ) { + let entityResult = new Array; + response.result.response.map(entityData => { + let entity = _.omit(entityData, ['identifier']); + entityResult.push(entity); + }) + result["data"] = entityResult; + result["count"] = response.result.count; + } else { + result.success = false; + } + } + return resolve(result); + } + + setTimeout(function () { + return resolve (result = { + success : false + }); + }, 5000); + + } catch (error) { + return reject(error); + } + }) + } + + console.log("Updated Project Count : ", updatedProjectIds.length) + console.log("completed") + connection.close(); + } + catch (error) { + console.log(error) + } +})().catch(err => console.error(err)); + +function checkIfValidUUID(value) { + const regexExp = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; + return regexExp.test(value); +} \ No newline at end of file diff --git a/models/certificateTemplates.js b/models/certificateTemplates.js new file mode 100644 index 00000000..0f5ba2d7 --- /dev/null +++ b/models/certificateTemplates.js @@ -0,0 +1,29 @@ +module.exports = { + name: "certificateTemplates", + schema: { + templateUrl: String, + issuer: { + type : Object, + required : true + }, + status: { + type : String, + required : true, + default : "ACTIVE" + }, + solutionId: { + type : "ObjectId", + index : true, + unique : true + }, + programId: { + type : "ObjectId", + index : true, + required : true + }, + criteria: { + type : Object, + required : true + } + } +}; \ No newline at end of file diff --git a/models/programUsers.js b/models/programUsers.js new file mode 100644 index 00000000..b276a3b8 --- /dev/null +++ b/models/programUsers.js @@ -0,0 +1,38 @@ +module.exports = { + name: "programUsers", + schema: { + programId: { + type : "ObjectId", + required: true, + index: true + }, + userId: { + type: String, + index: true, + required: true, + }, + resourcesStarted: { + type: Boolean, + index: true, + default: false + }, + userProfile: { + type : Object, + required: true + }, + userRoleInformation: Object, + appInformation: Object, + consentShared: { + type: Boolean, + default: false + } + }, + compoundIndex: [ + { + "name" :{ userId: 1, programId: 1 }, + "indexType" : { unique: true } + } + ] +}; + + \ No newline at end of file diff --git a/models/programs.js b/models/programs.js new file mode 100644 index 00000000..e4e906b9 --- /dev/null +++ b/models/programs.js @@ -0,0 +1,57 @@ +/** + * name : programs-model.js. + * author : Vishnu. + * created-date : 09-Mar-2022. + * Description : Schema for programs. + */ +module.exports = { + name: "programs", + schema: { + externalId: String, + name: String, + description: String, + owner: String, + createdBy: String, + updatedBy: String, + status: { + type : String, + index : true + }, + resourceType: [String], + language: [String], + keywords: [String], + concepts: ["json"], + imageCompression: {}, + components: ["json"], + isAPrivateProgram : { + default : false, + type : Boolean + }, + scope : { + entityType : String, + entities : { + type : Array, + index : true + }, + roles : [{ + _id : "ObjectId", + code : { + type : String, + index : true + } + }] + }, + isDeleted: { + default : false, + type : Boolean, + index : true + }, + requestForPIIConsent: Boolean, + metaInformation: Object, + rootOrganisations : { + type : Array, + require : true + }, + createdFor : Array + } + }; \ No newline at end of file diff --git a/models/project-template-tasks.js b/models/project-template-tasks.js index e6b97458..bdfb20bd 100644 --- a/models/project-template-tasks.js +++ b/models/project-template-tasks.js @@ -25,7 +25,8 @@ module.exports = { }, isDeleted : { type : Boolean, - default : false + default : false, + index: true }, externalId : { type : String, @@ -79,6 +80,9 @@ module.exports = { learningResources : { type : Array, default : [] + }, + sequenceNumber: { + type : String } } }; \ No newline at end of file diff --git a/models/project-templates.js b/models/project-templates.js index 45d1604e..e1df6b92 100644 --- a/models/project-templates.js +++ b/models/project-templates.js @@ -45,7 +45,8 @@ module.exports = { }, isDeleted : { type : Boolean, - default : false + default : false, + index : true }, recommendedFor : { type : Array, @@ -75,9 +76,6 @@ module.exports = { entityType : { type : String }, - entityTypeId : { - type : "ObjectId" - }, taskSequence : { type : Array, default : [] @@ -114,6 +112,7 @@ module.exports = { 4 : 0, 5 : 0 } - } + }, + certificateTemplateId : "ObjectId" } }; \ No newline at end of file diff --git a/models/projects.js b/models/projects.js index 3fe782c7..f6f5bb61 100644 --- a/models/projects.js +++ b/models/projects.js @@ -8,8 +8,14 @@ module.exports = { name: "projects", schema: { - title : String, - description : String, + title : { + type : String, + index: true + }, + description : { + type : String, + index: true + }, taskReport : { type : Object, default : {} @@ -28,20 +34,17 @@ module.exports = { default : "", index: true }, - createdFor : { - type : Array, - default : [] - }, status : { type : String, - default : "notStarted", + default : "started", index: true }, lastDownloadedAt : Date, syncedAt : Date, isDeleted : { type : Boolean, - default : false + default : false, + index: true }, categories : { type : Array, @@ -49,7 +52,8 @@ module.exports = { }, createdBy : { type : String, - default : "SYSTEM" + default : "SYSTEM", + index: true }, tasks : { type : Array, @@ -81,16 +85,12 @@ module.exports = { }, startDate: Date, endDate: Date, - rootOrganisations : { - type : [String], - default : [] - }, learningResources : { type : Array, default : [] }, entityId : { - type : "ObjectId", + type : String, index : true }, programId : { @@ -109,9 +109,12 @@ module.exports = { type : String, index : true }, - isAPrivateProgram : Boolean, + isAPrivateProgram : { + type : Boolean, + index : true + }, appInformation : Object, - userRoleInformtion : Object, + userRoleInformation : Object, hasAcceptedTAndC : { type : Boolean, default : false @@ -120,6 +123,61 @@ module.exports = { type : String, index : true }, - submissions : Object - } + submissions : Object, + link : { + type : String, + index : true + }, + taskSequence : { + type : Array, + default : [] + }, + completedDate: Date, + recommendedFor : { + type : Array, + default : [] + }, + attachments : { + type : Array, + default : [] + }, + remarks : String, + userProfile : Object, + certificate : { + templateId : "ObjectId", + osid : { + type : String, + index : true, + unique : true + }, + transactionId : { + type : String, + index : true, + unique : true + }, + templateUrl : String, + status : String, + eligible : Boolean, + message : String, + issuedOn : Date, + criteria : Object, + reIssuedAt : Date, + transactionIdCreatedAt : Date, + originalTransactionInformation :{ + transactionId : String, + osid : String + } + + } + }, + compoundIndex: [ + { + "name" :{ userId: 1, solutionId: 1 }, + "indexType" : { unique: true, partialFilterExpression: { solutionId: { $exists: true }}} + } + ] + + + }; + diff --git a/models/solutions.js b/models/solutions.js new file mode 100644 index 00000000..11163703 --- /dev/null +++ b/models/solutions.js @@ -0,0 +1,108 @@ +/** + * name : solutions-model.js. + * author : Vishnu. + * created-date : 26-jan-2022. + * Description : Schema for solutions. + */ +module.exports = { + name: "solutions", + schema: { + externalId: String, + isReusable: Boolean, + name: String, + description: String, + author: String, + parentSolutionId: "ObjectId", + resourceType: Array, + language: Array, + keywords: Array, + concepts: Array, + scoringSystem: String, + levelToScoreMapping: Object, + themes: Array, + flattenedThemes : Array, + questionSequenceByEcm: Object, + entityType: String, + type: String, + subType: String, + entities: Array, + programId: "ObjectId", + programExternalId: String, + programName: String, + programDescription: String, + entityProfileFieldsPerEntityTypes: Object, + startDate: Date, + endDate: { + type : Date, + index : true + }, + status: String, + evidenceMethods: Object, + sections: Object, + registry: Array, + frameworkId: "ObjectId", + frameworkExternalId: String, + parentSolutionId: "ObjectId", + noOfRatingLevels: Number, + isRubricDriven: { type : Boolean, default: false }, + enableQuestionReadOut: { type : Boolean, default: false }, + isReusable: Boolean, + roles: Object, + observationMetaFormKey: String, + updatedBy: String, + captureGpsLocationAtQuestionLevel:{ type : Boolean, default: false }, + sendSubmissionRatingEmailsTo: String, + creator: String, + linkTitle: String, + linkUrl: String, + isAPrivateProgram : { + default : false, + type : Boolean + }, + assessmentMetaFormKey : String, + allowMultipleAssessemts : { + default : false, + type : Boolean + }, + isDeleted: { + default : false, + type : Boolean, + index : true + }, + project : Object, + referenceFrom : String, + scope : { + entityType : String, + entities : { + type : Array, + index : true + }, + roles : [{ + _id : "ObjectId", + code : { + type : String, + index : true + } + }] + }, + pageHeading: { + default : "Domains", + type : String + }, + criteriaLevelReport : Boolean, + license:Object, + link: String, + minNoOfSubmissionsRequired: { + type: Number, + default: 1 + }, + reportInformation : Object, + certificateTemplateId : "ObjectId", + rootOrganisations : Array, + createdFor : Array, + projectTemplateId : { + type : "ObjectId", + index: true + } + } + }; \ No newline at end of file diff --git a/module/certificateValidations/helper.js b/module/certificateValidations/helper.js new file mode 100644 index 00000000..3f091862 --- /dev/null +++ b/module/certificateValidations/helper.js @@ -0,0 +1,233 @@ +/** + * name : helper.js + * author : vishnu + * created-date : 26-Oct-2022 + * Description : certificate validation helper functionality. + */ + +// Dependencies + +/** + * certificateValidationsHelper + * @class +*/ + +module.exports = class certificateValidationsHelper { + + /** + * validate certificate criteria. + * @method + * @name criteriaValidation + * @param {Object} data - project data for certificate creation + * @returns + */ + + static criteriaValidation(data) { + return new Promise(async (resolve, reject) => { + try { + let criteria = data.certificate.criteria; // criteria conditions for certificate + let validationResult = []; + let validationMessage = ""; + let validationExpression = criteria.expression + if ( criteria.conditions && Object.keys(criteria.conditions).length > 0 ) { + let conditions = criteria.conditions; + let conditionKeys = Object.keys(conditions) + + for ( let index = 0; index < conditionKeys.length; index++ ) { + // correntCondition contain the prefinal level data + let currentCondition = conditions[conditionKeys[index]]; + + //now pass expression and validation scope to another function which will start the validation procedure + let validation = await _subCriteriaValidation( currentCondition.conditions, currentCondition.expression, data ); + + validationResult.push(validation.success); + ( validation.success == false ) ? validationMessage = validationMessage + " " + currentCondition.validationText : ""; + } + // validate criteria using defined expression + let criteriaValidation = await _criteriaExpressionValidation( validationExpression, conditionKeys, validationResult ) + return resolve({ + success: criteriaValidation, + message: ( criteriaValidation == false ) ? validationMessage : CONSTANTS.common.PROJECT_CERTIFICATE_GENERATED_SUCCESSFULLY + }); + } + return resolve({ + success: false + }) + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: {} + }); + } + }) + } +}; + +/** + * _subCriteriaValidation. + * @method + * @name _subCriteriaValidation + * @param {Object} conditions - condition data. + * @param {String} expression - validation expression + * @returns {Boolean} validation. +*/ + +function _subCriteriaValidation(conditions, expression, data) { + return new Promise(async (resolve, reject) => { + try { + let conditionKeys = Object.keys(conditions) + let validationResult = []; + // loop throug conditions of subcriterias + for ( let index = 0; index < conditionKeys.length; index++ ) { + let currentCondition = conditions[conditionKeys[index]]; + // correntCondition contain the prefinal level data + //now pass expression and validation scope to another function which will start the validation procedure + let validation = await _validateCriteriaConditions( currentCondition, data ); + validationResult.push(validation); + } + // validate expression + let subcriteriaValidation = await _criteriaExpressionValidation( expression, conditionKeys, validationResult ) + return resolve({ + success: subcriteriaValidation + }); + + } catch (error) { + return resolve({ + message: error.message, + success: false, + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status + }) + } + }) +} + +/** + * _validateCriteriaConditions. + * @method + * @name _validateCriteriaConditions + * @param {Object} condition - condition data. + * @param {String} data - validation data + * @returns {Boolean} validation. +*/ + +function _validateCriteriaConditions(condition, data) { + return new Promise(async (resolve, reject) => { + try { + let result = false; + if ( !condition.function || condition.function == "" ) { + if ( condition.scope == CONSTANTS.common.PROJECT ){ + // if validation is on completedDate + if ( condition.key == "completedDate") { + let comparableDates = UTILS.createComparableDates( data[condition.key], condition.value ); + data[condition.key] = comparableDates.dateOne; + condition.value = comparableDates.dateTwo; + } + // validate prject value with condition value + result = UTILS.operatorValidation( data[condition.key], condition.value, condition.operator ); + + } + } else { + try { + let valueFromProject = 0; + // if: condition is in scope of project and contains a function to check + if ( condition.scope == CONSTANTS.common.PROJECT ) { + // get count of attachments at project level + valueFromProject = UTILS.noOfElementsInArray( data[condition.key], condition.filter ); + } else if ( condition.scope == CONSTANTS.common.TASK_ATTACHMENT ){ + // for task attachment validatiion _id of specific task or "all" key should be passed in an array called taskDetails + let tasksAttachments = []; + let projectTasks = data.tasks; + // check tasks and taskDetails exists + if ( projectTasks && projectTasks.length > 0 && condition.taskDetails.length > 0 && condition.taskDetails[0] == "all" ) { + // loop through tasks to get attachments + for ( let tasksIndex = 0; tasksIndex < projectTasks.length; tasksIndex++ ) { + + if ( projectTasks[tasksIndex][condition.key] && projectTasks[tasksIndex][condition.key].length > 0 ) + { + tasksAttachments.push(...projectTasks[tasksIndex][condition.key]) + } + } + + } else if ( projectTasks && projectTasks.length > 0 && condition.taskDetails.length > 0 ) { + + // specific task Id( from projectTemplates ) or Ids are passed for attachment validation + for ( let tasksIndex = 0; tasksIndex < projectTasks.length; tasksIndex++ ) { + for ( let taskDetailsPointer = 0; taskDetailsPointer < condition.taskDetails.length; taskDetailsPointer++ ) { + // get attachments data of specified task/ tasks + if ( projectTasks[tasksIndex].referenceId == condition.taskDetails[taskDetailsPointer] && projectTasks[tasksIndex][condition.key] && projectTasks[tasksIndex][condition.key].length > 0 ) { + tasksAttachments.push(...projectTasks[tasksIndex][condition.key]) + } + } + + } + + } else { + return resolve(result) + } + if ( !(tasksAttachments.length > 0) ) { + return resolve(result) + } + // get task attachments count + valueFromProject = UTILS.noOfElementsInArray( tasksAttachments, condition.filter ); + } + // validate against condition value + result = UTILS.operatorValidation( valueFromProject, condition.value, condition.operator ); + + } catch (fnError) { + return resolve(result) + } + } + return resolve(result); + } catch (error) { + return resolve({ + message: error.message, + success: false, + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status + }) + } + }) +} +/** + * _criteriaExpressionValidation + * @method + * @name _criteriaExpressionValidation + * @param {String} expression - criteria expression + * @param {Array} keys - condition keys + * @param {Array} result - condition result + * @returns {Boolean} validation result. +*/ + +function _criteriaExpressionValidation(expression, keys, result) { + return new Promise(async (resolve, reject) => { + try { + + if( expression == "" || + !(keys.length > 0) || + !(result.length > 0) || + keys.length != result.length ) { + return resolve(false); + } + // generate expression string that can be evaluated + for ( let pointerToKeys = 0; pointerToKeys < keys.length; pointerToKeys++ ) { + expression = expression.replace(keys[pointerToKeys],result[pointerToKeys].toString()) + } + let evalResult = eval(expression) + + return resolve(evalResult); + + } catch (error) { + return resolve(false); + } + }) +} + + + + + + diff --git a/module/dataPipeline/helper.js b/module/dataPipeline/helper.js new file mode 100644 index 00000000..78115ca8 --- /dev/null +++ b/module/dataPipeline/helper.js @@ -0,0 +1,44 @@ +/** + * name : helper.js + * author : Rakesh + * created-date : 11-Jun-2020 + * Description : DataPipeline helper functionality. + */ + +// Dependencies + +const UserProjectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); + +/** + * dataPipelineHelper + * @class + */ + +module.exports = class dataPipelineHelper { + + /** + * get uset project details. + * @method + * @name userProject + * @param {String} projectId - project id. + * @returns {Object} Project details. + */ + + static userProject(projectId) { + return new Promise(async (resolve, reject) => { + try { + + const projectDetails = await UserProjectsHelper.userProject(projectId); + return resolve(projectDetails); + + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: {} + }); + } + }) + } + +} \ No newline at end of file diff --git a/module/dataPipeline/validator/v1.js b/module/dataPipeline/validator/v1.js new file mode 100644 index 00000000..37d4cff5 --- /dev/null +++ b/module/dataPipeline/validator/v1.js @@ -0,0 +1,22 @@ +/** + * name : v1.js + * author : Rakesh + * created-date : 11-Jan-2020 + * Description : DataPipeline. + */ + + module.exports = (req) => { + + let dataPipeLineValidator = { + + userProject : function () { + req.checkParams('_id').exists().withMessage("required project id") + .isMongoId().withMessage("Invalid project id"); + } + } + + if (dataPipeLineValidator[req.params.method]) { + dataPipeLineValidator[req.params.method](); + } + +}; \ No newline at end of file diff --git a/module/entities/helper.js b/module/entities/helper.js new file mode 100644 index 00000000..735c0729 --- /dev/null +++ b/module/entities/helper.js @@ -0,0 +1,70 @@ +/** + * name : helper.js + * author : Ankit Shahu + * created-date : 16-Jan-2024 + * Description : entities helper functionality. + */ + +const userProfileService = require(GENERICS_FILES_PATH + "/services/users"); + +module.exports = class entitieHelper { + + /** + * update registry in entities. + * @method + * @name listByLocationIds + * @param {Object} locationIds - locationIds + * @returns {Object} entity Document + */ + + static listByLocationIds(locationIds) { + return new Promise(async (resolve, reject) => { + try { + //if not uuid considering as location code- for school. + let locationDeatails = UTILS.filterLocationIdandCode(locationIds,); + //set request body for learners api + let entityInformation = []; + let formatResult = true; + + if ( locationDeatails.ids.length > 0 ) { + let bodyData = { + "id" : locationDeatails.ids + } + let entityData = await userProfileService.locationSearch( bodyData, formatResult ); + if ( entityData.success ) { + entityInformation = entityData.data; + } + } + + if ( locationDeatails.codes.length > 0 ) { + let bodyData = { + "code" : locationDeatails.codes + } + let entityData = await userProfileService.locationSearch( bodyData, formatResult ); + if ( entityData.success ) { + entityInformation = entityInformation.concat(entityData.data); + } + } + + if ( !entityInformation.length > 0 ) { + throw { + message : CONSTANTS.apiResponses.ENTITY_NOT_FOUND + } + } + + return resolve({ + success : true, + message : CONSTANTS.apiResponses.ENTITY_FETCHED, + data : entityInformation + }); + + } catch(error) { + return resolve({ + success : false, + message : error.message + }); + } + }) + } + +} \ No newline at end of file diff --git a/module/library/categories/helper.js b/module/library/categories/helper.js index 9e2425f1..2e07a3bc 100644 --- a/module/library/categories/helper.js +++ b/module/library/categories/helper.js @@ -6,8 +6,11 @@ */ // Dependencies -const kendraService = require(GENERICS_FILES_PATH + "/services/kendra"); +const coreService = require(GENERICS_FILES_PATH + "/services/core"); const sessionHelpers = require(GENERIC_HELPERS_PATH+"/sessions"); +const projectCategoriesQueries = require(DB_QUERY_BASE_PATH + "/projectCategories"); +const projectTemplateQueries = require(DB_QUERY_BASE_PATH + "/projectTemplates"); +const projectTemplateTaskQueries = require(DB_QUERY_BASE_PATH + "/projectTemplateTask"); const moment = require("moment-timezone"); /** @@ -17,161 +20,6 @@ const moment = require("moment-timezone"); module.exports = class LibraryCategoriesHelper { - /** - * Library project categories documents. - * @method - * @name categoryDocuments - * @param {Object} [findQuery = "all"] - filtered data. - * @param {Array} [fields = "all"] - projected data. - * @param {Array} [skipFields = "none"] - fields to skip. - * @returns {Array} - Library project categories data. - */ - - static categoryDocuments( - findQuery = "all", - fields = "all", - skipFields = "none" - ) { - return new Promise(async (resolve, reject) => { - - try { - - let queryObject = {}; - - if (findQuery != "all") { - queryObject = findQuery; - } - - let projection = {}; - - if (fields != "all") { - fields.forEach(element => { - projection[element] = 1; - }); - } - - if (skipFields != "none") { - skipFields.forEach(element => { - projection[element] = 0; - }); - } - - let projectCategoriesData = - await database.models.projectCategories.find( - queryObject, - projection - ).lean(); - - return resolve(projectCategoriesData); - - } catch (error) { - return reject(error); - } - }); - } - - /** - * List of project categories. - * @method - * @name list - * @returns {Object} Project categories. - */ - - static list() { - return new Promise(async (resolve, reject) => { - try { - - let projectCategoriesData = - await this.categoryDocuments( - { - status : CONSTANTS.common.ACTIVE_STATUS - }, - [ - "externalId", - "name", - "icon", - "updatedAt", - "noOfProjects" - ] - ); - - if( !projectCategoriesData.length > 0 ) { - throw { - status : HTTP_STATUS_CODE['ok'].status, - message : CONSTANTS.apiResponses.LIBRARY_CATEGORIES_NOT_FOUND - }; - } - - - let categories = {}; - let icons = []; - - projectCategoriesData.map(category => { - - categories[category.icon] = { - name : category.name, - type : category.externalId, - updatedAt : category.updatedAt, - projectsCount : category.noOfProjects ? category.noOfProjects : 0 - }; - - if( category.icon !== "" ) { - icons.push(category.icon); - } - - }); - - let projectCategories = ""; - - if( icons.length > 0 ) { - - projectCategories = - await kendraService.getDownloadableUrl( - { - filePaths : icons - } - ); - - if( !projectCategories.success ) { - throw { - status : HTTP_STATUS_CODE['bad_request'].status, - message : CONSTANTS.apiResponses.KENDRA_URL_NOT_FETCHED - } - } - - projectCategories = - projectCategories.data.map( downloadableImage => { - return _.merge( - categories[downloadableImage.filePath], - { url : downloadableImage.url } - ) - }); - - } else { - projectCategories = Object.values(categories); - } - - projectCategories = projectCategories.sort((a,b)=> a.name.toString() > b.name.toString() ? 1 : -1); - - return resolve({ - success: true, - message : CONSTANTS.apiResponses.PROJECT_CATEGORIES_FETCHED, - data : projectCategories - }); - - } catch (error) { - return resolve({ - status : - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] - }); - } - }) - } - /** * List of library projects. * @method @@ -252,8 +100,7 @@ module.exports = class LibraryCategoriesHelper { } }); - let result = - await database.models.projectTemplates.aggregate(aggregateData); + let result = await projectTemplateQueries.getAggregate(aggregateData); if( result[0].data.length > 0 ) { @@ -289,6 +136,44 @@ module.exports = class LibraryCategoriesHelper { } /** + * Update categories + * @method + * @name update + * @param filterQuery - Filter query. + * @param updateData - Update data. + * @returns {Object} updated data + */ + + static update(filterQuery,updateData) { + return new Promise(async (resolve, reject) => { + try { + + let categoriesUpdated = await projectCategoriesQueries.updateMany(filterQuery,updateData); + + if( !categoriesUpdated.ok ) { + throw { + status : HTTP_STATUS_CODE['bad_request'].status, + message : CONSTANTS.apiResponses.PROJECT_CATEGORIES_NOT_UPDATED + } + } + + return resolve({ + success: true, + message : CONSTANTS.apiResponses.PROJECT_CATEGORIES_UPDATED, + data : categoriesUpdated + }); + + } catch (error) { + return resolve({ + success: false, + message: error.message, + data : {} + }); + } + }) + } + + /** * Details of library projects. * @method * @name projectDetails @@ -296,41 +181,37 @@ module.exports = class LibraryCategoriesHelper { * @returns {Object} Details of library projects. */ - static projectDetails(projectId,userToken = "") { + static projectDetails(projectId, userToken = "", isATargetedSolution = "") { return new Promise(async (resolve, reject) => { try { - let projectsData = - await database.models.projectTemplates.find( + let projectsData = await projectTemplateQueries.templateDocument( { - status : CONSTANTS.common.PUBLISHED, "_id" : projectId, - "isDeleted" : false - },{ - "__v" : 0 - } - ).lean(); - - if( !projectsData.length > 0 ) { + status : CONSTANTS.common.PUBLISHED, + "isDeleted" : false, + }, "all", ["__v"]); + + if( !(projectsData.length > 0 )) { throw { status : HTTP_STATUS_CODE['bad_request'].status, message : CONSTANTS.apiResponses.PROJECT_NOT_FOUND, }; } - + projectsData[0].showProgramAndEntity = false; if( projectsData[0].tasks && projectsData[0].tasks.length > 0 ) { - let tasks = - await database.models.projectTemplateTasks.find({ - _id : { + let tasks = await projectTemplateTaskQueries.taskDocuments( + { + _id : { $in : projectsData[0].tasks }, isDeleted : false - }).lean(); + }); - if( tasks && tasks.length > 0 ) { + if( tasks && tasks.length > 0 ) { let taskData = {}; @@ -339,6 +220,7 @@ module.exports = class LibraryCategoriesHelper { taskPointer < tasks.length; taskPointer ++ ) { + let currentTask = tasks[taskPointer]; if( @@ -371,43 +253,13 @@ module.exports = class LibraryCategoriesHelper { } } - if( userToken !== "" ) { - - let userProfileData = await kendraService.getProfile(userToken); - - if( !userProfileData.success ) { - throw { - status : HTTP_STATUS_CODE['bad_request'].status, - message : CONSTANTS.apiResponses.USER_PROFILE_NOT_FOUND - } - } - - projectsData[0].userRating = 0; - - if( - userProfileData.data && - userProfileData.data.ratings && - userProfileData.data.ratings.length > 0 - ) { - - let projectIndex = - userProfileData.data.ratings.findIndex( - project => project._id.toString() === projectId.toString() - ); - - if( projectIndex > 0 ) { - projectsData[0].userRating = userProfileData.data.ratings[projectIndex].rating; - } - } - } - return resolve({ success: true, message : CONSTANTS.apiResponses.PROJECTS_FETCHED, data : projectsData[0] }); - } catch (error) { + } catch (error) { return resolve({ status : error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, @@ -418,46 +270,4 @@ module.exports = class LibraryCategoriesHelper { }) } - /** - * Update categories - * @method - * @name update - * @param filterQuery - Filter query. - * @param updateData - Update data. - * @returns {Object} updated data - */ - - static update(filterQuery,updateData) { - return new Promise(async (resolve, reject) => { - try { - - let categoriesUpdated = - await database.models.projectCategories.updateMany( - filterQuery, - updateData - ); - - if( !categoriesUpdated.ok ) { - throw { - status : HTTP_STATUS_CODE['bad_request'].status, - message : CONSTANTS.apiResponses.PROJECT_CATEGORIES_NOT_UPDATED - } - } - - return resolve({ - success: true, - message : CONSTANTS.apiResponses.PROJECT_CATEGORIES_UPDATED, - data : categoriesUpdated - }); - - } catch (error) { - return resolve({ - success: false, - message: error.message, - data : {} - }); - } - }) - } - }; diff --git a/module/library/categories/validator/v1.js b/module/library/categories/validator/v1.js index 8346b8a0..d2d4968e 100644 --- a/module/library/categories/validator/v1.js +++ b/module/library/categories/validator/v1.js @@ -8,11 +8,6 @@ module.exports = (req) => { let projectsValidator = { - projectDetails: function () { - req.checkParams('_id') - .exists() - .withMessage("required project id"); - } } if (projectsValidator[req.params.method]) { diff --git a/module/project/templateTasks/helper.js b/module/project/templateTasks/helper.js index bc53565c..f7f2ff05 100644 --- a/module/project/templateTasks/helper.js +++ b/module/project/templateTasks/helper.js @@ -13,57 +13,13 @@ // Dependencies const projectTemplatesHelper = require(MODULES_BASE_PATH + "/project/templates/helper"); const learningResourcesHelper = require(MODULES_BASE_PATH + "/learningResources/helper"); -const assessmentService = require(GENERICS_FILES_PATH + "/services/assessment"); +const surveyService = require(GENERICS_FILES_PATH + "/services/survey"); +const projectTemplateTaskQueries = require(DB_QUERY_BASE_PATH + "/projectTemplateTask"); +const projectTemplateQueries = require(DB_QUERY_BASE_PATH + "/projectTemplates"); +const solutionsQueries = require(DB_QUERY_BASE_PATH + "/solutions"); module.exports = class ProjectTemplateTasksHelper { - /** - * Lists of tasks. - * @method - * @name taskDocuments - * @param {Array} [filterData = "all"] - tasks filter query. - * @param {Array} [fieldsArray = "all"] - projected fields. - * @param {Array} [skipFields = "none"] - field not to include - * @returns {Array} Lists of tasks. - */ - - static taskDocuments( - filterData = "all", - fieldsArray = "all", - skipFields = "none" - ) { - return new Promise(async (resolve, reject) => { - try { - - let queryObject = (filterData != "all") ? filterData : {}; - let projection = {} - - if (fieldsArray != "all") { - fieldsArray.forEach(field => { - projection[field] = 1; - }); - } - - if( skipFields !== "none" ) { - skipFields.forEach(field=>{ - projection[field] = 0; - }); - } - - let tasks = - await database.models.projectTemplateTasks.find( - queryObject, - projection - ).lean(); - - return resolve(tasks); - - } catch (error) { - return reject(error); - } - }); - } - /** * Extract csv information. * @method @@ -119,9 +75,9 @@ module.exports = class ProjectTemplateTasksHelper { } } - let tasksData = await this.taskDocuments( + let tasksData = await projectTemplateTaskQueries.taskDocuments( filterData, - ["_id","children","externalId","projectTemplateId","parentId"] + ["_id","children","externalId","projectTemplateId","parentId", "taskSequence", "hasSubTasks"] ); if( tasksData.length > 0 ) { @@ -136,12 +92,12 @@ module.exports = class ProjectTemplateTasksHelper { } let projectTemplate = - await projectTemplatesHelper.templateDocument({ + await projectTemplateQueries.templateDocument({ status : CONSTANTS.common.PUBLISHED, _id : projectTemplateId - },["_id","entityType","externalId"]); + },["_id","entityType","externalId", "taskSequence"]); - if( !projectTemplate.length > 0 ) { + if( !(projectTemplate.length > 0) ) { throw { message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, status : HTTP_STATUS_CODE['bad_request'].status @@ -160,7 +116,7 @@ module.exports = class ProjectTemplateTasksHelper { if ( solutionIds.length > 0 ) { let solutions = - await assessmentService.listSolutions(solutionIds); + await surveyService.listSolutions(solutionIds); if( !solutions.success ) { throw { @@ -243,7 +199,6 @@ module.exports = class ProjectTemplateTasksHelper { } else if ( solutionTypes.includes(allValues.type) ) { allValues.solutionDetails = {}; - if( parsedData.solutionType && parsedData.solutionType !== "" ) { allValues.solutionDetails.type = parsedData.solutionType; } else { @@ -289,11 +244,30 @@ module.exports = class ProjectTemplateTasksHelper { CONSTANTS.apiResponses.MIS_MATCHED_PROJECT_AND_TASK_ENTITY_TYPE; } else { - allValues.solutionDetails = - _.pick( + let projectionFields = _solutionDocumentProjectionFieldsForTask(); + allValues.solutionDetails["minNoOfSubmissionsRequired"] = CONSTANTS.common.DEFAULT_SUBMISSION_REQUIRED; + + if (parsedData.minNoOfSubmissionsRequired && parsedData.minNoOfSubmissionsRequired != "" ) { + + // minNoOfSubmissionsRequired present in csv + if ( parsedData.minNoOfSubmissionsRequired > CONSTANTS.common.DEFAULT_SUBMISSION_REQUIRED ) { + if ( solutionData[parsedData.solutionId].allowMultipleAssessemts ) { + allValues.solutionDetails["minNoOfSubmissionsRequired"] = parsedData.minNoOfSubmissionsRequired; + } + } + + }else{ + // minNoOfSubmissionsRequired not present in csv + if (solutionData[parsedData.solutionId].minNoOfSubmissionsRequired ) { + projectionFields.push("minNoOfSubmissionsRequired"); + } + } + + Object.assign(allValues.solutionDetails, _.pick( solutionData[parsedData.solutionId], - ["_id","isReusable","externalId","name","programId","type","subType","allowMultipleAssessemts","isRubricDriven","criteriaLevelReport","scoringSystem"] - ); + projectionFields + )) + } } @@ -335,7 +309,7 @@ module.exports = class ProjectTemplateTasksHelper { if( !update ) { taskData = - await database.models.projectTemplateTasks.create(allValues); + await projectTemplateTaskQueries.createTemplateTask(allValues); if ( !taskData._id ) { parsedData.STATUS = CONSTANTS.apiResponses.PROJECT_TEMPLATE_TASKS_NOT_CREATED; @@ -347,7 +321,7 @@ module.exports = class ProjectTemplateTasksHelper { } else { taskData = - await database.models.projectTemplateTasks.findOneAndUpdate({ + await projectTemplateTaskQueries.findOneAndUpdate({ _id : parsedData._SYSTEM_ID },{ $set : allValues @@ -361,7 +335,7 @@ module.exports = class ProjectTemplateTasksHelper { if( parsedData.hasAParentTask === "YES" ) { let parentTask = - await database.models.projectTemplateTasks.findOneAndUpdate({ + await projectTemplateTaskQueries.findOneAndUpdate({ externalId : parsedData.parentTaskId },{ $addToSet : { @@ -388,7 +362,7 @@ module.exports = class ProjectTemplateTasksHelper { value : parsedData.parentTaskValue }); - await database.models.projectTemplateTasks.findOneAndUpdate({ + await projectTemplateTaskQueries.findOneAndUpdate({ _id : taskData._id },{ $set : { @@ -403,7 +377,31 @@ module.exports = class ProjectTemplateTasksHelper { } } - await projectTemplatesHelper.updateProjectTemplateDocument + //update solution project key + if ( taskData.type == CONSTANTS.common.OBSERVATION && + taskData.solutionDetails && + taskData.solutionDetails._id + ) { + + let updateSolutionObj = { + "$set" : {} + }; + + updateSolutionObj["$set"]["referenceFrom"] = CONSTANTS.common.PROJECT; + updateSolutionObj["$set"]["project"] = { + _id: template._id.toString(), + taskId: taskData._id.toString() + }; + + await solutionsQueries.updateSolutionDocument + ( + { _id : taskData.solutionDetails._id }, + updateSolutionObj + ) + } + + //update project template + await projectTemplateQueries.updateProjectTemplateDocument ( { _id : template._id }, { $addToSet : { tasks : ObjectId(taskData._id) } } @@ -458,16 +456,26 @@ module.exports = class ProjectTemplateTasksHelper { } let pendingItems = []; + let taskSequence = csvData.data.template.taskSequence && csvData.data.template.taskSequence.length > 0 + ? csvData.data.template.taskSequence: []; + + let checkMandatoryTask = []; for ( let task = 0; task < tasks.length ; task ++ ) { let currentData = UTILS.valueParser(tasks[task]); currentData.createdBy = currentData.updatedBy = userId; + if ( currentData.isDeletable != "" && currentData.isDeletable === "TRUE" ) { + checkMandatoryTask.push(currentData.externalId); + } + if( currentData["hasAParentTask"] === "YES" && !csvData.data.tasks[currentData.parentTaskId] ) { + pendingItems.push(currentData); + } else { if( csvData.data.tasks[currentData.externalId] ) { @@ -482,23 +490,29 @@ module.exports = class ProjectTemplateTasksHelper { csvData.data.solutionData ); + if (createdTask._SYSTEM_ID != ""){ + taskSequence.push(createdTask.externalId); + } + input.push(createdTask); } } } + let childTaskSequence = {}; if ( pendingItems && pendingItems.length > 0 ) { for ( let item = 0; item < pendingItems.length ; item ++ ) { let currentData = pendingItems[item]; + currentData.createdBy = currentData.updatedBy = userId; if( csvData.data.tasks[currentData.externalId] ) { currentData._SYSTEM_ID = CONSTANTS.apiResponses.PROJECT_TEMPLATE_TASK_EXISTS; input.push(currentData); } else { - + let createdTask = await this.createOrUpdateTask( currentData, csvData.data.template, @@ -506,11 +520,44 @@ module.exports = class ProjectTemplateTasksHelper { csvData.data.observationData ); + if ( createdTask._SYSTEM_ID != "" ) { + + if (!childTaskSequence.hasOwnProperty(currentData.parentTaskId)){ + childTaskSequence[currentData.parentTaskId] = new Array(); + } + childTaskSequence[currentData.parentTaskId].push(currentData.externalId); + } + input.push(createdTask); } } } + + if ( taskSequence && taskSequence.length > 0 ) { + await projectTemplateQueries.updateProjectTemplateDocument + ( + { _id : ObjectId(projectTemplateId) }, + { $set : { taskSequence : taskSequence } } + ) + } + + if ( childTaskSequence && Object.keys(childTaskSequence).length > 0 ) { + for( let pointerToTask in childTaskSequence ) { + await projectTemplateTaskQueries.updateTaskDocument + ( + { externalId : pointerToTask }, + { $set : { taskSequence : childTaskSequence[pointerToTask] } } + ) + } + + } + + if ( checkMandatoryTask && checkMandatoryTask.length > 0 ) { + + await this.checkAndUpdateParentTaskmandatory(checkMandatoryTask); + + } input.push(null); @@ -571,13 +618,16 @@ module.exports = class ProjectTemplateTasksHelper { }) } + let updateChildTaskSequence = {}; + let updateTemplateTaskSequence = new Array(); + let checkMandatoryTask = []; for ( let task = 0; task < tasks.length ; task ++ ) { let currentData = UTILS.valueParser(tasks[task]); if ( !currentData._SYSTEM_ID || - !currentData._SYSTEM_ID === "" || + !(currentData._SYSTEM_ID === "")|| !csvData.data.tasks[currentData["_SYSTEM_ID"]] ) { currentData.STATUS = @@ -587,6 +637,10 @@ module.exports = class ProjectTemplateTasksHelper { } currentData.updatedBy = userId; + + if ( currentData.isDeletable != "" && currentData.isDeletable === "TRUE" ) { + checkMandatoryTask.push(currentData.externalId); + } let createdTask = await this.createOrUpdateTask( @@ -596,12 +650,25 @@ module.exports = class ProjectTemplateTasksHelper { true ); + if ( createdTask._SYSTEM_ID != "") { + + if ( currentData.parentTaskId != "" ) { + if ( !updateChildTaskSequence.hasOwnProperty(currentData.parentTaskId)){ + updateChildTaskSequence[currentData.parentTaskId] = new Array(); + } + + updateChildTaskSequence[currentData.parentTaskId].push(currentData.externalId); + }else{ + updateTemplateTaskSequence.push(currentData.externalId); + } + } + if( csvData.data.tasks[currentData._SYSTEM_ID].parentId && csvData.data.tasks[currentData._SYSTEM_ID].parentId.toString() !== createdTask._parentTaskId.toString() ) { - await database.models.projectTemplateTasks.findOneAndUpdate( + await projectTemplateTaskQueries.findOneAndUpdate( { _id: csvData.data.tasks[currentData._SYSTEM_ID].parentId }, @@ -614,6 +681,38 @@ module.exports = class ProjectTemplateTasksHelper { input.push(createdTask); } + + let checkTemplateTaskSequence = true; + let templateTaskSequence = csvData.data.template.taskSequence; + + if ( templateTaskSequence ) { + checkTemplateTaskSequence = _.isEqual(templateTaskSequence, updateTemplateTaskSequence); + } + + if ( updateTemplateTaskSequence && updateTemplateTaskSequence.length > 0 && checkTemplateTaskSequence == false ) { + await projectTemplateQueries.updateProjectTemplateDocument + ( + { _id : ObjectId(projectTemplateId) }, + { $set : { taskSequence : updateTemplateTaskSequence } } + ) + } + + if ( updateChildTaskSequence && Object.keys(updateChildTaskSequence).length > 0 ) { + for( let pointerToTask in updateChildTaskSequence ) { + await projectTemplateTaskQueries.updateTaskDocument + ( + { externalId : pointerToTask }, + { $set : { taskSequence : updateChildTaskSequence[pointerToTask] } } + ) + } + + } + + if ( checkMandatoryTask && checkMandatoryTask.length > 0 ) { + + await this.checkAndUpdateParentTaskmandatory(checkMandatoryTask); + + } input.push(null); @@ -622,4 +721,167 @@ module.exports = class ProjectTemplateTasksHelper { } }) } + + /** + * check parent task is mandatory. + * @method + * @name checkAndUpdateParentTaskmandatory + * @param {Array} mandatoryTask - task external Ids. + * @returns {Object} tasks. + */ + + static checkAndUpdateParentTaskmandatory( taskIds = [] ) { + return new Promise(async (resolve, reject) => { + try { + + let updateParentTask = []; + + let taskData = await projectTemplateTaskQueries.taskDocuments( + { externalId : { $in : taskIds}, + hasSubTasks : true + }, + ["children"] + ); + + if ( taskData && taskData.length > 0 ) { + + for ( let eachTask = 0 ; eachTask < taskData.length ; eachTask ++ ) { + + let currentTask = taskData[eachTask]; + if (currentTask.children && currentTask.children.length > 0) { + + let childTasks = await projectTemplateTaskQueries.taskDocuments( + { _id : { $in : currentTask.children} + }, + ["isDeletable", "parentId"] + ); + + if ( childTasks && childTasks.length > 0 ) { + + childTasks.forEach( eachChildTask => { + if( eachChildTask.isDeletable === false && eachChildTask.parentId != "" ) { + updateParentTask.push(eachChildTask.parentId); + } + }); + } + } + } + + if ( updateParentTask && updateParentTask.length > 0 ) { + let updatedTasks = await projectTemplateTaskQueries.updateTaskDocument + ( + { _id : { $in : updateParentTask }}, + { $set : { isDeletable : false } } + ) + } + + } + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.TASKS_MARKED_AS_ISDELETABLE_FALSE, + data : updateParentTask + }) + + } catch (error) { + return reject(error); + } + }) + } + + /** + * Task update. + * @method + * @name update + * @param {String} taskId - Task id. + * @param {Object} taskData - template task updation data + * @param {String} userId - logged in user id. + * @returns {Array} Project templates task data. + */ + + static update( taskId, taskData, userId ) { + return new Promise(async (resolve, reject) => { + try { + + let findQuery = {}; + + let validateTaskId = UTILS.isValidMongoId(taskId); + + if( validateTaskId ) { + findQuery["_id"] = taskId; + } else { + findQuery["externalId"] = taskId; + } + + let taskDocument = await projectTemplateTaskQueries.taskDocuments(findQuery, ["_id"]); + + if ( !(taskDocument.length > 0) ) { + throw { + status : HTTP_STATUS_CODE.bad_request.status, + message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_TASKS_NOT_FOUND + } + } + + let updateObject = { + "$set" : {} + }; + + let taskUpdateData = taskData; + + Object.keys(taskUpdateData).forEach(updationData=>{ + updateObject["$set"][updationData] = taskUpdateData[updationData]; + }); + + updateObject["$set"]["updatedBy"] = userId; + + let taskUpdatedData = await projectTemplateTaskQueries.findOneAndUpdate({ + _id : taskDocument[0]._id + }, updateObject, { new : true }); + + if( !taskUpdatedData._id ) { + throw { + message : CONSTANTS.apiResponses.TEMPLATE_TASK_NOT_UPDATED + } + } + + return resolve({ + success : true, + data : taskUpdatedData, + message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_TASK_UPDATED + }); + + } catch (error) { + return reject(error); + } + }) + } + }; + +/** + * Helper function for list of solution fields to be sent in response. + * @method + * @name solutionDocumentProjectionFieldsForTask + * @returns {Promise} Returns a Promise. + */ + +function _solutionDocumentProjectionFieldsForTask() { + + let projectionFields = [ + "_id", + "isReusable", + "externalId", + "name", + "programId", + "type", + "subType", + "allowMultipleAssessemts", + "isRubricDriven", + "criteriaLevelReport", + "scoringSystem" + ]; + + return projectionFields; +} + + diff --git a/module/project/templates/helper.js b/module/project/templates/helper.js index 39618865..4d6e86a9 100644 --- a/module/project/templates/helper.js +++ b/module/project/templates/helper.js @@ -6,1169 +6,1254 @@ */ /** - * ProjectTemplatesHelper - * @class -*/ + * ProjectTemplatesHelper + * @class + */ // Dependencies -const libraryCategoriesHelper = require(MODULES_BASE_PATH + "/library/categories/helper"); -const kendraService = require(GENERICS_FILES_PATH + "/services/kendra"); +const libraryCategoriesHelper = require(MODULES_BASE_PATH + + "/library/categories/helper"); +const coreService = require(GENERICS_FILES_PATH + "/services/core"); const kafkaProducersHelper = require(GENERICS_FILES_PATH + "/kafka/producers"); -const learningResourcesHelper = require(MODULES_BASE_PATH + "/learningResources/helper"); -const assessmentService = require(GENERICS_FILES_PATH + "/services/assessment"); +const learningResourcesHelper = require(MODULES_BASE_PATH + + "/learningResources/helper"); +const surveyService = require(GENERICS_FILES_PATH + "/services/survey"); +const projectTemplateQueries = require(DB_QUERY_BASE_PATH + + "/projectTemplates"); +const projectTemplateTaskQueries = require(DB_QUERY_BASE_PATH + + "/projectTemplateTask"); +const projectQueries = require(DB_QUERY_BASE_PATH + "/projects"); +const projectCategoriesQueries = require(DB_QUERY_BASE_PATH + + "/projectCategories"); +const solutionsQueries = require(DB_QUERY_BASE_PATH + "/solutions"); +const certificateTemplateQueries = require(DB_QUERY_BASE_PATH + + "/certificateTemplates"); module.exports = class ProjectTemplatesHelper { + /** + * Extract csv information. + * @method + * @name extractCsvInformation + * @param {Object} csvData - csv data. + * @returns {Object} Extra csv information. + */ + + static extractCsvInformation(csvData) { + return new Promise(async (resolve, reject) => { + try { + let categoryIds = []; + let roleIds = []; + let tasksIds = []; + // <- Entitytype validation removed {release-5.0.0} - entity generalisation + // let entityTypes = []; + + csvData.forEach((template) => { + let parsedData = UTILS.valueParser(template); + + categoryIds = _.concat(categoryIds, parsedData.categories); + + tasksIds = _.concat(tasksIds, parsedData.tasks); + + if (parsedData.recommendedFor) { + parsedData.recommendedFor = parsedData.recommendedFor.map( + (role) => { + return role.toUpperCase(); + } + ); + + roleIds = _.concat(roleIds, parsedData.recommendedFor); + } + // <- Entitytype validation removed {release-5.0.0} - entity generalisation + // if( parsedData.entityType ) { + // entityTypes.push(parsedData.entityType); + // } + }); + + let categoriesData = {}; + + if (categoryIds.length > 0) { + let categories = await projectCategoriesQueries.categoryDocuments( + { + externalId: { $in: categoryIds }, + }, + ["externalId", "name"] + ); + + if (!(categories.length > 0)) { + throw { + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.LIBRARY_CATEGORIES_NOT_FOUND, + }; + } + + categoriesData = categories.reduce( + (ac, category) => ({ + ...ac, + [category.externalId]: { + _id: ObjectId(category._id), + externalId: category.externalId, + name: category.name, + }, + }), + {} + ); + } - /** - * Lists of template. - * @method - * @name templateDocument - * @param {Array} [filterData = "all"] - template filter query. - * @param {Array} [fieldsArray = "all"] - projected fields. - * @param {Array} [skipFields = "none"] - field not to include - * @returns {Array} Lists of template. - */ - - static templateDocument( - filterData = "all", - fieldsArray = "all", - skipFields = "none" - ) { - return new Promise(async (resolve, reject) => { - try { - - let queryObject = (filterData != "all") ? filterData : {}; - let projection = {} - - if (fieldsArray != "all") { - fieldsArray.forEach(field => { - projection[field] = 1; - }); - } - - if( skipFields !== "none" ) { - skipFields.forEach(field=>{ - projection[field] = 0; - }); - } - - let templates = - await database.models.projectTemplates.find( - queryObject, - projection - ).lean(); - - return resolve(templates); - - } catch (error) { - return reject(error); - } - }); - } - - /** - * Extract csv information. - * @method - * @name extractCsvInformation - * @param {Object} csvData - csv data. - * @returns {Object} Extra csv information. - */ - - static extractCsvInformation(csvData) { - return new Promise(async (resolve, reject) => { - try { - - let categoryIds = []; - let roleIds = []; - let tasksIds = []; - let entityTypes = []; - - csvData.forEach(template=>{ - - let parsedData = UTILS.valueParser(template); - - categoryIds = _.concat( - categoryIds, - parsedData.categories - ); - - tasksIds = _.concat( - tasksIds, - parsedData.tasks - ); - - if( parsedData.recommendedFor ) { - - parsedData.recommendedFor = - parsedData.recommendedFor.map(role=>{ - return role.toUpperCase() - }); - - roleIds = _.concat( - roleIds, - parsedData.recommendedFor - ); - } - - if( parsedData.entityType ) { - entityTypes.push(parsedData.entityType); - } - - }); - - let categoriesData = {}; - - if( categoryIds.length > 0 ) { - - let categories = - await libraryCategoriesHelper.categoryDocuments({ - externalId : { $in : categoryIds } - },["externalId","name"]); - - if( !categories.length > 0 ) { - throw { - status : HTTP_STATUS_CODE['bad_request'].status, - message : CONSTANTS.apiResponses.LIBRARY_CATEGORIES_NOT_FOUND - } - } - - categoriesData = categories.reduce((ac,category)=> ({ - ...ac, - [category.externalId] : { - _id : ObjectId(category._id), - externalId : category.externalId, - name : category.name - } - }),{}); - } - - let recommendedFor = {}; - - if( roleIds.length > 0 ) { - - let userRolesData = - await kendraService.rolesDocuments({ - code : { $in : roleIds } - },["code"]); - - if( !userRolesData.success ) { - throw { - message : CONSTANTS.apiResponses.USER_ROLES_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } - } - - recommendedFor = userRolesData.data.reduce((ac,role)=> ({ - ...ac, - [role.code] : { - roleId : ObjectId(role._id), - code : role.code - } - }),{}); - } + let recommendedFor = {}; + + if (roleIds.length > 0) { + let userRolesData = await coreService.rolesDocuments( + { + code: { $in: roleIds }, + }, + ["code"] + ); + + if (!userRolesData.success) { + throw { + message: CONSTANTS.apiResponses.USER_ROLES_NOT_FOUND, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } + + recommendedFor = userRolesData.data.reduce( + (ac, role) => ({ + ...ac, + [role.code]: { + roleId: ObjectId(role._id), + code: role.code, + }, + }), + {} + ); + } + // <- Entitytype validation removed {release-5.0.0} - entity generalisation + // let entityTypesData = {}; + + // if( entityTypes.length > 0 ) { + + // let entityTypesDocument = + // await coreService.entityTypesDocuments(); + + // if( !entityTypesDocument.success ) { + // throw { + // message : CONSTANTS.apiResponses.ENTITY_TYPES_NOT_FOUND, + // status : HTTP_STATUS_CODE['bad_request'].status + // } + // } + + // entityTypesData = entityTypesDocument.data.reduce((ac,entityType)=> ({ + // ...ac, + // [entityType.name] : { + // _id : ObjectId(entityType._id), + // name : entityType.name + // } + // }),{}); + + // } + + return resolve({ + success: true, + data: { + categories: categoriesData, + roles: recommendedFor, + // <- Entitytype validation removed {release-5.0.0} - entity generalisation + // entityTypes : entityTypesData + }, + }); + } catch (error) { + return resolve({ + success: false, + message: error.message, + status: error.status + ? error.status + : HTTP_STATUS_CODE["internal_server_error"].status, + }); + } + }); + } + + /** + * Template data. + * @method + * @name templateData + * @param {Object} data - csv data. + * @param {Object} csvInformation - csv information. + * @returns {Object} Template data. + */ + + static templateData(data, csvInformation) { + return new Promise(async (resolve, reject) => { + try { + let templatesDataModel = Object.keys( + schemas["project-templates"].schema + ); + let parsedData = UTILS.valueParser(data); + delete parsedData._arrayFields; + + let categories = []; + + if (parsedData.categories && parsedData.categories.length > 0) { + parsedData.categories.forEach((category) => { + if (csvInformation.categories[category]) { + return categories.push(csvInformation.categories[category]); + } + }); + } - let entityTypesData = {}; + parsedData.categories = categories; - if( entityTypes.length > 0 ) { - - let entityTypesDocument = - await kendraService.entityTypesDocuments(); + let recommendedFor = []; - if( !entityTypesDocument.success ) { - throw { - message : CONSTANTS.apiResponses.ENTITY_TYPES_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } - } + if (parsedData.recommendedFor && parsedData.recommendedFor.length > 0) { + parsedData.recommendedFor.forEach((recommended) => { + if (csvInformation.roles[recommended]) { + return recommendedFor.push(csvInformation.roles[recommended]); + } + }); + } - entityTypesData = entityTypesDocument.data.reduce((ac,entityType)=> ({ - ...ac, - [entityType.name] : { - _id : ObjectId(entityType._id), - name : entityType.name - } - }),{}); + parsedData.recommendedFor = recommendedFor; + // <- Entitytype validation removed {release-5.0.0} - entity generalisation + // if( parsedData.entityType && parsedData.entityType !== "" ) { + // parsedData.entityType = csvInformation.entityTypes[parsedData.entityType].name; + // } + + let learningResources = + await learningResourcesHelper.extractLearningResourcesFromCsv( + parsedData + ); + parsedData.learningResources = learningResources.data; + + parsedData.metaInformation = {}; + let booleanData = UTILS.getAllBooleanDataFromModels( + schemas["project-templates"].schema + ); + + Object.keys(parsedData).forEach((eachParsedData) => { + if (!templatesDataModel.includes(eachParsedData)) { + if (!eachParsedData.startsWith("learningResources")) { + parsedData.metaInformation[eachParsedData] = + parsedData[eachParsedData]; + delete parsedData[eachParsedData]; + } + } else { + if (booleanData.includes(eachParsedData)) { + parsedData[eachParsedData] = UTILS.convertStringToBoolean( + parsedData[eachParsedData] + ); + } + } + }); - } + parsedData.isReusable = true; - return resolve({ - success : true, - data : { - categories : categoriesData, - roles : recommendedFor, - entityTypes : entityTypesData - } - }); + return resolve(parsedData); + } catch (error) { + return reject(error); + } + }); + } + + /** + * Bulk created project templates. + * @method + * @name bulkCreate - bulk create project templates. + * @param {Array} templates - csv templates data. + * @param {String} userId - logged in user id. + * @returns {Object} Bulk create project templates. + */ + + static bulkCreate(templates, userId) { + return new Promise(async (resolve, reject) => { + try { + const fileName = `project-templates-creation`; + let fileStream = new CSV_FILE_STREAM(fileName); + let input = fileStream.initStream(); + + (async function () { + await fileStream.getProcessorPromise(); + return resolve({ + isResponseAStream: true, + fileNameWithPath: fileStream.fileNameWithPath(), + }); + })(); + + let csvInformation = await this.extractCsvInformation(templates); + + if (!csvInformation.success) { + return resolve(csvInformation); + } - } catch(error) { - return resolve({ - success : false, - message : error.message, - status : error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status + for (let template = 0; template < templates.length; template++) { + let currentData = templates[template]; + + let templateData = await projectTemplateQueries.templateDocument( + { + status: CONSTANTS.common.PUBLISHED, + externalId: currentData.externalId, + isReusable: true, + }, + ["_id"] + ); + + if (templateData.length > 0 && templateData[0]._id) { + currentData["_SYSTEM_ID"] = + CONSTANTS.apiResponses.PROJECT_TEMPLATE_EXISTS; + } else { + let templateData = await this.templateData( + currentData, + csvInformation.data, + userId + ); + + templateData.status = CONSTANTS.common.PUBLISHED_STATUS; + templateData.createdBy = + templateData.updatedBy = + templateData.userId = + userId; + templateData.isReusable = true; + + let createdTemplate = await projectTemplateQueries.createTemplate( + templateData + ); + + if (!createdTemplate._id) { + currentData["_SYSTEM_ID"] = + CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND; + } else { + currentData["_SYSTEM_ID"] = createdTemplate._id; + + if ( + templateData.categories && + templateData.categories.length > 0 + ) { + let categories = templateData.categories.map((category) => { + return category._id; }); - } - }) - } - /** - * Template data. - * @method - * @name templateData - * @param {Object} data - csv data. - * @param {Object} csvInformation - csv information. - * @returns {Object} Template data. - */ - - static templateData(data,csvInformation) { - return new Promise(async (resolve, reject) => { - try { - - let templatesDataModel = - Object.keys(schemas["project-templates"].schema); - let parsedData = UTILS.valueParser(data); - delete parsedData._arrayFields; - - let categories = []; - - if( parsedData.categories && parsedData.categories.length > 0 ) { - - parsedData.categories.forEach( category => { - if( csvInformation.categories[category] ) { - return categories.push( - csvInformation.categories[category] - ); - } - }); - } + let updatedCategories = await libraryCategoriesHelper.update( + { + _id: { $in: categories }, + }, + { + $inc: { noOfProjects: 1 }, + } + ); - parsedData.categories = categories; - - let recommendedFor = []; - - if( parsedData.recommendedFor && parsedData.recommendedFor.length > 0 ) { - parsedData.recommendedFor.forEach(recommended => { - if( csvInformation.roles[recommended] ) { - return recommendedFor.push( - csvInformation.roles[recommended] - ); - } - }); + if (!updatedCategories.success) { + currentData["_SYSTEM_ID"] = updatedCategories.message; } + } + + // <- Dirty fix . Not required + // const kafkaMessage = + // await kafkaProducersHelper.pushProjectToKafka({ + // internal : false, + // text : + // templateData.categories.length === 1 ? + // `A new project has been added under ${templateData.categories[0].name} category in library.` : + // `A new project has been added in library`, + // type : "information", + // action : "mapping", + // payload : { + // project_id: createdTemplate._id + // }, + // is_read : false, + // internal : false, + // title : "New project Available!", + // created_at : new Date(), + // appType : process.env.IMPROVEMENT_PROJECT_APP_TYPE, + // inApp:false, + // push: true, + // pushToTopic: true, + // topicName : process.env.NODE_ENV + "-" + process.env.IMPROVEMENT_PROJECT_APP_NAME + process.env.TOPIC_FOR_ALL_USERS + // }); + + // if (kafkaMessage.status !== CONSTANTS.common.SUCCESS) { + // currentData["_SYSTEM_ID"] = CONSTANTS.apiResponses.COULD_NOT_PUSHED_TO_KAFKA; + // } + } + } - parsedData.recommendedFor = recommendedFor; - - if( parsedData.entityType && parsedData.entityType !== "" ) { - parsedData.entityType = csvInformation.entityTypes[parsedData.entityType].name; - parsedData.entityTypeId = csvInformation.entityTypes[parsedData.entityType]._id; - } + input.push(currentData); + } - let learningResources = - await learningResourcesHelper.extractLearningResourcesFromCsv(parsedData); - parsedData.learningResources = learningResources.data; + input.push(null); + } catch (error) { + return reject(error); + } + }); + } + + /** + * Bulk update project templates. + * @method + * @name bulkUpdate - bulk update project templates. + * @param {Array} templates - csv templates data. + * @param {String} userId - logged in user id. + * @returns {Object} Bulk Update Project templates. + */ + + static bulkUpdate(templates, userId) { + return new Promise(async (resolve, reject) => { + try { + const fileName = `project-templates-updation`; + let fileStream = new CSV_FILE_STREAM(fileName); + let input = fileStream.initStream(); + + (async function () { + await fileStream.getProcessorPromise(); + return resolve({ + isResponseAStream: true, + fileNameWithPath: fileStream.fileNameWithPath(), + }); + })(); + + let csvInformation = await this.extractCsvInformation(templates); + + if (!csvInformation.success) { + return resolve(csvInformation); + } - parsedData.metaInformation = {}; - let booleanData = - UTILS.getAllBooleanDataFromModels( - schemas["project-templates"].schema + for (let template = 0; template < templates.length; template++) { + const currentData = templates[template]; + + if (!currentData._SYSTEM_ID) { + currentData["UPDATE_STATUS"] = + CONSTANTS.apiResponses.MISSING_PROJECT_TEMPLATE_ID; + } else { + const template = await projectTemplateQueries.templateDocument( + { + status: CONSTANTS.common.PUBLISHED, + _id: currentData._SYSTEM_ID, + status: CONSTANTS.common.PUBLISHED, + }, + ["_id", "categories", "isReusable"] + ); + + if (!(template.length > 0 && template[0]._id)) { + currentData["UPDATE_STATUS"] = + CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND; + } else { + let templateData = await this.templateData( + _.omit(currentData, ["_SYSTEM_ID"]), + csvInformation.data, + userId + ); + + if (template[0].isReusable === false) { + templateData.isReusable = false; + } + + templateData.updatedBy = userId; + + let projectTemplateUpdated = + await projectTemplateQueries.findOneAndUpdate( + { + _id: currentData._SYSTEM_ID, + }, + { + $set: templateData, + }, + { + new: true, + } ); - Object.keys(parsedData).forEach( eachParsedData => { - if( - !templatesDataModel.includes(eachParsedData) - ) { - - if( !eachParsedData.startsWith("learningResources") ) { - parsedData.metaInformation[eachParsedData] = - parsedData[eachParsedData]; - delete parsedData[eachParsedData]; - } - - } else { - if( booleanData.includes(eachParsedData) ) { - parsedData[eachParsedData] = - UTILS.convertStringToBoolean(parsedData[eachParsedData]); - } - } + if (!projectTemplateUpdated || !projectTemplateUpdated._id) { + currentData["UPDATE_STATUS"] = + CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_UPDATED; + } + + // Add projects count to categories + if ( + templateData.categories && + templateData.categories.length > 0 + ) { + let categories = templateData.categories.map((category) => { + return category._id; }); - parsedData.isReusable = true; - - return resolve(parsedData); - - } catch(error) { - return reject(error); - } - }) - } - - /** - * Bulk created project templates. - * @method - * @name bulkCreate - bulk create project templates. - * @param {Array} templates - csv templates data. - * @param {String} userId - logged in user id. - * @returns {Object} Bulk create project templates. - */ - - static bulkCreate(templates,userId) { - - return new Promise(async (resolve, reject) => { - - try { - - const fileName = `project-templates-creation`; - let fileStream = new CSV_FILE_STREAM(fileName); - let input = fileStream.initStream(); - - (async function () { - await fileStream.getProcessorPromise(); - return resolve({ - isResponseAStream: true, - fileNameWithPath: fileStream.fileNameWithPath() - }); - })(); - - let csvInformation = await this.extractCsvInformation(templates); + let updatedCategories = await libraryCategoriesHelper.update( + { + _id: { $in: categories }, + }, + { + $inc: { noOfProjects: 1 }, + } + ); - if( !csvInformation.success ) { - return resolve(csvInformation); + if (!updatedCategories.success) { + currentData["UPDATE_STATUS"] = updatedCategories.message; } + } - for ( let template = 0; template < templates.length ; template ++ ) { - - let currentData = templates[template]; - - let templateData = - await this.templateDocument({ - status : CONSTANTS.common.PUBLISHED, - externalId : currentData.externalId, - isReusable : true - },["_id"]); - - if( templateData.length > 0 && templateData[0]._id ) { - currentData["_SYSTEM_ID"] = - CONSTANTS.apiResponses.PROJECT_TEMPLATE_EXISTS; - } else { - - let templateData = await this.templateData( - currentData, - csvInformation.data, - userId - ); - - templateData.status = CONSTANTS.common.PUBLISHED_STATUS; - templateData.createdBy = templateData.updatedBy = templateData.userId = userId; - templateData.isReusable = true; - - let createdTemplate = - await database.models.projectTemplates.create( - templateData - ); - - if( !createdTemplate._id ) { - currentData["_SYSTEM_ID"] = CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND; - } else { - - currentData["_SYSTEM_ID"] = createdTemplate._id; - - if( - templateData.categories && - templateData.categories.length > 0 - ) { - - let categories = templateData.categories.map(category => { - return category._id; - }); - - let updatedCategories = - await libraryCategoriesHelper.update({ - _id : { $in : categories } - },{ - $inc : { noOfProjects : 1 } - }); - - if( !updatedCategories.success ) { - currentData["_SYSTEM_ID"] = updatedCategories.message; - } - } - - // <- Dirty fix . Not required - // const kafkaMessage = - // await kafkaProducersHelper.pushProjectToKafka({ - // internal : false, - // text : - // templateData.categories.length === 1 ? - // `A new project has been added under ${templateData.categories[0].name} category in library.` : - // `A new project has been added in library`, - // type : "information", - // action : "mapping", - // payload : { - // project_id: createdTemplate._id - // }, - // is_read : false, - // internal : false, - // title : "New project Available!", - // created_at : new Date(), - // appType : process.env.IMPROVEMENT_PROJECT_APP_TYPE, - // inApp:false, - // push: true, - // pushToTopic: true, - // topicName : process.env.NODE_ENV + "-" + process.env.IMPROVEMENT_PROJECT_APP_NAME + process.env.TOPIC_FOR_ALL_USERS - // }); - - // if (kafkaMessage.status !== CONSTANTS.common.SUCCESS) { - // currentData["_SYSTEM_ID"] = CONSTANTS.apiResponses.COULD_NOT_PUSHED_TO_KAFKA; - // } - - } - - } - - input.push(currentData); + // Remove project count from existing categories + if (template[0].categories && template[0].categories.length > 0) { + const categoriesIds = template[0].categories.map((category) => { + return category._id; + }); - } + let categoriesUpdated = await libraryCategoriesHelper.update( + { + _id: { $in: categoriesIds }, + }, + { + $inc: { noOfProjects: -1 }, + } + ); - input.push(null); + if (!categoriesUpdated.success) { + currentData["UPDATE_STATUS"] = updatedCategories.message; + } + } - } catch (error) { - return reject(error); + currentData["UPDATE_STATUS"] = CONSTANTS.common.SUCCESS; } - }) - } + } - /** - * Bulk update project templates. - * @method - * @name bulkUpdate - bulk update project templates. - * @param {Array} templates - csv templates data. - * @param {String} userId - logged in user id. - * @returns {Object} Bulk Update Project templates. - */ - - static bulkUpdate(templates,userId) { - return new Promise(async (resolve, reject) => { - try { - - const fileName = `project-templates-updation`; - let fileStream = new CSV_FILE_STREAM(fileName); - let input = fileStream.initStream(); - - (async function () { - await fileStream.getProcessorPromise(); - return resolve({ - isResponseAStream: true, - fileNameWithPath: fileStream.fileNameWithPath() - }); - })(); + input.push(templates[template]); + } - let csvInformation = await this.extractCsvInformation(templates); + input.push(null); + } catch (error) { + return reject(error); + } + }); + } + + /** + * Bulk update project templates. + * @method + * @name importProjectTemplate - import templates from existing project templates. + * @param {String} templateId - project template id. + * @param {String} userId - logged in user id. + * @param {String} userToken - logged in user token. + * @param {String} solutionId - solution id. + * @param {Object} updateData - template update data. + * @returns {Object} imported templates data. + */ + + static importProjectTemplate( + templateId, + userId, + userToken, + solutionId, + updateData = {} + ) { + return new Promise(async (resolve, reject) => { + try { + let projectTemplateData = await projectTemplateQueries.templateDocument( + { + status: CONSTANTS.common.PUBLISHED, + externalId: templateId, + isReusable: true, + } + ); + + if (!(projectTemplateData.length > 0)) { + throw new Error(CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND); + } - if( !csvInformation.success ) { - return resolve(csvInformation); - } + let newProjectTemplate = { ...projectTemplateData[0] }; + newProjectTemplate.externalId = + projectTemplateData[0].externalId + "-" + UTILS.epochTime(); + newProjectTemplate.createdBy = newProjectTemplate.updatedBy = userId; - for ( let template = 0; template < templates.length ; template ++ ) { - - const currentData = templates[template]; - - if ( !currentData._SYSTEM_ID ) { - currentData["UPDATE_STATUS"] = - CONSTANTS.apiResponses.MISSING_PROJECT_TEMPLATE_ID; - } else { - - const template = - await this.templateDocument({ - status : CONSTANTS.common.PUBLISHED, - _id : currentData._SYSTEM_ID, - status : CONSTANTS.common.PUBLISHED - },["_id","categories"]); - - if ( !(template.length > 0 && template[0]._id) ) { - currentData["UPDATE_STATUS"] = - constants.apiResponses.PROJECT_TEMPLATE_NOT_FOUND; - } else { - - let templateData = await this.templateData( - _.omit(currentData,["_SYSTEM_ID"]), - csvInformation.data, - userId - ); - - templateData.updatedBy = userId; - - let projectTemplateUpdated = - await database.models.projectTemplates.findOneAndUpdate({ - _id : currentData._SYSTEM_ID - },{ - $set : templateData - },{ - new : true - }); - - if( !projectTemplateUpdated._id ) { - currentData["UPDATE_STATUS"] = - constants.apiResponses.PROJECT_TEMPLATE_NOT_UPDATED; - } - - // Add projects count to categories - if( - templateData.categories && - templateData.categories.length > 0 - ) { - - let categories = - templateData.categories.map(category => { - return category._id; - }); - - let updatedCategories = - await libraryCategoriesHelper.update({ - _id : { $in : categories } - },{ - $inc : { noOfProjects : 1 } - }); - - if( !updatedCategories.success ) { - currentData["UPDATE_STATUS"] = updatedCategories.message; - } - } - - // Remove project count from existing categories - if( - template[0].categories && - template[0].categories.length > 0 - ) { - - const categoriesIds = - template[0].categories.map(category=>{ - return category._id; - }); - - let categoriesUpdated = - await libraryCategoriesHelper.update({ - _id : { $in : categoriesIds } - },{ - $inc : { noOfProjects : -1 } - }); - - if( !categoriesUpdated.success ) { - currentData["UPDATE_STATUS"] = updatedCategories.message; - } - } - - currentData["UPDATE_STATUS"] = CONSTANTS.common.SUCCESS; - } - - } - - input.push(templates[template]); + let solutionData = await surveyService.listSolutions([solutionId]); - } + if (!solutionData.success) { + throw { + message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } - input.push(null); + if ( + solutionData.data[0].type !== CONSTANTS.common.IMPROVEMENT_PROJECT + ) { + throw { + message: + CONSTANTS.apiResponses.IMPROVEMENT_PROJECT_SOLUTION_NOT_FOUND, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } - } catch (error) { - return reject(error); - } - }) - } + if (solutionData.data[0].projectTemplateId) { + throw { + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_EXISTS_IN_SOLUTION, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } - /** - * Bulk update project templates. - * @method - * @name importProjectTemplate - import templates from existing project templates. - * @param {String} templateId - project template id. - * @param {String} userId - logged in user id. - * @param {String} userToken - logged in user token. - * @param {String} solutionId - solution id. - * @param {Object} updateData - template update data. - * @returns {Object} imported templates data. - */ - - static importProjectTemplate( templateId,userId,userToken,solutionId,updateData = {} ) { - return new Promise(async (resolve, reject) => { - try { - - let projectTemplateData = - await this.templateDocument({ - status : CONSTANTS.common.PUBLISHED, - externalId : templateId, - isReusable : true - }); + if ( + projectTemplateData[0].entityType && + projectTemplateData[0].entityType !== "" && + projectTemplateData[0].entityType !== solutionData.data[0].entityType + ) { + throw { + message: CONSTANTS.apiResponses.ENTITY_TYPE_MIS_MATCHED, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } - if ( !projectTemplateData.length > 0 ) { - throw new Error(CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND) - } + newProjectTemplate.solutionId = solutionData.data[0]._id; + newProjectTemplate.solutionExternalId = solutionData.data[0].externalId; + newProjectTemplate.programId = solutionData.data[0].programId; + newProjectTemplate.programExternalId = + solutionData.data[0].programExternalId; - let newProjectTemplate = {...projectTemplateData[0]}; - newProjectTemplate.externalId = - projectTemplateData[0].externalId +"-"+ UTILS.epochTime(); - newProjectTemplate.createdBy = newProjectTemplate.updatedBy = userId; - - let solutionData = - await assessmentService.listSolutions([solutionId]); - - if( !solutionData.success ) { - throw { - message : CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } - } + newProjectTemplate.parentTemplateId = projectTemplateData[0]._id; - if( solutionData.data[0].type !== CONSTANTS.common.IMPROVEMENT_PROJECT ) { - throw { - message : CONSTANTS.apiResponses.IMPROVEMENT_PROJECT_SOLUTION_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } - } + let updationKeys = Object.keys(updateData); + if (updationKeys.length > 0) { + updationKeys.forEach((singleKey) => { + if (newProjectTemplate[singleKey]) { + newProjectTemplate[singleKey] = updateData[singleKey]; + } + }); + } - if( solutionData.data[0].projectTemplateId ) { - throw { - message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_EXISTS_IN_SOLUTION, - status : HTTP_STATUS_CODE['bad_request'].status - } - } + let tasksIds; - if( - projectTemplateData[0].entityType && - projectTemplateData[0].entityType !== "" && - projectTemplateData[0].entityType !== solutionData.data[0].entityType - ) { - throw { - message : CONSTANTS.apiResponses.ENTITY_TYPE_MIS_MATCHED, - status : HTTP_STATUS_CODE['bad_request'].status - } - } - - newProjectTemplate.solutionId = solutionData.data[0]._id; - newProjectTemplate.solutionExternalId = solutionData.data[0].externalId; - newProjectTemplate.programId = solutionData.data[0].programId; - newProjectTemplate.programExternalId = solutionData.data[0].programExternalId; - - - newProjectTemplate.parentTemplateId = projectTemplateData[0]._id; - - let updationKeys = Object.keys(updateData); - if( updationKeys.length > 0 ) { - updationKeys.forEach(singleKey => { - if( newProjectTemplate[singleKey] ) { - newProjectTemplate[singleKey] = updateData[singleKey]; - } - }) - } + if (projectTemplateData[0].tasks) { + tasksIds = projectTemplateData[0].tasks; + } - let tasksIds; - - if(projectTemplateData[0].tasks){ - tasksIds = projectTemplateData[0].tasks; - } + newProjectTemplate.isReusable = false; - newProjectTemplate.isReusable = false; + let duplicateTemplateDocument = + await projectTemplateQueries.createTemplate( + _.omit(newProjectTemplate, ["_id"]) + ); - let duplicateTemplateDocument = - await database.models.projectTemplates.create( - _.omit(newProjectTemplate, ["_id"]) - ); + if (!duplicateTemplateDocument._id) { + throw new Error(CONSTANTS.apiResponses.PROJECT_TEMPLATES_NOT_CREATED); + } - if ( !duplicateTemplateDocument._id ) { - throw new Error(CONSTANTS.apiResponses.PROJECT_TEMPLATES_NOT_CREATED) - } + //duplicate task + if (Array.isArray(tasksIds) && tasksIds.length > 0) { + await this.duplicateTemplateTasks( + tasksIds, + duplicateTemplateDocument._id, + duplicateTemplateDocument.externalId + ); + } - //duplicate task - if(Array.isArray(tasksIds) && tasksIds.length > 0 ){ - await this.duplicateTemplateTasks( - tasksIds, - duplicateTemplateDocument._id, - duplicateTemplateDocument.externalId - ); - } + await surveyService.updateSolution( + userToken, + { + projectTemplateId: duplicateTemplateDocument._id, + name: duplicateTemplateDocument.title, + }, + newProjectTemplate.solutionExternalId + ); + + await this.ratings( + projectTemplateData[0]._id, + updateData.rating, + userToken + ); + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.DUPLICATE_PROJECT_TEMPLATES_CREATED, + data: { + _id: duplicateTemplateDocument._id, + }, + }); + } catch (error) { + return resolve({ + status: error.status + ? error.status + : HTTP_STATUS_CODE["internal_server_error"].status, + success: false, + message: error.message, + data: {}, + }); + } + }); + } + + /** + * Create ratings. + * @method + * @name ratings + * @param {String} templateId - project template id. + * @param {String} rating - rating for template. + * @returns {Object} rating object. + */ + + static ratings(templateId, rating, userToken) { + return new Promise(async (resolve, reject) => { + try { + let userProfileData = await coreService.getProfile(userToken); + + if (!userProfileData.success) { + throw { + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.USER_PROFILE_NOT_FOUND, + }; + } - await assessmentService.updateSolution( - userToken, - { - projectTemplateId : duplicateTemplateDocument._id, - name : duplicateTemplateDocument.title - }, - newProjectTemplate.solutionExternalId - ); - - await this.ratings( - projectTemplateData[0]._id, - updateData.rating, - userToken - ); - - return resolve({ - success: true, - message : CONSTANTS.apiResponses.DUPLICATE_PROJECT_TEMPLATES_CREATED, - data : { - _id : duplicateTemplateDocument._id - } - }) - - } catch (error) { - return resolve({ - status : - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: {} - }); + let templateData = await projectTemplateQueries.templateDocument( + { + status: CONSTANTS.common.PUBLISHED, + _id: templateId, + isReusable: true, + }, + ["averageRating", "noOfRatings", "ratings"] + ); + + let updateRating = { + ratings: { ...templateData[0].ratings }, + }; + + updateRating.ratings[rating] += 1; + + let userCurrentRating = 0; + let projectIndex = -1; + + if ( + userProfileData.data && + userProfileData.data.ratings && + userProfileData.data.ratings.length > 0 + ) { + projectIndex = userProfileData.data.ratings.findIndex( + (project) => project._id.toString() === templateId.toString() + ); + + if (!(projectIndex < 0)) { + userCurrentRating = + userProfileData.data.ratings[projectIndex].rating; + updateRating.ratings[userCurrentRating] -= 1; + } + } else { + userProfileData.data.ratings = []; + } + + let ratingUpdated = {}; + + if (userCurrentRating === rating) { + ratingUpdated = templateData[0]; + } else { + let calculateRating = _calculateRating(updateRating.ratings); + updateRating.averageRating = calculateRating.averageRating; + updateRating.noOfRatings = calculateRating.noOfRatings; + + ratingUpdated = await projectTemplateQueries.findOneAndUpdate( + { + _id: templateId, + }, + { + $set: updateRating, + }, + { + new: true, } - }) - } + ); + + let improvementProjects = [...userProfileData.data.ratings]; + if (projectIndex >= 0) { + improvementProjects[projectIndex].rating = rating; + } else { + improvementProjects.push({ + _id: ObjectId(templateId), + externalId: ratingUpdated.externalId, + rating: rating, + type: CONSTANTS.common.IMPROVEMENT_PROJECT, + }); + } - /** - * Create ratings. - * @method - * @name ratings - * @param {String} templateId - project template id. - * @param {String} rating - rating for template. - * @returns {Object} rating object. - */ - - static ratings( templateId,rating,userToken ) { - return new Promise(async (resolve, reject) => { - try { - - let userProfileData = await kendraService.getProfile(userToken); - - if( !userProfileData.success ) { - throw { - status : HTTP_STATUS_CODE['bad_request'].status, - message : CONSTANTS.apiResponses.USER_PROFILE_NOT_FOUND - } - } + await coreService.updateUserProfile(userToken, { + ratings: improvementProjects, + }); + } - let templateData = - await this.templateDocument({ - status : CONSTANTS.common.PUBLISHED, - _id : templateId, - isReusable : true - },[ - "averageRating", - "noOfRatings", - "ratings" - ]); - - let updateRating = { - ratings : {...templateData[0].ratings} - }; - - updateRating.ratings[rating] += 1; - - let userCurrentRating = 0; - let projectIndex = -1; - - if( - userProfileData.data && - userProfileData.data.ratings && - userProfileData.data.ratings.length > 0 + return resolve( + _.pick(ratingUpdated, ["averageRating", "noOfRatings", "ratings"]) + ); + } catch (error) { + return resolve({ + success: false, + message: error.message, + status: error.status + ? error.status + : HTTP_STATUS_CODE["internal_server_error"].status, + }); + } + }); + } + + /** + * Project template tasks + * @method + * @name duplicateTemplateTasks + * @param {Array} taskIds - Task ids + * @returns {Object} Duplicated tasks. + */ + + static duplicateTemplateTasks( + taskIds = [], + duplicateTemplateId, + duplicateTemplateExternalId + ) { + return new Promise(async (resolve, reject) => { + try { + let newTaskId = []; + + for ( + let pointerToTask = 0; + pointerToTask < taskIds.length; + pointerToTask++ + ) { + let taskId = taskIds[pointerToTask]; + let taskData = await projectTemplateTaskQueries.taskDocuments({ + _id: taskId, + parentId: { $exists: false }, + }); + + if (taskData && taskData.length > 0) { + taskData = taskData[0]; + } + + if (taskData && Object.keys(taskData).length > 0) { + //duplicate parent task + let newProjectTemplateTask = { ...taskData }; + newProjectTemplateTask.projectTemplateId = duplicateTemplateId; + newProjectTemplateTask.projectTemplateExternalId = + duplicateTemplateExternalId; + newProjectTemplateTask.externalId = + taskData.externalId + "-" + UTILS.epochTime(); + + let duplicateTemplateTask = + await database.models.projectTemplateTasks.create( + _.omit(newProjectTemplateTask, ["_id"]) + ); + + newTaskId.push(duplicateTemplateTask._id); + + //duplicate child task + if ( + duplicateTemplateTask.children && + duplicateTemplateTask.children.length > 0 + ) { + let childTaskIdArray = []; + let childTaskIds = duplicateTemplateTask.children; + + if (childTaskIds && childTaskIds.length > 0) { + for ( + let pointerToChild = 0; + pointerToChild < childTaskIds.length; + pointerToChild++ ) { - - projectIndex = - userProfileData.data.ratings.findIndex( - project => project._id.toString() === templateId.toString() - ); - - if( !(projectIndex < 0) ) { - userCurrentRating = userProfileData.data.ratings[projectIndex].rating; - updateRating.ratings[userCurrentRating] -= 1; - } - } else { - userProfileData.data.ratings = []; - } - - let ratingUpdated = {}; - - if( userCurrentRating === rating ) { - - ratingUpdated = templateData[0]; - - } else { - - let calculateRating = _calculateRating(updateRating.ratings); - updateRating.averageRating = calculateRating.averageRating; - updateRating.noOfRatings = calculateRating.noOfRatings; - - ratingUpdated = - await database.models.projectTemplates.findOneAndUpdate({ - _id : templateId - },{ - $set : updateRating - }, { - new : true + let childtaskId = childTaskIds[pointerToChild]; + let childTaskData = + await projectTemplateTaskQueries.taskDocuments({ + _id: childtaskId, }); - let improvementProjects = [...userProfileData.data.ratings]; - if( projectIndex >= 0 ) { - improvementProjects[projectIndex].rating = rating; - } else { - improvementProjects.push({ - _id : ObjectId(templateId), - externalId : ratingUpdated.externalId, - rating : rating, - type : CONSTANTS.common.IMPROVEMENT_PROJECT - }); - } - - await kendraService.updateUserProfile( - userToken, - { - "ratings" : improvementProjects - } + if (childTaskData && childTaskData.length > 0) { + childTaskData = childTaskData[0]; + } + + if (childTaskData && Object.keys(childTaskData).length > 0) { + let newProjectTemplateChildTask = { ...childTaskData }; + newProjectTemplateChildTask.projectTemplateId = + duplicateTemplateId; + newProjectTemplateChildTask.projectTemplateExternalId = + duplicateTemplateExternalId; + newProjectTemplateChildTask.parentId = + duplicateTemplateTask._id; + newProjectTemplateChildTask.externalId = + childTaskData.externalId + "-" + UTILS.epochTime(); + + let duplicateChildTemplateTask = + await database.models.projectTemplateTasks.create( + _.omit(newProjectTemplateChildTask, ["_id"]) + ); + + childTaskIdArray.push(duplicateChildTemplateTask._id); + newTaskId.push(duplicateChildTemplateTask._id); + } + } + //update new subtask ids to parent task + if (childTaskIdArray && childTaskIdArray.length > 0) { + let updateTaskData = + await projectTemplateTaskQueries.updateTaskDocument( + { + _id: duplicateTemplateTask._id, + }, + { + $set: { + children: childTaskIdArray, + }, + } ); } - - return resolve( - _.pick( - ratingUpdated, - ["averageRating","noOfRatings","ratings"] - ) - ); - - } catch (error) { - return resolve({ - success : false, - message : error.message, - status : error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status - }); + } } - }) - } - - /** - * Project template tasks - * @method - * @name duplicateTemplateTasks - * @param {Array} taskIds - Task ids - * @returns {Object} Duplicated tasks. - */ - - static duplicateTemplateTasks( taskIds=[], duplicateTemplateId, duplicateTemplateExternalId ) { - return new Promise(async (resolve, reject) => { - try { - - let newProjectTemplateTask, duplicateTemplateTask,newProjectTemplateChildTask,duplicateChildTemplateTask; - let newTaskId = []; - - await Promise.all(taskIds.map(async taskId => { - - let taskData = await database.models.projectTemplateTasks.findOne( - { - _id : taskId - }).lean(); - - if(taskData){ - //duplicate task - newProjectTemplateTask = {...taskData}; - newProjectTemplateTask.projectTemplateId = duplicateTemplateId; - newProjectTemplateTask.projectTemplateExternalId = duplicateTemplateExternalId; - newProjectTemplateTask.externalId = taskData.externalId +"-"+ UTILS.epochTime(); - duplicateTemplateTask = - await database.models.projectTemplateTasks.create( - _.omit(newProjectTemplateTask, ["_id"]) - ); - newTaskId.push(duplicateTemplateTask._id); - //duplicate child task - if(duplicateTemplateTask.children && duplicateTemplateTask.children.length > 0){ - let childTaskIdArray = []; - let childTaskIds = duplicateTemplateTask.children; - - if(childTaskIds && childTaskIds.length > 0){ - await Promise.all(childTaskIds.map(async childtaskId => { - let childTaskData = await database.models.projectTemplateTasks.findOne( - { - _id : childtaskId - }).lean(); - - if(childTaskData){ - newProjectTemplateChildTask = {...childTaskData}; - newProjectTemplateChildTask.projectTemplateId = duplicateTemplateId; - newProjectTemplateChildTask.projectTemplateExternalId = duplicateTemplateExternalId; - newProjectTemplateChildTask.parentId = duplicateTemplateTask._id; - newProjectTemplateChildTask.externalId = childTaskData.externalId +"-"+ UTILS.epochTime(); - duplicateChildTemplateTask = - await database.models.projectTemplateTasks.create( - _.omit(newProjectTemplateChildTask, ["_id"]) - ); - - childTaskIdArray.push(duplicateChildTemplateTask._id); - } - })) - - if(childTaskIdArray && childTaskIdArray.length > 0){ - let updateTaskData = await database.models.projectTemplateTasks.findOneAndUpdate( - { - _id : duplicateTemplateTask._id - }, - { - $set : { - children : childTaskIdArray - } - }).lean(); - } - } - } - } - })) - - let updateDuplicateTemplate; - - if(newTaskId && newTaskId.length > 0){ - - updateDuplicateTemplate = await database.models.projectTemplates.findOneAndUpdate( - { - _id : duplicateTemplateId - }, - { - $set : { - tasks : newTaskId - } - }).lean(); - } + } + } - return resolve( - updateDuplicateTemplate - ); - + let updateDuplicateTemplate; + //adding duplicate tasj to duplicate template + if (newTaskId && newTaskId.length > 0) { + updateDuplicateTemplate = + await projectTemplateQueries.findOneAndUpdate( + { + _id: duplicateTemplateId, + }, + { + $set: { + tasks: newTaskId, + }, + } + ); + } - } catch (error) { - return reject(error); - } - }) - } + return resolve(updateDuplicateTemplate); + } catch (error) { + return reject(error); + } + }); + } - /** - * Update projectTemplates document. - * @method - * @name updateProjectTemplateDocument - * @param {Object} query - query to find document - * @param {Object} updateObject - fields to update - * @returns {String} - message. - */ + /** + * Templates list. + * @method + * @name listByIds + * @param {Array} externalIds - External ids + * @returns {Array} List of templates data. + */ - static updateProjectTemplateDocument(query= {}, updateObject= {}) { + static listByIds(externalIds) { return new Promise(async (resolve, reject) => { - try { - - if (Object.keys(query).length == 0) { - throw new Error(CONSTANTS.apiResponses.UPDATE_QUERY_REQUIRED) - } - - if (Object.keys(updateObject).length == 0) { - throw new Error (CONSTANTS.apiResponses.UPDATE_OBJECT_REQUIRED) - } - - let updateResponse = await database.models.projectTemplates.updateOne - ( - query, - updateObject - ) - - if (updateResponse.nModified == 0) { - throw new Error(CONSTANTS.apiResponses.FAILED_TO_UPDATE) - } + try { + let templateData = await projectTemplateQueries.templateDocument( + { + externalId: { $in: externalIds }, + }, + ["title", "metaInformation.goal", "externalId"] + ); + + if (!(templateData.length > 0)) { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, + }; + } + templateData = templateData.map((template) => { + if (template.metaInformation && template.metaInformation.goal) { + template.goal = template.metaInformation.goal; + delete template.metaInformation; + } + + return template; + }); + + return resolve({ + success: false, + data: templateData, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATES_FETCHED, + }); + } catch (error) { + return reject(error); + } + }); + } + + /** + * Template details. + * @method + * @name details + * @param {String} templateId - Project template id. + * @param {String} userId - logged in user id. + * @params {String} link - solution link. + * @returns {Array} Project templates data. + */ + + static details(templateId = "", link = "", userId = "", isAPrivateProgram) { + return new Promise(async (resolve, reject) => { + try { + let solutionsResult = {}; + let findQuery = {}; + //get data when link is given + if (link) { + let queryData = {}; + queryData["link"] = link; + + let solutionDocument = await solutionsQueries.solutionsDocument( + queryData, + [ + "_id", + "name", + "programId", + "programName", + "projectTemplateId", + "link", + ] + ); + + if (!(solutionDocument.length > 0)) { + throw { + message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } + let solutiondata = solutionDocument; + templateId = solutiondata[0].projectTemplateId; + if (!templateId) { return resolve({ - success: true, - message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, - data: true + success: false, + data: solutiondata, + message: CONSTANTS.apiResponses.TEMPLATE_ID_NOT_FOUND_IN_SOLUTION, }); + } + solutionsResult = solutiondata; + templateId = templateId.toString(); + } - } catch (error) { - return resolve({ - success: false, - message: error.message, - data: false - }); + if (templateId) { + let validateTemplateId = UTILS.isValidMongoId(templateId); + if (validateTemplateId) { + findQuery["_id"] = templateId; + } else { + findQuery["externalId"] = templateId; + } + } + //getting template data using templateId + + let templateData = await projectTemplateQueries.templateDocument( + findQuery, + "all", + [ + "ratings", + "noOfRatings", + "averageRating", + "parentTemplateId", + "userId", + "createdBy", + "updatedBy", + "createdAt", + "updatedAt", + "__v", + ] + ); + + if (!(templateData.length > 0)) { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, + }; + } + if ( + templateData[0].certificateTemplateId && + templateData[0].certificateTemplateId !== "" + ) { + let certificateTemplateDetails = + await certificateTemplateQueries.certificateTemplateDocument( + { + _id: templateData[0].certificateTemplateId, + }, + ["criteria"] + ); + + //certificate template data do not exists. + if (!(certificateTemplateDetails.length > 0)) { + throw { + message: CONSTANTS.apiResponses.CERTIFICATE_TEMPLATE_NOT_FOUND, + }; + } + templateData[0].criteria = certificateTemplateDetails[0].criteria; } - }); - } - - /** - * Templates list. - * @method - * @name listByIds - * @param {Array} externalIds - External ids - * @returns {Array} List of templates data. - */ - - static listByIds( externalIds ) { - return new Promise(async (resolve, reject) => { - try { - - let templateData = await this.templateDocument({ - externalId : { $in : externalIds } - },["title","metaInformation.goal","externalId"]); - - if ( !templateData.length > 0 ) { - throw { - status : HTTP_STATUS_CODE.bad_request.status, - message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND - } - } - templateData = templateData.map( template => { - if( template.metaInformation && template.metaInformation.goal ) { - template.goal = template.metaInformation.goal; - delete template.metaInformation; - } + if (templateData[0].tasks && templateData[0].tasks.length > 0) { + templateData[0].tasks = await this.tasksAndSubTasks( + templateData[0]._id + ); + } + let result = await _templateInformation(templateData[0]); + if (!result.success) { + return resolve(result); + } + + if (!templateData[0].isReusable && userId !== "") { + templateData[0].projectId = ""; + + const projectIdQuery = { + userId: userId, + projectTemplateId: templateData[0]._id, + }; + + if (isAPrivateProgram !== "") { + projectIdQuery.isAPrivateProgram = isAPrivateProgram; + } + let project = await projectQueries.projectDocument(projectIdQuery, [ + "_id", + "hasAcceptedTAndC", + ]); + + if (project && project.length > 0) { + templateData[0].projectId = project[0]._id; + templateData[0].hasAcceptedTAndC = project[0].hasAcceptedTAndC; + } + } + if (!result.data.programInformation) { + result.data.programInformation = { + programId: solutionsResult.programId, + programName: solutionsResult.programName, + }; + } + result.data.solutionInformation = { + _id: solutionsResult._id, + name: solutionsResult.name, + link: solutionsResult.link, + }; + return resolve({ + success: false, + data: result.data, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_DETAILS_FETCHED, + }); + } catch (error) { + return reject(error); + } + }); + } - return template; - }) + /** + * Tasks and sub tasks. + * @method + * @name tasksAndSubTasks + * @param {Array} templateId - Template id. + * @returns {Array} Tasks and sub task. + */ - return resolve({ - success : false, - data : templateData, - message : CONSTANTS.apiResponses.PROJECT_TEMPLATES_FETCHED - }); - - } catch (error) { - return reject(error); + static tasksAndSubTasks(templateId) { + return new Promise(async (resolve, reject) => { + try { + const templateDocument = await projectTemplateQueries.templateDocument( + { + _id: templateId, + status: CONSTANTS.common.PUBLISHED, + }, + ["tasks", "taskSequence"] + ); + + let tasks = []; + + if ( + templateDocument[0].taskSequence && + templateDocument[0].taskSequence.length > 0 + ) { + let projectionKey = CONSTANTS.common.TASK_SEQUENCE; + let findQuery = { + externalId: { + $in: templateDocument[0].taskSequence, + }, + }; + + tasks = await _taskAndSubTaskinSequence(findQuery, projectionKey); + } else { + if ( + templateDocument[0].tasks && + templateDocument[0].tasks.length > 0 + ) { + let projectionKey = CONSTANTS.common.CHILDREN; + if (templateDocument[0].tasks) { + let findQuery = { + _id: { + $in: templateDocument[0].tasks, + }, + parentId: { $exists: false }, + }; + + tasks = await _taskAndSubTaskinSequence(findQuery, projectionKey); } - }) - } + } + } - /** - * Template details. - * @method - * @name details - * @param {String} templateId - Project template id. - * @param {String} userId - logged in user id. - * @returns {Array} Project templates data. - */ - - static details( templateId,userId ) { - return new Promise(async (resolve, reject) => { - try { - - let templateData = await this.templateDocument({ - externalId : templateId - },"all", - [ - "ratings", - "noOfRatings", - "averageRating", - "parentTemplateId", - "createdFor", - "rootOrganisations", - "userId", - "createdBy", - "updatedBy", - "createdAt", - "updatedAt", - "__v" - ]); - - if ( !templateData.length > 0 ) { - throw { - status : HTTP_STATUS_CODE.bad_request.status, - message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND - } - } + return resolve(tasks); + } catch (error) { + return reject(error); + } + }); + } + + /** + * Template update. + * @method + * @name update + * @param {String} templateId - Project template id. + * @param {Object} templateData - template updation data + * @param {String} userId - logged in user id. + * @returns {Array} Project templates data. + */ + + static update(templateId, templateData, userId) { + return new Promise(async (resolve, reject) => { + try { + let findQuery = {}; - if (templateData[0].tasks && templateData[0].tasks.length > 0) { - templateData[0].tasks = - await this.tasksAndSubTasks(templateData[0]._id); - } + let validateTemplateId = UTILS.isValidMongoId(templateId); - let result = await _templateInformation(templateData[0]) + if (validateTemplateId) { + findQuery["_id"] = templateId; + } else { + findQuery["externalId"] = templateId; + } - if( !result.success ) { - return resolve(result); - } + let templateDocument = await projectTemplateQueries.templateDocument( + findQuery, + ["_id"] + ); - if( !templateData[0].isReusable ) { - - templateData[0].projectId = ""; + if (!(templateDocument.length > 0)) { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, + }; + } - let project = await database.models.projects.findOne({ - userId : userId, - projectTemplateId : templateData[0]._id - },{ - _id : 1 - }).lean(); + let updateObject = { + $set: {}, + }; - if( project && project._id ) { - templateData[0].projectId = project._id; - } - } + let templateUpdateData = templateData; - return resolve({ - success : false, - data : result.data, - message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_DETAILS_FETCHED - }); - - } catch (error) { - return reject(error); - } - }) - } + Object.keys(templateUpdateData).forEach((updationData) => { + updateObject["$set"][updationData] = templateUpdateData[updationData]; + }); - /** - * Tasks and sub tasks. - * @method - * @name tasksAndSubTasks - * @param {Array} templateId - Template id. - * @returns {Array} Tasks and sub task. - */ - - static tasksAndSubTasks(templateId) { - return new Promise(async (resolve, reject) => { - try { - - const templateDocument = - await this.templateDocument({ - status : CONSTANTS.common.PUBLISHED, - _id : templateId - },["tasks"]); - - let tasks = []; - - if( templateDocument[0].tasks ) { - - tasks = await database.models.projectTemplateTasks.find({ - _id : { - $in : templateDocument[0].tasks - }, - parentId : { $exists : false } - },{ - "projectTemplateId" : 0, - "__v" : 0, - "projectTemplateExternalId" : 0 - }).lean(); - - for( let task = 0 ; task < tasks.length ; task ++ ) { - - if( tasks[task].children && tasks[task].children.length > 0 ) { - - let subTasks = await database.models.projectTemplateTasks.find({ - _id : { - $in : tasks[task].children - } - },{ - "projectTemplateId" : 0, - "__v" : 0, - "projectTemplateExternalId" : 0 - }).lean(); - - tasks[task].children = subTasks; - } - } - } + updateObject["$set"]["updatedBy"] = userId; - return resolve(tasks); + let templateUpdatedData = await projectTemplateQueries.findOneAndUpdate( + { + _id: templateDocument[0]._id, + }, + updateObject, + { new: true } + ); - } catch (error) { - return reject(error); - } - }); - } + if (!templateUpdatedData._id) { + throw { + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_UPDATED, + }; + } + return resolve({ + success: true, + data: templateUpdatedData, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_UPDATED, + }); + } catch (error) { + return reject(error); + } + }); + } }; /** @@ -1177,82 +1262,140 @@ module.exports = class ProjectTemplatesHelper { * @name _calculateRating * @param {Object} ratings - Ratings data. * @returns {Object} rating object. -*/ + */ function _calculateRating(ratings) { - let sum = 0; - let noOfRatings = 0; - - Object.keys(ratings).forEach(rating => { - sum += rating * ratings[rating]; - noOfRatings += ratings[rating]; - }); - - return { - averageRating : (sum/noOfRatings).toFixed(2), - noOfRatings : noOfRatings - } + let sum = 0; + let noOfRatings = 0; + + Object.keys(ratings).forEach((rating) => { + sum += rating * ratings[rating]; + noOfRatings += ratings[rating]; + }); + + return { + averageRating: (sum / noOfRatings).toFixed(2), + noOfRatings: noOfRatings, + }; } /** * Project information. * @method - * @name _templateInformation + * @name _templateInformation * @param {Object} project - Project data. * @returns {Object} Project information. -*/ + */ function _templateInformation(project) { + return new Promise(async (resolve, reject) => { + try { + if (project.programId) { + let programs = await surveyService.listProgramsBasedOnIds([ + project.programId, + ]); + + if (!programs.success) { + throw { + message: CONSTANTS.apiResponses.PROGRAM_NOT_FOUND, + status: HTTP_STATUS_CODE["bad_request"].status, + }; + } - return new Promise(async (resolve, reject) => { - try { - - if( project.programId ) { - - let programs = - await assessmentService.listProgramsBasedOnIds([project.programId]); - - if( !programs.success ) { - throw { - message : CONSTANTS.apiResponses.PROGRAM_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } - } - - project.programInformation = { - programId : project.programId, - programName : programs.data[0].name - } - - delete project.programId; - delete project.programExternalId; - } - - if (project.metaInformation) { - Object.keys(project.metaInformation).forEach(projectMetaKey => { - project[projectMetaKey] = project.metaInformation[projectMetaKey]; - }); - } - - delete project.metaInformation; - delete project.__v; - - project.status = - project.status ? project.status : CONSTANTS.common.NOT_STARTED_STATUS; + project.programInformation = { + programId: project.programId, + programName: programs.data[0].name, + }; + + delete project.programId; + delete project.programExternalId; + } + + if (project.metaInformation) { + Object.keys(project.metaInformation).forEach((projectMetaKey) => { + project[projectMetaKey] = project.metaInformation[projectMetaKey]; + }); + } + delete project.metaInformation; + delete project.__v; + + project.status = project.status + ? project.status + : CONSTANTS.common.NOT_STARTED_STATUS; + + return resolve({ + success: true, + data: project, + }); + } catch (error) { + return resolve({ + message: error.message, + success: false, + status: error.status + ? error.status + : HTTP_STATUS_CODE["internal_server_error"].status, + }); + } + }); +} - return resolve({ - success: true, - data: project - }); +/** + * Task and SubTask In Order. + * @method + * @name _taskAndSubTaskinSequence + * @param {Object} query - template Query. + * @param {String} projectionValue - children or taskSequence. + * @returns {Object} Task and SubTask information. + */ - } catch (error) { - return resolve({ - message: error.message, - success: false, - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status - }) +function _taskAndSubTaskinSequence(query, projectionValue) { + return new Promise(async (resolve, reject) => { + try { + let tasks = []; + tasks = await projectTemplateTaskQueries.taskDocuments(query, "all", [ + "projectTemplateId", + "__v", + "projectTemplateExternalId", + ]); + + for (let task = 0; task < tasks.length; task++) { + if ( + tasks[task][projectionValue] && + tasks[task][projectionValue].length > 0 + ) { + let subTaskQuery; + if (projectionValue == CONSTANTS.common.CHILDREN) { + subTaskQuery = { + _id: { + $in: tasks[task][projectionValue], + }, + }; + } else { + subTaskQuery = { + externalId: { + $in: tasks[task][projectionValue], + }, + }; + } + + let subTasks = await projectTemplateTaskQueries.taskDocuments( + subTaskQuery, + "all", + ["projectTemplateId", "__v", "projectTemplateExternalId"] + ); + tasks[task].children = subTasks; } - }) + } + + return resolve(tasks); + } catch (error) { + return resolve({ + message: error.message, + success: false, + status: error.status + ? error.status + : HTTP_STATUS_CODE["internal_server_error"].status, + }); + } + }); } diff --git a/module/project/templates/validator/v1.js b/module/project/templates/validator/v1.js index c21952fe..71d4cd83 100644 --- a/module/project/templates/validator/v1.js +++ b/module/project/templates/validator/v1.js @@ -13,9 +13,6 @@ module.exports = (req) => { req.checkParams('_id').exists().withMessage("required project template id"); req.checkQuery('solutionId').exists().withMessage("required solution id"); }, - details : function () { - req.checkParams("_id").exists().withMessage("required project template external id"); - } } if (projectTemplateValidator[req.params.method]) { diff --git a/module/reports/helper.js b/module/reports/helper.js index 26d670db..bd9ca111 100644 --- a/module/reports/helper.js +++ b/module/reports/helper.js @@ -8,7 +8,8 @@ // Dependencies const userProjectsHelper = require(MODULES_BASE_PATH + "/userProjects/helper"); -const dhitiService = require(GENERICS_FILES_PATH + "/services/dhiti"); +const reportService = require(GENERICS_FILES_PATH + "/services/report"); +const projectQueries = require(DB_QUERY_BASE_PATH + "/projects"); const moment = require('moment'); @@ -33,17 +34,18 @@ module.exports = class ReportsHelper { * @param {Boolean} getPdf - pdf true or false * @returns {Object} Entity report. */ - static entity(entityId = "", userId, userToken, userName, reportType, programId = "", getPdf) { + static entity(entityId = "", userId, userToken, userName, reportType, programId = "", getPdf,appVersion) { return new Promise(async (resolve, reject) => { try { - let query = { }; + if (entityId) { - query["entityId"] = ObjectId(entityId); + query["entityId"] = entityId; } else { query["userId"] = userId } + let dateRange = await _getDateRangeofReport(reportType); let endOf = dateRange.endOf; let startFrom = dateRange.startFrom; @@ -63,14 +65,14 @@ module.exports = class ReportsHelper { { "syncedAt": { $gte: new Date(startFrom), $lte: new Date(endOf) } }, { "tasks": { $elemMatch: { isDeleted: { $ne: true },syncedAt: { $gte: new Date(startFrom), $lte: new Date(endOf) } } } }, ] - - const projectDetails = await userProjectsHelper.projectDocument( + + const projectDetails = await projectQueries.projectDocument( query, - ["programId","programInformation.name", "entityInformation.name", "taskReport", "status", "tasks", "categories"], + ["programId","programInformation.name", "entityInformation.name", "taskReport", "status", "tasks", "categories", "endDate"], [] ); - + let tasksReport = { "total": 0, "overdue": 0 @@ -87,9 +89,9 @@ module.exports = class ReportsHelper { "total": 0, "overdue": 0, }; - projectReport[CONSTANTS.common.COMPLETED_STATUS] = 0; + projectReport[CONSTANTS.common.SUBMITTED_STATUS] = 0; projectReport[CONSTANTS.common.INPROGRESS_STATUS] = 0; - projectReport[CONSTANTS.common.NOT_STARTED_STATUS] = 0; + projectReport[CONSTANTS.common.STARTED] = 0; let types = await this.types(); @@ -99,8 +101,10 @@ module.exports = class ReportsHelper { } }); - if (!projectDetails.length > 0) { + if ( !(projectDetails.length > 0)) { + + if (getPdf == true) { let reportTaskData = {}; @@ -122,8 +126,8 @@ module.exports = class ReportsHelper { projects: projectReport, } - - let response = await dhitiService.entityReport(userToken, pdfRequest); + + let response = await reportService.entityReport(userToken, pdfRequest); if (response && response.success == true) { @@ -145,6 +149,8 @@ module.exports = class ReportsHelper { } } else { + + return resolve({ message: CONSTANTS.apiResponses.REPORTS_DATA_NOT_FOUND, data: { @@ -160,8 +166,8 @@ module.exports = class ReportsHelper { } await Promise.all(projectDetails.map(async function (project) { - - if (project.categories) { + + if ( project.categories ) { project.categories.map(category => { if( @@ -182,61 +188,73 @@ module.exports = class ReportsHelper { categories['total'] = categories['total'] + project.categories.length; } - let todayDate = moment(project.endDate, "DD.MM.YYYY"); - let endDate = moment().format(); - if (project.status == CONSTANTS.common.COMPLETED_STATUS) { - - projectReport[CONSTANTS.common.COMPLETED_STATUS] = projectReport[CONSTANTS.common.COMPLETED_STATUS] + 1; + //Add data into projectReport and check project overdue. + if ( project.status == CONSTANTS.common.SUBMITTED_STATUS ) { - } else if (project.status == CONSTANTS.common.INPROGRESS_STATUS) { + projectReport[CONSTANTS.common.SUBMITTED_STATUS] = projectReport[CONSTANTS.common.SUBMITTED_STATUS] + 1; - if (todayDate.diff(endDate, 'days') < 1) { + } else if ( project.status == CONSTANTS.common.INPROGRESS_STATUS || project.status == CONSTANTS.common.STARTED ) { + //Returns project overdue status true/false. + let overdue = _getOverdueStatus( project.endDate ); + + if ( overdue ) { projectReport['overdue'] = projectReport['overdue'] + 1; } else { - projectReport[CONSTANTS.common.INPROGRESS_STATUS] = projectReport[CONSTANTS.common.INPROGRESS_STATUS] + 1; - } - - } else if (project.status == CONSTANTS.common.NOT_STARTED_STATUS) { - - if (todayDate.diff(endDate, 'days') < 1) { - projectReport['overdue'] = projectReport['overdue'] + 1; - } else { - projectReport[CONSTANTS.common.NOT_STARTED_STATUS] = projectReport[CONSTANTS.common.NOT_STARTED_STATUS] + 1; + projectReport[project.status] = projectReport[project.status] + 1; } } - projectReport["total"] = projectReport[CONSTANTS.common.NOT_STARTED_STATUS] + + //get total project count + projectReport["total"] = projectReport[CONSTANTS.common.STARTED] + projectReport['overdue'] + projectReport[CONSTANTS.common.INPROGRESS_STATUS] + - projectReport[CONSTANTS.common.COMPLETED_STATUS]; + projectReport[CONSTANTS.common.SUBMITTED_STATUS]; - if (project.taskReport) { - let keys = Object.keys(project.taskReport); - keys.map(key => { - if (tasksReport[key]) { + //Get tasks summary deatail of project. + if ( project.taskReport ) { + let keys = Object.keys( project.taskReport ); + keys.map( key => { + if ( tasksReport[key] ) { tasksReport[key] = tasksReport[key] + project.taskReport[key]; } else { - tasksReport[key] = project.taskReport[key]; + tasksReport[key] = project.taskReport[key]; } }); } + //Get number of tasks overdued. await Promise.all(project.tasks.map(task => { - let taskCurrentDate = moment(task.endDate, "DD.MM.YYYY"); - if (taskCurrentDate.diff(endDate, 'days') < 1) { - if (tasksReport['overdue']) { - tasksReport['overdue'] = tasksReport['overdue'] + 1; - } else { - tasksReport['overdue'] = 1; - } - if(tasksReport[task.status]){ - tasksReport[task.status] = tasksReport[task.status] - 1; - } + //consider task only if not deleted + if ( task.isDeleted == false && task.status != CONSTANTS.common.COMPLETED_STATUS ){ + + //Returns true or false + let overdue = _getOverdueStatus( task.endDate ); + + if ( overdue ) { + + if ( tasksReport['overdue'] ) { + tasksReport['overdue'] = tasksReport['overdue'] + 1; + } else { + tasksReport['overdue'] = 1; + } + if ( tasksReport[task.status] ) { + tasksReport[task.status] = tasksReport[task.status] - 1; + } + + } } + })); - })); - if (getPdf == true) { + if ( UTILS.revertStatusorNot(appVersion) ) { + projectReport[CONSTANTS.common.COMPLETED_STATUS] = projectReport[CONSTANTS.common.SUBMITTED_STATUS]; + projectReport[CONSTANTS.common.NOT_STARTED_STATUS] = projectReport[CONSTANTS.common.STARTED]; + delete projectReport[CONSTANTS.common.SUBMITTED_STATUS]; + delete projectReport[CONSTANTS.common.STARTED]; + } + + if ( getPdf == true ) { + let reportTaskData = {}; Object.keys(tasksReport).map(taskData => { reportTaskData[UTILS.camelCaseToTitleCase(taskData)] = tasksReport[taskData]; @@ -268,8 +286,9 @@ module.exports = class ReportsHelper { if (entityId != "") { pdfRequest['entityName'] = projectDetails[0].entityInformation.name; } - - let response = await dhitiService.entityReport(userToken, pdfRequest); + + //send data to report service to generate PDF. + let response = await reportService.entityReport(userToken, pdfRequest); if (response && response.success == true) { return resolve({ success: true, @@ -302,6 +321,7 @@ module.exports = class ReportsHelper { } } catch (error) { return resolve({ + success: false, message: error.message, data: false @@ -311,6 +331,7 @@ module.exports = class ReportsHelper { } + /** * Get programs list. * @method @@ -334,21 +355,20 @@ module.exports = class ReportsHelper { $exists : true } }; - - if(entityId != "" && UTILS.isValidMongoId(entityId)) { - query.entityId = ObjectId(entityId); + + if( entityId != "" ) { + query.entityId = entityId; } - + if (userRole != "") { + let regex = userRole.split(","); + regex.push("") query.userRole = { - $in : [ - "", - userRole - ] + $regex:regex.join("|"), + $options: "i" } } - let searchQuery = []; if (search !== "") { searchQuery = [{ "programInformation.name": new RegExp(search, 'i') }]; @@ -467,11 +487,12 @@ module.exports = class ReportsHelper { }; if (entityId) { - query["entityId"] = ObjectId(entityId); + query["entityId"] = entityId; } else { query["userId"] = userId } + let chartObject = []; let dateRange = await _getDateRangeofReport(reportType); let endOf = dateRange.endOf; @@ -486,7 +507,7 @@ module.exports = class ReportsHelper { query['programId'] = ObjectId(programId); } - const projectDetails = await userProjectsHelper.projectDocument( + const projectDetails = await projectQueries.projectDocument( query, ["title", "taskReport", @@ -501,7 +522,7 @@ module.exports = class ReportsHelper { [] ); - if (!projectDetails.length > 0) { + if (!(projectDetails.length > 0)) { return resolve({ message: CONSTANTS.apiResponses.REPORTS_DATA_NOT_FOUND, @@ -542,7 +563,7 @@ module.exports = class ReportsHelper { "reportType": returnTypeInfo[0].label, "projectDetails": projectData } - let response = await dhitiService.viewFullReport(userToken, data); + let response = await reportService.viewFullReport(userToken, data); if (response && response.success == true) { @@ -595,9 +616,9 @@ module.exports = class ReportsHelper { } let color = ""; - if (status == CONSTANTS.common.NOT_STARTED_STATUS) { + if (status == CONSTANTS.common.STARTED) { color = "#f5f5f5"; - } else if (status == CONSTANTS.common.COMPLETED_STATUS) { + } else if (status == CONSTANTS.common.SUBMITTED_STATUS ) { color = "#20ba8d"; } else if (status == CONSTANTS.common.INPROGRESS_STATUS) { color = "#ef8c2b"; @@ -635,6 +656,7 @@ module.exports = class ReportsHelper { } }); } + } @@ -665,5 +687,24 @@ function _getDateRangeofReport(reportType) { } +/** + * Get overdue status + * @method + * @name _getOverdueStatus + * @param {String} endDate - date that task or project suppose to be finished. + * @returns {Boolean} - returns overdue status + */ + +function _getOverdueStatus( endDate ) { + let overdue = false; + let today = moment().format(); + let endDateObject = moment( endDate, 'YYYY-MM-DD' ); + //Find difference between present date and end date + if ( endDateObject.diff( today, 'days' ) < 1 ) { + overdue = true; + } + + return overdue; +} diff --git a/module/solutions/helper.js b/module/solutions/helper.js index 0b690fe7..168f7e60 100644 --- a/module/solutions/helper.js +++ b/module/solutions/helper.js @@ -7,7 +7,8 @@ // Dependencies -const kendraService = require(GENERICS_FILES_PATH + "/services/kendra"); +const coreService = require(GENERICS_FILES_PATH + "/services/core"); +const solutionsQueries = require(DB_QUERY_BASE_PATH + "/solutions"); /** * SolutionsHelper @@ -36,7 +37,7 @@ module.exports = class SolutionsHelper { requestedData.isReusable = false; let solutionCreated = - await kendraService.createSolution(requestedData,token); + await coreService.createSolution(requestedData,token); if( !solutionCreated.success ) { throw { @@ -59,4 +60,133 @@ module.exports = class SolutionsHelper { }); } + /** + * Update User District and Organisation In Solutions For Reporting. + * @method + * @name _addReportInformationInSolution + * @param {String} solutionId - solution id. + * @param {Object} userProfile - user profile details + * @returns {Object} Solution information. +*/ + + static addReportInformationInSolution(solutionId,userProfile) { + return new Promise(async (resolve, reject) => { + try { + + //check solution & userProfile is exist + if ( + solutionId && userProfile && + userProfile["userLocations"] && + userProfile["organisations"] + ) { + let district = []; + let organisation = []; + + //get the districts from the userProfile + for (const location of userProfile["userLocations"]) { + if ( location.type == CONSTANTS.common.DISTRICT ) { + let distData = {} + distData["locationId"] = location.id; + distData["name"] = location.name; + district.push(distData); + } + } + + //get the organisations from the userProfile + for (const org of userProfile["organisations"]) { + if ( !org.isSchool ) { + let orgData = {}; + orgData.orgName = org.orgName; + orgData.organisationId = org.organisationId; + organisation.push(orgData); + } + + } + + let updateQuery = {}; + updateQuery["$addToSet"] = {}; + + if ( organisation.length > 0 ) { + updateQuery["$addToSet"]["reportInformation.organisations"] = { $each : organisation}; + } + + if ( district.length > 0 ) { + updateQuery["$addToSet"]["reportInformation.districts"] = { $each : district}; + } + + //add user district and organisation in solution + if ( updateQuery["$addToSet"] && Object.keys(updateQuery["$addToSet"].length > 0)) { + await solutionsQueries.updateSolutionDocument + ( + { _id : solutionId }, + updateQuery + ) + } + + } else { + throw new Error(CONSTANTS.apiResponses.SOLUTION_ID_AND_USERPROFILE_REQUIRED); + } + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY + }); + + } catch (error) { + return resolve({ + success : false, + message : error.message, + data: [] + }); + } + }); + } + /** + * Solution Data + * @method + * @name solutionDocuments + * @param {Array} [filterQuery = "all"] - solution ids. + * @param {Array} [fieldsArray = "all"] - projected fields. + * @param {Array} [skipFields = "none"] - field not to include + * @returns {Array} List of solutions. + */ + + static solutionDocuments( + filterQuery = "all", + fieldsArray = "all", + skipFields = "none" + ) { + return new Promise(async (resolve, reject) => { + try { + + let queryObject = (filterQuery != "all") ? filterQuery : {}; + + let projection = {} + + if (fieldsArray != "all") { + fieldsArray.forEach(field => { + projection[field] = 1; + }); + } + + if( skipFields !== "none" ) { + skipFields.forEach(field=>{ + projection[field] = 0; + }) + } + + let solutionDocuments = + await database.models.solutions.find( + queryObject, + projection + ).lean(); + + return resolve(solutionDocuments); + + } catch (error) { + return reject(error); + } + }); + } + }; diff --git a/module/solutions/validator/v1.js b/module/solutions/validator/v1.js index 7eeacba6..2e4258cc 100644 --- a/module/solutions/validator/v1.js +++ b/module/solutions/validator/v1.js @@ -10,8 +10,6 @@ module.exports = (req) => { let solutionsValidator = { create : function () { - req.checkBody('createdFor').exists().withMessage("required organisation created for id"); - req.checkBody('rootOrganisations').exists().withMessage("required root organisations id"); req.checkBody('programExternalId').exists().withMessage("required program externalId"); req.checkBody('entityType').exists().withMessage("required entity type"); req.checkBody('externalId').exists().withMessage("required solution externalId"); diff --git a/module/userProjects/helper.js b/module/userProjects/helper.js index d565fb3d..57e6a852 100644 --- a/module/userProjects/helper.js +++ b/module/userProjects/helper.js @@ -7,14 +7,29 @@ // Dependencies -const kendraService = require(GENERICS_FILES_PATH + "/services/kendra"); +const coreService = require(GENERICS_FILES_PATH + "/services/core"); const libraryCategoriesHelper = require(MODULES_BASE_PATH + "/library/categories/helper"); const projectTemplatesHelper = require(MODULES_BASE_PATH + "/project/templates/helper"); const projectTemplateTasksHelper = require(MODULES_BASE_PATH + "/project/templateTasks/helper"); const { v4: uuidv4 } = require('uuid'); -const assessmentService = require(GENERICS_FILES_PATH + "/services/assessment"); -const dhitiService = require(GENERICS_FILES_PATH + "/services/dhiti"); - +const surveyService = require(GENERICS_FILES_PATH + "/services/survey"); +const reportService = require(GENERICS_FILES_PATH + "/services/report"); +const projectQueries = require(DB_QUERY_BASE_PATH + "/projects"); +const projectCategoriesQueries = require(DB_QUERY_BASE_PATH + "/projectCategories"); +const projectTemplateQueries = require(DB_QUERY_BASE_PATH + "/projectTemplates"); +const projectTemplateTaskQueries = require(DB_QUERY_BASE_PATH + "/projectTemplateTask"); +const kafkaProducersHelper = require(GENERICS_FILES_PATH + "/kafka/producers"); +const removeFieldsFromRequest = ["submissionDetails"]; +const programsQueries = require(DB_QUERY_BASE_PATH + "/programs"); +const userProfileService = require(GENERICS_FILES_PATH + "/services/users"); +const solutionsHelper = require(MODULES_BASE_PATH + "/solutions/helper"); +const certificateTemplateQueries = require(DB_QUERY_BASE_PATH + "/certificateTemplates"); +const certificateService = require(GENERICS_FILES_PATH + "/services/certificate"); +const certificateValidationsHelper = require(MODULES_BASE_PATH + "/certificateValidations/helper"); +const _ = require("lodash"); +const programUsersQueries = require(DB_QUERY_BASE_PATH + "/programUsers"); +const telemetryEventOnOff = process.env.TELEMETRY_ON_OFF +const entitieHelper = require(MODULES_BASE_PATH + "/entities/helper") /** * UserProjectsHelper * @class @@ -22,47 +37,119 @@ const dhitiService = require(GENERICS_FILES_PATH + "/services/dhiti"); module.exports = class UserProjectsHelper { - /** - * Lists of projects. - * @method - * @name projectDocument - * @param {Array} [filterData = "all"] - project filter query. - * @param {Array} [fieldsArray = "all"] - projected fields. - * @param {Array} [skipFields = "none"] - field not to include - * @returns {Array} Lists of projects. - */ - static projectDocument( - filterData = "all", - fieldsArray = "all", - skipFields = "none" - ) { + /** + * deleteUserPIIData function to delete users Data. + * @method + * @name deleteUserPIIData + * @param {userDeleteEvent} - userDeleteEvent message object + * { + "eid": "BE_JOB_REQUEST", + "ets": 1619527882745, + "mid": "LP.1619527882745.32dc378a-430f-49f6-83b5-bd73b767ad36", + "actor": { + "id": "delete-user", + "type": "System" + }, + "context": { + "channel": "01309282781705830427", + "pdata": { + "id": "org.sunbird.platform", + "ver": "1.0" + }, + "env": "dev" + }, + "object": { + "id": "", + "type": "User" + }, + "edata": { + "organisationId": "0126796199493140480", + "userId": "a102c136-c6da-4c6c-b6b7-0f0681e1aab9", + "suggested_users": [ + { + "role": "ORG_ADMIN", + "users": [ + "" + ] + }, + { + "role": "CONTENT_CREATOR", + "users": [ + "" + ] + }, + { + "role": "COURSE_MENTOR", + "users": [ + "" + ] + } + ], + "action": "delete-user", + "iteration": 1 + } + } + * @returns {Promise} success Data. + */ + static deleteUserPIIData(userDeleteEvent) { return new Promise(async (resolve, reject) => { - try { - - let queryObject = (filterData != "all") ? filterData : {}; - let projection = {} - - if (fieldsArray != "all") { - fieldsArray.forEach(field => { - projection[field] = 1; - }); - } - - if (skipFields !== "none") { - skipFields.forEach(field => { - projection[field] = 0; - }); + try{ + let userId = userDeleteEvent.edata.userId; + let filter = { + userId: userId, } + let updateProfile = { + $set: { + "userProfile.firstName": CONSTANTS.common.DELETED_USER, + }, + $unset: { + "userProfile.email": 1, + "userProfile.maskedEmail": 1, + "userProfile.maskedPhone": 1, + "userProfile.recoveryEmail": 1, + "userProfile.phone": 1, + "userProfile.lastName": 1, + "userProfile.prevUsedPhone": 1, + "userProfile.prevUsedEmail": 1, + "userProfile.recoveryPhone": 1, + "userProfile.dob": 1, + "userProfile.encEmail": 1, + "userProfile.encPhone": 1 + }, + }; + let deleteUserPIIDataResult = await projectQueries.updateMany(filter, updateProfile) + if (deleteUserPIIDataResult && deleteUserPIIDataResult.nModified > 0) { + if(telemetryEventOnOff !== CONSTANTS.common.OFF){ + /** + * Telemetry Raw Event + * {"eid":"","ets":1700188609568,"ver":"3.0","mid":"e55a91cd-7964-46bc-b756-18750787fb32","actor":{},"context":{"channel":"","pdata":{"id":"projectservice","pid":"manage-learn","ver":"7.0.0"},"env":"","cdata":[{"id":"adf3b621-619b-4195-a82d-d814eecdb21f","type":"Request"}],"rollup":{}},"object":{},"edata":{}} + */ + let rawEvent = await UTILS.generateTelemetryEventSkeletonStructure(); + rawEvent.eid = CONSTANTS.common.AUDIT; + rawEvent.context.channel = userDeleteEvent.context.channel; + rawEvent.context.env = CONSTANTS.common.USER; + rawEvent.edata.state = CONSTANTS.common.DELETE_STATE; + rawEvent.edata.type = CONSTANTS.common.USER_DELETE_TYPE; + rawEvent.edata.props = []; + let userObject = { + id: userId, + type: CONSTANTS.common.USER, + }; + rawEvent.actor = userObject; + rawEvent.object = userObject; + rawEvent.context.pdata.pid = `${process.env.ID}.${CONSTANTS.common.USER_DELETE_MODULE}` - let templates = - await database.models.projects.find( - queryObject, - projection - ).lean(); - - return resolve(templates); + let telemetryEvent = await UTILS.generateTelemetryEvent(rawEvent); + telemetryEvent.lname = CONSTANTS.common.TELEMTRY_EVENT_LOGGER; + telemetryEvent.level = CONSTANTS.common.INFO_LEVEL + await kafkaProducersHelper.pushTelemetryEventToKafka(telemetryEvent); + } + return resolve({ success: true }); + }else{ + return resolve({ success: true }); + } } catch (error) { return reject(error); } @@ -70,763 +157,630 @@ module.exports = class UserProjectsHelper { } /** - * List of projects. + * Projects boolean data. * @method - * @name list - * @param {String} userId - logged in user id. - * @param {Boolean} updateLastDownloadedAt - update last downloaded at. - * @returns {Object} Projects. - */ - - static list(userId, updateLastDownloadedAt) { - return new Promise(async (resolve, reject) => { - try { + * @name booleanData + * @returns {Array} Boolean data. + */ - let projects = - await this.projectDocument( - { - userId: userId, - isDeleted: false - }, "all", [ - "createdBy", - "updatedBy", - "rootOrganisations", - "taskReport", - "createdFor", - "projectTemplateId", - "projectTemplateExternalId", - "__v" - ]); + static booleanData() { - if (!projects.length > 0) { + const projectsSchema = schemas["projects"].schema; + const projectSchemaKey = Object.keys(projectsSchema); - throw { - status: HTTP_STATUS_CODE['ok'].status, - message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND - }; - } + let booleanProjects = []; - let projectIds = []; + projectSchemaKey.forEach(projectSchema => { + const currentSchema = projectsSchema[projectSchema]; - let updatedDate = ""; + if ( + currentSchema.hasOwnProperty('default') && + typeof currentSchema.default === "boolean" + ) { + booleanProjects.push(projectSchema); + } + }); - if (updateLastDownloadedAt) { - updatedDate = new Date(); - } + return booleanProjects; + } - for (let project = 0; project < projects.length; project++) { - let projectInformation = await _projectInformation(projects[project]); + /** + * Projects object id field. + * @method + * @name mongooseIdData + * @returns {Array} Projects object id field. + */ - if (!projectInformation.success) { - return resolve(projectInformation); - } + static mongooseIdData() { - if (updatedDate !== "") { - projectInformation.data.lastDownloadedAt = updatedDate; - } + const projectsSchema = schemas["projects"].schema; + const projectSchemaKey = Object.keys(projectsSchema); - projectIds.push(projectInformation.data._id); - } + let mongooseIds = []; - if (updatedDate !== "") { - await database.models.projects.updateMany({ - _id: { $in: projectIds } - }, { - $set: { - lastDownloadedAt: updatedDate - } - }); - } + projectSchemaKey.forEach(projectSchema => { - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECTS_FETCHED, - data: projects - }); + const currentSchemaType = projectsSchema[projectSchema]; - } catch (error) { - return resolve({ - status: error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] - }); + if (currentSchemaType === "ObjectId") { + mongooseIds.push(projectSchema); } - }) + }); + + return mongooseIds; } /** - * List of projects meta form. - * @method - * @name metaForm - * @returns {Object} List of projects meta form. - */ + * Sync project. + * @method + * @name sync + * @param {String} projectId - id of the project. + * @param {String} lastDownloadedAt - last downloaded at time. + * @param {Object} data - body data. + * @param {String} userId - Logged in user id. + * @param {String} userToken - User token. + * @param {String} [appName = ""] - App Name. + * @param {String} [appVersion = ""] - App Version. + * @returns {Object} Project created information. + */ - static metaForm() { + static sync(projectId, lastDownloadedAt, data, userId, userToken, appName = "", appVersion = "") { return new Promise(async (resolve, reject) => { try { - - let forms = await kendraService.formDetails("projects"); - - if (!forms.success) { + + const userProject = await projectQueries.projectDocument({ + _id: projectId, + userId: userId + }, [ + "_id", + "tasks", + "programInformation._id", + "solutionInformation._id", + "solutionInformation.externalId", + "entityInformation._id", + "lastDownloadedAt", + "appInformation", + "status" + ]); + + if (!(userProject.length > 0)) { throw { - status: HTTP_STATUS_CODE['ok'].status, - message: CONSTANTS.apiResponses.PROJECTS_FORM_NOT_FOUND + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.USER_PROJECT_NOT_FOUND }; - } - let categoriesData = - await libraryCategoriesHelper.categoryDocuments({}, ["name", "externalId"]); - - if (!categoriesData.length > 0) { - + if (userProject[0].lastDownloadedAt.toISOString() !== lastDownloadedAt) { throw { status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.LIBRARY_CATEGORIES_NOT_FOUND + message: CONSTANTS.apiResponses.USER_ALREADY_SYNC }; - } - categoriesData = categoriesData.map(category => { - return { - _id: category._id, - label: category.name, - value: category.externalId + if ( userProject[0].status == CONSTANTS.common.SUBMITTED_STATUS ) { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.FAILED_TO_SYNC_PROJECT_ALREADY_SUBMITTED }; - }); - - categoriesData.push({ - _id: "", - label: CONSTANTS.common.OTHERS, - value: CONSTANTS.common.OTHERS.toLowerCase() - }); - - let formsData = forms.data; - - formsData[formsData.length - 1].options = categoriesData; + } - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECTS_METAFORM_FETCHED, - data: formsData - }); + const projectsModel = Object.keys(schemas["projects"].schema); - } catch (error) { - return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] - }); - } - }) - } + let keysToRemoveFromUpdation = ["userRoleInformation","userProfile","certificate"] + keysToRemoveFromUpdation.forEach( key => { + if (data[key])delete data[key]; + }) + + let updateProject = {}; + let projectData = await _projectData(data); + if (projectData && projectData.success == true) { + updateProject = _.merge(updateProject, projectData.data); + } + let createNewProgramAndSolution = false; + let solutionExists = false; - /** - * List of projects tasks meta form. - * @method - * @name tasksMetaForm - * @returns {Object} List of projects tasks meta form. - */ + if (data.programId && data.programId !== "") { - static tasksMetaForm() { - return new Promise(async (resolve, reject) => { - try { + // Check if program already existed in project and if its not an existing program. + if (!userProject[0].programInformation) { + createNewProgramAndSolution = true; + } else if ( + userProject[0].programInformation && + userProject[0].programInformation._id && + userProject[0].programInformation._id.toString() !== data.programId + ) { + // Not an existing program. - let forms = await kendraService.formDetails("projectTasks"); + solutionExists = true; + } - if (!forms.success) { + } else if (data.programName) { - throw { - status: HTTP_STATUS_CODE['ok'].status, - message: CONSTANTS.apiResponses.PROJECT_TASKS_FORM_NOT_FOUND + if (!userProject[0].solutionInformation) { + createNewProgramAndSolution = true; + } else { + solutionExists = true; + // create new program using current name and add existing solution and remove program from it. } } - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECT_TASKS_METAFORM_FETCHED, - data: forms.data - }); - - } catch (error) { - return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] - }); - } - }) - } - - /** - * Bulk create user projects. - * @method - * @name bulkCreate - Bulk create user projects. - * @param {Array} csvData - csv data. - * @param {String} userToken - logged in user token. - * @returns {Object} Bulk create user projects. - */ - - static bulkCreate(csvData, userToken) { - return new Promise(async (resolve, reject) => { - try { - - const fileName = `bulk-create-user-`; - let fileStream = new CSV_FILE_STREAM(fileName); - let input = fileStream.initStream(); - - (async function () { - await fileStream.getProcessorPromise(); - return resolve({ - isResponseAStream: true, - fileNameWithPath: fileStream.fileNameWithPath() - }); - })(); - - let templateIds = []; - let entityIds = []; - - csvData.forEach(data => { - templateIds.push(data.templateId); + let addOrUpdateEntityToProject = false; - if (data.entityId && data.entityId !== "") { - entityIds.push(data.entityId); + if (data.entityId) { + + // If entity is not present in project or new entity is updated. + if ( + !userProject[0].entityInformation || + ( + userProject[0].entityInformation && + userProject[0].entityInformation._id !== data.entityId + ) + ) { + addOrUpdateEntityToProject = true; } - }); - - let entityDocument = {}; - - if (entityIds.length > 0) { + } + + if (addOrUpdateEntityToProject) { - const entitiesData = await _entitiesInformation(entityIds); + let entityInformation = + await _entitiesInformation([data.entityId]); - if (!entitiesData.success) { - return resolve(entitiesData); + if (!entityInformation.success) { + return resolve(entityInformation); } - entityDocument = - entitiesData.data.reduce((ac, entity) => ({ ...ac, [entity._id.toString()]: entity }), {}); + updateProject["entityInformation"] = entityInformation.data[0]; + updateProject.entityId = entityInformation.data[0]._id; } + + if (createNewProgramAndSolution || solutionExists) { + + let programAndSolutionInformation = + await this.createProgramAndSolution( + data.programId, + data.programName, + updateProject.entityId ? [updateProject.entityId] : "", + userToken, + userProject[0].solutionInformation && userProject[0].solutionInformation._id ? + userProject[0].solutionInformation._id : "" + ); - let templateData = {}; - let solutionIds = []; - let programIds = []; - - if (templateIds.length > 0) { - - const projectTemplates = - await projectTemplatesHelper.templateDocument({ - status: CONSTANTS.common.PUBLISHED, - externalId: { - $in: templateIds - }, - isReusable: false - }, ["_id", "solutionExternalId", "programExternalId", "externalId", "programId"]); - - if (!projectTemplates.length > 0) { - throw { - message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } + if (!programAndSolutionInformation.success) { + return resolve(programAndSolutionInformation); } - projectTemplates.forEach(template => { - - templateData[template.externalId] = template; + if (solutionExists) { - if (template.solutionExternalId) { - solutionIds.push(template.solutionExternalId); - } + let updateProgram = + await surveyService.removeSolutionsFromProgram( + userToken, + userProject[0].programInformation._id, + [userProject[0].solutionInformation._id] + ); - if (template.programId) { - programIds.push(template.programId); + if (!updateProgram.success) { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.PROGRAM_NOT_UPDATED + } } + } - }) - + updateProject = + _.merge(updateProject, programAndSolutionInformation.data); } - let solutions = {}; + let booleanData = this.booleanData(schemas["projects"].schema); + let mongooseIdData = this.mongooseIdData(schemas["projects"].schema); - if (solutionIds.length > 0) { + if (data.tasks) { - let solutionData = - await assessmentService.listSolutions(solutionIds); + let taskReport = {}; - if (!solutionData.success) { - throw { - message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } + updateProject.tasks = await _projectTask( + data.tasks + ); if ( - solutionData.data && solutionData.data.length > 0 + userProject[0].tasks && + userProject[0].tasks.length > 0 ) { - solutionData.data.forEach(solution => { - solutions[solution.externalId] = { - "name": solution.name, - "externalId": solution.externalId, - "description": solution.description, - "_id": solution._id - }; - }) - } - - } - - let programs = {}; - - if (programIds.length > 0) { - - let programData = - await assessmentService.listProgramsBasedOnIds(programIds); - if (!programData.success) { - throw { - message: CONSTANTS.apiResponses.PROGRAM_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } + updateProject.tasks.forEach(task => { - if (programData.data && programData.data.length > 0) { + task.updatedBy = userId; + task.updatedAt = new Date(); - programData.data.forEach(program => { - programs[program.externalId] = { - _id: program._id, - name: program.name, - description: program.description ? program.description : "", - externalId: program.externalId - }; - }) - } - } + let taskIndex = + userProject[0].tasks.findIndex( + projectTask => projectTask._id === task._id + ); - for ( - let pointerToCsvData = 0; - pointerToCsvData < csvData.length; - pointerToCsvData++ - ) { + if (taskIndex < 0) { + userProject[0].tasks.push( + task + ); + } else { - let currentCsvData = csvData[pointerToCsvData]; + let keepFieldsFromTask = ["observationInformation", "submissions"]; + + removeFieldsFromRequest.forEach((removeField) => { + delete userProject[0].tasks[taskIndex][removeField]; + }); + + keepFieldsFromTask.forEach((field) => { + if ( userProject[0].tasks[taskIndex][field] ){ + task[field] = userProject[0].tasks[taskIndex][field]; + } + }); + + userProject[0].tasks[taskIndex] = task; + } + }); - if (!templateData[currentCsvData.templateId]) { - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND; - input.push(currentCsvData); - continue; + updateProject.tasks = userProject[0].tasks; } - let currentTemplateData = templateData[currentCsvData.templateId]; + taskReport.total = updateProject.tasks.length; - let projectCreation = - await this.userAssignedProjectCreation( - currentTemplateData._id, - currentCsvData["keycloak-userId"], - userToken - ); + updateProject.tasks.forEach(task => { + //consider tasks where isDeleted is false. + if ( task.isDeleted == false ) { + if (!taskReport[task.status]) { + taskReport[task.status] = 1; + } else { + taskReport[task.status] += 1; + } + } else { + taskReport.total = taskReport.total - 1; + } + + }); - if (!projectCreation.success) { - return resolve(projectCreation); - } + updateProject["taskReport"] = taskReport; + } - let solutionInformation = {}; + Object.keys(data).forEach(updateData => { + if ( + !updateProject[updateData] && + projectsModel.includes(updateData) + ) { - if (currentTemplateData.solutionExternalId) { + if (booleanData.includes(updateData)) { - if (!solutions[currentTemplateData.solutionExternalId]) { + updateProject[updateData] = + UTILS.convertStringToBoolean(data[updateData]); - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.SOLUTION_NOT_FOUND; - input.push(currentCsvData); - continue; + } else if (mongooseIdData.includes(updateData)) { + updateProject[updateData] = ObjectId(data[updateData]); + } else { + updateProject[updateData] = data[updateData]; } - - solutionInformation = solutions[currentTemplateData.solutionExternalId]; - projectCreation.data.solutionInformation = solutionInformation; } + }); - if (currentTemplateData.programExternalId) { - - if (!programs[currentTemplateData.programExternalId]) { - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.PROGRAM_NOT_FOUND; - input.push(currentCsvData); - continue; - } + updateProject.updatedBy = userId; + updateProject.updatedAt = new Date(); - projectCreation.data.programInformation = - programs[currentTemplateData.programExternalId]; + if (!userProject[0].appInformation) { + updateProject["appInformation"] = {}; - projectCreation.data.isAPrivateProgram = - programs[currentTemplateData.programExternalId].isAPrivateProgram; + if (appName !== "") { + updateProject["appInformation"]["appName"] = appName; } - if ( - projectCreation.data.assesmentOrObservationTask && - !currentCsvData.entityId - ) { - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.ENTITY_REQUIRED_FOR_ASSESSMENT_OBSERVATION; - input.push(currentCsvData); - continue; + if (appVersion !== "") { + updateProject["appInformation"]["appVersion"] = appVersion; } + } - if (currentCsvData.entityId) { + if ( data.status && data.status !== "" ) { + updateProject.status = UTILS.convertProjectStatus(data.status); + } + + if ( data.status == CONSTANTS.common.COMPLETED_STATUS || data.status == CONSTANTS.common.SUBMITTED_STATUS ) { + updateProject.completedDate = new Date(); + } + + let projectUpdated = + await projectQueries.findOneAndUpdate( + { + _id: userProject[0]._id + }, + { + $set: updateProject + }, { + new: true + } + ); - if (!entityDocument[currentCsvData.entityId]) { - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.ENTITIES_NOT_FOUND; - input.push(currentCsvData); - continue; - } + if (!projectUpdated._id) { + throw { + message: CONSTANTS.apiResponses.USER_PROJECT_NOT_UPDATED, + status: HTTP_STATUS_CODE['bad_request'].status + } + } + + // push project details to kafka + await kafkaProducersHelper.pushProjectToKafka(projectUpdated); + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.USER_PROJECT_UPDATED, + data : { + programId : + projectUpdated.programInformation && projectUpdated.programInformation._id ? + projectUpdated.programInformation._id : "", + hasAcceptedTAndC : projectUpdated.hasAcceptedTAndC ? projectUpdated.hasAcceptedTAndC : false + } + }); - if ( - currentTemplateData.entityType && currentTemplateData.entityType !== "" && - currentTemplateData.entityType !== entityDocument[currentCsvData.entityId].entityType - ) { - currentCsvData["STATUS"] = - CONSTANTS.apiResponses.ENTITY_TYPE_MIS_MATCHED; - input.push(currentCsvData); - continue; - } + } catch (error) { + return resolve({ + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, + success: false, + message: error.message, + data: {} + }); + } + }) + } - let entities = []; + /** + * Program and solution information + * @method + * @name createProgramAndSolution + * @param {String} entityId - entity id. + * @param {String} userToken - Logged in user token. + * @param {String} [ programId = "" ] - Program Id. + * @param {String} [ programName = "" ] - Program Name. + * @returns {Object} Created program and solution data. + */ - if ( - solutionInformation.entities && - solutionInformation.entities.length > 0 - ) { + static createProgramAndSolution( + programId = "", + programName = "", + entities, + userToken, + solutionId, + isATargetedSolution = "" + ) { + return new Promise(async (resolve, reject) => { + try { - let entityIndex = - solutionInformation.entities.findIndex(entity => entity._id === csvData[pointerToCsvData].entityId); + let result = {}; - if (entityIndex < 0) { + let programAndSolutionData = { + type: CONSTANTS.common.IMPROVEMENT_PROJECT, + subType: CONSTANTS.common.IMPROVEMENT_PROJECT, + isReusable: false, + solutionId: solutionId, + entities: entities + }; - entities = - solutionInformation.entities.push(ObjectId(currentCsvData.entityId)) - } - } else { - entities = [ObjectId(currentCsvData.entityId)]; - } + if (programName !== "") { + programAndSolutionData["programName"] = programName; + } - if (entities.length > 0) { + if (programId !== "") { + programAndSolutionData["programId"] = programId; + } - let solutionUpdated = - await assessmentService.updateSolution( - userToken, - { - entities: entities - }, - solutionInformation.externalId - ); + let solutionAndProgramCreation = + await coreService.createUserProgramAndSolution( + programAndSolutionData, + userToken, + isATargetedSolution + ); + + if (!solutionAndProgramCreation.success) { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.SOLUTION_PROGRAMS_NOT_CREATED + }; + } - if (!solutionUpdated.success) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.SOLUTION_NOT_UPDATED - } - } - } + result.solutionInformation = _.pick( + solutionAndProgramCreation.data.solution, + ["name", "externalId", "description", "_id", "entityType", "certificateTemplateId"] + ); + + result.solutionInformation._id = + ObjectId(result.solutionInformation._id); - projectCreation.data.entityId = ObjectId(currentCsvData.entityId); + result["solutionId"] = ObjectId(result.solutionInformation._id); + result["solutionExternalId"] = result.solutionInformation.externalId; - projectCreation.data.entityInformation = - entityDocument[currentCsvData.entityId]; - } + result.programInformation = _.pick( + solutionAndProgramCreation.data.program, + ["_id", "name", "externalId", "description", "isAPrivateProgram"] + ); - projectCreation.data.status = CONSTANTS.common.NOT_STARTED_STATUS; - projectCreation.data.lastDownloadedAt = new Date(); - const project = - await database.models.projects.create(projectCreation.data); + result["programId"] = ObjectId(result.programInformation._id); + result["programExternalId"] = result.programInformation.externalId; + result["isAPrivateProgram"] = result.programInformation.isAPrivateProgram; - if (project._id) { - currentCsvData["STATUS"] = project._id; - } else { - currentCsvData["STATUS"] = CONSTANTS.apiResponses.PROJECT_NOT_CREATED; - } + result.programInformation._id = + ObjectId(result.programInformation._id); - input.push(currentCsvData); + if( solutionAndProgramCreation.data.parentSolutionInformation ){ + result["link"] = solutionAndProgramCreation.data.parentSolutionInformation.link ? solutionAndProgramCreation.data.parentSolutionInformation.link : ""; } - input.push(null); + return resolve({ + success: true, + data: result + }); } catch (error) { - return reject(error); + return resolve({ + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, + success: false, + message: error.message, + data: {} + }); } - }) + }); } - /** - * Projects boolean data. - * @method - * @name booleanData - * @returns {Array} Boolean data. - */ - - static booleanData() { - - const projectsSchema = schemas["projects"].schema; - const projectSchemaKey = Object.keys(projectsSchema); - - let booleanProjects = []; - - projectSchemaKey.forEach(projectSchema => { - const currentSchema = projectsSchema[projectSchema]; - - if ( - currentSchema.hasOwnProperty('default') && - typeof currentSchema.default === "boolean" - ) { - booleanProjects.push(projectSchema); - } - }); - - return booleanProjects; - } - - /** - * Projects object id field. - * @method - * @name mongooseIdData - * @returns {Array} Projects object id field. + /** + * Program and solution information + * @method + * @name getProgramAndSolutionDetails + * @param {Object} solutionDetails - solution details. + * @returns {Object} return program and solution data. + * example : + * response : { + "success":true, + "data":{ + "solutionInformation":{ + "name":"Keep Our Schools Alive (Petition) - With Certificate 2", + "externalId":"IDEAIMP-4-1687489894154-PROJECT-SOLUTION-1688365299788", + "description":"Leveraging the huge number of private schools to show the significance of the financial problem by creating a petition and presenting to the authorities.", + "_id":64a268f34b9d0c124fd8ed80, + "certificateTemplateId":64950d67955f600008e2aff3 + }, + "solutionId":64a268f34b9d0c124fd8ed80, + "solutionExternalId":"IDEAIMP-4-1687489894154-PROJECT-SOLUTION-1688365299788", + "programInformation":{ + "_id":64a268f34b9d0c124fd8ed7d, + "description":"Certificate Test Program 6.0", + "externalId":"Pgm_Certificate_Test_Program_6.0_QA-1688365299788", + "isAPrivateProgram":true, + "name":"Certificate Test Program 6.0" + }, + "programId":64a268f34b9d0c124fd8ed7d, + "programExternalId":"Pgm_Certificate_Test_Program_6.0_QA-1688365299788", + "isAPrivateProgram":true, + "link":"750f4f6ebb390ad5f7038a8fbc8e1c3f" + } + } */ - static mongooseIdData() { - - const projectsSchema = schemas["projects"].schema; - const projectSchemaKey = Object.keys(projectsSchema); - - let mongooseIds = []; - - projectSchemaKey.forEach(projectSchema => { - - const currentSchemaType = projectsSchema[projectSchema]; - - if (currentSchemaType === "ObjectId") { - mongooseIds.push(projectSchema); - } - }); - - return mongooseIds; - } - - /** - * Sync project. - * @method - * @name importFromLibrary - * @param {String} projectTemplateId - project template id. - * @param {Object} requestedData - body data. - * @param {String} userId - Logged in user id. - * @param {String} userToken - User token. - * @returns {Object} Project created information. - */ - - static importFromLibrary(projectTemplateId, requestedData, userToken, userId) { + static getProgramAndSolutionDetails( + solutionDetails + ) { return new Promise(async (resolve, reject) => { try { - let libraryProjects = - await libraryCategoriesHelper.projectDetails( - projectTemplateId - ); - - if ( - libraryProjects.data && - !Object.keys(libraryProjects.data).length > 0 - ) { - throw { - message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - }; - } - - let taskReport = {}; - - if ( - libraryProjects.data.tasks && - libraryProjects.data.tasks.length > 0 - ) { - - libraryProjects.data.tasks = await _projectTask( - libraryProjects.data.tasks, - true - ); - - taskReport.total = libraryProjects.data.tasks.length; - - libraryProjects.data.tasks.forEach(task => { - if (!taskReport[task.status]) { - taskReport[task.status] = 1; - } else { - taskReport[task.status] += 1; - } - }); - - libraryProjects.data["taskReport"] = taskReport; - } - - if (requestedData.entityId && requestedData.entityId !== "") { - - let entityInformation = - await _entitiesInformation([requestedData.entityId]); - - if (!entityInformation.success) { - return resolve(entityInformation); - } - - libraryProjects.data["entityInformation"] = entityInformation.data[0]; - libraryProjects.data.entityId = entityInformation.data[0]._id; - } - - if ( - (requestedData.programId && requestedData.programId !== "") || - (requestedData.programName && requestedData.programName !== "") - ) { - - let programAndSolutionInformation = - await this.createProgramAndSolution( - requestedData.programId, - requestedData.programName, - requestedData.entityId ? [requestedData.entityId] : "", - userToken - ); - - if (!programAndSolutionInformation.success) { - return resolve(programAndSolutionInformation); - } - - if ( - libraryProjects.data["entityInformation"] && - libraryProjects.data["entityInformation"].entityType !== - programAndSolutionInformation.data.solutionInformation.entityType - ) { - throw { - message: CONSTANTS.apiResponses.ENTITY_TYPE_MIS_MATCHED, - status: HTTP_STATUS_CODE['bad_request'].status - } - } - - libraryProjects.data = _.merge( - libraryProjects.data, - programAndSolutionInformation.data - ) - } - - let userOrganisations = - await kendraService.getUserOrganisationsAndRootOrganisations( - userToken - ); - - if (!userOrganisations.success) { - throw { - message: CONSTANTS.apiResponses.USER_ORGANISATION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } - - libraryProjects.data.createdFor = userOrganisations.data.createdFor; - libraryProjects.data.rootOrganisations = userOrganisations.data.rootOrganisations; - - libraryProjects.data.userId = libraryProjects.data.updatedBy = libraryProjects.data.createdBy = userId; - libraryProjects.data.lastDownloadedAt = new Date(); - libraryProjects.data.status = CONSTANTS.common.NOT_STARTED_STATUS; - - if (requestedData.startDate) { - libraryProjects.data.startDate = requestedData.startDate; - } - - if (requestedData.endDate) { - libraryProjects.data.endDate = requestedData.endDate; - } + let result = {}; + result.solutionInformation = _.pick( + solutionDetails, + ["name", "externalId", "description", "_id", "entityType", "certificateTemplateId"] + ); + + result.solutionInformation._id = + ObjectId(result.solutionInformation._id); - libraryProjects.data.projectTemplateId = libraryProjects.data._id; - libraryProjects.data.projectTemplateExternalId = libraryProjects.data.externalId; + result["solutionId"] = ObjectId(result.solutionInformation._id); + result["solutionExternalId"] = result.solutionInformation.externalId; + + // Adding program informations + result.programInformation = { + _id: solutionDetails.programId, + description: solutionDetails.programDescription, + externalId: solutionDetails.programExternalId, + isAPrivateProgram: solutionDetails.isAPrivateProgram, + name: solutionDetails.programName + }; + - let projectCreation = await database.models.projects.create( - _.omit(libraryProjects.data, ["_id"]) - ); + result["programId"] = ObjectId(result.programInformation._id); + result["programExternalId"] = result.programInformation.externalId; + result["isAPrivateProgram"] = result.programInformation.isAPrivateProgram; - if (requestedData.rating && requestedData.rating > 0) { - await projectTemplatesHelper.ratings( - projectTemplateId, - requestedData.rating, - userToken - ); - } + result.programInformation._id = + ObjectId(result.programInformation._id); + // Get link from parent solution + let solutionData = await solutionsHelper.solutionDocuments({ + _id: solutionDetails.parentSolutionId, + }, + ["link"]); - projectCreation = await _projectInformation(projectCreation._doc); + result["link"] = ( solutionData.length > 0 && solutionData[0].link ) ? solutionData[0].link : ""; return resolve({ success: true, - message: CONSTANTS.apiResponses.PROJECTS_FETCHED, - data: projectCreation.data + data: result }); } catch (error) { return resolve({ + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, data: {} }); } - }) + }); } /** - * Create user projects. - * @method - * @name create - * @param userId - Logged in user id. - * @param userToken - Logged in user token. - * @returns {Object} Return _id and lastDownloadedAt - */ + * Project details. + * @method + * @name details + * @param {String} projectId - project id. + * @returns {Object} + */ - static create(userId, userToken) { + static details(projectId, userId,userRoleInformation = {}) { return new Promise(async (resolve, reject) => { try { - let creationData = { - lastDownloadedAt: new Date(), - createdFor: [], - rootOrganisations: [] - } - - creationData["userId"] = creationData["createdBy"] = creationData["updatedBy"] = userId; + const projectDetails = await projectQueries.projectDocument({ + _id: projectId, + userId: userId + }, "all", + [ + "taskReport", + "projectTemplateId", + "projectTemplateExternalId", + "userId", + "createdBy", + "updatedBy", + "createdAt", + "updatedAt", + "userRoleInformation", + "__v" + ]); - let userOrganisations = - await kendraService.getUserOrganisationsAndRootOrganisations( - userToken - ); + if (!(projectDetails.length > 0)) { - if (!userOrganisations.success) { throw { - message: CONSTANTS.apiResponses.USER_ORGANISATION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND } } - if (userOrganisations.data) { - creationData.createdFor = userOrganisations.data.createdFor; - creationData.rootOrganisations = userOrganisations.data.rootOrganisations; + if (Object.keys(userRoleInformation).length > 0) { + + if (!projectDetails[0].userRoleInformation || !(Object.keys(projectDetails[0].userRoleInformation).length > 0)) { + await projectQueries.findOneAndUpdate({ + _id: projectId + },{ + $set: {userRoleInformation: userRoleInformation} + }); + } + } + + let result = await _projectInformation(projectDetails[0]); + + if (!result.success) { + return resolve(result); } - let userProject = await database.models.projects.create( - creationData - ); return resolve({ success: true, - message: CONSTANTS.apiResponses.CREATED_USER_PROJECT, - data: _.pick(userProject, ["_id", "lastDownloadedAt"]) + message: CONSTANTS.apiResponses.PROJECT_DETAILS_FETCHED, + data: result.data }); } catch (error) { @@ -836,994 +790,1237 @@ module.exports = class UserProjectsHelper { error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, - data: {} + data: [] }); } }) } /** - * Sync project. + * List of library projects. * @method - * @name sync - * @param {String} projectId - id of the project. - * @param {String} lastDownloadedAt - last downloaded at time. - * @param {Object} data - body data. - * @param {String} userId - Logged in user id. - * @param {String} userToken - User token. - * @param {String} [appName = ""] - App Name. - * @param {String} [appVersion = ""] - App Version. - * @returns {Object} Project created information. - */ + * @name projects + * @param pageSize - Size of page. + * @param pageNo - Recent page no. + * @param search - search text. + * @param fieldsArray - array of projections fields. + * @param groupBy - groupBy query. + * @returns {Object} List of library projects. + */ - static sync(projectId, lastDownloadedAt, data, userId, userToken, appName = "", appVersion = "") { + static projects(query, pageSize, pageNo, searchQuery, fieldsArray, groupBy = "") { return new Promise(async (resolve, reject) => { try { - const userProject = await this.projectDocument({ - _id: projectId, - userId: userId - }, [ - "_id", - "tasks", - "programInformation._id", - "solutionInformation._id", - "solutionInformation.externalId", - "entityInformation._id", - "lastDownloadedAt", - "appInformation" - ]); - - if (!userProject.length > 0) { + let matchQuery = { + $match: query + }; - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.USER_PROJECT_NOT_FOUND - }; + if (searchQuery && searchQuery.length > 0) { + matchQuery["$match"]["$or"] = searchQuery; } - if (userProject[0].lastDownloadedAt.toISOString() !== lastDownloadedAt) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.USER_ALREADY_SYNC - }; - } + let projection = {} + fieldsArray.forEach(field => { + projection[field] = 1; + }); - const projectsModel = Object.keys(schemas["projects"].schema); + let aggregateData = []; + aggregateData.push(matchQuery); + aggregateData.push({ + $sort : { "syncedAt" : -1 } + }) - let updateProject = {}; - let projectData = await _projectData(data); - if (projectData && projectData.success == true) { - updateProject = _.merge(updateProject, projectData.data); + if (groupBy !== "") { + aggregateData.push({ + $group: groupBy + }); + } else { + aggregateData.push({ + $project: projection + }); } - let createNewProgramAndSolution = false; - let solutionExists = false; - - if (data.programId && data.programId !== "") { - - // Check if program already existed in project and if its not an existing program. + aggregateData.push( + { + $facet: { + "totalCount": [ + { "$count": "count" } + ], + "data": [ + { $skip: pageSize * (pageNo - 1) }, + { $limit: pageSize } + ], + } + }, + { + $project: { + "data": 1, + "count": { + $arrayElemAt: ["$totalCount.count", 0] + } + } + } + ); - if (!userProject[0].programInformation) { - createNewProgramAndSolution = true; - } else if ( - userProject[0].programInformation && - userProject[0].programInformation._id && - userProject[0].programInformation._id.toString() !== data.programId - ) { - // Not an existing program. + let result = + await projectQueries.getAggregate(aggregateData); - solutionExists = true; + return resolve({ + success: true, + message: CONSTANTS.apiResponses.PROJECTS_FETCHED, + data: { + data: result[0].data, + count: result[0].count ? result[0].count : 0 } + }) - } else if (data.programName) { - - if (!userProject[0].solutionInformation) { - createNewProgramAndSolution = true; - } else { - solutionExists = true; - // create new program using current name and add existing solution and remove program from it. + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: { + data: [], + count: 0 } - } + }); + } + }) + } - let addOrUpdateEntityToProject = false; + /** + * Get tasks from a user project. + * @method + * @name tasks + * @param {String} projectId - Project id. + * @param {Array} taskIds - Array of tasks ids. + * @returns {Object} - return tasks from a project. + */ - if (data.entityId) { + static tasks(projectId, taskIds) { + return new Promise(async (resolve, reject) => { + try { - // If entity is not present in project or new entity is updated. - if ( - !userProject[0].entityInformation || - ( - userProject[0].entityInformation && - userProject[0].entityInformation._id !== data.entityId - ) - ) { - addOrUpdateEntityToProject = true; + let aggregatedData = [{ + $match: { + _id: ObjectId(projectId) } - } + }]; - if (addOrUpdateEntityToProject) { + if (taskIds.length > 0) { - let entityInformation = - await _entitiesInformation([data.entityId]); + let unwindData = { + "$unwind": "$tasks" + } - if (!entityInformation.success) { - return resolve(entityInformation); + let matchData = { + "$match": { + "tasks._id": { $in: taskIds } + } + }; + + let groupData = { + "$group": { + "_id": "$_id", + "tasks": { "$push": "$tasks" } + } } - updateProject["entityInformation"] = entityInformation.data[0]; - updateProject.entityId = entityInformation.data[0]._id; + aggregatedData.push(unwindData, matchData, groupData); } - if (createNewProgramAndSolution || solutionExists) { - - let programAndSolutionInformation = - await this.createProgramAndSolution( - data.programId, - data.programName, - updateProject.entityId ? [updateProject.entityId] : "", - userToken, - userProject[0].solutionInformation && userProject[0].solutionInformation._id ? - userProject[0].solutionInformation._id : "" - ); + let projectData = { + "$project": { "tasks": 1 } + } - if (!programAndSolutionInformation.success) { - return resolve(programAndSolutionInformation); - } + aggregatedData.push(projectData); - if (solutionExists) { + let projects = + await projectQueries.getAggregate(aggregatedData); - let updateProgram = - await assessmentService.removeSolutionsFromProgram( - userToken, - userProject[0].programInformation._id, - [userProject[0].solutionInformation._id] - ); + return resolve({ + success: true, + data: projects + }); - if (!updateProgram.success) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.PROGRAM_NOT_UPDATED - } - } - } + } catch (error) { + return resolve({ + success: false, + data: [] + }); + } + }) + } - updateProject = - _.merge(updateProject, programAndSolutionInformation.data); - } + /** + * Status of tasks. + * @method + * @name tasksStatus + * @param {String} projectId - Project id. + * @param {Array} taskIds - Tasks ids. + * @returns {Object} + */ - let booleanData = this.booleanData(schemas["projects"].schema); - let mongooseIdData = this.mongooseIdData(schemas["projects"].schema); + static tasksStatus(projectId, taskIds = []) { + return new Promise(async (resolve, reject) => { + try { - if (data.tasks) { + let tasks = await this.tasks(projectId, taskIds); - let taskReport = {}; + if (!tasks.success || !(tasks.data.length > 0)) { - updateProject.tasks = await _projectTask( - data.tasks - ); + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND + }; + } - if ( - userProject[0].tasks && - userProject[0].tasks.length > 0 - ) { + let projectTasks = tasks.data[0].tasks; + let result = []; - updateProject.tasks.forEach(task => { + for (let task = 0; task < projectTasks.length; task++) { - task.updatedBy = userId; - task.updatedAt = new Date(); + let currentTask = projectTasks[task]; - let taskIndex = - userProject[0].tasks.findIndex( - projectTask => projectTask._id === task._id - ); - - if (taskIndex < 0) { - userProject[0].tasks.push( - task - ); - } else { - - if (userProject[0].tasks[taskIndex].submissionDetails) { - task.submissionDetails = userProject[0].tasks[taskIndex].submissionDetails; - } - userProject[0].tasks[taskIndex] = task; - } - }); + let data = { + type: currentTask.type, + status: currentTask.status, + _id: currentTask._id + }; - updateProject.tasks = userProject[0].tasks; - } + if ( + currentTask.type === CONSTANTS.common.ASSESSMENT || + currentTask.type === CONSTANTS.common.OBSERVATION + ) { - taskReport.total = updateProject.tasks.length; + let completedSubmissionCount = 0; - updateProject.tasks.forEach(task => { - if (!taskReport[task.status]) { - taskReport[task.status] = 1; - } else { - taskReport[task.status] += 1; - } - }); + let minNoOfSubmissionsRequired = currentTask.solutionDetails.minNoOfSubmissionsRequired ? currentTask.solutionDetails.minNoOfSubmissionsRequired : CONSTANTS.common.DEFAULT_SUBMISSION_REQUIRED; - updateProject["taskReport"] = taskReport; - } + data["submissionStatus"] = CONSTANTS.common.STARTED; - Object.keys(data).forEach(updateData => { - if ( - !updateProject[updateData] && - projectsModel.includes(updateData) - ) { + // For 4.7 Urgent fix, need to check why observationInformation is not present at task level. + let submissionDetails = {}; + if(currentTask.observationInformation) { + submissionDetails = currentTask.observationInformation + } else if (currentTask.submissionDetails) { + submissionDetails = currentTask.submissionDetails + } - if (booleanData.includes(updateData)) { + data["submissionDetails"] = submissionDetails; + + if ( currentTask.submissions && currentTask.submissions.length > 0 ) { - updateProject[updateData] = - UTILS.convertStringToBoolean(data[updateData]); + let completedSubmissionDoc; + completedSubmissionCount = currentTask.submissions.filter((eachSubmission) => eachSubmission.status === CONSTANTS.common.COMPLETED_STATUS).length; - } else if (mongooseIdData.includes(updateData)) { - updateProject[updateData] = ObjectId(data[updateData]); - } else { - updateProject[updateData] = data[updateData]; - } - } - }); + if (completedSubmissionCount >= minNoOfSubmissionsRequired ) { + + completedSubmissionDoc = currentTask.submissions.find(eachSubmission => eachSubmission.status === CONSTANTS.common.COMPLETED_STATUS); + data.submissionStatus = CONSTANTS.common.COMPLETED_STATUS; - updateProject.updatedBy = userId; - updateProject.updatedAt = new Date(); + } else { - if (!userProject[0].appInformation) { - updateProject["appInformation"] = {}; + completedSubmissionDoc = currentTask.submissions.find(eachSubmission => eachSubmission.status === CONSTANTS.common.STARTED); + } - if (appName !== "") { - updateProject["appInformation"]["appName"] = appName; - } + Object.assign(data["submissionDetails"],completedSubmissionDoc) - if (appVersion !== "") { - updateProject["appInformation"]["appVersion"] = appVersion; - } - } + } else { - let projectUpdated = - await database.models.projects.findOneAndUpdate( - { - _id: userProject[0]._id - }, - { - $set: updateProject - }, { - new: true + data["submissionDetails"].status = CONSTANTS.common.STARTED; + } + } - ); - if (!projectUpdated._id) { - throw { - message: CONSTANTS.apiResponses.USER_PROJECT_NOT_UPDATED, - status: HTTP_STATUS_CODE['bad_request'].status - } + result.push(data); } return resolve({ success: true, - message: CONSTANTS.apiResponses.USER_PROJECT_UPDATED, - data : { - programId : - projectUpdated.programInformation && projectUpdated.programInformation._id ? - projectUpdated.programInformation._id : "", - hasAcceptedTAndC : projectUpdated.hasAcceptedTAndC ? projectUpdated.hasAcceptedTAndC : false - } + message: CONSTANTS.apiResponses.TASKS_STATUS_FETCHED, + data: result }); } catch (error) { - return resolve({ + return reject({ + success: false, + message: error.message, status: error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: {} + data: [] }); } }) } /** - * Program and solution information + * Update task. * @method - * @name createProgramAndSolution - * @param {String} entityId - entity id. - * @param {String} userToken - Logged in user token. - * @param {String} [ programId = "" ] - Program Id. - * @param {String} [ programName = "" ] - Program Name. - * @returns {Object} Created program and solution data. + * @name updateTask + * @param {String} projectId - Project id. + * @param {String} taskId - Task id. + * @param {Object} updatedData - Update data. + * @returns {Object} */ - static createProgramAndSolution( - programId = "", - programName = "", - entities, - userToken, - solutionId - ) { + static pushSubmissionToTask(projectId, taskId, updatedData) { return new Promise(async (resolve, reject) => { try { - let result = {}; + let updateSubmission = []; - let programAndSolutionData = { - type: CONSTANTS.common.IMPROVEMENT_PROJECT, - subType: CONSTANTS.common.IMPROVEMENT_PROJECT, - isReusable: false, - solutionId: solutionId, - entities: entities - }; + let projectDocument = await projectQueries.projectDocument( + { + _id: projectId, + "tasks._id": taskId + }, [ + "tasks" + ]); - if (programName !== "") { - programAndSolutionData["programName"] = programName; + let currentTask = projectDocument[0].tasks.find(task => task._id == taskId); + let submissions = currentTask.submissions && currentTask.submissions.length > 0 ? currentTask.submissions : [] ; + + // if submission array is empty + if ( !submissions && !(submissions.length > 0) ) { + updateSubmission.push(updatedData); } + + // submission not exist + let checkSubmissionExist = submissions.findIndex(submission => submission._id == updatedData._id); - if (programId !== "") { - programAndSolutionData["programId"] = programId; + if ( checkSubmissionExist == -1 ) { + + updateSubmission = submissions; + updateSubmission.push(updatedData); + + } else { + //submission exist + submissions[checkSubmissionExist] = updatedData; + updateSubmission = submissions; } - let solutionAndProgramCreation = - await kendraService.createUserProgramAndSolution( - programAndSolutionData, - userToken - ); + let tasksUpdated = await projectQueries.findOneAndUpdate({ + "_id": projectId, + "tasks._id": taskId + }, { + $set: { + "tasks.$.submissions": updateSubmission + } + }); - if (!solutionAndProgramCreation.success) { + return resolve(tasksUpdated); + + } catch (error) { + return reject(error); + } + }) + } + + /** + * Solutions details + * @method + * @name solutionDetails + * @param {String} userToken - Logged in user token. + * @param {String} projectId - Project id. + * @param {Array} taskId - Tasks id. + * @returns {Object} + */ + + static solutionDetails(userToken, projectId, taskId, bodyData = {}) { + return new Promise(async (resolve, reject) => { + try { + + let project = await projectQueries.projectDocument( + { + "_id": projectId, + "tasks._id": taskId + }, [ + "entityInformation._id", + "entityInformation.entityType", + "tasks.type", + "tasks._id", + "tasks.solutionDetails", + "tasks.submissions", + "tasks.observationInformation", + "tasks.externalId", + "programInformation._id", + "projectTemplateId" + ] + ); + if (!(project.length > 0)) { throw { status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.SOLUTION_PROGRAMS_NOT_CREATED + message: CONSTANTS.apiResponses.USER_PROJECT_NOT_FOUND }; } - result.solutionInformation = _.pick( - solutionAndProgramCreation.data.solution, - ["name", "externalId", "description", "_id", "entityType"] - ); + let currentTask = project[0].tasks.find(task => task._id == taskId); - result.solutionInformation._id = - ObjectId(result.solutionInformation._id); + let solutionDetails = currentTask.solutionDetails; - result["solutionId"] = ObjectId(result.solutionInformation._id); - result["solutionExternalId"] = result.solutionInformation.externalId; + let assessmentOrObservationData = {}; + + if (project[0].entityInformation && project[0].entityInformation._id && project[0].programInformation && project[0].programInformation._id) { + + assessmentOrObservationData = { + entityId: project[0].entityInformation._id, + programId: project[0].programInformation._id + } - result.programInformation = _.pick( - solutionAndProgramCreation.data.program, - ["_id", "name", "externalId", "description", "isAPrivateProgram"] - ); + if (currentTask.observationInformation) { + assessmentOrObservationData = currentTask.observationInformation; + } else { + + let assessmentOrObservation = { + token: userToken, + solutionDetails: solutionDetails, + entityId: assessmentOrObservationData.entityId, + programId: assessmentOrObservationData.programId, + project: { + "_id": projectId, + "taskId": taskId + } - result["programId"] = ObjectId(result.programInformation._id); - result["programExternalId"] = result.programInformation.externalId; - result["isAPrivateProgram"] = result.programInformation.isAPrivateProgram; + }; - result.programInformation._id = - ObjectId(result.programInformation._id); + let assignedAssessmentOrObservation = + solutionDetails.type === CONSTANTS.common.ASSESSMENT ? + await _assessmentDetails(assessmentOrObservation) : + await _observationDetails(assessmentOrObservation, bodyData); + + if (!assignedAssessmentOrObservation.success) { + return resolve(assignedAssessmentOrObservation); + } + + assessmentOrObservationData = + _.merge(assessmentOrObservationData, assignedAssessmentOrObservation.data); + + if (!currentTask.solutionDetails.isReusable) { + assessmentOrObservationData["programId"] = + currentTask.solutionDetails.programId; + } + + await projectQueries.findOneAndUpdate({ + "_id": projectId, + "tasks._id": taskId + }, { + $set: { + "tasks.$.observationInformation": assessmentOrObservationData + } + }); + + } + + assessmentOrObservationData["entityType"] = project[0].entityInformation.entityType; + + } + + if(currentTask.solutionDetails && !(_.isEmpty(currentTask.solutionDetails))) { + + assessmentOrObservationData.solutionDetails = currentTask.solutionDetails; + } return resolve({ success: true, - data: result + message: CONSTANTS.apiResponses.SOLUTION_DETAILS_FETCHED, + data: assessmentOrObservationData }); } catch (error) { return resolve({ status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, data: {} }); } - }); + }) } - /** - * Project details. + /** + * Creation of user targeted projects. * @method - * @name details + * @name detailsV2 * @param {String} projectId - project id. - * @returns {Object} + * @param {String} solutionId - solution id. + * @param {String} userId - logged in user id. + * @param {String} userToken - logged in user token. + * @param {Object} bodyData - Requested body data. + * @param {String} [appName = ""] - App name. + * @param {String} [appVersion = ""] - App version. + * @returns {Object} Project details. */ - static details(projectId, userId,userRoleInformtion = {}) { - return new Promise(async (resolve, reject) => { - try { - - const projectDetails = await this.projectDocument({ - _id: projectId, - userId: userId - }, "all", - [ - "taskReport", - "createdFor", - "rootOrganisations", - "projectTemplateId", - "projectTemplateExternalId", - "userId", - "createdBy", - "updatedBy", - "createdAt", - "updatedAt", - "userRoleInformation", - "__v" - ]); - - if (!projectDetails.length > 0) { + static detailsV2( projectId,solutionId,userId,userToken,bodyData,appName = "",appVersion = "",templateId = "" ) { + + return new Promise(async (resolve, reject) => { + try { + + let solutionExternalId = ""; + + if( templateId !== "" ) { + + let templateDocuments = + await projectTemplateQueries.templateDocument({ + "externalId" : templateId, + "isReusable" : false, + "solutionId" : { $exists : true } + },["solutionId","solutionExternalId"]); + if( !(templateDocuments.length > 0) ) { throw { - status: HTTP_STATUS_CODE["bad_request"].status, - message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND - } - } - - if (Object.keys(userRoleInformtion).length > 0) { - - if (!projectDetails[0].userRoleInformtion || !Object.keys(projectDetails[0].userRoleInformtion).length > 0) { - await database.models.projects.findOneAndUpdate({ - _id: projectId - },{ - $set: {userRoleInformtion: userRoleInformtion} - }); + message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, + status : HTTP_STATUS_CODE['bad_request'].status } } - let result = await _projectInformation(projectDetails[0]); - - if (!result.success) { - return resolve(projectInformation); - } - - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECT_DETAILS_FETCHED, - data: result.data - }); - - } catch (error) { - return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] - }); + solutionId = templateDocuments[0].solutionId; + solutionExternalId = templateDocuments[0].solutionExternalId; } - }) - } - - /** - * List of library projects. - * @method - * @name projects - * @param pageSize - Size of page. - * @param pageNo - Recent page no. - * @param search - search text. - * @param fieldsArray - array of projections fields. - * @param groupBy - groupBy query. - * @returns {Object} List of library projects. - */ - - static projects(query, pageSize, pageNo, searchQuery, fieldsArray, groupBy = "") { - return new Promise(async (resolve, reject) => { - try { - - let matchQuery = { - $match: query - }; + + if(bodyData.hasOwnProperty("detailsPayload")){ + let detailsPayload = {...bodyData.detailsPayload} + delete bodyData.detailsPayload + bodyData = {...detailsPayload, ...bodyData} - if (searchQuery && searchQuery.length > 0) { - matchQuery["$match"]["$or"] = searchQuery; - } + } + + let userRoleInformation = _.omit(bodyData,["referenceFrom","submissions","hasAcceptedTAndC","link"]); + + if (projectId === "") { + // This will check wether the user user is targeted to solution or not based on his userRoleInformation + const targetedSolutionId = await coreService.checkIfSolutionIsTargetedForUserProfile(userToken,userRoleInformation,solutionId) + //based on above api will check for projects wether its is private project or public project + const projectDetails = await projectQueries.projectDocument({ + solutionId: solutionId, + userId: userId, + isAPrivateProgram: targetedSolutionId.data.isATargetedSolution ? false : true + }, ["_id"]); + if( projectDetails.length > 0 ) { + projectId = projectDetails[0]._id; + } else { + let isAPrivateSolution = (targetedSolutionId.data.isATargetedSolution === false) ? true : false; + let solutionDetails = {} - let projection = {} - fieldsArray.forEach(field => { - projection[field] = 1; - }); + if( templateId === "" ) { + // If solution Id of a private program is passed, fetch solution details + if ( isAPrivateSolution && solutionId != "" ) { + solutionDetails = await solutionsHelper.solutionDocuments({ + _id: solutionId, + isAPrivateProgram: true + }, + [ + "name", + "externalId", + "description", + "programId", + "programName", + "programDescription", + "programExternalId", + "isAPrivateProgram", + "projectTemplateId", + "entityType", + "entityTypeId", + "language", + "creator" + ]); + if( !(solutionDetails.length > 0) ) { + throw { + status : HTTP_STATUS_CODE["bad_request"].status, + message : CONSTANTS.apiResponses.SOLUTION_NOT_FOUND + } + } + solutionDetails = solutionDetails[0]; + } else { + solutionDetails = + await coreService.solutionDetailsBasedOnRoleAndLocation( + userToken, + bodyData, + solutionId, + isAPrivateSolution + ); - let aggregateData = []; - aggregateData.push(matchQuery); - aggregateData.push({ - $sort : { "syncedAt" : -1 } - }) + if( !solutionDetails.success || (solutionDetails.data.data && !(solutionDetails.data.data.length > 0)) ) { + throw { + status : HTTP_STATUS_CODE["bad_request"].status, + message : CONSTANTS.apiResponses.SOLUTION_DOES_NOT_EXISTS_IN_SCOPE + } + } - if (groupBy !== "") { - aggregateData.push({ - $group: groupBy - }); - } else { - aggregateData.push({ - $project: projection - }); - } + solutionDetails = solutionDetails.data; + } - aggregateData.push( - { - $facet: { - "totalCount": [ - { "$count": "count" } - ], - "data": [ - { $skip: pageSize * (pageNo - 1) }, - { $limit: pageSize } - ], + } else { + + solutionDetails = + await surveyService.listSolutions([solutionExternalId]); + if( !solutionDetails.success ) { + throw { + message : CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, + status : HTTP_STATUS_CODE['bad_request'].status + } } - }, - { - $project: { - "data": 1, - "count": { - $arrayElemAt: ["$totalCount.count", 0] + + solutionDetails = solutionDetails.data[0]; + + } + // check for requestForPIIConsent data + let queryData = {}; + queryData["_id"] = solutionDetails.programId; + let programDetails = await programsQueries.programsDocument(queryData,["requestForPIIConsent"]); + + // if requestForPIIConsent not there do not call program join + if ( programDetails.length > 0 && programDetails[0].hasOwnProperty('requestForPIIConsent')) { + + // program join API call it will increment the noOfResourcesStarted counter and will make user join program + // before creating any project this api has to called + let programUsers = await programUsersQueries.programUsersDocument( + { + userId : userId, + programId : solutionDetails.programId + }, + [ + "_id", + "resourcesStarted" + ] + ); + + if (!(programUsers.length > 0) || ( programUsers.length > 0 && programUsers[0].resourcesStarted == false)) { + let programJoinBody = {}; + programJoinBody.userRoleInformation = userRoleInformation; + programJoinBody.isResource = true; + programJoinBody.consentShared = true; + let joinProgramData = await coreService.joinProgram ( + solutionDetails.programId, + programJoinBody, + userToken + ); + if ( !joinProgramData.success ) { + return resolve({ + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.PROGRAM_JOIN_FAILED + }); } } } - ); - - let result = - await database.models.projects.aggregate(aggregateData); + + let projectCreation = + await this.userAssignedProjectCreation( + solutionDetails.projectTemplateId, + userId, + userToken + ); - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECTS_FETCHED, - data: { - data: result[0].data, - count: result[0].count ? result[0].count : 0 + if( !projectCreation.success ) { + return resolve(projectCreation); } - }) - - } catch (error) { - return resolve({ - success: false, - message: error.message, - data: { - data: [], - count: 0 + + projectCreation.data["isAPrivateProgram"] = + solutionDetails.isAPrivateProgram; + + projectCreation.data.programInformation = { + _id : ObjectId(solutionDetails.programId), + externalId : solutionDetails.programExternalId, + description : + solutionDetails.programDescription ? solutionDetails.programDescription : "", + name : solutionDetails.programName } - }); - } - }) - } - - /** - * Get tasks from a user project. - * @method - * @name tasks - * @param {String} projectId - Project id. - * @param {Array} taskIds - Array of tasks ids. - * @returns {Object} - return tasks from a project. - */ - - static tasks(projectId, taskIds) { - return new Promise(async (resolve, reject) => { - try { - - let aggregatedData = [{ - $match: { - _id: ObjectId(projectId) + + projectCreation.data.solutionInformation = { + _id : ObjectId(solutionDetails._id), + externalId : solutionDetails.externalId, + description : + solutionDetails.description ? + solutionDetails.description : "", + name : solutionDetails.name + }; + + projectCreation.data["programId"] = + projectCreation.data.programInformation._id; + + projectCreation.data["programExternalId"] = + projectCreation.data.programInformation.externalId; + + projectCreation.data["solutionId"] = + projectCreation.data.solutionInformation._id; + + projectCreation.data["solutionExternalId"] = + projectCreation.data.solutionInformation.externalId; + + projectCreation.data["userRole"] = + bodyData.role; + + projectCreation.data["appInformation"] = {}; + + if( appName !== "" ) { + projectCreation.data["appInformation"]["appName"] = appName; } - }]; - - if (taskIds.length > 0) { - - let unwindData = { - "$unwind": "$tasks" + + if( appVersion !== "" ) { + projectCreation.data["appInformation"]["appVersion"] = appVersion; } - - let matchData = { - "$match": { - "tasks._id": { $in: taskIds } - } - }; - - let groupData = { - "$group": { - "_id": "$_id", - "tasks": { "$push": "$tasks" } + + if ( solutionDetails.certificateTemplateId && solutionDetails.certificateTemplateId !== "" ) { + // <- Add certificate template details to projectCreation data if present -> + const certificateTemplateDetails = await certificateTemplateQueries.certificateTemplateDocument({ + _id : solutionDetails.certificateTemplateId + }); + + // create certificate object and add data if certificate template is present. + if ( certificateTemplateDetails.length > 0 ) { + projectCreation.data["certificate"] = _.pick(certificateTemplateDetails[0], ['templateUrl', 'status', 'criteria']); + projectCreation.data["certificate"]["templateId"] = solutionDetails.certificateTemplateId; } } + + let getUserProfileFromObservation = false; + + if( bodyData && Object.keys(bodyData).length > 0 ) { + + if( bodyData.hasAcceptedTAndC ) { + projectCreation.data.hasAcceptedTAndC = bodyData.hasAcceptedTAndC; + } + if( bodyData.link ) { + projectCreation.data.link = bodyData.link; + } - aggregatedData.push(unwindData, matchData, groupData); - } - - let projectData = { - "$project": { "tasks": 1 } - } - - aggregatedData.push(projectData); - - let projects = - await database.models.projects.aggregate(aggregatedData); - - return resolve({ - success: true, - data: projects - }); - - } catch (error) { - return resolve({ - success: false, - data: [] - }); - } - }) - } + if( bodyData.referenceFrom ) { + projectCreation.data.referenceFrom = bodyData.referenceFrom; + + if( bodyData.submissions ) { + if ( bodyData.submissions.observationId && bodyData.submissions.observationId != "" ) { + getUserProfileFromObservation = true; + } + projectCreation.data.submissions = bodyData.submissions; - /** - * Status of tasks. - * @method - * @name tasksStatus - * @param {String} projectId - Project id. - * @param {Array} taskIds - Tasks ids. - * @returns {Object} - */ + let entityInformation = + await entitieHelper.listByLocationIds( + [bodyData.submissions.entityId] + ); + + if( !entityInformation.success ) { + throw { + message : CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + status : HTTP_STATUS_CODE['bad_request'].status + } + } + + let entityDetails = await _entitiesMetaInformation( + entityInformation.data + ); + + if ( entityDetails && entityDetails.length > 0 ) { + projectCreation.data["entityInformation"] = entityDetails[0]; + } + + projectCreation.data.entityId = entityInformation.data[0]._id; + } + } else { + if( + solutionDetails.entityType && bodyData[solutionDetails.entityType] + ) { + let entityInformation = + await entitieHelper.listByLocationIds( + [bodyData[solutionDetails.entityType]] + ); + + if( !entityInformation.success ) { + throw { + message : CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + status : HTTP_STATUS_CODE['bad_request'].status + } + } + + let entityDetails = await _entitiesMetaInformation( + entityInformation.data + ); + + if ( entityDetails && entityDetails.length > 0 ) { + projectCreation.data["entityInformation"] = entityDetails[0]; + } + + projectCreation.data.entityId = entityInformation.data[0]._id; + } + } + + if( bodyData.role ) { + projectCreation.data["userRole"] = bodyData.role; + } + + } + + projectCreation.data.status = CONSTANTS.common.STARTED; + projectCreation.data.lastDownloadedAt = new Date(); + + // fetch userRoleInformation from observation if referenecFrom is observation + let addReportInfoToSolution = false; + if ( getUserProfileFromObservation ){ - static tasksStatus(projectId, taskIds = []) { - return new Promise(async (resolve, reject) => { - try { + let observationDetails = await surveyService.observationDetails( + userToken, + bodyData.submissions.observationId + ); + - let tasks = await this.tasks(projectId, taskIds); + if( observationDetails.data && + Object.keys(observationDetails.data).length > 0 && + observationDetails.data.userRoleInformation && + Object.keys(observationDetails.data.userRoleInformation).length > 0 + ) { - if (!tasks.success || !tasks.data.length > 0) { + userRoleInformation = observationDetails.data.userRoleInformation; + + } - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND - }; - } + if( observationDetails.data && + Object.keys(observationDetails.data).length > 0 && + observationDetails.data.userProfile && + Object.keys(observationDetails.data.userProfile).length > 0 + ) { - let projectTasks = tasks.data[0].tasks; - let result = []; + projectCreation.data.userProfile = observationDetails.data.userProfile; + addReportInfoToSolution = true; + + } else { + //Fetch user profile information by calling sunbird's user read api. - for (let task = 0; task < projectTasks.length; task++) { + let userProfile = await userProfileService.profile(userToken, userId); + if ( userProfile.success && + userProfile.data && + userProfile.data.response + ) { + projectCreation.data.userProfile = userProfile.data.response; + addReportInfoToSolution = true; + } else { + throw { + message: CONSTANTS.apiResponses.FAILED_TO_START_RESOURCE, + status: HTTP_STATUS_CODE["failed_dependency"].status, + }; + } + } - let currentTask = projectTasks[task]; + } else { + //Fetch user profile information by calling sunbird's user read api. - let data = { - type: currentTask.type, - status: currentTask.status, - _id: currentTask._id - }; + let userProfileData = await userProfileService.profile(userToken, userId); + if ( userProfileData.success && + userProfileData.data && + userProfileData.data.response + ) { + projectCreation.data.userProfile = userProfileData.data.response; + addReportInfoToSolution = true; + } else { + throw { + message: CONSTANTS.apiResponses.FAILED_TO_START_RESOURCE, + status: HTTP_STATUS_CODE["failed_dependency"].status, + }; + } + } + + projectCreation.data.userRoleInformation = userRoleInformation; + + //compare & update userProfile with userRoleInformation + if ( projectCreation.data.userProfile && userRoleInformation && Object.keys(userRoleInformation).length > 0 && Object.keys(projectCreation.data.userProfile).length > 0 ) { - if ( - currentTask.type === CONSTANTS.common.ASSESSMENT || - currentTask.type === CONSTANTS.common.OBSERVATION - ) { + let updatedUserProfile = await _updateUserProfileBasedOnUserRoleInfo( + projectCreation.data.userProfile, + userRoleInformation + ); - data["submissionDetails"] = - currentTask.submissionDetails ? currentTask.submissionDetails : {}; + if (updatedUserProfile && updatedUserProfile.success == true && updatedUserProfile.profileMismatchFound == true) { + projectCreation.data.userProfile = updatedUserProfile.data; + } + // checking the reqbody data and userLocation type matches or not + for (let key in userRoleInformation) { + // Skip role validation + if (key === 'role') { + continue; + } + let dataPresent; + if (key === 'school') { + dataPresent = projectCreation.data.userProfile.userLocations.find(location => location.code === userRoleInformation[key]); + } else { + dataPresent = projectCreation.data.userProfile.userLocations.find(location => location.id === userRoleInformation[key]); + } + if (!dataPresent) { + throw { + message: CONSTANTS.apiResponses.FAILED_TO_START_RESOURCE, + status: HTTP_STATUS_CODE["failed_dependency"].status, + }; + } + } + } else { + throw { + message: CONSTANTS.apiResponses.FAILED_TO_START_RESOURCE, + status: HTTP_STATUS_CODE["failed_dependency"].status, + }; + } + let project = await projectQueries.createProject(projectCreation.data); + + if ( addReportInfoToSolution && project.solutionId ) { + let updateSolution = await solutionsHelper.addReportInformationInSolution( + project.solutionId, + project.userProfile + ); } - result.push(data); + await kafkaProducersHelper.pushProjectToKafka(project); + + projectId = project._id; } - - return resolve({ - success: true, - message: CONSTANTS.apiResponses.TASKS_STATUS_FETCHED, - data: result - }); - - } catch (error) { - return reject({ - success: false, - message: error.message, - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - data: [] - }); } - }) - } - - /** - * Update task. - * @method - * @name updateTask - * @param {String} projectId - Project id. - * @param {String} taskId - Task id. - * @param {Object} updatedData - Update data. - * @returns {Object} - */ - - static updateTask(projectId, taskId, updatedData) { - return new Promise(async (resolve, reject) => { - try { - let update = {}; - - Object.keys(updatedData).forEach(taskData => { - update["tasks.$." + taskData] = updatedData[taskData]; - }); - - const tasksUpdated = - await database.models.projects.findOneAndUpdate({ - _id: projectId, - "tasks._id": taskId - }, { $set: update }); + let projectDetails = await this.details( + projectId, + userId, + userRoleInformation + ); + + let revertStatusorNot = UTILS.revertStatusorNot(appVersion); + if ( revertStatusorNot ) { + projectDetails.data.status = UTILS.revertProjectStatus(projectDetails.data.status); + } else { + projectDetails.data.status = UTILS.convertProjectStatus(projectDetails.data.status); + } + // make templateUrl downloadable befor passing to front-end + if ( projectDetails.data.certificate && + projectDetails.data.certificate.templateUrl && + projectDetails.data.certificate.templateUrl !== "" + ) { + let certificateTemplateDownloadableUrl = + await coreService.getDownloadableUrl( + { + filePaths: [projectDetails.data.certificate.templateUrl] + } + ); + if ( certificateTemplateDownloadableUrl.success ) { + projectDetails.data.certificate.templateUrl = certificateTemplateDownloadableUrl.data[0].url; + } + } - return resolve(tasksUpdated); + return resolve({ + success: true, + message: CONSTANTS.apiResponses.PROJECT_DETAILS_FETCHED, + data: projectDetails.data + }); - } catch (error) { - return reject(error); - } - }) - } + } catch (error) { + return resolve({ + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, + success: false, + message: error.message, + data: [] + }); + } + }) +} /** - * Solutions details - * @method - * @name solutionDetails - * @param {String} userToken - Logged in user token. - * @param {String} projectId - Project id. - * @param {Array} taskId - Tasks id. - * @returns {Object} - */ + * User assigned project creation data. + * @method + * @name userAssignedProjectCreation + * @param {String} templateId - Project template id. + * @param {String} userId - Logged in user id. + * @param {String} userToken - Logged in user token. + * @returns {String} - message. + */ - static solutionDetails(userToken, projectId, taskId) { + static userAssignedProjectCreation(templateId, userId, userToken) { return new Promise(async (resolve, reject) => { try { + const projectTemplateData = + await projectTemplateQueries.templateDocument({ + status: CONSTANTS.common.PUBLISHED, + _id: templateId, + isReusable: false + }, "all", + [ + "ratings", + "noOfRatings", + "averageRating" + ]); - let project = await this.projectDocument( - { - "_id": projectId, - "tasks._id": taskId - }, [ - "entityInformation._id", - "entityInformation.entityType", - "tasks.type", - "tasks._id", - "tasks.solutionDetails", - "tasks.submissionDetails", - "tasks.externalId", - "programInformation._id", - "projectTemplateId" - ] - ); - - if (!project.length > 0) { + if (!(projectTemplateData.length > 0)) { throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.USER_PROJECT_NOT_FOUND - }; + message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, + status: HTTP_STATUS_CODE['bad_request'].status + } } - let currentTask = project[0].tasks.find(task => task._id == taskId); + let result = { ...projectTemplateData[0] }; - let solutionDetails = currentTask.solutionDetails; + result.projectTemplateId = result._id; + result.projectTemplateExternalId = result.externalId; + result.userId = userId; + result.createdBy = userId; + result.updatedBy = userId; - let assessmentOrObservationData = { - entityId: project[0].entityInformation._id, - programId: project[0].programInformation._id - } + result.createdAt = new Date(); + result.updatedAt = new Date(); - if (currentTask.submissionDetails) { - assessmentOrObservationData = currentTask.submissionDetails; - } else { - - let assessmentOrObservation = { - token: userToken, - solutionDetails: solutionDetails, - entityId: assessmentOrObservationData.entityId, - programId: assessmentOrObservationData.programId, - project: { - "_id": projectId, - "taskId": taskId - } - }; + result.assesmentOrObservationTask = false; - let assignedAssessmentOrObservation = - solutionDetails.type === CONSTANTS.common.ASSESSMENT ? - await _assessmentDetails(assessmentOrObservation) : - await _observationDetails(assessmentOrObservation); + if (projectTemplateData[0].tasks && projectTemplateData[0].tasks.length > 0) { - if (!assignedAssessmentOrObservation.success) { - return resolve(assignedAssessmentOrObservation); - } + const tasksAndSubTasks = + await projectTemplatesHelper.tasksAndSubTasks( + projectTemplateData[0]._id + ); + + if (tasksAndSubTasks.length > 0) { + + result.tasks = _projectTask(tasksAndSubTasks); + result.tasks.forEach(task => { + if ( + task.type === CONSTANTS.common.ASSESSMENT || + task.type === CONSTANTS.common.OBSERVATION + ) { + result.assesmentOrObservationTask = true; + } + }); - assessmentOrObservationData = - _.merge(assessmentOrObservationData, assignedAssessmentOrObservation.data); - if (!currentTask.solutionDetails.isReusable) { - assessmentOrObservationData["programId"] = - currentTask.solutionDetails.programId; - } + let taskReport = { + total: result.tasks.length + }; - await database.models.projects.findOneAndUpdate({ - "_id": projectId, - "tasks._id": taskId - }, { - $set: { - "tasks.$.submissionDetails": assessmentOrObservationData - } - }); + result.tasks.forEach(task => { + if ( task.isDeleted == false ) { - } + if (!taskReport[task.status]) { + taskReport[task.status] = 1; + } else { + taskReport[task.status] += 1; + } - assessmentOrObservationData["entityType"] = project[0].entityInformation.entityType; + } else { + taskReport.total = taskReport.total - 1; + } + + }); - if(currentTask.solutionDetails && !(_.isEmpty(currentTask.solutionDetails))) { + result["taskReport"] = taskReport; - assessmentOrObservationData.solutionDetails = currentTask.solutionDetails; + } } + delete result._id; + return resolve({ success: true, - message: CONSTANTS.apiResponses.SOLUTION_DETAILS_FETCHED, - data: assessmentOrObservationData + message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, + data: result }); } catch (error) { return resolve({ status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, data: {} }); } - }) + }); } /** - * Bulk create user projects By entityId and role. - * @method - * @name bulkCreateByUserRoleAndEntity - Bulk create user projects by entity and role. - * @param {Object} userProjectData - user project data - * @param {String} userToken - logged in user token. - * @returns {Object} Bulk create user projects. + * Add project. + * @method + * @name add + * @param {Object} data - body data. + * @param {String} userId - Logged in user id. + * @param {String} userToken - User token. + * @param {String} [appName = ""] - App Name. + * @param {String} [appVersion = ""] - App Version. + * @returns {Object} Project created information. */ - static bulkCreateByUserRoleAndEntity(userProjectData, userToken) { + static add(data, userId, userToken, appName = "", appVersion = "") { return new Promise(async (resolve, reject) => { try { + const projectsModel = Object.keys(schemas["projects"].schema); + let createProject = {}; + createProject["userId"] = createProject["createdBy"] = createProject["updatedBy"] = userId; - let userAndEntityList = await kendraService.getUsersByEntityAndRole - ( - userProjectData.entityId, - userProjectData.role - ) - - if (!userAndEntityList.success || !userAndEntityList.data) { - throw { - message: CONSTANTS.apiResponses.USERS_AND_ENTITIES_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } + //Fetch user profile information by calling sunbird's user read api. - let userProjectBulkCreationData = []; + let userProfile = await userProfileService.profile(userToken, userId); + if ( userProfile.success && + userProfile.data && + userProfile.data.response + ) { + createProject.userProfile = userProfile.data.response; + } - await Promise.all(userAndEntityList.data.map(async user => { - userProjectBulkCreationData.push({ - "templateId": userProjectData.templateId, - "keycloak-userId": user.userId, - "entityId": user.entityId - }) - })); + - let userProjects = await this.bulkCreate - ( - userProjectBulkCreationData, - userToken - ) + let projectData = await _projectData(data); + if (projectData && projectData.success == true) { + createProject = _.merge(createProject, projectData.data); + } - return resolve(userProjects); + let createNewProgramAndSolution = false; - } catch (error) { - return reject(error); - } - }) - } + if (data.programId && data.programId !== "") { + createNewProgramAndSolution = false; + } + else if (data.programName) { + createNewProgramAndSolution = true; + } + + if (data.entityId) { + let entityInformation = + await _entitiesInformation([data.entityId]); - /** - * User assigned project creation data. - * @method - * @name userAssignedProjectCreation - * @param {String} templateId - Project template id. - * @param {String} userId - Logged in user id. - * @param {String} userToken - Logged in user token. - * @returns {String} - message. - */ + if (!entityInformation.success) { + return resolve(entityInformation); + } - static userAssignedProjectCreation(templateId, userId, userToken) { - return new Promise(async (resolve, reject) => { - try { + createProject["entityInformation"] = entityInformation.data[0]; + createProject.entityId = entityInformation.data[0]._id; + } + if (createNewProgramAndSolution) { - const projectTemplateData = - await projectTemplatesHelper.templateDocument({ - status: CONSTANTS.common.PUBLISHED, - _id: templateId, - isReusable: false - }, "all", - [ - "ratings", - "noOfRatings", - "averageRating" - ]); + let programAndSolutionInformation = + await this.createProgramAndSolution( + data.programId, + data.programName, + createProject.entityId ? [createProject.entityId] : "", + userToken + ); - if (!projectTemplateData.length > 0) { - throw { - message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status + if (!programAndSolutionInformation.success) { + return resolve(programAndSolutionInformation); } + createProject = + _.merge(createProject, programAndSolutionInformation.data); + } + + if (data.programId && data.programId !== "") { + + let queryData = {}; + queryData["_id"] = data.programId; + let programDetails = await programsQueries.programsDocument(queryData, + [ + "_id", + "name", + "description", + "isAPrivateProgram" + ] + ); + if( !(programDetails.length > 0) ){ + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.PROGRAM_NOT_FOUND + }; + } + let programInformationData = {}; + programInformationData["programInformation"] = programDetails[0]; + createProject = + _.merge(createProject, programInformationData); } + - let result = { ...projectTemplateData[0] }; + if (data.tasks) { - result.projectTemplateId = result._id; - result.projectTemplateExternalId = result.externalId; - result.userId = userId; - result.createdBy = userId; - result.updatedBy = userId; + let taskReport = {}; - let userOrganisations = - await kendraService.getUserOrganisationsAndRootOrganisations( - userToken, - userId + createProject.tasks = await _projectTask( + data.tasks ); - if (!userOrganisations.success) { - throw { - message: CONSTANTS.apiResponses.USER_ORGANISATION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } + taskReport.total = createProject.tasks.length; - result.createdFor = - userOrganisations.data.createdFor; + createProject.tasks.forEach(task => { + if ( task.isDeleted == false ) { + if (!taskReport[task.status]) { + taskReport[task.status] = 1; + } else { + taskReport[task.status] += 1; + } + } else { + //if task is deleted it is not counted in total. + taskReport.total = taskReport.total - 1; + } + + }); - result.rootOrganisations = - userOrganisations.data.rootOrganisations; + createProject["taskReport"] = taskReport; + } - result.assesmentOrObservationTask = false; + let booleanData = this.booleanData(schemas["projects"].schema); + let mongooseIdData = this.mongooseIdData(schemas["projects"].schema); - if (projectTemplateData[0].tasks && projectTemplateData[0].tasks.length > 0) { - const tasksAndSubTasks = - await projectTemplatesHelper.tasksAndSubTasks( - projectTemplateData[0]._id - ); + Object.keys(data).forEach(updateData => { + if ( + !createProject[updateData] && + projectsModel.includes(updateData) + ) { - if (tasksAndSubTasks.length > 0) { + if (booleanData.includes(updateData)) { - result.tasks = _projectTask(tasksAndSubTasks); + createProject[updateData] = + UTILS.convertStringToBoolean(data[updateData]); - result.tasks.forEach(task => { - if ( - task.type === CONSTANTS.common.ASSESSMENT || - task.type === CONSTANTS.common.OBSERVATION - ) { - result.assesmentOrObservationTask = true; - } - }); + } else if (mongooseIdData.includes(updateData)) { + createProject[updateData] = ObjectId(data[updateData]); + } else { + createProject[updateData] = data[updateData]; + } + } + }); + createProject["appInformation"] = {}; + if (appName !== "") { + createProject["appInformation"]["appName"] = appName; + } - let taskReport = { - total: result.tasks.length - }; + if (appVersion !== "") { + createProject["appInformation"]["appVersion"] = appVersion; + } - result.tasks.forEach(task => { - if (!taskReport[task.status]) { - taskReport[task.status] = 1; - } else { - taskReport[task.status] += 1; - } - }); + createProject["lastDownloadedAt"] = new Date(); - result["taskReport"] = taskReport; + if (data.profileInformation) { + createProject.userRoleInformation = data.profileInformation; + } + + createProject.status = UTILS.convertProjectStatus(data.status); + let userProject = await projectQueries.createProject( + createProject + ); + + await kafkaProducersHelper.pushProjectToKafka(userProject); + if (!userProject._id) { + throw { + message: CONSTANTS.apiResponses.USER_PROJECT_NOT_CREATED, + status: HTTP_STATUS_CODE['bad_request'].status } } - delete result._id; - return resolve({ success: true, - message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, - data: result + message: CONSTANTS.apiResponses.PROJECT_CREATED, + data: { + programId: + userProject.programInformation && userProject.programInformation._id ? + userProject.programInformation._id : data.programId, + projectId: userProject._id, + lastDownloadedAt: userProject.lastDownloadedAt, + hasAcceptedTAndC : userProject.hasAcceptedTAndC ? userProject.hasAcceptedTAndC : false + } }); - } catch (error) { return resolve({ status: @@ -1834,810 +2031,1045 @@ module.exports = class UserProjectsHelper { data: {} }); } - }); + }) } - /** -* Get list of user projects with the targetted ones. -* @method -* @name getProject -* @param {String} userId - Logged in user id. -* @param {String} userToken - Logged in user token. -* @returns {Object} -*/ - static getProject(bodyData, userId, userToken, pageSize, pageNo, search, filter) { + /** + * share project and task pdf report. + * @method + * @name share + * @param {String} [projectId] - projectId. + * @returns {Object} Downloadable pdf url. + */ + + static share(projectId = "", taskIds = [], userToken,appVersion) { return new Promise(async (resolve, reject) => { try { + let projectPdf = true; + let projectDocument = []; + let query = { - userId: userId, + _id: projectId, isDeleted: false } - let searchQuery = []; + if (!taskIds.length ) { - if (search !== "") { - searchQuery = [ - { "title": new RegExp(search, 'i') }, - { "description": new RegExp(search, 'i') } - ]; + projectDocument = await projectQueries.projectDocument + ( + query, + [ + "title", + "status", + "metaInformation.goal", + "metaInformation.duration", + "startDate", + "description", + "endDate", + "tasks", + "categories", + "programInformation.name", + "recommendedFor", + "link", + "remarks", + "attachments", + "taskReport.completed" + ] + ); } - - bodyData["filter"] = {}; - - if (filter && filter !== "") { - if (filter === CONSTANTS.common.CREATED_BY_ME) { - query["isAPrivateProgram"] = bodyData["filter"]["isAPrivateProgram"] = { - "$ne": false - }; - } else if (filter == CONSTANTS.common.ASSIGN_TO_ME) { - query["isAPrivateProgram"] = bodyData["filter"]["isAPrivateProgram"] = false; + else { + projectPdf = false; + + let aggregateData = [ + { "$match": { _id: ObjectId(projectId), isDeleted: false} }, + { "$project": { + "status": 1, "title": 1, "startDate": 1, "metaInformation.goal": 1, "metaInformation.duration":1, + "categories" : 1, "programInformation.name": 1, "description" : 1, "recommendedFor" : 1, "link" : 1, "remarks" : 1, "attachments" : 1, "taskReport.completed" : 1, + tasks: { "$filter": { + input: '$tasks', + as: 'tasks', + cond: { "$in": ['$$tasks._id', taskIds]} + }} + }}] + + projectDocument = await projectQueries.getAggregate(aggregateData); + } + + if (!projectDocument.length) { + throw { + message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND, + status: HTTP_STATUS_CODE['bad_request'].status } } - let projects = await this.projects( - query, - CONSTANTS.common.DEFAULT_PAGE_SIZE, - CONSTANTS.common.DEFAULT_PAGE_NO, - searchQuery, - [ - "title", - "description", - "solutionId", - "programId", - "programInformation.name", - "projectTemplateId", - "solutionExternalId", - "lastDownloadedAt", - "hasAcceptedTAndC" - ] - ); - - let solutionIds = []; - - let totalCount = 0; - let mergedData = []; - - if (projects.success && projects.data) { - - totalCount = projects.data.count; - mergedData = projects.data.data; - - if (mergedData.length > 0) { - mergedData.forEach(projectData => { - projectData.name = projectData.title; + projectDocument = projectDocument[0]; + projectDocument.goal = projectDocument.metaInformation ? projectDocument.metaInformation.goal : ""; + projectDocument.duration = projectDocument.metaInformation ? projectDocument.metaInformation.duration : ""; + projectDocument.programName = projectDocument.programInformation ? projectDocument.programInformation.name : ""; + projectDocument.remarks = projectDocument.remarks ? projectDocument.remarks : ""; + projectDocument.taskcompleted = projectDocument.taskReport.completed ? projectDocument.taskReport.completed : CONSTANTS.common.DEFAULT_TASK_COMPLETED; + + //store tasks and attachment data into object + let projectFilter = { + tasks : projectDocument.tasks, + attachments : projectDocument.attachments + } + + //returns project tasks and attachments with downloadable urls + let projectDataWithUrl = await _projectInformation( projectFilter ); + + //replace projectDocument Data + if ( projectDataWithUrl.success && + projectDataWithUrl.data && + projectDataWithUrl.data.tasks && + projectDataWithUrl.data.tasks.length > 0 + ) { + + projectDocument.tasks = projectDataWithUrl.data.tasks ; + } - if (projectData.programInformation) { - projectData.programName = projectData.programInformation.name; - delete projectData.programInformation; - } + if ( projectDataWithUrl.success && + projectDataWithUrl.data && + projectDataWithUrl.data.attachments && + projectDataWithUrl.data.attachments.length > 0 + ) { + projectDocument.attachments = projectDataWithUrl.data.attachments ; + } + - if (projectData.solutionExternalId) { - projectData.externalId = projectData.solutionExternalId; - delete projectData.solutionExternalId; + //get image link and other document links + let imageLink = []; + let evidenceLink = []; + if ( projectDocument.attachments && projectDocument.attachments.length > 0 ) { + projectDocument.attachments.forEach( attachment => { + if( attachment.type == CONSTANTS.common.IMAGE_DATA_TYPE && attachment.url && attachment.url !== "" ) { + imageLink.push( attachment.url ); + } else if ( attachment.type == CONSTANTS.common.ATTACHMENT_TYPE_LINK && attachment.name && attachment.name !== "" ) { + let data = { + type : attachment.type, + url : attachment.name } - - projectData.type = CONSTANTS.common.IMPROVEMENT_PROJECT; - - if (projectData.solutionId) { - solutionIds.push(projectData.solutionId); + evidenceLink.push( data ); + } else if ( attachment.url && attachment.url !== "" ) { + let data = { + type : attachment.type, + url : attachment.url } - - delete projectData.title; - }); - } + evidenceLink.push( data ); + } + }) } + projectDocument.evidenceLink = evidenceLink; + projectDocument.imageLink = imageLink; + + projectDocument.category = []; - if (solutionIds.length > 0) { - bodyData["filter"]["skipSolutions"] = solutionIds; + if (projectDocument.categories && projectDocument.categories.length > 0) { + projectDocument.categories.forEach( category => { + projectDocument.category.push(category.name); + }) } - bodyData.filter["projectTemplateId"] = { - $exists: true - }; - - let targetedSolutions = - await kendraService.solutionBasedOnRoleAndLocation( - userToken, - bodyData, - CONSTANTS.common.IMPROVEMENT_PROJECT, - search - ); + projectDocument.recommendedForRoles = []; - if (targetedSolutions.success) { - - if (targetedSolutions.data.data && targetedSolutions.data.data.length > 0) { - totalCount += targetedSolutions.data.count; + if (projectDocument.recommendedFor && projectDocument.recommendedFor.length > 0) { + projectDocument.recommendedFor.forEach( recommend => { + projectDocument.recommendedForRoles.push(recommend.code); + }) + } + + let tasks = []; + if (projectDocument.tasks.length > 0) { + projectDocument.tasks.forEach( task => { + let subtasks = []; + if (!task.isDeleted) { + if (task.children.length > 0) { + task.children.forEach(children => { + if (!children.isDeleted) { + subtasks.push(children); + } + }) + } + task.children = subtasks; + tasks.push(task); + } + }) + projectDocument.tasks = tasks; + } - if (mergedData.length !== pageSize) { + delete projectDocument.categories; + delete projectDocument.metaInformation; + delete projectDocument.programInformation; + delete projectDocument.recommendedFor; + + if (UTILS.revertStatusorNot(appVersion)) { + projectDocument.status = UTILS.revertProjectStatus(projectDocument.status); + } + let response = await reportService.projectAndTaskReport(userToken, projectDocument, projectPdf); - targetedSolutions.data.data.forEach(targetedSolution => { - targetedSolution.solutionId = targetedSolution._id; - targetedSolution._id = ""; - mergedData.push(targetedSolution); - }) + if (response && response.success == true) { + return resolve({ + success: true, + message: CONSTANTS.apiResponses.REPORT_GENERATED_SUCCESSFULLY, + data: { + data: { + downloadUrl: response.data.pdfUrl + } } - } + }); } - if (mergedData.length > 0) { - let startIndex = pageSize * (pageNo - 1); - let endIndex = startIndex + pageSize; - mergedData = mergedData.slice(startIndex, endIndex); + else { + throw { + message: CONSTANTS.apiResponses.COULD_NOT_GENERATE_PDF_REPORT, + } } - return resolve({ - success: true, - message: CONSTANTS.apiResponses.TARGETED_PROJECT_FETCHED, - data: { - description: CONSTANTS.common.PROJECT_DESCRIPTION, - data: mergedData, - count: totalCount - } - }); } catch (error) { return resolve({ - success : false, - message : error.message, - status : - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - data : { - description : CONSTANTS.common.PROJECT_DESCRIPTION, - data : [], - count : 0 - } + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, + success: false, + message: error.message, + data: {} }); } }) - } + } - /** - * Creation of user targeted projects. - * @method - * @name detailsV2 - * @param {String} projectId - project id. - * @param {String} solutionId - solution id. - * @param {String} userId - logged in user id. - * @param {String} userToken - logged in user token. - * @param {Object} bodyData - Requested body data. - * @param {String} [appName = ""] - App name. - * @param {String} [appVersion = ""] - App version. - * @returns {Object} Project details. - */ + /** + * Get list of user projects with the targetted ones. + * @method + * @name userAssigned + * @param {String} userId - Logged in user id. + * @param {Number} pageSize - Page size. + * @param {Number} pageNo - Page No. + * @param {String} search - Search text. + * @param {String} filter - filter text. + * @returns {Object} + */ - static detailsV2( projectId,solutionId,userId,userToken,bodyData,appName = "",appVersion = "",templateId = "" ) { + static userAssigned( userId,pageSize,pageNo,search, filter ) { return new Promise(async (resolve, reject) => { try { - - let solutionExternalId = ""; - - if( templateId !== "" ) { - - let templateDocuments = - await projectTemplatesHelper.templateDocument({ - "externalId" : templateId, - "isReusable" : false, - "solutionId" : { $exists : true } - },["solutionId","solutionExternalId"]); - - if( !templateDocuments.length > 0 ) { - throw { - message : CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } + + let query = { + userId : userId, + isDeleted : false + } + + let searchQuery = []; + + if (search !== "") { + searchQuery = [ + { "title" : new RegExp(search, 'i') }, + { "description" : new RegExp(search, 'i') } + ]; + } + + if ( filter && filter !== "" ) { + if( filter === CONSTANTS.common.CREATED_BY_ME ) { + query["referenceFrom"] = { + $ne : CONSTANTS.common.LINK + }; + query["isAPrivateProgram"] = { + $ne : false + }; + } else if( filter == CONSTANTS.common.ASSIGN_TO_ME ) { + query["isAPrivateProgram"] = false; + } else{ + query["referenceFrom"] = CONSTANTS.common.LINK; } - - - solutionId = templateDocuments[0].solutionId; - solutionExternalId = templateDocuments[0].solutionExternalId; } + + let projects = await this.projects( + query, + pageSize, + pageNo, + searchQuery, + [ + "title", + "description", + "solutionId", + "programId", + "programInformation.name", + "projectTemplateId", + "solutionExternalId", + "lastDownloadedAt", + "hasAcceptedTAndC", + "referenceFrom", + "status", + "certificate" + ] + ); + + let totalCount = 0; + let data = []; + if( projects.success && projects.data && projects.data.data && Object.keys(projects.data.data).length > 0 ) { - let userRoleInformation = _.omit(bodyData,["referenceFrom","submissions","hasAcceptedTAndC"]); - - if (projectId === "") { - - const projectDetails = await this.projectDocument({ - solutionId: solutionId, - userId: userId - }, ["_id"]); - - if( projectDetails.length > 0 ) { - projectId = projectDetails[0]._id; - } else { + totalCount = projects.data.count; + data = projects.data.data; - let solutionDetails = {} + if( data.length > 0 ) { + let templateFilePath = []; + data.forEach( projectData => { + + projectData.name = projectData.title; - if( templateId === "" ) { - - solutionDetails = - await kendraService.solutionDetailsBasedOnRoleAndLocation( - userToken, - bodyData, - solutionId - ); - if( !solutionDetails.success || (solutionDetails.data.data && !solutionDetails.data.data.length > 0) ) { - throw { - status : HTTP_STATUS_CODE["bad_request"].status, - message : CONSTANTS.apiResponses.SOLUTION_DOES_NOT_EXISTS_IN_SCOPE - } + if (projectData.programInformation) { + projectData.programName = projectData.programInformation.name; + delete projectData.programInformation; } - solutionDetails = solutionDetails.data; + if (projectData.solutionExternalId) { + projectData.externalId = projectData.solutionExternalId; + delete projectData.solutionExternalId; + } - } else { - solutionDetails = - await assessmentService.listSolutions([solutionExternalId]); + projectData.type = CONSTANTS.common.IMPROVEMENT_PROJECT; + delete projectData.title; - if( !solutionDetails.success ) { - throw { - message : CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, - status : HTTP_STATUS_CODE['bad_request'].status - } + if (projectData.certificate && + projectData.certificate.osid && + projectData.certificate.osid !== "" && + projectData.certificate.templateUrl && + projectData.certificate.templateUrl !== "" + ) { + templateFilePath.push(projectData.certificate.templateUrl); } - solutionDetails = solutionDetails.data[0]; - } - let projectCreation = - await this.userAssignedProjectCreation( - solutionDetails.projectTemplateId, - userId, - userToken - ); - - if( !projectCreation.success ) { - return resolve(projectCreation); - } - - projectCreation.data["isAPrivateProgram"] = - solutionDetails.isAPrivateProgram; - - projectCreation.data.programInformation = { - _id : ObjectId(solutionDetails.programId), - externalId : solutionDetails.programExternalId, - description : - solutionDetails.programDescription ? solutionDetails.programDescription : "", - name : solutionDetails.programName - } - - projectCreation.data.solutionInformation = { - _id : ObjectId(solutionDetails._id), - externalId : solutionDetails.externalId, - description : - solutionDetails.description ? - solutionDetails.description : "", - name : solutionDetails.name - }; - - projectCreation.data["programId"] = - projectCreation.data.programInformation._id; - - projectCreation.data["programExternalId"] = - projectCreation.data.programInformation.externalId; - - projectCreation.data["solutionId"] = - projectCreation.data.solutionInformation._id; - - projectCreation.data["solutionExternalId"] = - projectCreation.data.solutionInformation.externalId; - - projectCreation.data["userRole"] = - bodyData.role; - - projectCreation.data["appInformation"] = {}; - - if( appName !== "" ) { - projectCreation.data["appInformation"]["appName"] = appName; - } - - if( appVersion !== "" ) { - projectCreation.data["appInformation"]["appVersion"] = appVersion; - } - - if( bodyData && Object.keys(bodyData).length > 0 ) { - - if( bodyData.hasAcceptedTAndC ) { - projectCreation.data.hasAcceptedTAndC = bodyData.hasAcceptedTAndC; - } + }); - if( bodyData.referenceFrom ) { - projectCreation.data.referenceFrom = bodyData.referenceFrom; - - if( bodyData.submissions ) { - projectCreation.data.submissions = bodyData.submissions; - } - } - - if( bodyData.role ) { - projectCreation.data["userRole"] = bodyData.role; + if( templateFilePath.length > 0 ) { + + let certificateTemplateDownloadableUrl = + await coreService.getDownloadableUrl( + { + filePaths: templateFilePath + } + ); + if ( !certificateTemplateDownloadableUrl.success ) { + throw { + message: CONSTANTS.apiResponses.DOWNLOADABLE_URL_NOT_FOUND + }; } - - if( - solutionDetails.entityType && bodyData[solutionDetails.entityType] - ) { - let entityInformation = - await assessmentService.listEntitiesByLocationIds( - userToken, - [bodyData[solutionDetails.entityType]] - ); - - if( !entityInformation.success ) { - return resolve(entityInformation); + // map downloadable templateUrl to corresponding project data + data.forEach(projectData => { + if (projectData.certificate) { + var itemFromUrlArray = certificateTemplateDownloadableUrl.data.find(item=> item.filePath == projectData.certificate.templateUrl); + if (itemFromUrlArray) { + projectData.certificate.templateUrl = itemFromUrlArray.url; + } + } } - - projectCreation.data["entityInformation"] = _entitiesMetaInformation( - entityInformation.data - )[0]; - - projectCreation.data.entityId = entityInformation.data[0]._id; - } - + + ) } - - projectCreation.data.status = CONSTANTS.common.NOT_STARTED_STATUS; - projectCreation.data.lastDownloadedAt = new Date(); - projectCreation.data.userRoleInformtion = userRoleInformation; - - let project = await database.models.projects.create(projectCreation.data); - projectId = project._id; + } } + + return resolve({ + success : true, + message : CONSTANTS.apiResponses.USER_ASSIGNED_PROJECT_FETCHED, + data : { + data: data, + count: totalCount + } + }); - let projectDetails = await this.details( - projectId, - userId, - userRoleInformation + } catch (error) { + return resolve({ + success : false, + message : error.message, + status : + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status, + data : { + description : CONSTANTS.common.PROJECT_DESCRIPTION, + data : [], + count : 0 + } + }); + } + }) + } + + /** + * List of user imported projects. + * @method + * @name importedProjects + * @param {String} userId - Logged in user id. + * @param {String} programId - program id. + * @returns {Object} + */ + + static importedProjects( userId,programId ) { + return new Promise(async (resolve, reject) => { + try { + + let filterQuery = { + userId : userId, + // referenceFrom : { $exists : true,$eq : CONSTANTS.common.OBSERVATION_REFERENCE_KEY }, - Commented as this filter is not useful. - 4.7 Sprint - 25Feb2022 + isDeleted: false + }; + + if( programId !== "" ) { + filterQuery["programId"] = programId; + } + + let importedProjects = await projectQueries.projectDocument( + filterQuery, + [ + "solutionInformation", + "programInformation", + "title", + "description", + "projectTemplateId", + "certificate.templateId" + ] ); return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECT_DETAILS_FETCHED, - data: projectDetails.data + success : true, + message : CONSTANTS.apiResponses.IMPORTED_PROJECTS_FETCHED, + data : importedProjects }); } catch (error) { return resolve({ - status: - error.status ? + success : false, + message : error.message, + status : + error.status ? error.status : HTTP_STATUS_CODE['internal_server_error'].status, - success: false, - message: error.message, - data: [] + data : { + description : CONSTANTS.common.PROJECT_DESCRIPTION, + data : [], + count : 0 + } }); } }) -} + } - /** - * User assigned project creation data. + /** + * List of projects. * @method - * @name userAssignedProjectCreation - * @param {String} templateId - Project template id. - * @param {String} userId - Logged in user id. - * @param {String} userToken - Logged in user token. - * @returns {String} - message. + * @name list + * @returns {Array} List of projects. */ + + static list( bodyData ) { + return new Promise(async (resolve, reject) => { + try { + let projects = await projectQueries.projectDocument( + bodyData.query, + bodyData.projection, + bodyData.skipFields + ); + + return resolve({ + success : true, + message : CONSTANTS.apiResponses.PROJECTS_FETCHED, + result : projects + }); + + } catch (error) { + return reject(error); + } + }); + } - static userAssignedProjectCreation(templateId, userId, userToken) { + /** + * Create project from template. + * @method + * @name importFromLibrary + * @param {String} projectTemplateId - project template id. + * @param {Object} requestedData - body data. + * @param {String} userId - Logged in user id. + * @param {String} userToken - User token. + * @param {Boolean} isATargetedSolution - User targeted or not . + * @returns {Object} Project created information. + */ + + static importFromLibrary(projectTemplateId, requestedData, userToken, userId, isATargetedSolution = "" ) { return new Promise(async (resolve, reject) => { try { + isATargetedSolution = UTILS.convertStringToBoolean(isATargetedSolution); - const projectTemplateData = - await projectTemplatesHelper.templateDocument({ - status: CONSTANTS.common.PUBLISHED, - _id: templateId, - isReusable: false - }, "all", - [ - "ratings", - "noOfRatings", - "averageRating" - ]); - - if (!projectTemplateData.length > 0) { + let libraryProjects = + await libraryCategoriesHelper.projectDetails( + projectTemplateId, + "", + isATargetedSolution + ); + + if ( + libraryProjects.data && + !(Object.keys(libraryProjects.data).length > 0) + ) { throw { - message: CONSTANTS.apiResponses.SOLUTION_NOT_FOUND, + message: CONSTANTS.apiResponses.PROJECT_TEMPLATE_NOT_FOUND, status: HTTP_STATUS_CODE['bad_request'].status - } + }; + } + + let taskReport = {}; - let result = { ...projectTemplateData[0] }; + if ( + libraryProjects.data.tasks && + libraryProjects.data.tasks.length > 0 + ) { - result.projectTemplateId = result._id; - result.projectTemplateExternalId = result.externalId; - result.userId = userId; - result.createdBy = userId; - result.updatedBy = userId; + libraryProjects.data.tasks = await _projectTask( + libraryProjects.data.tasks, + isATargetedSolution === false ? false : true + ); - let userOrganisations = - await kendraService.getUserOrganisationsAndRootOrganisations( - userToken, - userId - ); + taskReport.total = libraryProjects.data.tasks.length; - if (!userOrganisations.success) { - throw { - message: CONSTANTS.apiResponses.USER_ORGANISATION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } + libraryProjects.data.tasks.forEach(task => { + if ( task.isDeleted == false ) { + if (!taskReport[task.status]) { + taskReport[task.status] = 1; + } else { + taskReport[task.status] += 1; + } + } else { + //reduce total count if task is deleted. + taskReport.total = taskReport.total - 1; + } + + }); - result.createdFor = - userOrganisations.data.createdFor; + libraryProjects.data["taskReport"] = taskReport; + } - result.rootOrganisations = - userOrganisations.data.rootOrganisations; + if (requestedData.entityId && requestedData.entityId !== "") { - result.createdAt = new Date(); - result.updatedAt = new Date(); + let entityInformation = + await _entitiesInformation([requestedData.entityId]); - result.assesmentOrObservationTask = false; + if (!entityInformation.success) { + return resolve(entityInformation); + } - if (projectTemplateData[0].tasks && projectTemplateData[0].tasks.length > 0) { + libraryProjects.data["entityInformation"] = entityInformation.data[0]; + libraryProjects.data.entityId = entityInformation.data[0]._id; + } - const tasksAndSubTasks = - await projectTemplatesHelper.tasksAndSubTasks( - projectTemplateData[0]._id + if( requestedData.solutionId && requestedData.solutionId !== "" && isATargetedSolution === false ){ + let programAndSolutionInformation = {}; + // Check if solutionId passed is private or not, if private and data is present, create program and solution information. + let solutionDetails = await solutionsHelper.solutionDocuments({ + _id: requestedData.solutionId, + isAPrivateProgram: true + }, + [ + "_id", + "name", + "externalId", + "description", + "programId", + "programName", + "programDescription", + "programExternalId", + "isAPrivateProgram", + "projectTemplateId", + "entityType", + "certificateTemplateId", + "parentSolutionId" + ]); + // private solution exists + if ( solutionDetails.length > 0 && solutionDetails[0].parentSolutionId ) { + // This function will return programAndSolutionInformation + /** + * function privateProgramAndSolutionDetails + * Request: + * @param {solutionDetails} solution data + * @response Program and solution details + */ + programAndSolutionInformation = await this.getProgramAndSolutionDetails(solutionDetails[0]); + } else { + programAndSolutionInformation = + await this.createProgramAndSolution( + requestedData.programId, + requestedData.programName, + requestedData.entityId ? [requestedData.entityId] : "", + userToken, + requestedData.solutionId, + isATargetedSolution ); + } + + if (!programAndSolutionInformation.success) { + return resolve(programAndSolutionInformation); + } - if (tasksAndSubTasks.length > 0) { + libraryProjects.data = _.merge( + libraryProjects.data, + programAndSolutionInformation.data + ) - result.tasks = _projectTask(tasksAndSubTasks); + libraryProjects.data["referenceFrom"] = CONSTANTS.common.LINK; + } + else if ( + (requestedData.programId && requestedData.programId !== "") || + (requestedData.programName && requestedData.programName !== "" ) + ) { - result.tasks.forEach(task => { - if ( - task.type === CONSTANTS.common.ASSESSMENT || - task.type === CONSTANTS.common.OBSERVATION - ) { - result.assesmentOrObservationTask = true; - } - }); + let programAndSolutionInformation = + await this.createProgramAndSolution( + requestedData.programId, + requestedData.programName, + requestedData.entityId ? [requestedData.entityId] : "", + userToken + ); + + if (!programAndSolutionInformation.success) { + return resolve(programAndSolutionInformation); + } + if ( + libraryProjects.data["entityInformation"] && + libraryProjects.data["entityInformation"].entityType !== + programAndSolutionInformation.data.solutionInformation.entityType + ) { + throw { + message: CONSTANTS.apiResponses.ENTITY_TYPE_MIS_MATCHED, + status: HTTP_STATUS_CODE['bad_request'].status + } + } - let taskReport = { - total: result.tasks.length - }; + libraryProjects.data = _.merge( + libraryProjects.data, + programAndSolutionInformation.data + ) + } + // <- Add certificate template data + if ( + libraryProjects.data.certificateTemplateId && + libraryProjects.data.certificateTemplateId !== "" + ){ + // <- Add certificate template details to projectCreation data if present -> + const certificateTemplateDetails = await certificateTemplateQueries.certificateTemplateDocument({ + _id : libraryProjects.data.certificateTemplateId + }); + + // create certificate object and add data if certificate template is present. + if ( certificateTemplateDetails.length > 0 ) { + libraryProjects.data["certificate"] = _.pick(certificateTemplateDetails[0], ['templateUrl', 'status', 'criteria']); + } + libraryProjects.data["certificate"]["templateId"] = libraryProjects.data.certificateTemplateId; + delete libraryProjects.data.certificateTemplateId; + } + + //Fetch user profile information by calling sunbird's user read api. + let addReportInfoToSolution = false; + let userProfile = await userProfileService.profile(userToken, userId); + if ( userProfile.success && + userProfile.data && + userProfile.data.response + ) { + libraryProjects.data.userProfile = userProfile.data.response; + addReportInfoToSolution = true; + } + + libraryProjects.data.userId = libraryProjects.data.updatedBy = libraryProjects.data.createdBy = userId; + libraryProjects.data.lastDownloadedAt = new Date(); + libraryProjects.data.status = CONSTANTS.common.STARTED; - result.tasks.forEach(task => { - if (!taskReport[task.status]) { - taskReport[task.status] = 1; - } else { - taskReport[task.status] += 1; - } - }); + if (requestedData.startDate) { + libraryProjects.data.startDate = requestedData.startDate; + } - result["taskReport"] = taskReport; + if (requestedData.endDate) { + libraryProjects.data.endDate = requestedData.endDate; + } - } + if (requestedData.hasAcceptedTAndC) { + libraryProjects.data.hasAcceptedTAndC = true; } - delete result._id; + libraryProjects.data.projectTemplateId = libraryProjects.data._id; + libraryProjects.data.projectTemplateExternalId = libraryProjects.data.externalId; + + let projectCreation = await database.models.projects.create( + _.omit(libraryProjects.data, ["_id"]) + ); + + if ( addReportInfoToSolution && projectCreation._doc.solutionId ) { + + let updateSolution = await solutionsHelper.addReportInformationInSolution( + projectCreation._doc.solutionId, + projectCreation._doc.userProfile + ); + } + + await kafkaProducersHelper.pushProjectToKafka(projectCreation); + if (requestedData.rating && requestedData.rating > 0) { + await projectTemplatesHelper.ratings( + projectTemplateId, + requestedData.rating, + userToken + ); + } + + projectCreation = await _projectInformation(projectCreation._doc); + return resolve({ success: true, - message: CONSTANTS.apiResponses.UPDATED_DOCUMENT_SUCCESSFULLY, - data: result + message: CONSTANTS.apiResponses.PROJECTS_FETCHED, + data: projectCreation.data }); } catch (error) { return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, data: {} }); } - }); + }) } /** - * Add project. - * @method - * @name add - * @param {Object} data - body data. - * @param {String} userId - Logged in user id. - * @param {String} userToken - User token. - * @param {String} [appName = ""] - App Name. - * @param {String} [appVersion = ""] - App Version. - * @returns {Object} Project created information. - */ - - static add(data, userId, userToken, appName = "", appVersion = "") { - return new Promise(async (resolve, reject) => { - try { - - const projectsModel = Object.keys(schemas["projects"].schema); - let createProject = {}; - - createProject["userId"] = createProject["createdBy"] = createProject["updatedBy"] = userId; - - let userOrganisations = - await kendraService.getUserOrganisationsAndRootOrganisations( - userToken - ); - - if (!userOrganisations.success) { - throw { - message: CONSTANTS.apiResponses.USER_ORGANISATION_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status - } - } - - if (userOrganisations.data) { - createProject.createdFor = userOrganisations.data.createdFor; - createProject.rootOrganisations = userOrganisations.data.rootOrganisations; - } - - let projectData = await _projectData(data); - if (projectData && projectData.success == true) { - createProject = _.merge(createProject, projectData.data); - } - - let createNewProgramAndSolution = false; - - if (data.programId && data.programId !== "") { - createNewProgramAndSolution = true; - } - else if (data.programName) { - createNewProgramAndSolution = true; - } - - if (data.entityId) { - let entityInformation = - await _entitiesInformation([data.entityId]); - - if (!entityInformation.success) { - return resolve(entityInformation); - } + * get project details. + * @method + * @name userProject + * @param {String} projectId - project id. + * @returns {Object} Project details. + */ - createProject["entityInformation"] = entityInformation.data[0]; - createProject.entityId = entityInformation.data[0]._id; - } + static userProject(projectId) { + return new Promise(async (resolve, reject) => { + try { - if (createNewProgramAndSolution) { + const projectDetails = await projectQueries.projectDocument({ + _id: projectId, + }, "all"); - let programAndSolutionInformation = - await this.createProgramAndSolution( - data.programId, - data.programName, - createProject.entityId ? [createProject.entityId] : "", - userToken - ); + if (!(projectDetails.length > 0)) { - if (!programAndSolutionInformation.success) { - return resolve(programAndSolutionInformation); + throw { + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND } - - createProject = - _.merge(createProject, programAndSolutionInformation.data); } + + return resolve( { + success: true, + message: CONSTANTS.apiResponses.PROJECT_DETAILS_FETCHED, + data: projectDetails[0] + }); - if (data.tasks) { - - let taskReport = {}; - - createProject.tasks = await _projectTask( - data.tasks - ); + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: {} + }); + } + }) + } - taskReport.total = createProject.tasks.length; + /** + * generate project certificate. + * @method + * @name generateCertificate + * @param {Object} data - project data for certificate creation data. + * @returns {JSON} certificate details. + */ - createProject.tasks.forEach(task => { - if (!taskReport[task.status]) { - taskReport[task.status] = 1; - } else { - taskReport[task.status] += 1; - } - }); + static generateCertificate(data) { + return new Promise(async (resolve, reject) => { + try { - createProject["taskReport"] = taskReport; + // check eligibility of project for certificate creation + let eligibility = await this.checkCertificateEligibility(data); + if (!eligibility ){ + throw { + message: CONSTANTS.apiResponses.NOT_ELIGIBLE_FOR_CERTIFICATE + }; } - let booleanData = this.booleanData(schemas["projects"].schema); - let mongooseIdData = this.mongooseIdData(schemas["projects"].schema); + // create payload for certificate generation + const certificateData = await this.createCertificatePayload(data); + + // call sunbird-RC to create certificate for project + const certificate = await this.createCertificate(certificateData, data._id) + + return resolve(certificate); + } catch (error) { + return resolve({ + success: false, + message: error.message + }); + } + }) + } - Object.keys(data).forEach(updateData => { - if ( - !createProject[updateData] && - projectsModel.includes(updateData) - ) { + /** + * check project eligibility for certificate. + * @method + * @name checkCertificateEligibility + * @param {Object} data - project data for certificate creation data. + * @returns {Boolean} certificate eligibilty status. + */ - if (booleanData.includes(updateData)) { + static checkCertificateEligibility(data) { + return new Promise(async (resolve, reject) => { + try { + let eligible = false; + let updateObject = { + "$set" : {} + }; + // validate certificate data, checking if it passes all criteria + let validateCriteria = await certificateValidationsHelper.criteriaValidation(data) + if ( validateCriteria.success ) { + eligible = true; + } else { + updateObject["$set"]["certificate.message"] = validateCriteria.message; + } + updateObject["$set"]["certificate.eligible"] = eligible; - createProject[updateData] = - UTILS.convertStringToBoolean(data[updateData]); + // update project certificate data + await projectQueries.findOneAndUpdate( + { + _id: data._id + }, + updateObject + ); + + return resolve(eligible); + } catch (error) { + return resolve({ + success: false, + message: error.message - } else if (mongooseIdData.includes(updateData)) { - createProject[updateData] = ObjectId(data[updateData]); - } else { - createProject[updateData] = data[updateData]; - } - } }); + } + }) + } - createProject["appInformation"] = {}; - if (appName !== "") { - createProject["appInformation"]["appName"] = appName; - } + /** + * createCertificatePayload. + * @method + * @name createCertificatePayload + * @param {Object} data - project data for certificate creation data. + * @returns {Object} payload for certificate creation. + */ - if (appVersion !== "") { - createProject["appInformation"]["appVersion"] = appVersion; + static createCertificatePayload(data) { + return new Promise(async (resolve, reject) => { + try { + console.log("Certificate issuer Kid: ",CERTIFICATE_ISSUER_KID) + + if(data.title.length > 75) { + data.title = data.title.substring(0, 75) + '...'; } - - createProject["lastDownloadedAt"] = new Date(); - - if (data.profileInformation) { - createProject.userRoleInformtion = data.profileInformation; + + // get downloadable url for certificate template + if ( data.certificate.templateUrl && data.certificate.templateUrl !== "" ) { + let certificateTemplateDownloadableUrl = + await coreService.getDownloadableUrl( + { + filePaths: [data.certificate.templateUrl] + } + ); + if ( certificateTemplateDownloadableUrl.success ) { + data.certificate.templateUrl = certificateTemplateDownloadableUrl.data[0].url; + } else { + throw { + message: CONSTANTS.apiResponses.DOWNLOADABLE_URL_NOT_FOUND + }; + } } - let userProject = await database.models.projects.create( - createProject - ); + let certificateTemplateDetails =[]; + if ( data.certificate.templateId && data.certificate.templateId !== "" ) { + certificateTemplateDetails = await certificateTemplateQueries.certificateTemplateDocument({ + _id : data.certificate.templateId + },["issuer","solutionId","programId"]); - if (!userProject._id) { - throw { - message: CONSTANTS.apiResponses.USER_PROJECT_NOT_CREATED, - status: HTTP_STATUS_CODE['bad_request'].status + //certificate template data do not exists. + if ( !(certificateTemplateDetails.length > 0) ) { + throw { + message: CONSTANTS.apiResponses.CERTIFICATE_TEMPLATE_NOT_FOUND + }; } + certificateTemplateDetails[0].issuer.kid = CERTIFICATE_ISSUER_KID; } - - return resolve({ - success: true, - message: CONSTANTS.apiResponses.PROJECT_CREATED, - data: { - programId: - userProject.programInformation && userProject.programInformation._id ? - userProject.programInformation._id : "", - projectId: userProject._id, - lastDownloadedAt: userProject.lastDownloadedAt, - hasAcceptedTAndC : userProject.hasAcceptedTAndC ? userProject.hasAcceptedTAndC : false - } - }); + + //create certificate request body + let certificateData = { + recipient : { + id : data.userId, + name : `${data.userProfile.firstName} ${data.userProfile.lastName}`, + type : data.userProfile.profileUserType.type + }, + templateUrl : data.certificate.templateUrl, + issuer : certificateTemplateDetails[0].issuer, + status : data.certificate.status.toUpperCase(), + projectId : (data._id).toString(), + projectName : data.title, + programId : (certificateTemplateDetails[0].programId).toString(), + programName : ( data.programInformation && data.programInformation.name ) ? data.programInformation.name : "", + solutionId : (certificateTemplateDetails[0].solutionId).toString(), + solutionName : ( data.solutionInformation && data.solutionInformation.name ) ? data.solutionInformation.name : "", + completedDate : data.completedDate + }; + return resolve(certificateData); } catch (error) { + console.log("error:",error.message) return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, - message: error.message, - data: {} + message: error.message + }); } }) - } - + } - /** - * share project and task pdf report. - * @method - * @name share - * @param {String} [projectId] - projectId. - * @returns {Object} Downloadable pdf url. - */ + /** + * call sunbird-RC for certificate creation. + * @method + * @name createCertificate + * @param {Object} certificateData - payload for certificate creation data. + * @param {string} projectId - project Id. + * @returns {Boolean} certificate creation status. + */ - static share(projectId = "", taskIds = [], userToken) { + static createCertificate(certificateData, projectId) { return new Promise(async (resolve, reject) => { try { - let projectPdf = true; - let projectDocument = []; - - let query = { - _id: projectId, - isDeleted: false + const certificateDetails = await certificateService.createCertificate( certificateData ); + + if ( !certificateDetails.success || !certificateDetails.data || !certificateDetails.data.ProjectCertificate ) { + throw { + message: CONSTANTS.apiResponses.CERTIFICATE_GENERATION_FAILED + }; } + + let updateObject = { + "$set" : {} + }; - if (!taskIds.length ) { - - projectDocument = await this.projectDocument - ( - query, - [ - "title", - "status", - "metaInformation.goal", - "metaInformation.duration", - "startDate", - "description", - "endDate", - "tasks", - "categories", - "programInformation.name" - ] - ); - } - else { - projectPdf = false; + // if transaction id is present. + if ( certificateDetails.data.ProjectCertificate.transactionId && + certificateDetails.data.ProjectCertificate.transactionId !== "" + ) { + let transactionIdvalue = certificateDetails.data.ProjectCertificate.transactionId; + const first2 = transactionIdvalue.slice(0, 2); - let aggregateData = [ - { "$match": { _id: ObjectId(projectId), isDeleted: false} }, - { "$project": { - "status": 1, "title": 1, "startDate": 1, "metaInformation.goal": 1, "metaInformation.duration":1, - "categories" : 1, "programInformation.name": 1, "description" : 1, - tasks: { "$filter": { - input: '$tasks', - as: 'tasks', - cond: { "$in": ['$$tasks._id', taskIds]} - }} - }}] - - projectDocument = await database.models.projects.aggregate(aggregateData); - } - - if (!projectDocument.length) { - throw { - message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND, - status: HTTP_STATUS_CODE['bad_request'].status + if ( first2 === "1-" ) { + transactionIdvalue = transactionIdvalue.split(/1-(.*)/s) + updateObject["$set"]["certificate.transactionId"] = transactionIdvalue[1]; + } else { + updateObject["$set"]["certificate.transactionId"] = transactionIdvalue; } + } - projectDocument = projectDocument[0]; - projectDocument.goal = projectDocument.metaInformation ? projectDocument.metaInformation.goal : ""; - projectDocument.duration = projectDocument.metaInformation ? projectDocument.metaInformation.duration : ""; - projectDocument.programName = projectDocument.programInformation ? projectDocument.programInformation.name : ""; - projectDocument.category = []; - - if (projectDocument.categories && projectDocument.categories.length > 0) { - projectDocument.categories.forEach( category => { - projectDocument.category.push(category.name); - }) - } - - let tasks = []; - if (projectDocument.tasks.length > 0) { - projectDocument.tasks.forEach( task => { - let subtasks = []; - if (!task.isDeleted) { - if (task.children.length > 0) { - task.children.forEach(children => { - if (!children.isDeleted) { - subtasks.push(children); - } - }) - } - task.children = subtasks; - tasks.push(task); - } - }) - projectDocument.tasks = tasks; + // update project details certificate details + if ( certificateDetails.data.ProjectCertificate.osid && + certificateDetails.data.ProjectCertificate.osid !== "" + ) { + updateObject["$set"]["certificate.osid"] = certificateDetails.data.ProjectCertificate.osid; + updateObject["$set"]["certificate.issuedOn"] = new Date(); } + updateObject["$set"]["certificate.transactionIdCreatedAt"] = new Date();; + + if ( Object.keys(updateObject["$set"]).length > 0 ) { + let updatedProject = await projectQueries.findOneAndUpdate( + { + _id: projectId + }, + updateObject + ); + } + return resolve( { + success: true + }); + } catch (error) { + return resolve({ + success: false, + message: error.message + + }); + } + }) + } - delete projectDocument.categories; - delete projectDocument.metaInformation; - delete projectDocument.programInformation; - - let response = await dhitiService.projectAndTaskReport(userToken, projectDocument, projectPdf); + /** + * certificate callback + * @method + * @name certificateCallback + * @param {String} transactionId - transactionId for create certificate. + * @param {String} osid - osid for created certificate. + * @returns {JSON} certificate data updation details. + */ - if (response && response.success == true) { - return resolve({ - success: true, - message: CONSTANTS.apiResponses.REPORT_GENERATED_SUCCESSFULLY, - data: { - data: { - downloadUrl: response.data.pdfUrl - } - } - }); + static certificateCallback(transactionId, osid) { + return new Promise(async (resolve, reject) => { + try { + // adding comments to check call back is called properly or not + console.log("<==================callback called====================>",transactionId,osid) + console.log("transactionId :",transactionId) + console.log("osid :",osid) + console.log("<==================callback called====================>") + // callback request structure nested so validating transactionId and osid here instead in validator. + if ( transactionId == "" || osid == "" ) { + throw { + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.TRANSACTION_ID_AND_OSID_REQUIRED + } } + let updateObject = { + "$set" : {} + }; - else { + // update osid and eligibility based on transactionId + updateObject["$set"]["certificate.osid"] = osid; + updateObject["$set"]["certificate.message"] = CONSTANTS.common.PROJECT_CERTIFICATE_GENERATED_SUCCESSFULLY; + updateObject["$set"]["certificate.issuedOn"] = new Date(); + + let projectDetails = await projectQueries.findOneAndUpdate( + { + "certificate.transactionId" : transactionId + }, + updateObject + ); + + if ( projectDetails == null || !(Object.keys(projectDetails).length > 0) ) { throw { - message: CONSTANTS.apiResponses.COULD_NOT_GENERATE_PDF_REPORT, + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.PROJECT_NOT_FOUND } } + await kafkaProducersHelper.pushProjectToKafka(projectDetails); + return resolve({ + success: true, + message: CONSTANTS.apiResponses.PROJECT_CERTIFICATE_GENERATED, + data : { + _id : ObjectId(projectDetails._id) + } + + }); } catch (error) { return resolve({ - status: - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, success: false, message: error.message, data: {} @@ -2647,174 +3079,242 @@ module.exports = class UserProjectsHelper { } /** - * Get list of user projects with the targetted ones. - * @method - * @name userAssigned - * @param {String} userId - Logged in user id. - * @param {Number} pageSize - Page size. - * @param {Number} pageNo - Page No. - * @param {String} search - Search text. - * @param {String} filter - filter text. - * @returns {Object} - */ + * List user project details with certificate + * @method + * @name certificates + * @param {String} userId - userId. + * @returns {JSON} certificate data updation details. + */ - static userAssigned( userId,pageSize,pageNo,search, filter ) { - return new Promise(async (resolve, reject) => { - try { + static certificates(userId) { + return new Promise(async (resolve, reject) => { + try { + + // get project details of user which have certificate. + const userProject = await projectQueries.projectDocument({ + userId: userId, + status: CONSTANTS.common.SUBMITTED_STATUS, + certificate: {$exists:true} + }, [ + "_id", + "title", + "status", + "certificate.osid", + "certificate.transactioId", + "certificate.templateUrl", + "certificate.status", + "certificate.eligible", + "certificate.message", + "certificate.issuedOn", + "completedDate" + ]); + + if ( !(userProject.length > 0 )) { + throw { + status: HTTP_STATUS_CODE["bad_request"].status, + message: CONSTANTS.apiResponses.PROJECT_WITH_CERTIFICATE_NOT_FOUND + } + } + let templateFilePath = []; + //loop through user projects and get downloadable url for templateUrl if osid is present. + for( let userProjectPointer = 0; userProjectPointer < userProject.length; userProjectPointer++ ) { + if ( userProject[userProjectPointer].certificate.osid && + userProject[userProjectPointer].certificate.osid !== "" && + userProject[userProjectPointer].certificate.templateUrl && + userProject[userProjectPointer].certificate.templateUrl !== "" + ) { + templateFilePath.push(userProject[userProjectPointer].certificate.templateUrl); + } + } + + if( templateFilePath.length > 0 ) { - let query = { - userId : userId, - isDeleted : false - } + let certificateTemplateDownloadableUrl = + await coreService.getDownloadableUrl( + { + filePaths: templateFilePath + } + ); + if ( !certificateTemplateDownloadableUrl.success ) { + throw { + message: CONSTANTS.apiResponses.DOWNLOADABLE_URL_NOT_FOUND + }; + } + // map downloadable templateUrl to corresponding project data + userProject.forEach(projectData => { + var itemFromUrlArray = certificateTemplateDownloadableUrl.data.find(item=> item.filePath == projectData.certificate.templateUrl); + if (itemFromUrlArray) { + projectData.certificate.templateUrl = itemFromUrlArray.url; + } + } + ) + } - let searchQuery = []; + let count = _.countBy(userProject, (rec) => { + return (rec.certificate && rec.certificate.osid && rec.certificate.osid !== "" )? 'generated': 'notGenerated'; + }); + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.PROJECTS_FETCHED, + data : { + data : userProject, + count : userProject.length, + certificateCount : count.generated + } - if (search !== "") { - searchQuery = [ - { "title" : new RegExp(search, 'i') }, - { "description" : new RegExp(search, 'i') } - ]; + }); + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: {} + }); } + }) + } - if ( filter && filter !== "" ) { - if( filter === CONSTANTS.common.CREATED_BY_ME ) { - query["isAPrivateProgram"] = { - $ne : false + /** + * Re-Issue project certificate + * @method + * @name certificateReIssue + * @param {String} projectId - projectId. + * @returns {JSON} certificate re-issued details. + */ + + static certificateReIssue(projectId) { + return new Promise(async (resolve, reject) => { + try { + // get project details project for which certificate re-issue required . + const userProject = await projectQueries.projectDocument({ + _id: projectId, + status: CONSTANTS.common.SUBMITTED_STATUS, + certificate: {$exists:true} + }); + + // if project details not found. + if (!(userProject.length > 0)) { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.USER_PROJECT_NOT_FOUND + }; + } + let updateObject = { + "$set" : {} + }; + + // fetch user data using userId of project and calling the profile API + let userProfileData = await userProfileService.profileReadPrivate(userProject[0].userId); + if ( userProfileData.success && + userProfileData.data && + userProfileData.data.response && + userProfileData.data.response.firstName && + userProfileData.data.response.firstName !== "" + ) { + userProject[0].userProfile.firstName = userProfileData.data.response.firstName; + userProject[0].userProfile.lastName = userProfileData.data.response.lastName; + } else { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.USER_PROFILE_NOT_FOUND }; - } else if( filter == CONSTANTS.common.ASSIGN_TO_ME ) { - query["isAPrivateProgram"] = false; } - } - - let projects = await this.projects( - query, - pageSize, - pageNo, - searchQuery, - [ - "title", - "description", - "solutionId", - "programId", - "programInformation.name", - "projectTemplateId", - "solutionExternalId", - "lastDownloadedAt", - "hasAcceptedTAndC" - ] - ); - let totalCount = 0; - let data = []; + // create payload for certificate generation + const certificateData = await this.createCertificatePayload(userProject[0]); - if( projects.success && projects.data ) { + // call sunbird-RC to create certificate for project + const certificate = await this.createCertificate(certificateData, userProject[0]._id); - totalCount = projects.data.count; - data = projects.data.data; + if ( !certificate.success ) { + throw { + message: CONSTANTS.apiResponses.CERTIFICATE_GENERATION_FAILED + }; + } - if( data.length > 0 ) { - data.forEach( projectData => { - projectData.name = projectData.title; + if ( userProject[0].certificate.transactionId ) { + updateObject["$set"]["certificate.originalTransactionInformation.transactionId"] = userProject[0].certificate.transactionId + } + if ( userProject[0].certificate.osid ) { + updateObject["$set"]["certificate.originalTransactionInformation.osid"] = userProject[0].certificate.osid; + } - if (projectData.programInformation) { - projectData.programName = projectData.programInformation.name; - delete projectData.programInformation; - } + if (userProject[0].userProfile.firstName ) { + updateObject["$set"]["userProfile.firstName"] = userProject[0].userProfile.firstName; + } - if (projectData.solutionExternalId) { - projectData.externalId = projectData.solutionExternalId; - delete projectData.solutionExternalId; - } + if (userProject[0].userProfile.lastName ) { + updateObject["$set"]["userProfile.lastName"] = userProject[0].userProfile.lastName; + } - projectData.type = CONSTANTS.common.IMPROVEMENT_PROJECT; + updateObject["$set"]["certificate.reIssuedAt"] = new Date(); + await projectQueries.findOneAndUpdate( + { + _id: userProject[0]._id + }, + updateObject + ); + + return resolve({ + success: true, + message: CONSTANTS.apiResponses.PROJECT_SUBMITTED_FOR_REISSUE, + data : { + _id : userProject[0]._id + } - delete projectData.title; - }); - } + }); + } catch (error) { + return resolve({ + success: false, + message: error.message, + data: {} + }); } - - return resolve({ - success : true, - message : CONSTANTS.apiResponses.USER_ASSIGNED_PROJECT_FETCHED, - data : { - data: data, - count: totalCount - } - }); + }) + } - } catch (error) { - return resolve({ - success : false, - message : error.message, - status : - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - data : { - description : CONSTANTS.common.PROJECT_DESCRIPTION, - data : [], - count : 0 - } - }); - } - }) - } + /** + * Fetches project details based on the provided query. + * @method + * @name details + * @param {Object} args - Query object for fetching project details. + * @returns {Array} - A promise that resolves to an array of project details matching the query. + */ + static userProjectOverview(args,fields='all',stats) { + return new Promise(async (resolve, reject) => { + try { + const projectIdQuery = { + ...args, + }; - /** - * List of user imported projects. - * @method - * @name importedProjects - * @param {String} userId - Logged in user id. - * @param {String} programId - program id. - * @returns {Object} - */ + let fieldarray = []; - static importedProjects( userId,programId ) { - return new Promise(async (resolve, reject) => { - try { + if (fields != "all") { + fields.forEach((field) => { + fieldarray.push(field); + }); + } - let filterQuery = { - userId : userId, - referenceFrom : { $exists : true,$eq : CONSTANTS.common.OBSERVATION_REFERENCE_KEY }, - isDeleted: false - }; + if (stats == true) { + let count = await projectQueries.countDocuments(projectIdQuery); - if( programId !== "" ) { - filterQuery["programId"] = programId; - } + resolve(count); + } + + let projects = await projectQueries.projectDocument( + projectIdQuery, + fieldarray + ); + + resolve(projects); + } catch (error) { + reject(error); + } + }); + } + - let importedProjects = await this.projectDocument( - filterQuery, - [ - "solutionInformation", - "programInformation", - "title", - "description", - "projectTemplateId" - ] - ); - - return resolve({ - success : true, - message : CONSTANTS.apiResponses.IMPORTED_PROJECTS_FETCHED, - data : importedProjects - }); - } catch (error) { - return resolve({ - success : false, - message : error.message, - status : - error.status ? - error.status : HTTP_STATUS_CODE['internal_server_error'].status, - data : { - description : CONSTANTS.common.PROJECT_DESCRIPTION, - data : [], - count : 0 - } - }); - } - }) - } }; /** @@ -2829,7 +3329,7 @@ function _projectInformation(project) { return new Promise(async (resolve, reject) => { try { - + if (project.entityInformation) { project.entityId = project.entityInformation._id; project.entityName = project.entityInformation.name; @@ -2839,11 +3339,44 @@ function _projectInformation(project) { project.programId = project.programInformation._id; project.programName = project.programInformation.name; } + + //project attachments + if ( project.attachments && project.attachments.length > 0 ) { + + let projectLinkAttachments = []; + let projectAttachments = []; + + for ( + let pointerToAttachment = 0; + pointerToAttachment < project.attachments.length; + pointerToAttachment++ + ) { + let currentProjectAttachment = project.attachments[pointerToAttachment]; + if ( currentProjectAttachment.type == CONSTANTS.common.ATTACHMENT_TYPE_LINK ) { + projectLinkAttachments.push(currentProjectAttachment); + } else { + projectAttachments.push(currentProjectAttachment.sourcePath); + } + } + + let projectAttachmentsUrl = await _attachmentInformation(projectAttachments, projectLinkAttachments, project.attachments, CONSTANTS.common.PROJECT_ATTACHMENT); + if ( projectAttachmentsUrl.data && projectAttachmentsUrl.data.length > 0 ) { + project.attachments = projectAttachmentsUrl.data; + } + + } + + //task attachments if (project.tasks && project.tasks.length > 0) { + //order task based on task sequence + if ( project.taskSequence && project.taskSequence.length > 0 ) { + project.tasks = taskArrayBySequence(project.tasks, project.taskSequence, 'externalId'); + } let attachments = []; let mapTaskIdToAttachment = {}; + let mapLinkAttachment = {}; for (let task = 0; task < project.tasks.length; task++) { @@ -2856,56 +3389,31 @@ function _projectInformation(project) { attachment++ ) { let currentAttachment = currentTask.attachments[attachment]; - attachments.push(currentAttachment.sourcePath); - if (!mapTaskIdToAttachment[currentAttachment.sourcePath]) { + if (currentAttachment.type == CONSTANTS.common.ATTACHMENT_TYPE_LINK ) { + if (!Array.isArray(mapLinkAttachment[currentTask._id]) || !mapLinkAttachment[currentTask._id].length ) { + mapLinkAttachment[currentTask._id] = []; + } + mapLinkAttachment[currentTask._id].push(currentAttachment); + } else { + attachments.push(currentAttachment.sourcePath); + } + + if (!mapTaskIdToAttachment[currentAttachment.sourcePath] && currentAttachment.type != CONSTANTS.common.ATTACHMENT_TYPE_LINK ) { mapTaskIdToAttachment[currentAttachment.sourcePath] = { taskId: currentTask._id }; - } } } - } - if (attachments.length > 0) { - - let attachmentsUrl = - await kendraService.getDownloadableUrl( - { - filePaths: attachments - } - ); - - if (!attachmentsUrl.success) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.ATTACHMENTS_URL_NOT_FOUND - } - } - - if (attachmentsUrl.data.length > 0) { - attachmentsUrl.data.forEach(attachmentUrl => { - - let taskIndex = - project.tasks.findIndex(task => task._id === mapTaskIdToAttachment[attachmentUrl.filePath].taskId); - - if (taskIndex > -1) { - let attachmentIndex = - project.tasks[taskIndex].attachments.findIndex(attachment => attachment.sourcePath === attachmentUrl.filePath); - - if (attachmentIndex > -1) { - project.tasks[taskIndex].attachments[attachmentIndex].url = attachmentUrl.url; - } - } - }) - } - + let taskAttachmentsUrl = await _attachmentInformation(attachments, mapLinkAttachment, [], CONSTANTS.common.TASK_ATTACHMENT, mapTaskIdToAttachment, project.tasks); + if ( taskAttachmentsUrl.data && taskAttachmentsUrl.data.length > 0 ) { + project.tasks = taskAttachmentsUrl.data; } - } - + project.status = project.status ? project.status : CONSTANTS.common.NOT_STARTED_STATUS; @@ -2938,6 +3446,117 @@ function _projectInformation(project) { }) } +function taskArrayBySequence (taskArray, sequenceArray, key) { + var map = sequenceArray.reduce((acc, value, index) => (acc[value] = index + 1, acc), {}) + const sortedTaskArray = taskArray.sort((a, b) => (map[a[key]] || Infinity) - (map[b[key]] || Infinity)) + return sortedTaskArray +}; + +/** + * Attachment information of project. + * @method + * @name _attachmentInformation + * @param {Array} attachments - attachments data. + * @param {String} type - project or task attachement. + * @returns {Object} Project attachments. +*/ +function _attachmentInformation ( attachmentWithSourcePath = [], linkAttachments = [], attachments = [] , type, mapTaskIdToAttachment = {}, tasks = []) { + return new Promise(async (resolve, reject) => { + try { + + let attachmentOrTask = []; + + if ( attachmentWithSourcePath && attachmentWithSourcePath.length > 0 ) { + + let attachmentsUrl = + await coreService.getDownloadableUrl( + { + filePaths: attachmentWithSourcePath + } + ); + + if (!attachmentsUrl.success) { + throw { + status: HTTP_STATUS_CODE['bad_request'].status, + message: CONSTANTS.apiResponses.ATTACHMENTS_URL_NOT_FOUND + } + } + + if ( attachmentsUrl.data && attachmentsUrl.data.length > 0) { + + if (type === CONSTANTS.common.PROJECT_ATTACHMENT ) { + + attachmentsUrl.data.forEach(eachAttachment => { + + let projectAttachmentIndex = + attachments.findIndex(attachmentData => attachmentData.sourcePath == eachAttachment.filePath); + + if (projectAttachmentIndex > -1) { + attachments[projectAttachmentIndex].url = eachAttachment.url; + } + }) + + } else { + + attachmentsUrl.data.forEach(taskAttachments => { + + let taskIndex = + tasks.findIndex(task => task._id === mapTaskIdToAttachment[taskAttachments.filePath].taskId); + + if (taskIndex > -1) { + + let attachmentIndex = + tasks[taskIndex].attachments.findIndex(attachment => attachment.sourcePath === taskAttachments.filePath); + + if (attachmentIndex > -1) { + tasks[taskIndex].attachments[attachmentIndex].url = taskAttachments.url; + } + } + }) + } + } + } + + if ( linkAttachments && linkAttachments.length > 0 ) { + + if (type === CONSTANTS.common.PROJECT_ATTACHMENT ) { + attachments.concat(linkAttachments); + + + } else { + + Object.keys(linkAttachments).forEach(eachTaskId => { + + let taskIdIndex = tasks.findIndex(task => task._id === eachTaskId); + if ( taskIdIndex > -1 ) { + tasks[taskIdIndex].attachments.concat(linkAttachments[eachTaskId]); + } + }) + + } + } + + attachmentOrTask = (type === CONSTANTS.common.PROJECT_ATTACHMENT) ? attachments : tasks + + return resolve({ + success: true, + data: attachmentOrTask + }); + + } catch (error) { + + return resolve({ + message: error.message, + success: false, + status: + error.status ? + error.status : HTTP_STATUS_CODE['internal_server_error'].status + }) + } + + }) +} + /** * Task of project. * @method @@ -2950,7 +3569,7 @@ function _projectInformation(project) { function _projectTask(tasks, isImportedFromLibrary = false, parentTaskId = "") { tasks.forEach(singleTask => { - + singleTask.externalId = singleTask.externalId ? singleTask.externalId : singleTask.name.toLowerCase(); singleTask.type = singleTask.type ? singleTask.type : CONSTANTS.common.SIMPLE_TASK_TYPE; singleTask.status = singleTask.status ? singleTask.status : CONSTANTS.common.NOT_STARTED_STATUS; @@ -2959,7 +3578,9 @@ function _projectTask(tasks, isImportedFromLibrary = false, parentTaskId = "") { if (!singleTask.hasOwnProperty("isDeletable")) { singleTask.isDeletable = true; } - + if ( UTILS.isValidMongoId(singleTask._id.toString()) ) { + singleTask.referenceId = singleTask._id.toString(); + } singleTask.createdAt = singleTask.createdAt ? singleTask.createdAt : new Date(); singleTask.updatedAt = new Date(); singleTask._id = UTILS.isValidMongoId(singleTask._id.toString()) ? uuidv4() : singleTask._id; @@ -2982,7 +3603,11 @@ function _projectTask(tasks, isImportedFromLibrary = false, parentTaskId = "") { }); } } - + + removeFieldsFromRequest.forEach((removeField) => { + delete singleTask[removeField]; + }); + if (singleTask.children) { _projectTask( singleTask.children, @@ -2994,7 +3619,7 @@ function _projectTask(tasks, isImportedFromLibrary = false, parentTaskId = "") { } }) - + return tasks; } @@ -3023,11 +3648,11 @@ function _projectCategories(categories) { if (categoryIds.length > 0) { categoryData = - await libraryCategoriesHelper.categoryDocuments({ + await projectCategoriesQueries.categoryDocuments({ _id: { $in: categoryIds } }, ["name", "externalId"]); - if (!categoryData.length > 0) { + if (!(categoryData.length > 0)) { throw { status: HTTP_STATUS_CODE['bad_request'].status, message: CONSTANTS.apiResponses.CATEGORY_NOT_FOUND @@ -3093,27 +3718,48 @@ function _projectCategories(categories) { function _entitiesInformation(entityIds) { return new Promise(async (resolve, reject) => { try { + let locationIds = []; + let locationCodes = []; + let entityInformations = []; + entityIds.forEach(entity=>{ + if (UTILS.checkValidUUID(entity)) { + locationIds.push(entity); + } else { + locationCodes.push(entity); + } + }); - let entityData = - await kendraService.entityDocuments( - entityIds, - ["metaInformation", "entityType", "entityTypeId", "registryDetails"] - ); + if ( locationIds.length > 0 ) { + let bodyData = { + "id" : locationIds + } + let entityData = await userProfileService.locationSearch( bodyData, formatResult = true); + if ( entityData.success ) { + entityInformations = entityData.data; + } + } - if (!entityData.success) { + if ( locationCodes.length > 0 ) { + let bodyData = { + "code" : locationCodes + } + let entityData = await userProfileService.locationSearch( bodyData , formatResult = true ); + if ( entityData.success ) { + entityInformations = entityInformations.concat(entityData.data); + } + } + + if ( !(entityInformations.length > 0) ) { throw { status: HTTP_STATUS_CODE['bad_request'].status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND } } - let entitiesData = []; - - if (entityData.success && entityData.data.length > 0) { - - entitiesData = _entitiesMetaInformation(entityData.data); + if ( entityInformations.length > 0 ) { + entitiesData = await _entitiesMetaInformation(entityInformations); } - + return resolve({ success: true, data: entitiesData @@ -3149,7 +3795,7 @@ function _assessmentDetails(assessmentData) { if (assessmentData.project) { let templateTasks = - await projectTemplateTasksHelper.taskDocuments({ + await projectTemplateTaskQueries.taskDocuments({ externalId: assessmentData.project.taskId }, ["_id"]) @@ -3161,7 +3807,7 @@ function _assessmentDetails(assessmentData) { if (assessmentData.solutionDetails.isReusable) { let createdAssessment = - await assessmentService.createAssessmentSolutionFromTemplate( + await surveyService.createAssessmentSolutionFromTemplate( assessmentData.token, assessmentData.solutionDetails._id, { @@ -3188,7 +3834,7 @@ function _assessmentDetails(assessmentData) { } else { let assignedAssessmentToUser = - await assessmentService.createEntityAssessors( + await surveyService.createEntityAssessors( assessmentData.token, assessmentData.solutionDetails.programId, assessmentData.solutionDetails._id, @@ -3203,7 +3849,7 @@ function _assessmentDetails(assessmentData) { } let entitiesAddedToSolution = - await assessmentService.addEntitiesToSolution( + await surveyService.addEntitiesToSolution( assessmentData.token, assessmentData.solutionDetails._id, [assessmentData.entityId.toString()] @@ -3217,7 +3863,7 @@ function _assessmentDetails(assessmentData) { } let solutionUpdated = - await assessmentService.updateSolution( + await surveyService.updateSolution( assessmentData.token, { "project": assessmentData.project, @@ -3260,7 +3906,7 @@ function _assessmentDetails(assessmentData) { * @returns {Object} */ -function _observationDetails(observationData) { +function _observationDetails(observationData, userRoleAndProfileInformation = {}) { return new Promise(async (resolve, reject) => { try { @@ -3269,7 +3915,7 @@ function _observationDetails(observationData) { if (observationData.project) { let templateTasks = - await projectTemplateTasksHelper.taskDocuments({ + await projectTemplateTaskQueries.taskDocuments({ externalId: observationData.project.taskId }, ["_id"]) @@ -3281,7 +3927,7 @@ function _observationDetails(observationData) { if (observationData.solutionDetails.isReusable) { let observationCreatedFromTemplate = - await assessmentService.createObservationFromSolutionTemplate( + await surveyService.createObservationFromSolutionTemplate( observationData.token, observationData.solutionDetails._id, { @@ -3309,23 +3955,6 @@ function _observationDetails(observationData) { } else { - let solutionUpdated = - await assessmentService.updateSolution( - observationData.token, - { - project: observationData.project, - referenceFrom: "project" - }, - observationData.solutionDetails.externalId - ); - - if (!solutionUpdated.success) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.SOLUTION_NOT_UPDATED - } - } - let startDate = new Date(); let endDate = new Date(); endDate.setFullYear(endDate.getFullYear() + 1); @@ -3340,21 +3969,19 @@ function _observationDetails(observationData) { project: observationData.project }; - let observationCreated = await assessmentService.createObservation( + let observationCreated = await surveyService.createObservation( observationData.token, observationData.solutionDetails._id, - observation + observation, + userRoleAndProfileInformation && Object.keys(userRoleAndProfileInformation).length > 0 ? userRoleAndProfileInformation : {} ); - if (!observationCreated.success) { - throw { - status: HTTP_STATUS_CODE['bad_request'].status, - message: CONSTANTS.apiResponses.OBSERVATION_NOT_CREATED - } + if ( observationCreated.success ) { + result["observationId"] = observationCreated.data._id; } result["solutionId"] = observationData.solutionDetails._id; - result["observationId"] = observationCreated.data._id; + } return resolve({ @@ -3382,16 +4009,19 @@ function _observationDetails(observationData) { */ function _entitiesMetaInformation(entitiesData) { + return new Promise(async (resolve, reject) => { + let entityInformation = [] + for ( let index = 0; index < entitiesData.length; index++ ) { + let entityHierarchy = await userProfileService.getParentEntities( entitiesData[index]._id ); + entitiesData[index].metaInformation.hierarchy = entityHierarchy; + entitiesData[index].metaInformation._id = entitiesData[index]._id; + entitiesData[index].metaInformation.entityType = entitiesData[index].entityType; + entitiesData[index].metaInformation.registryDetails = entitiesData[index].registryDetails; + entityInformation.push(entitiesData[index].metaInformation) + } - entitiesData = entitiesData.map(entity => { - entity.metaInformation._id = ObjectId(entity._id); - entity.metaInformation.entityType = entity.entityType; - entity.metaInformation.entityTypeId = ObjectId(entity.entityTypeId); - entity.metaInformation.registryDetails = entity.registryDetails; - return entity.metaInformation; - }); - - return entitiesData; + return resolve (entityInformation); + }) } @@ -3452,6 +4082,193 @@ function _projectData(data) { }) } +/** + * Validate & Update UserProfile in Projects. + * @method + * @name _updateUserProfileBasedOnUserRoleInfo + * @param {Object} userProfile - userProfile data. + * @param {Object} userRoleInformation - userRoleInformation data. + * @returns {Object} updated UserProfile information. +*/ + +function _updateUserProfileBasedOnUserRoleInfo(userProfile, userRoleInformation) { + return new Promise(async (resolve, reject) => { + try { + + + let updateUserProfileRoleInformation = false; // Flag to see if roleInformation i.e. userProfile.profileUserTypes has to be updated based on userRoleInfromation.roles + + if(userRoleInformation.role) { // Check if userRoleInformation has role value. + let rolesInUserRoleInformation = userRoleInformation.role.split(","); // userRoleInfomration.role can be multiple with comma separated. + let resetCurrentUserProfileRoles = false; // Flag to reset current userProfile.profileUserTypes i.e. if current role in profile is not at all there in userRoleInformation.roles + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + // Loop through current roles in userProfile.profileUserTypes + for (let pointerToCurrentProfileUserTypes = 0; pointerToCurrentProfileUserTypes < userProfile.profileUserTypes.length; pointerToCurrentProfileUserTypes++) { + const currentProfileUserType = userProfile.profileUserTypes[pointerToCurrentProfileUserTypes]; + if(currentProfileUserType.subType && currentProfileUserType.subType !== null) { // If the role has a subType + + // Check if subType exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!userRoleInformation.role.toUpperCase().includes(currentProfileUserType.subType.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } else { // If the role subType is null or is not there + + // Check if type exists in userRoleInformation role, if not means profile data is old and should be reset. + if(!userRoleInformation.role.toUpperCase().includes(currentProfileUserType.type.toUpperCase())) { + resetCurrentUserProfileRoles = true; // Reset userProfile.profileUserTypes + break; + } + } + } + } + if(resetCurrentUserProfileRoles) { // Reset userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + } + + // Loop through each subRole in userRoleInformation + for (let pointerToRolesInUserInformation = 0; pointerToRolesInUserInformation < rolesInUserRoleInformation.length; pointerToRolesInUserInformation++) { + const subRole = rolesInUserRoleInformation[pointerToRolesInUserInformation]; + // Check if userProfile.profileUserTypes exists and is an array of length > 0 + if(userProfile.profileUserTypes && Array.isArray(userProfile.profileUserTypes) && userProfile.profileUserTypes.length >0) { + if(!_.find(userProfile.profileUserTypes, { 'type': subRole.toLowerCase() }) && !_.find(userProfile.profileUserTypes, { 'subType': subRole.toLowerCase() })) { + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + if(subRole.toUpperCase() === "TEACHER") { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } else { // Make a new entry if userProfile.profileUserTypes is empty or does not exist. + updateUserProfileRoleInformation = true; // Need to update userProfile.profileUserTypes + userProfile.profileUserTypes = new Array; + if(subRole.toUpperCase() === "TEACHER") { // If subRole is teacher + userProfile.profileUserTypes.push({ + "subType" : null, + "type" : "teacher" + }) + } else { // If subRole is not teacher + userProfile.profileUserTypes.push({ + "subType" : subRole.toLowerCase(), + "type" : "administrator" + }) + } + } + } + } + + if(updateUserProfileRoleInformation) { // If profileUserTypes in userProfile was wrong and is updated as per userRoleInformation + userProfile.userRoleMismatchFoundAndUpdated = true; + } + + // Create location only object from userRoleInformation + let userRoleInformationLocationObject = _.omit(userRoleInformation, ['role']); + + // All location keys from userRoleInformation + let userRoleInfomrationLocationKeys = Object.keys(userRoleInformationLocationObject); + + let updateUserProfileLocationInformation = false; // Flag to see if userLocations i.e. userProfile.userLocations has to be updated based on userRoleInfromation location values + + // Loop through all location keys. + for (let pointerToUserRoleInfromationLocationKeys = 0; pointerToUserRoleInfromationLocationKeys < userRoleInfomrationLocationKeys.length; pointerToUserRoleInfromationLocationKeys++) { + + const locationType = userRoleInfomrationLocationKeys[pointerToUserRoleInfromationLocationKeys]; // e.g. state, district, school + const locationValue = userRoleInformationLocationObject[locationType]; // Location UUID values or school code. + + // Check if userProfile.userLocations exists and is an array of length > 0 + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + + if(locationType === "school") { // If location type school exist check if same is there in userProfile.userLocations + if(!_.find(userProfile.userLocations, { 'type': "school", 'code': locationValue })) { + updateUserProfileLocationInformation = true; // School does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } else { // Check if location type is there in userProfile.userLocations and has same value as userRoleInformation + if(!_.find(userProfile.userLocations, { 'type': locationType, 'id': locationValue })) { + updateUserProfileLocationInformation = true; // Location does not exist in userProfile.userLocations, update entire userProfile.userLocations + break; + } + } + } else { + updateUserProfileLocationInformation = true; + break; + } + } + + if(userProfile.userLocations && Array.isArray(userProfile.userLocations) && userProfile.userLocations.length >0) { + if(userProfile.userLocations.length != userRoleInfomrationLocationKeys.length) { + updateUserProfileLocationInformation = true; + } + } + + // If userProfile.userLocations has to be updated, get all values and set in userProfile. + if(updateUserProfileLocationInformation) { + + //update userLocations in userProfile + let locationIds = []; + let locationCodes = []; + let userLocations = new Array; + + userRoleInfomrationLocationKeys.forEach( requestedDataKey => { + if (UTILS.checkValidUUID(userRoleInformationLocationObject[requestedDataKey])) { + locationIds.push(userRoleInformationLocationObject[requestedDataKey]); + } else { + locationCodes.push(userRoleInformationLocationObject[requestedDataKey]); + } + }) + + //query for fetch location using id + if ( locationIds.length > 0 ) { + let locationQuery = { + "id" : locationIds + } + + let entityData = await userProfileService.locationSearch(locationQuery); + if ( entityData.success ) { + userLocations = entityData.data; + } + } + + // query for fetch location using code + if ( locationCodes.length > 0 ) { + let codeQuery = { + "code" : locationCodes + } + + let entityData = await userProfileService.locationSearch(codeQuery); + if ( entityData.success ) { + userLocations = userLocations.concat(entityData.data); + } + } + + if ( userLocations.length > 0 ) { + userProfile["userLocations"] = userLocations; + userProfile.userLocationsMismatchFoundAndUpdated = true; // If userLocations in userProfile was wrong and is updated as per userRoleInformation + } + } + + return resolve({ + success: true, + profileMismatchFound : (updateUserProfileLocationInformation || updateUserProfileRoleInformation) ? true : false, + data: userProfile + }); + + } catch (error) { + return resolve({ + status: error.status || HTTP_STATUS_CODE['internal_server_error'].status, + message: error.message || HTTP_STATUS_CODE['internal_server_error'].message, + data : false + }); + } + }) +} \ No newline at end of file diff --git a/module/userProjects/validator/v1.js b/module/userProjects/validator/v1.js index c0d63cd7..bd20e83f 100644 --- a/module/userProjects/validator/v1.js +++ b/module/userProjects/validator/v1.js @@ -9,20 +9,10 @@ module.exports = (req) => { let projectsValidator = { - createSelf : function () { - req.checkBody('title').exists().withMessage("required project title"); - req.checkBody('categories').exists().withMessage("required categories for project"); - }, - importFromLibrary : function () { - req.checkParams('_id').exists().withMessage("required project template id"); - }, sync : function () { req.checkParams('_id').exists().withMessage("required project id"); req.checkQuery('lastDownloadedAt').exists().withMessage("required last downloaded at"); }, - details : function () { - req.checkParams('_id').exists().withMessage("required project id"); - }, tasksStatus : function () { req.checkParams('_id').exists().withMessage("required project id"); }, @@ -30,16 +20,26 @@ module.exports = (req) => { req.checkParams('_id').exists().withMessage("required project id"); req.checkQuery('taskId').exists().withMessage("required task id"); }, - bulkCreateByUserRoleAndEntity : function () { - req.checkBody('templateId').exists().withMessage("required template id"); - req.checkBody('entityId').exists().withMessage("required entity id"); - req.checkBody('role').exists().withMessage("required role"); - }, add : function () { req.checkBody('title').exists().withMessage("required project title"); }, share : function () { req.checkParams('_id').exists().withMessage("required project id"); + }, + certificateReIssue : function () { + req.checkParams('_id').exists().withMessage("required project id"); + }, + certificateCallback : function () { + req.checkBody("data").exists().withMessage("data is required"); + req.checkBody("data.transactionId").exists().withMessage("transactionId is required"); + req.checkBody("data.osid").exists().withMessage("osid is required"); + }, + listUserProjects:function(){ + req.checkQuery('stats') + .optional() + .isIn(['true','false']) + .withMessage("The 'stats' parameter must be either 'true' or 'false' "); + } } diff --git a/package.json b/package.json index 665c3f8c..da90639c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sl-improvement-service", - "version": "1.0.0", + "version": "8.0.0", "description": "Improvement service", "main": "app.js", "bin": { @@ -31,6 +31,8 @@ "bunyan": "^1.8.12", "bunyan-format": "^0.2.1", "cache-manager": "^3.1.0", + "chai": "^4.2.0", + "chai-http": "^4.3.0", "cli-table": "^0.3.1", "cors": "^2.8.5", "csvtojson": "^2.0.10", @@ -49,21 +51,20 @@ "keycloak-auth-utils": "^3.3.0", "lodash": "^4.17.15", "log": "^1.4.0", + "mocha": "^10.1.0", "moment-timezone": "^0.5.31", "mongoose": "^5.9.4", "mongoose-autopopulate": "^0.12.0", "mongoose-delete": "^0.5.1", "mongoose-timestamp": "^0.6.0", "mongoose-ttl": "0.0.3", + "node-cache": "^5.1.2", "p-each-series": "^2.1.0", "path": "^0.12.7", "request": "^2.88.2", "require-all": "^3.0.0", "uuid": "^8.3.0", - "xml-js": "^1.6.11", - "chai": "^4.2.0", - "chai-http": "^4.3.0", - "mocha": "^6.2.2" + "xml-js": "^1.6.11" }, "devDependencies": { "grunt-apidoc": "^0.11.0", diff --git a/release-notes/6.0.0.md b/release-notes/6.0.0.md new file mode 100644 index 00000000..3955eb5d --- /dev/null +++ b/release-notes/6.0.0.md @@ -0,0 +1,27 @@ +# Release Note 6.0.0 ML projects Service + +This version contains set of manual activites tasks that must be completed in order to improve to upgrade the ML Projects service code to 6.0.0. Please consider the following list of tasks to be completed. + +### Deploy ml-projects-services + +To retrieve the latest release tag for version 6.0.0, please visit the following URL: https://github.com/project-sunbird/ml-projects-service/tags e.g. release-6.0.0_RC8 + +To proceed with the deployment process, follow the steps below: + + 1. Log in to Jenkins. + 2. Navigate to Dashboard -> AutoBuild -> StagingManual -> managed-learn -> ml-projects-service. OR for dev Navigate to Dashboard -> Build -> managed-learn -> ml-projects-service + 3. Click on "Build with parameters" and provide the latest release tag in the field labeled "github_release_tag". Initiate the build process. + 4. The build job will take approximately 5 minutes to complete. + 5. After the job finishes, go to Dashboard -> Deploy -> staging -> managed-learn -> ml-projects-service. OR for dev go to Dashboard -> Deploy -> dev -> managed-learn -> ml-projects-service This job will be executed automatically in the dev environment. If not, then it should be built manually. + 6. Click on "Build with parameters" to initiate the deployment process. + 7. Once the job is completed, the services will be deployed on the staging environment + +## Migrations + +For this release, we haven't included any migrations specifically for the 6.0.0 release. However, we have included three scripts to address a bug in Diksha Prod, and it's important to note that these scripts are not related to the 6.0.0 release. You can find these scripts in the release documentation. + +Here is the ticket for this issue:- (Click Here)[https://project-sunbird.atlassian.net/browse/ED-3101]. + +Script 1 & 2 - [Click here](https://github.com/project-sunbird/ml-projects-service/blob/release-6.0.0/migrations/updateDistrictNameInProjects/Readme.md) + +Script 3 - [Click here](https://github.com/project-sunbird/ml-projects-service/blob/release-6.0.0/migrations/updateUserProfileAndMissMatchOfRoleInformation/Readme.md) diff --git a/release-notes/7.0.0.md b/release-notes/7.0.0.md new file mode 100644 index 00000000..169707c2 --- /dev/null +++ b/release-notes/7.0.0.md @@ -0,0 +1,31 @@ +# Release Note 7.0.0 ML Projects Service + +This version contains set of manual activites tasks that must be completed in order to improve to upgrade the ML Projects service code to 7.0.0. Please consider the following list of tasks to be completed. + +### Deploy ml-projects-services + +To retrieve the latest release tag for version 7.0.0, please visit the following URL: https://github.com/project-sunbird/ml-projects-service/tags e.g. release-7.0.0_RC2 + +To proceed with the deployment process, follow the steps below: + + 1. Log in to Jenkins. + 2. Navigate to Dashboard -> AutoBuild -> StagingManual -> managed-learn -> ml-projects-service. OR for dev Navigate to Dashboard -> Build -> managed-learn -> ml-projects-service + 3. Click on "Build with parameters" and provide the latest release tag in the field labeled "github_release_tag". Initiate the build process. + 4. The build job will take approximately 5 minutes to complete. + 5. After the job finishes, go to Dashboard -> Deploy -> staging -> managed-learn -> ml-projects-service. OR for dev go to Dashboard -> Deploy -> dev -> managed-learn -> ml-projects-service This job will be executed automatically in the dev environment. If not, then it should be built manually. + 6. Click on "Build with parameters" to initiate the deployment process. + 7. Once the job is completed, the services will be deployed on the staging environment + +### New Environment Keys Added + +We added new environment keys to the DevOps repository ([PR link](https://github.com/project-sunbird/sunbird-devops/pull/3921/files)) to as required for new features and functionality. For configuration and access to outside services or resources, these keys will be utilised. + +Please note you don't need to deploy the DevOps repo. Once the PR is merged, deploy this service, env variable will automatically add from the DevOps branch. + +In this release, we have introduced four new environment variables. + + USER_DELETE_ON_OFF={{user_delete_on_off | default("ON")}} + USER_DELETE_TOPIC={{user_delete_topic_name | default("dev.delete.user")}} + ID={{ml_survey_service_id | default("dev.sunbird.ml.project.service")}} + TELEMETRY_ON_OFF={{telemetry_on_off | default("ON")}} + TELEMETRY_TOPIC={{telemetry_raw_topic_name | default("dev.telemetry.raw"")}} \ No newline at end of file diff --git a/release-notes/8.0.0.md b/release-notes/8.0.0.md new file mode 100644 index 00000000..d8581662 --- /dev/null +++ b/release-notes/8.0.0.md @@ -0,0 +1,17 @@ +# Release Note 8.0.0 ML Projects Service + +This version contains set of manual activites tasks that must be completed in order to improve to upgrade the ML Projects service code to 8.0.0. Please consider the following list of tasks to be completed. + +### Deploy ml-projects-services + +To retrieve the latest release tag for version 8.0.0, please visit the following URL: https://github.com/project-sunbird/ml-projects-service/tags e.g. release-8.0.0_RC1 + +To proceed with the deployment process, follow the steps below: + + 1. Log in to Jenkins. + 2. Navigate to Dashboard -> AutoBuild -> StagingManual -> managed-learn -> ml-projects-service. OR for dev Navigate to Dashboard -> Build -> managed-learn -> ml-projects-service + 3. Click on "Build with parameters" and provide the latest release tag in the field labeled "github_release_tag". Initiate the build process. + 4. The build job will take approximately 5 minutes to complete. + 5. After the job finishes, go to Dashboard -> Deploy -> staging -> managed-learn -> ml-projects-service. OR for dev go to Dashboard -> Deploy -> dev -> managed-learn -> ml-projects-service This job will be executed automatically in the dev environment. If not, then it should be built manually. + 6. Click on "Build with parameters" to initiate the deployment process. + 7. Once the job is completed, the services will be deployed on the staging environment diff --git a/routes/index.js b/routes/index.js index cad0fe2a..a11d4bba 100644 --- a/routes/index.js +++ b/routes/index.js @@ -64,7 +64,7 @@ module.exports = function (app) { 'attachment; filename=' + result.fileNameWithPath.split('/').pop() ); res.set('Content-Type', 'application/octet-stream'); - fs.createReadStream(result.fileNameWithPath).pipe(res); + fs.createReadStream(result.fileNameWithPath.replace(/\.\.\//g, '')).pipe(res); } else { @@ -89,7 +89,7 @@ module.exports = function (app) { } console.log('-------------------Response log starts here-------------------'); - console.log(result); + console.log(JSON.stringify(result)); console.log('-------------------Response log ends here-------------------'); } catch (error) {