diff --git a/Makefile b/Makefile index 47c1dbd01..2e5a4000f 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,14 @@ test: build @cd java/apitest && MODE=events ../mvnw -Pe2e test || echo 'Tests failed' @docker-compose down @rm -rf db-data-5 || echo "no permission to delete" + @NOTIFICATION_URL=http://notification-ms:8765/notification-service/v1/notification TRACK_NOTIFICATIONS=true KAFKA_BOOTSTRAP_SERVERS=kafka:9092 NOTIFICATION_ENABLED=true NOTIFICATION_ASYNC_ENABLED=false RELEASE_VERSION=latest KEYCLOAK_IMPORT_DIR=java/apitest/src/test/resources KEYCLOAK_SECRET=a52c5f4a-89fd-40b9-aea2-3f711f14c889 DB_DIR=db-data-6 docker-compose up -d db es keycloak registry certificate-signer certificate-api notification-ms kafka + @echo "Starting the test" && sh build/wait_for_port.sh 8080 + @echo "Starting the test" && sh build/wait_for_port.sh 8081 + @docker-compose ps + @curl -v http://localhost:8081/health + @cd java/apitest && MODE=notification ../mvnw -Pe2e test || echo 'Tests failed' + @docker-compose down + @rm -rf db-data-6 || echo "no permission to delete" make -C services/certificate-signer test make -C services/public-key-service test make -C services/context-proxy-service test diff --git a/docker-compose.yml b/docker-compose.yml index 727a98a74..46a0756d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,6 +77,9 @@ services: - redis_host=redis - redis_port=6379 - manager_type=${MANAGER_TYPE-DefinitionsManager} + - notification_async_enabled=${NOTIFICATION_ASYNC_ENABLED-false} + - notification_enabled=${NOTIFICATION_ENABLED-false} + - notification_url=${NOTIFICATION_URL-http://notification-ms:8765/notification-service/v1/notification} ports: - "8081:8081" depends_on: @@ -188,6 +191,12 @@ services: interval: 30s timeout: 10s retries: 10 + environment: + - TRACK_NOTIFICATIONS=${TRACK_NOTIFICATIONS-false} + - KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS-kafka:9092} + depends_on: + kafka: + condition: service_started zookeeper: image: confluentinc/cp-zookeeper:latest ports: @@ -211,7 +220,7 @@ services: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" healthcheck: test: - [ "CMD", "kafka-topics", "--list", "--zookeeper", "zookeeper:2181" ] + [ "CMD", "kafka-topics", "--list", "--bootstrap-server", "localhost:9092" ] interval: 30s timeout: 10s retries: 10 diff --git a/java/apitest/src/test/java/e2e/registry/BirthCertificateSchemaRequestForRevokeFlow.json b/java/apitest/src/test/java/e2e/registry/BirthCertificateSchemaRequestForRevokeFlow.json index 294ae47d5..2f42f42ac 100644 --- a/java/apitest/src/test/java/e2e/registry/BirthCertificateSchemaRequestForRevokeFlow.json +++ b/java/apitest/src/test/java/e2e/registry/BirthCertificateSchemaRequestForRevokeFlow.json @@ -1,5 +1,5 @@ { - "name": "BirthCertificate39", - "schema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"BirthCertificate\": {\n \"$ref\": \"#/definitions/BirthCertificate\"\n }\n },\n \"required\": [\n \"BirthCertificate\"\n ],\n \"title\": \"BirthCertificate\",\n \"definitions\": {\n \"BirthCertificate\": {\n \"$id\": \"#/properties/BirthCertificate\",\n \"type\": \"object\",\n \"title\": \"The BirthCertificate Schema\",\n \"required\": [\n \"name\",\n \"gender\",\n \"date_of_birth\",\n \"place_of_birth\",\n \"contact\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"gender\": {\n \"type\": \"string\"\n },\n \"date_of_birth\": {\n \"type\": \"string\",\n \"format\": \"date-time\"\n },\n \"hospital\": {\n \"type\": \"string\"\n },\n \"place_of_birth\": {\n \"type\": \"string\",\n \"enum\": [\"Bangalore\", \"Mysore\", \"Mandya\"]\n },\n \"name_of_mother\": {\n \"type\": \"string\"\n },\n \"name_of_father\": {\n \"type\": \"string\"\n },\n \"present_address\": {\n \"type\": \"string\",\n \"minLength\": 10,\n \"maxLength\": 50\n },\n \"contact\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"_osConfig\": {\n \"uniqueIndexFields\": [\n \"contact\"\n ],\n \"ownershipAttributes\": [],\n \"roles\": [],\n \"inviteRoles\": [\n \"anonymous\"\n ],\n \"credentialTemplate\": {\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n {\n \"@context\": {\n \"@version\": 1.1,\n \"@protected\": true,\n \"BirthCertificate\": {\n \"@id\": \"https://github.com/sunbird-specs/vc-specs#BirthCertificate\",\n \"@context\": {\n \"id\": \"@id\",\n \"@version\": 1.1,\n \"@protected\": true,\n \"skills\": \"schema:Text\",\n \"name\": \"schema:Text\",\n \"gender\": \"schema:Text\",\n \"date_of_birth\": \"schema:Text\",\n \"hospital\": \"schema:Text\",\n \"place_of_birth\": \"schema:Text\",\n \"name_of_mother\": \"schema:Text\",\n \"name_of_father\": \"schema:Text\",\n \"present_address\": \"schema:Text\",\n \"contact\": \"schema:Text\"\n }\n }\n }\n }\n ],\n \"type\": [\n \"VerifiableCredential\"\n ],\n \"issuanceDate\": \"2021-08-27T10:57:57.237Z\",\n \"credentialSubject\": {\n \"type\": \"BirthCertificate\",\n \"name\": \"{{name}}\",\n \"gender\": \"{{gender}}\",\n \"date_of_birth\": \"{{date_of_birth}}\",\n \"hospital\": \"{{hospital}}\",\n \"place_of_birth\": \"{{place_of_birth}}\",\n \"name_of_mother\": \"{{name_of_mother}}\",\n \"name_of_father\": \"{{name_of_father}}\",\n \"present_address\": \"{{present_address}}\",\n \"contact\": \"{{contact}}\"\n },\n \"issuer\": \"did:web:sunbirdrc.dev/vc/BirthCertificate\"\n },\n \"certificateTemplates\": {\n \"html\": \"https://gist.githubusercontent.com/tejash-jl/56b97ffcb99f93e2e1ec49e88c0c2c7f/raw/1242b9af7f58b9d5ca1e4f11d442aa4815598a31/BirthCertificate.html\"\n }\n }\n}", + "name": "BirthCertificate139", + "schema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"BirthCertificate1\": {\n \"$ref\": \"#/definitions/BirthCertificate1\"\n }\n },\n \"required\": [\n \"BirthCertificate1\"\n ],\n \"title\": \"BirthCertificate1\",\n \"definitions\": {\n \"BirthCertificate1\": {\n \"$id\": \"#/properties/BirthCertificate1\",\n \"type\": \"object\",\n \"title\": \"The BirthCertificate1 Schema\",\n \"required\": [\n \"name\",\n \"gender\",\n \"date_of_birth\",\n \"place_of_birth\",\n \"contact\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"gender\": {\n \"type\": \"string\"\n },\n \"date_of_birth\": {\n \"type\": \"string\",\n \"format\": \"date-time\"\n },\n \"hospital\": {\n \"type\": \"string\"\n },\n \"place_of_birth\": {\n \"type\": \"string\",\n \"enum\": [\"Bangalore\", \"Mysore\", \"Mandya\"]\n },\n \"name_of_mother\": {\n \"type\": \"string\"\n },\n \"name_of_father\": {\n \"type\": \"string\"\n },\n \"present_address\": {\n \"type\": \"string\",\n \"minLength\": 10,\n \"maxLength\": 50\n },\n \"contact\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"_osConfig\": {\n \"uniqueIndexFields\": [\n \"contact\"\n ],\n \"ownershipAttributes\": [],\n \"roles\": [],\n \"inviteRoles\": [\n \"anonymous\"\n ],\n \"credentialTemplate\": {\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n {\n \"@context\": {\n \"@version\": 1.1,\n \"@protected\": true,\n \"BirthCertificate1\": {\n \"@id\": \"https://github.com/sunbird-specs/vc-specs#BirthCertificate1\",\n \"@context\": {\n \"id\": \"@id\",\n \"@version\": 1.1,\n \"@protected\": true,\n \"skills\": \"schema:Text\",\n \"name\": \"schema:Text\",\n \"gender\": \"schema:Text\",\n \"date_of_birth\": \"schema:Text\",\n \"hospital\": \"schema:Text\",\n \"place_of_birth\": \"schema:Text\",\n \"name_of_mother\": \"schema:Text\",\n \"name_of_father\": \"schema:Text\",\n \"present_address\": \"schema:Text\",\n \"contact\": \"schema:Text\"\n }\n }\n }\n }\n ],\n \"type\": [\n \"VerifiableCredential\"\n ],\n \"issuanceDate\": \"2021-08-27T10:57:57.237Z\",\n \"credentialSubject\": {\n \"type\": \"BirthCertificate1\",\n \"name\": \"{{name}}\",\n \"gender\": \"{{gender}}\",\n \"date_of_birth\": \"{{date_of_birth}}\",\n \"hospital\": \"{{hospital}}\",\n \"place_of_birth\": \"{{place_of_birth}}\",\n \"name_of_mother\": \"{{name_of_mother}}\",\n \"name_of_father\": \"{{name_of_father}}\",\n \"present_address\": \"{{present_address}}\",\n \"contact\": \"{{contact}}\"\n },\n \"issuer\": \"did:web:sunbirdrc.dev/vc/BirthCertificate1\"\n },\n \"certificateTemplates\": {\n \"html\": \"https://gist.githubusercontent.com/tejash-jl/56b97ffcb99f93e2e1ec49e88c0c2c7f/raw/1242b9af7f58b9d5ca1e4f11d442aa4815598a31/BirthCertificate1.html\"\n }\n }\n}", "status": "PUBLISHED" } \ No newline at end of file diff --git a/java/apitest/src/test/java/e2e/registry/StudentSchemaRequest.json b/java/apitest/src/test/java/e2e/registry/StudentSchemaRequest.json index 5efb7c0bf..4e6286bae 100644 --- a/java/apitest/src/test/java/e2e/registry/StudentSchemaRequest.json +++ b/java/apitest/src/test/java/e2e/registry/StudentSchemaRequest.json @@ -1,5 +1,5 @@ { "name": "Student7", - "schema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"Student\": {\n \"$ref\": \"#/definitions/Student\"\n }\n },\n \"required\": [\n \"Student\"\n ],\n \"title\": \"Student\",\n \"definitions\": {\n \"Student\": {\n \"$id\": \"#/properties/Student\",\n \"type\": \"object\",\n \"title\": \"The Student Schema\",\n \"required\": [\n \"name\",\n \"gender\",\n \"contact\",\n \"email\"\n, \n \"favoriteSubject\" ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"gender\": {\n \"type\": \"string\"\n },\n \"contact\": {\n \"type\": \"string\"\n },\n \"email\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"_osConfig\": {\n\"privateFields\": [\"contact\"], \n\"internalFields\": [\"favoriteSubject\"],\n \"uniqueIndexFields\": [],\n \"ownershipAttributes\": [{\n \"email\": \"/email\",\n \"mobile\": \"/contact\",\n \"userId\": \"/contact\"\n }],\n \"roles\": [],\n \"inviteRoles\": [\n \"anonymous\"\n ]\n }\n}", + "schema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"Student\": {\n \"$ref\": \"#/definitions/Student\"\n }\n },\n \"required\": [\n \"Student\"\n ],\n \"title\": \"Student\",\n \"definitions\": {\n \"Student\": {\n \"$id\": \"#/properties/Student\",\n \"type\": \"object\",\n \"title\": \"The Student Schema\",\n \"required\": [\n \"name\",\n \"gender\",\n \"contact\",\n \"email\"\n,\n \"favoriteSubject\" ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"gender\": {\n \"type\": \"string\"\n },\n \"contact\": {\n \"type\": \"string\"\n },\n \"email\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"_osConfig\": {\n\"privateFields\": [\"contact\"],\n\"internalFields\": [\"favoriteSubject\"],\n \"uniqueIndexFields\": [],\n \"ownershipAttributes\": [{\n \"email\": \"/email\",\n \"mobile\": \"/contact\",\n \"userId\": \"/contact\"\n }],\n \"roles\": [],\n \"inviteRoles\": [\n \"anonymous\"\n ],\n \"notificationTemplates\": {\n \"invite\": [{\n \"subject\": \"Student\",\n \"body\": \"{\\\"sender\\\": \\\"AppName\\\",\\\"route\\\": \\\"4\\\",\\\"country\\\": \\\"91\\\",\\\"unicode\\\": 1,\\\"sms\\\": [{ \\\"message\\\": \\\"{{name}}, Your {{entityType}} credential has been created\\\", \\\"to\\\": [ \\\"{{contact}}\\\"]}]}\"\n }],\n \"create\": [{\n \"subject\": \"Student\",\n \"body\": \"{\\\"sender\\\": \\\"AppName\\\",\\\"route\\\": \\\"4\\\",\\\"country\\\": \\\"91\\\",\\\"unicode\\\": 1,\\\"sms\\\": [{ \\\"message\\\": \\\"{{name}}, Your {{entityType}} credential has been created\\\", \\\"to\\\": [ \\\"{{contact}}\\\"]}]}\"\n }]\n }\n }\n}", "status": "PUBLISHED" } diff --git a/java/apitest/src/test/java/e2e/registry/registry.feature b/java/apitest/src/test/java/e2e/registry/registry.feature index 2fccccaf0..4348f936f 100644 --- a/java/apitest/src/test/java/e2e/registry/registry.feature +++ b/java/apitest/src/test/java/e2e/registry/registry.feature @@ -3,6 +3,7 @@ Feature: Registry api tests Background: * string registryUrl = "http://localhost:8081" * string metricsUrl = "http://localhost:8070" + * string notificationsUrl = "http://localhost:8765" * string authUrl = "http://localhost:8080" * url registryUrl * def admin_token = "" @@ -420,21 +421,21 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.params.status == "SUCCESSFUL" # create entity for birth certificate Given url registryUrl - And path 'api/v1/BirthCertificate' + And path 'api/v1/BirthCertificate1' And request read('BirthCertificateRequest.json') When method post Then status 200 - And def birthCertificateOsid = response.result.BirthCertificate.osid + And def birthCertificateOsid = response.result.BirthCertificate1.osid # get entity by id Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token When method get Then status 200 And response._osSignedData.length > 0 # modify entity Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token * def requestBody = read('BirthCertificateRequest.json') * requestBody.name = "test" @@ -444,7 +445,7 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.params.status == "SUCCESSFUL" # get entity by id Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token When method get Then status 200 @@ -452,7 +453,7 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response._osSignedData.contains("test") # get certificate for entity Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token And header Accept = "text/html" And header template-key = "html" @@ -461,7 +462,7 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.length > 0 # get VC for entity Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token And header Accept = "application/vc+ld+json" When method get @@ -469,21 +470,21 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.credentialSubject.name == "test" # revoke entity by id Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + '/revoke' + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid + '/revoke' And header Authorization = admin_token When method post Then status 200 And response.params.status == "SUCCESSFUL" # get entity by id and check whether signed data got removed and still we are able to fetch the data Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token When method get Then status 200 And response._osSignedData.length = 0 # Try to revoke the same entity again it should inform that the VC is already revoked Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + '/revoke' + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid + '/revoke' And header Authorization = admin_token When method post Then status 500 @@ -491,7 +492,7 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.params.errmsg == "Credential is already revoked" # Now try deleting the entity by id Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token When method delete Then status 200 @@ -499,7 +500,7 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And response.params.status == "SUCCESSFUL" # get entity by id and check for its status Given url registryUrl - And path 'api/v1/BirthCertificate/' + birthCertificateOsid + And path 'api/v1/BirthCertificate1/' + birthCertificateOsid And header Authorization = admin_token When method get Then status 404 @@ -518,4 +519,15 @@ Scenario: Create birth certificate schema, issue credentials then revoke the cre And assert response.birthcertificate.READ == "5" And assert response.birthcertificate.UPDATE == "1" And assert response.birthcertificate.ADD == "1" - And assert response.birthcertificate.DELETE == "1" \ No newline at end of file + And assert response.birthcertificate.DELETE == "1" + + @env=notification + Scenario: Check if notifications are sent + Given url notificationsUrl + And path '/notification-service/v1/notification' + When method get + Then status 200 + * def studentRequest = read('StudentRequest.json') + * def notificationStudent = studentRequest.contact + And print response[notificationStudent] + And assert response[notificationStudent] != null \ No newline at end of file diff --git a/java/registry/pom.xml b/java/registry/pom.xml index 46fb30968..3a1dc4b06 100644 --- a/java/registry/pom.xml +++ b/java/registry/pom.xml @@ -83,7 +83,11 @@ keycloak-spring-security-adapter 14.0.0 - + + com.github.jknack + handlebars + 4.3.1 + org.keycloak keycloak-spring-boot-starter diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/Constants.java b/java/registry/src/main/java/dev/sunbirdrc/registry/Constants.java index 29bd920d6..fc2360f16 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/Constants.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/Constants.java @@ -9,6 +9,10 @@ public class Constants { public static final String CLAIM_STATUS_SUBJECT_TEMPLATE = "CLAIM %s"; public static final String CLAIM_STATUS_BODY_TEMPLATE = "Your claim request has been %s"; public static final String INVITE = "INVITE"; + public static final String CREATE = "CREATE"; + public static final String UPDATE = "UPDATE"; + public static final String DELETE = "DELETE"; + public static final String REVOKE = "REVOKE"; public static final String CLAIM_GRANTED = "CLAIM_GRANTED"; public static final String CLAIM_REJECTED = "CLAIM_REJECTED"; public static final String USER_ANONYMOUS = "anonymous"; diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryAttestationPolicyController.java b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryAttestationPolicyController.java index be13ab3b7..d515d4714 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryAttestationPolicyController.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryAttestationPolicyController.java @@ -135,7 +135,7 @@ public ResponseEntity deleteAttestationPolicy(@PathVariable String entityName, @ if (attestationPolicyOptional.isPresent() && attestationPolicyOptional.get().getCreatedBy().equals(userId)) { logger.info("Updating attestation policy status of id: {}", policyId); AttestationPolicy attestationPolicy = attestationPolicyOptional.get(); - registryHelper.deleteAttestationPolicy(attestationPolicy); + registryHelper.deleteAttestationPolicy(entityName, attestationPolicy); response.setResult("deleted"); responseParams.setStatus(Response.Status.SUCCESSFUL); return new ResponseEntity<>(response, HttpStatus.OK); diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryController.java b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryController.java index 9512a7184..aaa24997d 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryController.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryController.java @@ -79,7 +79,7 @@ public ResponseEntity deleteEntity() { RecordIdentifier recordId = RecordIdentifier.parse(entityId); String shardId = dbConnectionInfoMgr.getShardId(recordId.getShardLabel()); Shard shard = shardManager.activateShard(shardId); - registryService.deleteEntityById(shard, apiMessage.getUserID(), recordId.getUuid()); + registryService.deleteEntityById(shard, entityType, apiMessage.getUserID(), recordId.getUuid()); responseParams.setErrmsg(""); responseParams.setStatus(Response.Status.SUCCESSFUL); } catch (UnsupportedOperationException e) { diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryEntityController.java b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryEntityController.java index 838bf4a79..eb339f5a8 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryEntityController.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/controller/RegistryEntityController.java @@ -130,7 +130,7 @@ public ResponseEntity deleteEntity( try { String tag = "RegistryController.delete " + entityName; watch.start(tag); - Vertex deletedEntity = registryHelper.deleteEntity(entityId, userId); + Vertex deletedEntity = registryHelper.deleteEntity(entityName, entityId, userId); if (deletedEntity != null && deletedEntity.keys().contains(OSSystemFields._osSignedData.name())) { registryHelper.revokeExistingCredentials(entityName, entityId, userId, deletedEntity.value(OSSystemFields._osSignedData.name())); } diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java index f931f8cd8..c17e646b7 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/helper/RegistryHelper.java @@ -16,6 +16,7 @@ import dev.sunbirdrc.pojos.attestation.Action; import dev.sunbirdrc.pojos.attestation.States; import dev.sunbirdrc.pojos.attestation.exception.PolicyNotFoundException; +import dev.sunbirdrc.registry.dao.VertexReader; import dev.sunbirdrc.registry.entities.AttestationPolicy; import dev.sunbirdrc.registry.entities.AttestationType; import dev.sunbirdrc.registry.entities.FlowType; @@ -89,7 +90,8 @@ public class RegistryHelper { @Value("${notification.service.enabled}") boolean notificationEnabled; @Value("${invite.required_validation_enabled}") boolean skipRequiredValidationForInvite = true; @Value("${invite.signature_enabled}") boolean skipSignatureForInvite = true; - + @Autowired + private NotificationHelper notificationHelper; @Autowired private ShardManager shardManager; @@ -190,12 +192,14 @@ public JsonNode removeFormatAttr(JsonNode requestBody) { * @throws Exception */ public String addEntity(JsonNode inputJson, String userId) throws Exception { - return addEntityHandler(inputJson, userId, false, false); + String entityId = addEntityHandler(inputJson, userId, false, false); + notificationHelper.sendNotification(inputJson, CREATE); + return entityId; } public String inviteEntity(JsonNode inputJson, String userId) throws Exception { String entityId = addEntityHandler(inputJson, userId, skipRequiredValidationForInvite, skipSignatureForInvite); - sendInviteNotification(inputJson); + notificationHelper.sendNotification(inputJson, INVITE); return entityId; } @@ -222,27 +226,6 @@ private String addEntityHandler(JsonNode inputJson, String userId, boolean skipR return addEntity(inputJson, userId, entityType, skipSignature); } - private void sendInviteNotification(JsonNode inputJson) throws Exception { - String entityType = inputJson.fields().next().getKey(); - sendNotificationToOwners(inputJson, INVITE, String.format(INVITE_SUBJECT_TEMPLATE, entityType), String.format(INVITE_BODY_TEMPLATE, entityType)); - } - - private void sendNotificationToOwners(JsonNode inputJson, String operation, String subject, String message) throws Exception { - if (notificationEnabled) { - String entityType = inputJson.fields().next().getKey(); - for (ObjectNode owners : entityStateHelper.getOwnersData(inputJson, entityType)) { - String ownerMobile = owners.get(MOBILE).asText(""); - String ownerEmail = owners.get(EMAIL).asText(""); - if (!StringUtils.isEmpty(ownerMobile)) { - registryService.callNotificationActors(operation, String.format("tel:%s", ownerMobile), subject, message); - } - if (!StringUtils.isEmpty(ownerEmail)) { - registryService.callNotificationActors(operation, String.format("mailto:%s", ownerEmail), subject, message); - } - } - } - } - private String addEntity(JsonNode inputJson, String userId, String entityType, boolean skipSignature) throws Exception { RecordIdentifier recordId; try { @@ -378,6 +361,7 @@ public String updateProperty(JsonNode inputJson, String userId) throws Exception RecordIdentifier recordId = RecordIdentifier.parse(label); logger.info("Update Api: shard id: " + recordId.getShardLabel() + " for uuid: " + recordId.getUuid()); registryService.updateEntity(shard, userId, recordId.getUuid(), jsonString, false); + notificationHelper.sendNotification(inputJson, UPDATE); return "SUCCESS"; } @@ -388,6 +372,7 @@ public void updateEntityAndState(JsonNode existingNode, JsonNode updatedNode, St updatedNode = entityStateHelper.applyWorkflowTransitions(existingNode, updatedNode, attestationPolicies); } updateEntity(updatedNode, userId); + notificationHelper.sendNotification(updatedNode, UPDATE); } public void addEntityProperty(String entityName, String entityId, JsonNode inputJson, HttpServletRequest request) throws Exception { @@ -1004,11 +989,16 @@ public void signDocument(String entityName, String entityId, String userId) thro } } - public Vertex deleteEntity(String entityId, String userId) throws Exception { + public Vertex deleteEntity(String entityName, String entityId, String userId) throws Exception { RecordIdentifier recordId = RecordIdentifier.parse(entityId); String shardId = dbConnectionInfoMgr.getShardId(recordId.getShardLabel()); Shard shard = shardManager.activateShard(shardId); - return registryService.deleteEntityById(shard, userId, recordId.getUuid()); + ReadConfigurator configurator = ReadConfiguratorFactory.getOne(false); + Vertex vertex = registryService.deleteEntityById(shard, entityName, userId, recordId.getUuid()); + VertexReader vertexReader = new VertexReader(shard.getDatabaseProvider(), vertex.graph(), configurator, uuidPropertyName, definitionsManager); + JsonNode deletedNode = JsonNodeFactory.instance.objectNode().set(entityName, vertexReader.constructObject(vertex)); + notificationHelper.sendNotification(deletedNode, DELETE); + return vertex; } public JsonNode revokeAnEntity (String entityName, String entityId, String userId, JsonNode currentJsonNode) throws Exception { @@ -1122,8 +1112,8 @@ public Optional findAttestationPolicyById(String userId, Stri } - public void deleteAttestationPolicy(AttestationPolicy attestationPolicy) throws Exception { - deleteEntity(attestationPolicy.getOsid(), attestationPolicy.getCreatedBy()); + public void deleteAttestationPolicy(String entityName, AttestationPolicy attestationPolicy) throws Exception { + deleteEntity(entityName, attestationPolicy.getOsid(), attestationPolicy.getCreatedBy()); } public boolean doesEntityOperationRequireAuthorization(String entity) { diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplate.java b/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplate.java new file mode 100644 index 000000000..3d54f4eaa --- /dev/null +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplate.java @@ -0,0 +1,13 @@ +package dev.sunbirdrc.registry.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class NotificationTemplate { + private String subject; + private String body; +} diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplates.java b/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplates.java new file mode 100644 index 000000000..ea036b6ae --- /dev/null +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/model/NotificationTemplates.java @@ -0,0 +1,23 @@ +package dev.sunbirdrc.registry.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class NotificationTemplates { + private List create; + private List update; + private List invite; + private List delete; + private List revoke; + + public NotificationTemplates() { + create = new ArrayList<>(); + update = new ArrayList<>(); + invite = new ArrayList<>(); + delete = new ArrayList<>(); + revoke = new ArrayList<>(); + } +} diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/NotificationHelper.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/NotificationHelper.java new file mode 100644 index 000000000..36fafa781 --- /dev/null +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/NotificationHelper.java @@ -0,0 +1,100 @@ +package dev.sunbirdrc.registry.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Template; +import dev.sunbirdrc.registry.helper.EntityStateHelper; +import dev.sunbirdrc.registry.middleware.util.JSONUtil; +import dev.sunbirdrc.registry.model.NotificationTemplate; +import dev.sunbirdrc.registry.util.IDefinitionsManager; +import dev.sunbirdrc.registry.util.OSSchemaConfiguration; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static dev.sunbirdrc.registry.Constants.*; +import static dev.sunbirdrc.registry.middleware.util.Constants.EMAIL; +import static dev.sunbirdrc.registry.middleware.util.Constants.MOBILE; + +@Service +public class NotificationHelper { + private static Logger logger = LoggerFactory.getLogger(NotificationHelper.class); + boolean notificationEnabled; + private IDefinitionsManager definitionsManager; + private EntityStateHelper entityStateHelper; + private RegistryService registryService; + private ObjectMapper objectMapper; + @Autowired + public NotificationHelper(@Value("${notification.service.enabled}") boolean notificationEnabled, IDefinitionsManager definitionsManager, EntityStateHelper entityStateHelper, RegistryService registryService, ObjectMapper objectMapper) { + this.notificationEnabled = notificationEnabled; + this.definitionsManager = definitionsManager; + this.entityStateHelper = entityStateHelper; + this.registryService = registryService; + this.objectMapper = objectMapper; + } + + public NotificationHelper() { + } + + public void sendNotification(JsonNode inputJson, String operationType) throws Exception { + String entityType = inputJson.fields().next().getKey(); + List templates = getNotificationTemplate(entityType, operationType); + Map objectNodeMap = (Map) JSONUtil.convertJsonNodeToMap(inputJson).get(entityType); + objectNodeMap.put("entityType", entityType); + for(NotificationTemplate template: templates) { + String bodyTemplate = template.getBody(); + String subjectTemplate = template.getSubject(); + String bodyString = compileMessageFromTemplate(bodyTemplate, objectNodeMap); + String subjectString = compileMessageFromTemplate(subjectTemplate, objectNodeMap); + List owners = entityStateHelper.getOwnersData(inputJson, entityType); + sendNotificationToOwners(owners, operationType, subjectString, bodyString); + } + } + + private void sendNotificationToOwners(List owners, String operation, String subject, String message) throws Exception { + if (notificationEnabled) { + for (ObjectNode owner :owners) { + String ownerMobile = owner.get(MOBILE).asText(""); + String ownerEmail = owner.get(EMAIL).asText(""); + if (!StringUtils.isEmpty(ownerMobile)) { + registryService.callNotificationActors(operation, String.format("tel:%s", ownerMobile), subject, message); + } + if (!StringUtils.isEmpty(ownerEmail)) { + registryService.callNotificationActors(operation, String.format("mailto:%s", ownerEmail), subject, message); + } + } + } + } + private List getNotificationTemplate(String entityType, String operationType) { + OSSchemaConfiguration osSchemaConfiguration = definitionsManager.getDefinition(entityType).getOsSchemaConfiguration(); + switch(operationType) { + case CREATE: + return osSchemaConfiguration.getNotificationTemplates().getCreate(); + case UPDATE: + return osSchemaConfiguration.getNotificationTemplates().getUpdate(); + case INVITE: + return osSchemaConfiguration.getNotificationTemplates().getInvite(); + case DELETE: + return osSchemaConfiguration.getNotificationTemplates().getDelete(); + case REVOKE: + return osSchemaConfiguration.getNotificationTemplates().getRevoke(); + } + return null; + } + + private String compileMessageFromTemplate(String messageBodySubject, Map objectNodeMap) throws IOException { + Handlebars handlebars = new Handlebars(); + Template messageTemplate = handlebars.compileInline(messageBodySubject); + return messageTemplate.apply(objectNodeMap); + } + +} diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/RegistryService.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/RegistryService.java index 2ca864f53..73e1ad87d 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/RegistryService.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/RegistryService.java @@ -12,7 +12,7 @@ public interface RegistryService { HealthCheckResponse health(Shard shard) throws Exception; - Vertex deleteEntityById(Shard shard, String userId, String id) throws Exception; + Vertex deleteEntityById(Shard shard, String entityName, String userId, String id) throws Exception; String addEntity(Shard shard, String userId, JsonNode inputJson, boolean skipSignature) throws Exception; diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java index dab01bbf4..fd4305430 100755 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/service/impl/RegistryServiceImpl.java @@ -32,6 +32,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.sunbird.akka.core.ActorCache; @@ -100,6 +102,15 @@ public class RegistryServiceImpl implements RegistryService { @Value("${registry.context.base}") private String registryBaseUrl; + @Value("${notification.async.enabled}") + private boolean asyncEnabled; + + @Value("${notification.topic}") + private String notifyTopic; + + @Autowired + private KafkaTemplate kafkaTemplate; + @Autowired private EntityParenter entityParenter; @@ -123,7 +134,6 @@ public class RegistryServiceImpl implements RegistryService { @Autowired private List healthIndicators; - public HealthCheckResponse health(Shard shard) throws Exception { HealthCheckResponse healthCheck; AtomicBoolean overallHealthStatus = new AtomicBoolean(true); @@ -147,7 +157,7 @@ public HealthCheckResponse health(Shard shard) throws Exception { * @throws Exception */ @Override - public Vertex deleteEntityById(Shard shard, String userId, String uuid) throws Exception { + public Vertex deleteEntityById(Shard shard, String entityName, String userId, String uuid) throws Exception { DatabaseProvider databaseProvider = shard.getDatabaseProvider(); IRegistryDao registryDao = new RegistryDaoImpl(databaseProvider, definitionsManager, uuidPropertyName); try (OSGraph osGraph = databaseProvider.getOSGraph()) { @@ -155,7 +165,7 @@ public Vertex deleteEntityById(Shard shard, String userId, String uuid) throws E try (Transaction tx = databaseProvider.startTransaction(graph)) { ReadConfigurator configurator = ReadConfiguratorFactory.getOne(false); VertexReader vertexReader = new VertexReader(databaseProvider, graph, configurator, uuidPropertyName, definitionsManager); - Vertex vertex = vertexReader.getVertex(null, uuid); + Vertex vertex = vertexReader.getVertex(entityName, uuid); String index = vertex.property(Constants.TYPE_STR_JSON_LD).isPresent() ? (String) vertex.property(Constants.TYPE_STR_JSON_LD).value() : null; if (!StringUtils.isEmpty(index) && index.equals(Schema)) { schemaService.deleteSchemaIfExists(vertex); @@ -407,6 +417,11 @@ private boolean isElasticSearchEnabled() { @Override @Async("taskExecutor") public void callNotificationActors(String operation, String to, String subject, String message) throws JsonProcessingException { + if(asyncEnabled) { + String payload = "{\"message\":\"" + message + "\", \"subject\": \"" + subject + "\", \"recipient\": \"" + to + "\"}"; + kafkaTemplate.send(notifyTopic, null, payload); + return; + } logger.debug("callNotificationActors started"); MessageProtos.Message messageProto = MessageFactory.instance().createNotificationActorMessage(operation, to, subject, message); ActorCache.instance().get(Router.ROUTER_NAME).tell(messageProto, null); diff --git a/java/registry/src/main/java/dev/sunbirdrc/registry/util/OSSchemaConfiguration.java b/java/registry/src/main/java/dev/sunbirdrc/registry/util/OSSchemaConfiguration.java index 0e4b4c366..fd4769d79 100644 --- a/java/registry/src/main/java/dev/sunbirdrc/registry/util/OSSchemaConfiguration.java +++ b/java/registry/src/main/java/dev/sunbirdrc/registry/util/OSSchemaConfiguration.java @@ -4,6 +4,7 @@ import dev.sunbirdrc.pojos.OwnershipsAttributes; import dev.sunbirdrc.pojos.attestation.auto.AutoAttestationPolicy; import dev.sunbirdrc.registry.entities.AttestationPolicy; +import dev.sunbirdrc.registry.model.NotificationTemplates; import dev.sunbirdrc.registry.model.EventConfig; import dev.sunbirdrc.views.FunctionDefinition; import lombok.Data; @@ -93,6 +94,7 @@ public class OSSchemaConfiguration { private EventConfig internalFieldConfig = EventConfig.NONE; private List functionDefinitions; + private NotificationTemplates notificationTemplates = new NotificationTemplates(); public Set getAllTheAttestorEntities(){ return attestationPolicies.stream() diff --git a/java/registry/src/main/resources/application.yml b/java/registry/src/main/resources/application.yml index 1b5fb9890..230122be1 100644 --- a/java/registry/src/main/resources/application.yml +++ b/java/registry/src/main/resources/application.yml @@ -26,6 +26,10 @@ notification: enabled: ${notification_enabled:false} connection_url: ${notification_url:http://localhost:8765/notification-service/v1/notification} health_url: ${notification_url:http://localhost:8765/notification-service/v1/health} + async: + enabled: ${notification_async_enabled:false} + topic: ${notification_topic:notify} + invite: required_validation_enabled: ${invite_required_validation_enabled:true} signature_enabled: ${invite_signature_enabled:true} @@ -225,7 +229,7 @@ audit: keycloak: #publicKey: ${sunbird_sso_publickey:pk} - auth-server-url: ${sunbird_sso_url:https://localhost:8888/auth} + auth-server-url: ${sunbird_sso_url:http://localhost:8080/auth} realm: ${sunbird_sso_realm:sunbird-rc} resource: ${sunbird_sso_client_id:client} keycloak-admin: @@ -239,7 +243,7 @@ keycloak-user: # email details should be configured in keycloak realm settings email-actions: ${keycloack_user_email_actions:} claims: - url: ${claims_url:http://localhost:8081} + url: ${claims_url:http://localhost:8082} authentication: enabled: ${authentication_enabled:true} publicKey: ${authentication_publickey:} @@ -329,7 +333,9 @@ name: test-yaml enviroment: test workflow: enabled: ${workflow.enable:true} - +notification: + service: + enabled: true perf: monitoring: enabled: false diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java index 6c00506a4..8be779999 100644 --- a/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/helper/RegistryHelperTest.java @@ -18,6 +18,7 @@ import dev.sunbirdrc.registry.middleware.util.Constants; import dev.sunbirdrc.registry.model.DBConnectionInfoMgr; import dev.sunbirdrc.registry.service.*; +import dev.sunbirdrc.registry.sink.DatabaseProvider; import dev.sunbirdrc.registry.sink.shard.Shard; import dev.sunbirdrc.registry.sink.shard.ShardManager; import dev.sunbirdrc.registry.util.*; @@ -28,6 +29,8 @@ import dev.sunbirdrc.workflow.KieConfiguration; import dev.sunbirdrc.workflow.RuleEngineService; import org.apache.commons.io.IOUtils; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Before; @@ -39,8 +42,11 @@ import org.mockito.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.ReflectionTestUtils; import org.sunbird.akka.core.SunbirdActorFactory; @@ -51,9 +57,7 @@ import java.nio.charset.Charset; import java.util.*; -import static dev.sunbirdrc.registry.Constants.ATTESTATION_POLICY; -import static dev.sunbirdrc.registry.Constants.REQUESTER; -import static dev.sunbirdrc.registry.Constants.REVOKED_CREDENTIAL; +import static dev.sunbirdrc.registry.Constants.*; import static dev.sunbirdrc.registry.middleware.util.Constants.FILTERS; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; @@ -127,15 +131,10 @@ private String getBaseDir() { @Mock private SignatureService signatureService; - - private static final String INSTITUTE = "Institute"; - @Before public void initMocks() { objectMapper = new ObjectMapper(); registryHelper.setObjectMapper(objectMapper); - ReflectionTestUtils.setField(registryHelper, "auditSuffix", "Audit"); - ReflectionTestUtils.setField(registryHelper, "auditSuffixSeparator", "_"); MockitoAnnotations.initMocks(this); registryHelper.uuidPropertyName = "osid"; RuleEngineService ruleEngineService = new RuleEngineService(kieContainer, keycloakAdminUtil); @@ -338,6 +337,9 @@ public void shouldAbleToGetThePropertyIdForTheRequestBody() throws Exception { @Mock AsyncRequest asyncRequest; + @Mock + NotificationHelper notificationHelper; + @Test public void shouldCreateOwnersForInvite() throws Exception { JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"instituteName\":\"gecasu\"}}"); @@ -347,9 +349,11 @@ public void shouldCreateOwnersForInvite() throws Exception { when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString()); when(shardManager.getShard(any())).thenReturn(new Shard()); ReflectionTestUtils.setField(registryHelper, "workflowEnabled", true); + doNothing().when(notificationHelper).sendNotification(any(), any()); registryHelper.inviteEntity(inviteJson, ""); Mockito.verify(registryService).addEntity(shardCapture.capture(), userIdCapture.capture(), inputJsonCapture.capture(), anyBoolean()); assertEquals("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"instituteName\":\"gecasu\",\"osOwner\":[\"" + testUserId + "\"]}}", inputJsonCapture.getValue().toString()); + verify(notificationHelper, times(1)).sendNotification(any(), any()); } @Test @@ -361,12 +365,10 @@ public void shouldSendInviteInvitationsAfterCreatingOwners() throws Exception { when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString()); when(shardManager.getShard(any())).thenReturn(new Shard()); ReflectionTestUtils.setField(registryHelper, "notificationEnabled", true); + doNothing().when(notificationHelper).sendNotification(inviteJson, INVITE); registryHelper.inviteEntity(inviteJson, ""); Mockito.verify(registryService).addEntity(shardCapture.capture(), userIdCapture.capture(), inputJsonCapture.capture(), anyBoolean()); - Mockito.verify(registryService, atLeastOnce()).callNotificationActors(operationCapture.capture(), toCapture.capture(), subjectCapture.capture(), messageCapture.capture()); - assertEquals("mailto:gecasu.ihises@tovinit.com", toCapture.getValue()); - assertEquals("INVITATION TO JOIN Institute", subjectCapture.getValue()); - assertEquals("You have been invited to join Institute registry. You can complete your profile here: https://ndear.xiv.in", messageCapture.getValue()); + verify(notificationHelper, times(1)).sendNotification(inviteJson, INVITE); } private void mockDefinitionManager() throws IOException { @@ -393,15 +395,10 @@ public void shouldSendMultipleInviteInvitationsAfterCreatingOwners() throws Exce when(shardManager.getShard(any())).thenReturn(new Shard()); mockDefinitionManager(); ReflectionTestUtils.setField(registryHelper, "notificationEnabled", true); + doNothing().when(notificationHelper).sendNotification(any(), any()); registryHelper.inviteEntity(inviteJson, ""); Mockito.verify(registryService).addEntity(shardCapture.capture(), userIdCapture.capture(), inputJsonCapture.capture(), anyBoolean()); - Mockito.verify(registryService, times(4)).callNotificationActors(operationCapture.capture(), toCapture.capture(), subjectCapture.capture(), messageCapture.capture()); - assertEquals("tel:123123", toCapture.getAllValues().get(0)); - assertEquals("INVITATION TO JOIN Institute", subjectCapture.getAllValues().get(0)); - assertEquals("You have been invited to join Institute registry. You can complete your profile here: https://ndear.xiv.in", messageCapture.getAllValues().get(0)); - assertEquals("mailto:gecasu.ihises@tovinit.com", toCapture.getAllValues().get(1)); - assertEquals("tel:1234", toCapture.getAllValues().get(2)); - assertEquals("mailto:admin@email.com", toCapture.getAllValues().get(3)); + verify(notificationHelper, times(1)).sendNotification(any(), any()); } @Test @@ -484,6 +481,7 @@ public void shouldTriggerNextAttestationFlow() throws Exception { .status("GRANT_CLAIM") .response("{}") .build(); + ObjectNode studentJson = getMockStudent(); ObjectNode attestationPolicyObject = JsonNodeFactory.instance.objectNode(); ArrayNode attestationArrayNodes = JsonNodeFactory.instance.arrayNode(); ObjectNode mockAttestationPolicy = JsonNodeFactory.instance.objectNode(); @@ -496,16 +494,17 @@ public void shouldTriggerNextAttestationFlow() throws Exception { attestationArrayNodes.add(mockAttestationPolicy2); attestationPolicyObject.set(ATTESTATION_POLICY, attestationArrayNodes); when(searchService.search(any())).thenReturn(attestationPolicyObject); - when(readService.getEntity(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(getMockStudent()); + when(readService.getEntity(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(studentJson); registryHelper.entityStateHelper = mock(EntityStateHelper.class); - when(registryHelper.entityStateHelper.manageState(any(), any(), any(), any(), any())).thenReturn(getMockStudent()); + when(registryHelper.entityStateHelper.manageState(any(), any(), any(), any(), any())).thenReturn(studentJson); when(dbConnectionInfoMgr.getUuidPropertyName()).thenReturn("osid"); + doNothing().when(notificationHelper).sendNotification(any(), any()); Config config = ConfigFactory.parseResources("sunbirdrc-actors.conf"); SunbirdActorFactory sunbirdActorFactory = new SunbirdActorFactory(config, "dev.sunbirdrc.actors"); sunbirdActorFactory.init("sunbirdrc-actors"); registryHelper.updateState(pluginResponseMessage); verify(registryHelper, times(1)).triggerAttestation(any(), any()); - + verify(notificationHelper, times(1)).sendNotification(any(), any()); } @Test @@ -536,7 +535,6 @@ public void shouldNotTriggerNextAttestationFlowIfOnCompleteIsNotPresent() throws sunbirdActorFactory.init("sunbirdrc-actors"); registryHelper.updateState(pluginResponseMessage); verify(registryHelper, times(0)).triggerAttestation(any(), any()); - } @Test @@ -577,7 +575,6 @@ public void shouldTriggerConcatFunctionOnAttestationCompleted() throws Exception sunbirdActorFactory.init("sunbirdrc-actors"); registryHelper.updateState(pluginResponseMessage); verify(functionExecutorMock, times(1)).execute(any(), any(), any()); - } @Test @@ -619,7 +616,6 @@ public void shouldTriggerProviderFunctionOnAttestationCompleted() throws Excepti sunbirdActorFactory.init("sunbirdrc-actors"); registryHelper.updateState(pluginResponseMessage); verify(functionExecutorMock, times(1)).execute(any(), any(), any()); - } @Test @@ -786,9 +782,11 @@ public void shouldRaiseClaimIfAttestationTypeIsAutomated() throws Exception { objectNode.set("fullName", JsonNodeFactory.instance.textNode("First Avenger")); objectNode.set("gender", JsonNodeFactory.instance.textNode("Male")); ReflectionTestUtils.setField(registryHelper, "workflowEnabled", true); + doNothing().when(notificationHelper).sendNotification(any(), any()); registryHelper.autoRaiseClaim("Student", "12345", "556302c9-d8b4-4f60-9ac1-c16c8839a9f3", null, requestBody, ""); verify(conditionResolverService, times(1)).resolve(objectNode, REQUESTER, null, Collections.emptyList()); verify(registryHelper, times(1)).triggerAttestation(any(), any()); + verify(notificationHelper, times(1)).sendNotification(any(), any()); } public void shouldStoredSignedDataInRevokedCredentialsRegistry() throws Exception { @@ -840,18 +838,21 @@ public void shouldNotContainShardIdInAsyncMode() throws Exception { @Test public void shouldContainShardIdInSyncMode() throws Exception { + mockDefinitionManager(); JsonNode inviteJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"instituteName\":\"gecasu\"}}"); Shard shard = mock(Shard.class); when(shard.getShardLabel()).thenReturn("1"); when(shardManager.getShard(any())).thenReturn(shard); - + doNothing().when(notificationHelper).sendNotification(any(), any()); when(registryService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString()); when(registryAsyncService.addEntity(any(), any(), any(), anyBoolean())).thenReturn(UUID.randomUUID().toString()); when(asyncRequest.isEnabled()).thenReturn(Boolean.FALSE); + ReflectionTestUtils.setField(registryHelper, "notificationEnabled", true); String entity = registryHelper.addEntity(inviteJson, ""); verify(registryService, atLeastOnce()).addEntity(any(), anyString(), any(), anyBoolean()); verify(registryAsyncService, never()).addEntity(any(), anyString(), any(), anyBoolean()); assertTrue(entity.startsWith("1-")); + verify(notificationHelper, times(1)).sendNotification(any(), any()); } void mockValidationService() throws IOException { @@ -893,8 +894,26 @@ public void shouldNotRaiseRequiredExceptionsIFFlagDisabled() throws Exception { when(shardManager.getShard(any())).thenReturn(new Shard()); ReflectionTestUtils.setField(registryHelper, "workflowEnabled", true); ReflectionTestUtils.setField(registryHelper, "skipRequiredValidationForInvite", true); + doNothing().when(notificationHelper).sendNotification(any(), any()); registryHelper.inviteEntity(inviteJson, ""); Mockito.verify(registryService).addEntity(shardCapture.capture(), userIdCapture.capture(), inputJsonCapture.capture(), anyBoolean()); assertEquals("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"osOwner\":[\"" + testUserId + "\"]}}", inputJsonCapture.getValue().toString()); + verify(notificationHelper, times(1)).sendNotification(any(), any()); + } + + @Test + public void shouldUpdateEntityAndSendNotificationToOwners() throws Exception { + JsonNode updateJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\", \"instituteName\": \"Insitute1\", \"osid\": \"123\"}}"); + JsonNode existingJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\", \"instituteName\": \"Insitute2\", \"osid\": \"123\"}}"); + mockDefinitionManager(); + mockValidationService(); + when(shardManager.getShard(any())).thenReturn(new Shard()); + when(dbConnectionInfoMgr.getUuidPropertyName()).thenReturn("osid"); + ReflectionTestUtils.setField(registryHelper, "notificationEnabled", true); + doNothing().when(registryService).updateEntity(any(), any(), any(), any(), anyBoolean()); + doNothing().when(notificationHelper).sendNotification(any(), any()); + registryHelper.updateEntityAndState(existingJson, updateJson, ""); + verify(registryService, times(1)).updateEntity(any(), any(), any(), any(), anyBoolean()); + verify(notificationHelper, times(1)).sendNotification(any(), any()); } } diff --git a/java/registry/src/test/java/dev/sunbirdrc/registry/service/NotificationHelperTest.java b/java/registry/src/test/java/dev/sunbirdrc/registry/service/NotificationHelperTest.java new file mode 100644 index 000000000..6448199b2 --- /dev/null +++ b/java/registry/src/test/java/dev/sunbirdrc/registry/service/NotificationHelperTest.java @@ -0,0 +1,162 @@ +package dev.sunbirdrc.registry.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import dev.sunbirdrc.registry.helper.EntityStateHelper; +import dev.sunbirdrc.registry.middleware.util.Constants; +import dev.sunbirdrc.registry.model.NotificationTemplate; +import dev.sunbirdrc.registry.model.NotificationTemplates; +import dev.sunbirdrc.registry.util.Definition; +import dev.sunbirdrc.registry.util.DefinitionsManager; +import dev.sunbirdrc.registry.util.OSSchemaConfiguration; +import dev.sunbirdrc.workflow.KieConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; + +import static dev.sunbirdrc.registry.middleware.util.Constants.EMAIL; +import static dev.sunbirdrc.registry.middleware.util.Constants.MOBILE; +import static org.mockito.Mockito.*; + +@RunWith(SpringRunner.class) +@ActiveProfiles(Constants.TEST_ENVIRONMENT) +@SpringBootTest(classes = {ObjectMapper.class, KieConfiguration.class}) +public class NotificationHelperTest { + @Mock + private RegistryService registryService; + @Mock + private DefinitionsManager definitionsManager; + + private ObjectMapper objectMapper; + @Value("${notification.service.enabled}") + boolean notificationEnabled; + private NotificationHelper notificationHelper; + @Mock + EntityStateHelper entityStateHelper; + JsonNode inputJson; + OSSchemaConfiguration osSchemaConfiguration; + NotificationTemplates notificationTemplates; + + @Before + public void setUp() throws Exception { + objectMapper = new ObjectMapper(); + notificationHelper = new NotificationHelper(notificationEnabled, definitionsManager, entityStateHelper, registryService, objectMapper); + osSchemaConfiguration = Mockito.mock(OSSchemaConfiguration.class); + Definition definition = mock(Definition.class); + when(definitionsManager.getDefinition("Institute")).thenReturn(definition); + when(definition.getOsSchemaConfiguration()).thenReturn(osSchemaConfiguration); + ObjectNode owners = mock(ObjectNode.class); + inputJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"contactNumber\": \"1234123423\", \"instituteName\": \"Insitute2\", \"osid\": \"123\"}}"); + when(owners.get(MOBILE)).thenReturn(JsonNodeFactory.instance.textNode("1234123423")); + when(owners.get(EMAIL)).thenReturn(JsonNodeFactory.instance.textNode("gecasu.ihises@tovinit.com")); + when(entityStateHelper.getOwnersData(inputJson, "Institute")).thenReturn(Collections.singletonList(owners)); + notificationTemplates = mock(NotificationTemplates.class); + when(osSchemaConfiguration.getNotificationTemplates()).thenReturn(notificationTemplates); + } + + @Test + public void shouldSendNotificationForCreateEntity() throws Exception { + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Credential Created", "{{name}}, Your {{entityType}} credential has been created")); + when(notificationTemplates.getCreate()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("CREATE", "mailto:gecasu.ihises@tovinit.com", "Credential Created", ", Your Institute credential has been created"); + doNothing().when(registryService).callNotificationActors("CREATE", "tel:1234123423", "Credential Created", ", Your Institute credential has been created"); + notificationHelper.sendNotification(inputJson, "CREATE"); + verify(registryService, times(1)).callNotificationActors("CREATE", "tel:1234123423", "Credential Created", ", Your Institute credential has been created"); + verify(registryService, times(1)).callNotificationActors("CREATE", "mailto:gecasu.ihises@tovinit.com", "Credential Created", ", Your Institute credential has been created"); + } + + @Test + public void shouldSendNotificationForUpdateEntity() throws Exception { + JsonNode inputJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"contactNumber\": \"1234123423\", \"instituteName\": \"Insitute2\", \"osid\": \"123\"}}"); + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Credential Updated", "{{name}}, Your {{entityType}} credential has been updated")); + when(notificationTemplates.getUpdate()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("UPDATE", "mailto:gecasu.ihises@tovinit.com", "Credential Updated", ", Your Institute credential has been updated"); + doNothing().when(registryService).callNotificationActors("UPDATE", "tel:1234123423", "Credential Updated", ", Your Institute credential has been updated"); + notificationHelper.sendNotification(inputJson, "UPDATE"); + verify(registryService, times(1)).callNotificationActors("UPDATE", "mailto:gecasu.ihises@tovinit.com", "Credential Updated", ", Your Institute credential has been updated"); + verify(registryService, times(1)).callNotificationActors("UPDATE", "tel:1234123423", "Credential Updated", ", Your Institute credential has been updated"); + } + + @Test + public void shouldSendNotificationForInviteEntity() throws Exception { + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Invitation", "{{name}}, You have been invited")); + when(notificationTemplates.getInvite()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("INVITE", "mailto:gecasu.ihises@tovinit.com", "Invitation", ", You have been invited"); + doNothing().when(registryService).callNotificationActors("INVITE", "tel:1234123423", "Invitation", ", You have been invited"); + notificationHelper.sendNotification(inputJson, "INVITE"); + verify(registryService, times(1)).callNotificationActors("INVITE", "mailto:gecasu.ihises@tovinit.com", "Invitation", ", You have been invited"); + verify(registryService, times(1)).callNotificationActors("INVITE", "tel:1234123423", "Invitation", ", You have been invited"); + } + + @Test + public void shouldSendNotificationForDeleteEntity() throws Exception { + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Revoked", "{{name}}, Your credential has been revoked")); + when(notificationTemplates.getDelete()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", ", Your credential has been revoked"); + doNothing().when(registryService).callNotificationActors("DELETE", "tel:1234123423", "Revoked", ", Your credential has been revoked"); + notificationHelper.sendNotification(inputJson, "DELETE"); + verify(registryService, times(1)).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", ", Your credential has been revoked"); + verify(registryService, times(1)).callNotificationActors("DELETE", "tel:1234123423", "Revoked", ", Your credential has been revoked"); + } + + @Test + public void shouldSendMultipleNotificationsIfMultipleOwnersPresent() throws Exception { + JsonNode inputJson = new ObjectMapper().readTree("{\"Institute\":{\"email\":\"gecasu.ihises@tovinit.com\",\"instituteName\":\"gecasu\",\"contactNumber\": \"123123\", \"adminEmail\": \"admin@email.com\",\n" + + " \"adminMobile\": \"1234\"\n" + + "}}"); + ObjectNode owner1 = mock(ObjectNode.class); + ObjectNode owner2 = mock(ObjectNode.class); + when(owner1.get(MOBILE)).thenReturn(JsonNodeFactory.instance.textNode("123123")); + when(owner2.get(MOBILE)).thenReturn(JsonNodeFactory.instance.textNode("1234")); + when(owner1.get(EMAIL)).thenReturn(JsonNodeFactory.instance.textNode("gecasu.ihises@tovinit.com")); + when(owner2.get(EMAIL)).thenReturn(JsonNodeFactory.instance.textNode("admin@email.com")); + List owners = new ArrayList<>(); + owners.add(owner1); + owners.add(owner2); + when(entityStateHelper.getOwnersData(inputJson, "Institute")).thenReturn(owners); + + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Invitation", "{{name}}, You have been invited")); + when(notificationTemplates.getInvite()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("INVITE", "mailto:gecasu.ihises@tovinit.com", "Invitation", ", You have been invited"); + doNothing().when(registryService).callNotificationActors("INVITE", "mailto:admin@email.com", "Invitation", ", You have been invited"); + doNothing().when(registryService).callNotificationActors("INVITE", "tel:123123", "Invitation", ", You have been invited"); + doNothing().when(registryService).callNotificationActors("INVITE", "tel:1234", "Invitation", ", You have been invited"); + notificationHelper.sendNotification(inputJson, "INVITE"); + verify(registryService, times(1)).callNotificationActors("INVITE", "mailto:gecasu.ihises@tovinit.com", "Invitation", ", You have been invited"); + verify(registryService, times(1)).callNotificationActors("INVITE", "mailto:admin@email.com", "Invitation", ", You have been invited"); + verify(registryService, times(1)).callNotificationActors("INVITE", "tel:123123", "Invitation", ", You have been invited"); + verify(registryService, times(1)).callNotificationActors("INVITE", "tel:1234", "Invitation", ", You have been invited"); + } + + @Test + public void shouldSendMultipleNotificationsIfMultipleTemplatesConfigured() throws Exception { + List notificationTemplates1 = new ArrayList<>(); + notificationTemplates1.add(new NotificationTemplate("Revoked", "{{name}}, Your credential has been revoked")); + notificationTemplates1.add(new NotificationTemplate("Revoked", "{{instituteName}}, Your credential has been revoked")); + when(notificationTemplates.getDelete()).thenReturn(notificationTemplates1); + doNothing().when(registryService).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", ", Your credential has been revoked"); + doNothing().when(registryService).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", "Insitute2, Your credential has been revoked"); + doNothing().when(registryService).callNotificationActors("DELETE", "tel:1234123423", "Revoked", ", Your credential has been revoked"); + doNothing().when(registryService).callNotificationActors("DELETE", "tel:1234123423", "Revoked", "Insitute2, Your credential has been revoked"); + notificationHelper.sendNotification(inputJson, "DELETE"); + verify(registryService, times(1)).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", ", Your credential has been revoked"); + verify(registryService, times(1)).callNotificationActors("DELETE", "mailto:gecasu.ihises@tovinit.com", "Revoked", "Insitute2, Your credential has been revoked"); + verify(registryService, times(1)).callNotificationActors("DELETE", "tel:1234123423", "Revoked", ", Your credential has been revoked"); + verify(registryService, times(1)).callNotificationActors("DELETE", "tel:1234123423", "Revoked", "Insitute2, Your credential has been revoked"); + } +} \ No newline at end of file diff --git a/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/NotificationActor.java b/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/NotificationActor.java index 7c0d430b1..726eeede9 100644 --- a/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/NotificationActor.java +++ b/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/NotificationActor.java @@ -8,6 +8,8 @@ import org.sunbird.akka.core.BaseActor; import org.sunbird.akka.core.MessageProtos; +import java.util.Map; + public class NotificationActor extends BaseActor { public ObjectMapper objectMapper; private NotificationService notificationService; @@ -18,8 +20,8 @@ public void onReceive(MessageProtos.Message request) throws Throwable { objectMapper = new ObjectMapper(); notificationService = new NotificationService(); NotificationMessage notificationMessage = objectMapper.readValue(request.getPayload().getStringValue(), NotificationMessage.class); - Response response = notificationService.notify(notificationMessage); - logger.info("{}", response.body()); + Map response = notificationService.notify(notificationMessage); + logger.info("{}", response); } @Override diff --git a/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/services/NotificationService.java b/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/services/NotificationService.java index 74d699657..f235b2949 100644 --- a/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/services/NotificationService.java +++ b/java/sunbirdrc-actors/src/main/java/dev/sunbirdrc/actors/services/NotificationService.java @@ -7,10 +7,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.net.URI; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import static dev.sunbirdrc.registry.middleware.util.Constants.CONNECTION_FAILURE; import static dev.sunbirdrc.registry.middleware.util.Constants.SUNBIRD_NOTIFICATION_SERVICE_NAME; @@ -28,21 +31,13 @@ public NotificationService(String connection, String healthInfo, Boolean notific public NotificationService() { } - public Response notify(NotificationMessage notificationMessage) throws IOException { - OkHttpClient client = new OkHttpClient(); - MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - RequestBody requestBody = RequestBody.create("{\n" + - " \"recipient\": \"" + notificationMessage.getTo() + "\",\n" + - " \"message\": \"" + notificationMessage.getMessage() + "\",\n" + - " \"subject\": \"" + notificationMessage.getSubject() + "\"\n" + - "}", JSON); - Request httpRequest = new Request.Builder() - .url(connectionInfo) - .method("POST", requestBody) - .header("Content-Type", "application/json") - .build(); - Response response = client.newCall(httpRequest).execute(); - return response; + public Map notify(NotificationMessage notificationMessage) throws IOException { + Map map = new HashMap<>(); + map.put("recipient", notificationMessage.getTo()); + map.put("message", notificationMessage.getMessage()); + map.put("subject", notificationMessage.getSubject()); + RestTemplate restTemplate = new RestTemplate(); + return restTemplate.postForObject(connectionInfo, map, HashMap.class); } @Override diff --git a/services/notification-service/config/application-default.yml b/services/notification-service/config/application-default.yml index 2a1d8653f..12c09de20 100644 --- a/services/notification-service/config/application-default.yml +++ b/services/notification-service/config/application-default.yml @@ -8,7 +8,7 @@ smsapi: url: https://api.msg91.com/api/v2/sendsms authKey: secret enable: true - requestTemplate: '{"sender": "SOCKET","route": "4","country": "91","unicode": "1","sms": [{"message": "{{.message}}","to": ["{{.to}}"]}]}' + requestTemplate: '{"sender": "AppName","route": "4","country": "91","unicode": 1,"sms": [{"message": "{{.message}}","to": ["{{.to}}"]}],"DLT_TE_ID": "templateId"}' emailsmtp: fromAddress: from@gmail.com diff --git a/services/notification-service/config/config.go b/services/notification-service/config/config.go index ce00420d5..3c933638f 100644 --- a/services/notification-service/config/config.go +++ b/services/notification-service/config/config.go @@ -25,7 +25,6 @@ var Config = struct { URL string `env:"SMS_URL" default:"https://api.msg91.com/api/v2/sendsms" yaml:"url"` AuthKey string `env:"SMS_AUTH_KEY" default:"" yaml:"authKey"` Enable bool `env:"ENABLE_SMS" yaml:"enable"` - RequestTemplate string `env:"SMS_REQUEST_TEMPLATE" yaml:"requestTemplate" default:""` } EmailSMTP struct { FromAddress string `env:"SENDER_EMAIL" yaml:"fromAddress"` diff --git a/services/notification-service/go.mod b/services/notification-service/go.mod index 5bd44a77c..f08c11196 100644 --- a/services/notification-service/go.mod +++ b/services/notification-service/go.mod @@ -19,4 +19,4 @@ require ( github.com/sirupsen/logrus v1.7.0 golang.org/x/net v0.0.0-20201224014010-6772e930b67b gopkg.in/confluentinc/confluent-kafka-go.v1 v1.5.2 -) \ No newline at end of file +) diff --git a/services/notification-service/go.sum b/services/notification-service/go.sum index 518db2958..93a23880f 100644 --- a/services/notification-service/go.sum +++ b/services/notification-service/go.sum @@ -191,7 +191,6 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/services/notification-service/notification-service b/services/notification-service/notification-service index 05577dd8d..5b0461717 100755 Binary files a/services/notification-service/notification-service and b/services/notification-service/notification-service differ diff --git a/services/notification-service/pkg/services/sms_service.go b/services/notification-service/pkg/services/sms_service.go index 858ee74b8..837402eeb 100644 --- a/services/notification-service/pkg/services/sms_service.go +++ b/services/notification-service/pkg/services/sms_service.go @@ -1,13 +1,12 @@ package services import ( - "bytes" "encoding/json" "errors" - "github.com/sunbirdrc/notification-service/config" + "github.com/imroc/req" log "github.com/sirupsen/logrus" - "text/template" + "github.com/sunbirdrc/notification-service/config" ) func SendSMS(mobileNumber string, message string) (map[string]interface{}, error) { @@ -43,18 +42,15 @@ func SendSMS(mobileNumber string, message string) (map[string]interface{}, error } func GetSmsRequestPayload(message string, mobileNumber string) map[string]interface{} { - smsRequestTemplate := template.Must(template.New("").Parse(config.Config.SmsAPI.RequestTemplate)) - buf := bytes.Buffer{} - if err := smsRequestTemplate.Execute(&buf, map[string]interface{}{ - "message": message, - "to": mobileNumber, - }); err == nil { - smsRequest := make(map[string]interface{}) - if err = json.Unmarshal(buf.Bytes(), &smsRequest); err == nil { - return smsRequest - } else { - log.Error(err) - } + smsRequest := make(map[string]interface{}) + log.Infof("%v", message) + if err := json.Unmarshal([]byte(message), &smsRequest); err == nil { + log.Infof("success") + return smsRequest + } else { + log.Infof("error") + log.Error(err) } + log.Infof("error") return nil } diff --git a/services/notification-service/swagger_gen/models/error.go b/services/notification-service/swagger_gen/models/error.go index 6672f912c..87af03bb7 100644 --- a/services/notification-service/swagger_gen/models/error.go +++ b/services/notification-service/swagger_gen/models/error.go @@ -6,6 +6,8 @@ package models // Editing this file might prove futile when you re-run the swagger generate command import ( + "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" @@ -62,6 +64,11 @@ func (m *Error) validateMessage(formats strfmt.Registry) error { return nil } +// ContextValidate validates this error based on context it is used +func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + // MarshalBinary interface implementation func (m *Error) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/services/notification-service/swagger_gen/models/notification_request.go b/services/notification-service/swagger_gen/models/notification_request.go index c8d37ff3b..58cfd821d 100644 --- a/services/notification-service/swagger_gen/models/notification_request.go +++ b/services/notification-service/swagger_gen/models/notification_request.go @@ -6,6 +6,8 @@ package models // Editing this file might prove futile when you re-run the swagger generate command import ( + "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" @@ -65,6 +67,11 @@ func (m *NotificationRequest) validateRecipient(formats strfmt.Registry) error { return nil } +// ContextValidate validates this notification request based on context it is used +func (m *NotificationRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + // MarshalBinary interface implementation func (m *NotificationRequest) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/services/notification-service/swagger_gen/models/success.go b/services/notification-service/swagger_gen/models/success.go index 1629d95f5..3ab7c2b82 100644 --- a/services/notification-service/swagger_gen/models/success.go +++ b/services/notification-service/swagger_gen/models/success.go @@ -6,6 +6,8 @@ package models // Editing this file might prove futile when you re-run the swagger generate command import ( + "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" @@ -62,6 +64,11 @@ func (m *Success) validateMessage(formats strfmt.Registry) error { return nil } +// ContextValidate validates this success based on context it is used +func (m *Success) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + // MarshalBinary interface implementation func (m *Success) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/services/notification-service/swagger_gen/restapi/doc.go b/services/notification-service/swagger_gen/restapi/doc.go index e4385fd89..9a85b0f5f 100644 --- a/services/notification-service/swagger_gen/restapi/doc.go +++ b/services/notification-service/swagger_gen/restapi/doc.go @@ -2,18 +2,18 @@ // Package restapi Notification service // -// Notification service -// Schemes: -// https -// Host: sunbirdrc.dev -// BasePath: /notification-service/v1 -// Version: 1.0.0 +// Notification service +// Schemes: +// https +// Host: sunbirdrc.dev +// BasePath: /notification-service/v1 +// Version: 1.0.0 // -// Consumes: -// - application/json +// Consumes: +// - application/json // -// Produces: -// - application/json +// Produces: +// - application/json // // swagger:meta package restapi diff --git a/services/notification-service/swagger_gen/restapi/operations/health/get_health.go b/services/notification-service/swagger_gen/restapi/operations/health/get_health.go index e7df21caf..fb1fa0fec 100644 --- a/services/notification-service/swagger_gen/restapi/operations/health/get_health.go +++ b/services/notification-service/swagger_gen/restapi/operations/health/get_health.go @@ -29,12 +29,12 @@ func NewGetHealth(ctx *middleware.Context, handler GetHealthHandler) *GetHealth return &GetHealth{Context: ctx, Handler: handler} } -/*GetHealth swagger:route GET /health health getHealth +/* + GetHealth swagger:route GET /health health getHealth -Get the health status +# Get the health status API to get the notification health status - */ type GetHealth struct { Context *middleware.Context @@ -44,17 +44,15 @@ type GetHealth struct { func (o *GetHealth) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewGetHealthParams() - if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params o.Context.Respond(rw, r, route.Produces, route, err) return } res := o.Handler.Handle(Params) // actually handle the request - o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/services/notification-service/swagger_gen/restapi/operations/health/get_health_parameters.go b/services/notification-service/swagger_gen/restapi/operations/health/get_health_parameters.go index a576930e9..f975f8866 100644 --- a/services/notification-service/swagger_gen/restapi/operations/health/get_health_parameters.go +++ b/services/notification-service/swagger_gen/restapi/operations/health/get_health_parameters.go @@ -13,7 +13,8 @@ import ( ) // NewGetHealthParams creates a new GetHealthParams object -// no default values defined in spec. +// +// There are no default values defined in the spec. func NewGetHealthParams() GetHealthParams { return GetHealthParams{} diff --git a/services/notification-service/swagger_gen/restapi/operations/health/get_health_responses.go b/services/notification-service/swagger_gen/restapi/operations/health/get_health_responses.go index 509d77266..23e43a3ed 100644 --- a/services/notification-service/swagger_gen/restapi/operations/health/get_health_responses.go +++ b/services/notification-service/swagger_gen/restapi/operations/health/get_health_responses.go @@ -14,7 +14,8 @@ import ( // GetHealthOKCode is the HTTP code returned for type GetHealthOK const GetHealthOKCode int = 200 -/*GetHealthOK OK +/* +GetHealthOK OK swagger:response getHealthOK */ diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification.go b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification.go index 199c4b1ff..75c7b7a90 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification.go @@ -29,12 +29,12 @@ func NewGetNotification(ctx *middleware.Context, handler GetNotificationHandler) return &GetNotification{Context: ctx, Handler: handler} } -/*GetNotification swagger:route GET /notification notification getNotification +/* + GetNotification swagger:route GET /notification notification getNotification -Get the last notifications sent +# Get the last notifications sent Temporary API to get the last notifications sent - */ type GetNotification struct { Context *middleware.Context @@ -44,17 +44,15 @@ type GetNotification struct { func (o *GetNotification) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewGetNotificationParams() - if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params o.Context.Respond(rw, r, route.Produces, route, err) return } res := o.Handler.Handle(Params) // actually handle the request - o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_parameters.go b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_parameters.go index 518e39ded..7bd146410 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_parameters.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_parameters.go @@ -13,7 +13,8 @@ import ( ) // NewGetNotificationParams creates a new GetNotificationParams object -// no default values defined in spec. +// +// There are no default values defined in the spec. func NewGetNotificationParams() GetNotificationParams { return GetNotificationParams{} diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_responses.go b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_responses.go index 339fa46df..6c65cc6f9 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_responses.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/get_notification_responses.go @@ -14,7 +14,8 @@ import ( // GetNotificationOKCode is the HTTP code returned for type GetNotificationOK const GetNotificationOKCode int = 200 -/*GetNotificationOK OK +/* +GetNotificationOK OK swagger:response getNotificationOK */ diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification.go b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification.go index a8f8d2a59..e246c75ff 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification.go @@ -29,12 +29,12 @@ func NewPostNotification(ctx *middleware.Context, handler PostNotificationHandle return &PostNotification{Context: ctx, Handler: handler} } -/*PostNotification swagger:route POST /notification notification postNotification +/* + PostNotification swagger:route POST /notification notification postNotification -Notify the intended person using different channels +# Notify the intended person using different channels Common notification service for different channels like eSMS, email etc. Target address can depict specifics about mode and channel - */ type PostNotification struct { Context *middleware.Context @@ -44,17 +44,15 @@ type PostNotification struct { func (o *PostNotification) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewPostNotificationParams() - if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params o.Context.Respond(rw, r, route.Produces, route, err) return } res := o.Handler.Handle(Params) // actually handle the request - o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_parameters.go b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_parameters.go index 30cbc2462..122654bb6 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_parameters.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_parameters.go @@ -11,12 +11,14 @@ import ( "github.com/go-openapi/errors" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" "github.com/sunbirdrc/notification-service/swagger_gen/models" ) // NewPostNotificationParams creates a new PostNotificationParams object -// no default values defined in spec. +// +// There are no default values defined in the spec. func NewPostNotificationParams() PostNotificationParams { return PostNotificationParams{} @@ -57,6 +59,11 @@ func (o *PostNotificationParams) BindRequest(r *http.Request, route *middleware. res = append(res, err) } + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + if len(res) == 0 { o.Body = &body } diff --git a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_responses.go b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_responses.go index dcaa68407..e63d65e09 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_responses.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification/post_notification_responses.go @@ -16,7 +16,8 @@ import ( // PostNotificationOKCode is the HTTP code returned for type PostNotificationOK const PostNotificationOKCode int = 200 -/*PostNotificationOK OK +/* +PostNotificationOK OK swagger:response postNotificationOK */ @@ -60,7 +61,8 @@ func (o *PostNotificationOK) WriteResponse(rw http.ResponseWriter, producer runt // PostNotificationBadRequestCode is the HTTP code returned for type PostNotificationBadRequest const PostNotificationBadRequestCode int = 400 -/*PostNotificationBadRequest Bad Request +/* +PostNotificationBadRequest Bad Request swagger:response postNotificationBadRequest */ diff --git a/services/notification-service/swagger_gen/restapi/operations/notification_service_api.go b/services/notification-service/swagger_gen/restapi/operations/notification_service_api.go index 05a023f3a..aa4c39623 100644 --- a/services/notification-service/swagger_gen/restapi/operations/notification_service_api.go +++ b/services/notification-service/swagger_gen/restapi/operations/notification_service_api.go @@ -73,9 +73,11 @@ type NotificationServiceAPI struct { // BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function. // It has a default implementation in the security package, however you can replace it for your particular usage. BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator + // APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function. // It has a default implementation in the security package, however you can replace it for your particular usage. APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator + // BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function. // It has a default implementation in the security package, however you can replace it for your particular usage. BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator @@ -94,6 +96,7 @@ type NotificationServiceAPI struct { NotificationGetNotificationHandler notification.GetNotificationHandler // NotificationPostNotificationHandler sets the operation handler for the post notification operation NotificationPostNotificationHandler notification.PostNotificationHandler + // ServeError is called when an error is received, there is a default handler // but you can set your own with this ServeError func(http.ResponseWriter, *http.Request, error) diff --git a/services/notification-service/swagger_gen/restapi/server.go b/services/notification-service/swagger_gen/restapi/server.go index 3511555e7..cb8bf4ef6 100644 --- a/services/notification-service/swagger_gen/restapi/server.go +++ b/services/notification-service/swagger_gen/restapi/server.go @@ -8,7 +8,6 @@ import ( "crypto/x509" "errors" "fmt" - "io/ioutil" "log" "net" "net/http" @@ -274,7 +273,7 @@ func (s *Server) Serve() (err error) { if s.TLSCACertificate != "" { // include specified CA certificate - caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate)) + caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate)) if caCertErr != nil { return caCertErr } @@ -305,9 +304,6 @@ func (s *Server) Serve() (err error) { s.Fatalf("no certificate was configured for TLS") } - // must have at least one certificate or panics - httpsServer.TLSConfig.BuildNameToCertificate() - configureServer(httpsServer, "https", s.httpsServerL.Addr().String()) servers = append(servers, httpsServer)