From 6f257ab16c787b747bb16b5e463232bd6aaa0ef9 Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Thu, 20 Jul 2023 15:06:46 +0530 Subject: [PATCH 1/7] Issue #KN-903 feat: Initial commit --- .../sunbird/content/actors/ObjectActor.scala | 49 ++++++++++++++++++- .../app/controllers/v4/ObjectController.scala | 38 +++++++++++++- content-api/content-service/conf/routes | 6 ++- .../test/controllers/v4/ObjectSpec.scala | 2 +- schemas/crop/1.0/config.json | 42 ++++++++++++++++ schemas/crop/1.0/schema.json | 39 +++++++++++++++ 6 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 schemas/crop/1.0/config.json create mode 100644 schemas/crop/1.0/schema.json diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala index 5a570c854..f7f645443 100644 --- a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala @@ -4,10 +4,13 @@ import org.apache.commons.lang3.StringUtils import org.sunbird.actor.core.BaseActor import org.sunbird.cloudstore.StorageService import org.sunbird.common.dto.{Request, Response, ResponseHandler} -import org.sunbird.common.exception.ResponseCode +import org.sunbird.common.exception.{ClientException, ResponseCode, ServerException} import org.sunbird.graph.OntologyEngineContext +import org.sunbird.graph.dac.model.Node import org.sunbird.graph.nodes.DataNode import org.sunbird.graph.utils.NodeUtil +import org.sunbird.util.RequestUtil + import java.util import javax.inject.Inject import scala.collection.JavaConverters @@ -20,6 +23,10 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer override def onReceive(request: Request): Future[Response] = { request.getOperation match { case "readObject" => read(request) + case "createObject" => create(request) + case "updateObject" => update(request) + case "retireObject" => retire(request) + case _ => ERROR(request.getOperation) } } @@ -33,5 +40,45 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer ResponseHandler.OK.put("object", metadata) }) } + @throws[Exception] + private def create(request: Request): Future[Response] = { + try { + RequestUtil.restrictProperties(request) + DataNode.create(request).map(node => { + ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier) + .put("versionKey", node.getMetadata.get("versionKey")) + }) + } catch { + case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + } + } + + @throws[Exception] + private def update(request: Request): Future[Response] = { + if (StringUtils.isBlank(request.getRequest.getOrDefault("versionKey", "").asInstanceOf[String])) throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!") + try { + RequestUtil.restrictProperties(request) + DataNode.update(request).map(node => { + val identifier: String = node.getIdentifier.replace(".img", "") + ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier) + .put("versionKey", node.getMetadata.get("versionKey")) + }) + } catch { + case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + } + } + + @throws[Exception] + private def retire(request: Request): Future[Response] = { + request.getRequest.put("status", "Retired") + try { + DataNode.update(request).map(node => { + ResponseHandler.OK.put("node_id", node.getIdentifier) + .put("identifier", node.getIdentifier) + }) + } catch { + case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + } + } } diff --git a/content-api/content-service/app/controllers/v4/ObjectController.scala b/content-api/content-service/app/controllers/v4/ObjectController.scala index efb97610a..1c1575beb 100644 --- a/content-api/content-service/app/controllers/v4/ObjectController.scala +++ b/content-api/content-service/app/controllers/v4/ObjectController.scala @@ -13,13 +13,47 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A val version = "1.0" val apiVersion = "4.0" - def read(identifier: String, fields: Option[String]) = Action.async { implicit request => + def read(schema: String, identifier: String, fields: Option[String]) = Action.async { implicit request => val headers = commonHeaders() val app = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]] app.putAll(headers) app.putAll(Map("identifier" -> identifier, "mode" -> "read", "fields" -> fields.getOrElse("")).asJava) val readRequest = getRequest(app, headers, "readObject") - setRequestContext(readRequest, version,"Content","content") + setRequestContext(readRequest, version, schema.capitalize, schema) getResult(ApiId.READ_OBJECT, objectActor, readRequest, version = apiVersion) } + + def create(schema: String) = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]; + content.putAll(headers) + val createRequest = getRequest(content, headers, "createObject", true) + setRequestContext(createRequest, version, schema.capitalize, schema) + getResult(ApiId.CREATE_CONTENT, objectActor, createRequest, version = apiVersion) + } + + def update(schema: String, identifier: String) = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]]; + content.putAll(headers) + val updateRequest = getRequest(content, headers, "updateObject") + setRequestContext(updateRequest, version, schema.capitalize, schema) + updateRequest.getContext.put("identifier", identifier); + getResult(ApiId.UPDATE_CONTENT, objectActor, updateRequest, version = apiVersion) + } + + def retire(schema: String, identifier: String) = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]] + content.put("identifier", identifier) + content.putAll(headers) + val retireRequest = getRequest(content, headers, "retireObject") + setRequestContext(retireRequest, version, schema.capitalize, schema) + getResult(ApiId.RETIRE_CONTENT, objectActor, retireRequest, version = apiVersion) + } + + } \ No newline at end of file diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes index 86f74263d..8c67841f2 100644 --- a/content-api/content-service/conf/routes +++ b/content-api/content-service/conf/routes @@ -128,8 +128,10 @@ DELETE /eventset/v4/discard/:identifier controllers.v4.EventSetControll DELETE /private/eventset/v4/retire/:identifier controllers.v4.EventSetController.retire(identifier:String) # Object v4 Api's -GET /object/v4/read/:identifier controllers.v4.ObjectController.read(identifier:String, fields:Option[String]) - +GET /:schema/v4/read/:identifier controllers.v4.ObjectController.read(schema:String, identifier:String, fields:Option[String]) +POST /:schema/v4/create controllers.v4.ObjectController.create(schema:String) +PATCH /:schema/v4/update/:identifier controllers.v4.ObjectController.update(schema:String, identifier:String) +DELETE /:schema/v4/retire/:identifier controllers.v4.ObjectController.retire(schema:String, identifier:String) # Collection V4 APIs POST /collection/v4/import/:collectionId controllers.v4.CollectionController.importCollection(collectionId:String) diff --git a/content-api/content-service/test/controllers/v4/ObjectSpec.scala b/content-api/content-service/test/controllers/v4/ObjectSpec.scala index 66f246f8f..a25d9a324 100644 --- a/content-api/content-service/test/controllers/v4/ObjectSpec.scala +++ b/content-api/content-service/test/controllers/v4/ObjectSpec.scala @@ -13,7 +13,7 @@ class ObjectSpec extends BaseSpec { "Object controller" should { "return success response for read API" in { val controller = app.injector.instanceOf[controllers.v4.ObjectController] - val result = controller.read("do_1234", None)(FakeRequest()) + val result = controller.read("crop","do_1234", None)(FakeRequest()) isOK(result) status(result) must equalTo(OK) } diff --git a/schemas/crop/1.0/config.json b/schemas/crop/1.0/config.json new file mode 100644 index 000000000..0bb952022 --- /dev/null +++ b/schemas/crop/1.0/config.json @@ -0,0 +1,42 @@ +{ + "restrictProps": { + "create" : [ + "status" + ], + "copy" : [ + "status" + ], + "update": [] + }, + "objectType": "Crop", + "external": { + }, + "relations": { + "concepts": { + "type": "associatedTo", + "direction": "out", + "objects": ["Concept"] + } + }, + "version": "enable", + "versionCheckMode": "ON", + "frameworkCategories": [], + "orgFrameworkTerms": [], + "targetFrameworkTerms": [], + "cacheEnabled": true, + "schema_restrict_api": false, + "transitions": [ + { + "name": "Review", + "api": "review", + "from": ["Draft", "FlagDraft"], + "to": "Review" + }, + { + "name": "Publish", + "api": "publish", + "from": ["Review"], + "to": "Live" + } + ] +} \ No newline at end of file diff --git a/schemas/crop/1.0/schema.json b/schemas/crop/1.0/schema.json new file mode 100644 index 000000000..de07ab8e0 --- /dev/null +++ b/schemas/crop/1.0/schema.json @@ -0,0 +1,39 @@ +{ + "$id": "crop-schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Crop", + "type": "object", + "required": [ + "name", + "code", + "status" + ], + "properties": { + "name": { + "type": "string", + "minLength": 5 + }, + "code": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "Draft", + "Review", + "Live", + "Retired" + ], + "default": "Draft" + }, + "createdOn": { + "type": "string" + }, + "lastUpdatedOn": { + "type": "string" + }, + "description": { + "type": "string" + } + } +} \ No newline at end of file From e684fe4697c2e94adc9e4011963d15bd41cdb3f6 Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Fri, 21 Jul 2023 14:56:25 +0530 Subject: [PATCH 2/7] Issue #KN-903 feat: object transition API added --- .../sunbird/content/actors/ObjectActor.scala | 48 +++++++++++++++++-- .../app/controllers/v4/ObjectController.scala | 19 ++++++-- .../content-service/app/utils/ApiId.scala | 4 ++ content-api/content-service/conf/routes | 1 + .../sunbird/graph/schema/DefinitionDTO.scala | 10 ++++ .../sunbird/graph/schema/DefinitionNode.scala | 5 ++ 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala index f7f645443..9a44b398c 100644 --- a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala @@ -5,15 +5,18 @@ import org.sunbird.actor.core.BaseActor import org.sunbird.cloudstore.StorageService import org.sunbird.common.dto.{Request, Response, ResponseHandler} import org.sunbird.common.exception.{ClientException, ResponseCode, ServerException} +import org.sunbird.content.util.ContentConstants import org.sunbird.graph.OntologyEngineContext import org.sunbird.graph.dac.model.Node import org.sunbird.graph.nodes.DataNode +import org.sunbird.graph.schema.DefinitionNode import org.sunbird.graph.utils.NodeUtil import org.sunbird.util.RequestUtil import java.util import javax.inject.Inject import scala.collection.JavaConverters +import scala.collection.JavaConverters._ import scala.concurrent.{ExecutionContext, Future} class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageService) extends BaseActor { @@ -26,7 +29,7 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer case "createObject" => create(request) case "updateObject" => update(request) case "retireObject" => retire(request) - case _ => ERROR(request.getOperation) + case _ => handleDefault(request) //ERROR(request.getOperation) } } @@ -34,6 +37,7 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer private def read(request: Request): Future[Response] = { val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava request.getRequest.put("fields", fields) + println("request "+ request) DataNode.read(request).map(node => { if (NodeUtil.isRetired(node)) ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name, "Object not found with identifier: " + node.getIdentifier) val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields,null, request.getContext.get("version").asInstanceOf[String]) @@ -49,7 +53,7 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer .put("versionKey", node.getMetadata.get("versionKey")) }) } catch { - case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") } } @@ -58,13 +62,14 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer if (StringUtils.isBlank(request.getRequest.getOrDefault("versionKey", "").asInstanceOf[String])) throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!") try { RequestUtil.restrictProperties(request) + println("request "+ request) DataNode.update(request).map(node => { val identifier: String = node.getIdentifier.replace(".img", "") ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier) .put("versionKey", node.getMetadata.get("versionKey")) }) } catch { - case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") } } @@ -77,7 +82,42 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer .put("identifier", node.getIdentifier) }) } catch { - case e: Exception => throw new ClientException("SERVER_ERROR", "The schema does not exist for the provided object.") + case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") + } + } + + private def handleDefault(request: Request): Future[Response] = { + val req = new Request(request) + val graph_id = req.getContext.getOrDefault("graph_id", "domain").asInstanceOf[String] + val schemaName = req.getContext.getOrDefault("schemaName", "framework").asInstanceOf[String] + val schemaVersion = req.getContext.getOrDefault("schemaVersion", "1.0").asInstanceOf[String] + try { + val transitionProps = DefinitionNode.getTransitionProps(graph_id, schemaVersion, schemaName) + val operation = request.getOperation + if(transitionProps.contains(operation)){ + val transitionData = transitionProps.asJava.get(operation).asInstanceOf[util.Map[String, AnyRef]] + val fromStatus = transitionData.get("from").asInstanceOf[util.List[String]] + val identifier = request.getContext.get("identifier").asInstanceOf[String] + val readReq = new Request(); + readReq.setContext(request.getContext) + readReq.put("identifier", identifier) + DataNode.read(readReq).map(node => { + if (!fromStatus.contains(node.getMetadata.get("status").toString)) { + throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Transition not allowed! "+ schemaName.capitalize +" Object status should be one of :" + fromStatus.toString()) + } + val toStatus = transitionData.get("to").asInstanceOf[String] + request.getRequest.put("status", toStatus) + DataNode.update(request).map(updateNode => { + ResponseHandler.OK.put("transition", s"Transition of the object is successful!") + }) + }).flatMap(f => f) + } else { + ERROR(request.getOperation) + } + } catch { + case e: Exception => { + throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") + } } } diff --git a/content-api/content-service/app/controllers/v4/ObjectController.scala b/content-api/content-service/app/controllers/v4/ObjectController.scala index 1c1575beb..36ddd7d09 100644 --- a/content-api/content-service/app/controllers/v4/ObjectController.scala +++ b/content-api/content-service/app/controllers/v4/ObjectController.scala @@ -30,7 +30,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A content.putAll(headers) val createRequest = getRequest(content, headers, "createObject", true) setRequestContext(createRequest, version, schema.capitalize, schema) - getResult(ApiId.CREATE_CONTENT, objectActor, createRequest, version = apiVersion) + getResult(ApiId.CREATE_OBJECT, objectActor, createRequest, version = apiVersion) } def update(schema: String, identifier: String) = Action.async { implicit request => @@ -41,18 +41,29 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A val updateRequest = getRequest(content, headers, "updateObject") setRequestContext(updateRequest, version, schema.capitalize, schema) updateRequest.getContext.put("identifier", identifier); - getResult(ApiId.UPDATE_CONTENT, objectActor, updateRequest, version = apiVersion) + getResult(ApiId.UPDATE_OBJECT, objectActor, updateRequest, version = apiVersion) } def retire(schema: String, identifier: String) = Action.async { implicit request => val headers = commonHeaders() val body = requestBody() val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]] - content.put("identifier", identifier) content.putAll(headers) val retireRequest = getRequest(content, headers, "retireObject") setRequestContext(retireRequest, version, schema.capitalize, schema) - getResult(ApiId.RETIRE_CONTENT, objectActor, retireRequest, version = apiVersion) + retireRequest.getContext.put("identifier", identifier); + getResult(ApiId.RETIRE_OBJECT, objectActor, retireRequest, version = apiVersion) + } + + def transition(schema: String, transition: String, identifier: String) = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]] + content.putAll(headers) + val transitionRequest = getRequest(content, headers, transition) + setRequestContext(transitionRequest, version, schema.capitalize, schema) + transitionRequest.getContext.put("identifier", identifier); + getResult(ApiId.TRANSITION_OBJECT, objectActor, transitionRequest, version = apiVersion) } diff --git a/content-api/content-service/app/utils/ApiId.scala b/content-api/content-service/app/utils/ApiId.scala index e2efd2372..20bd8550e 100644 --- a/content-api/content-service/app/utils/ApiId.scala +++ b/content-api/content-service/app/utils/ApiId.scala @@ -99,6 +99,10 @@ object ApiId { //Object APIs val READ_OBJECT = "api.object.read" + val CREATE_OBJECT = "api.object.create" + val UPDATE_OBJECT = "api.object.update" + val RETIRE_OBJECT = "api.object.retire" + val TRANSITION_OBJECT = "api.object.transition" //Collection CSV APIs val IMPORT_CSV = "api.collection.import" diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes index 8c67841f2..4038c4ae4 100644 --- a/content-api/content-service/conf/routes +++ b/content-api/content-service/conf/routes @@ -132,6 +132,7 @@ GET /:schema/v4/read/:identifier controllers.v4.ObjectController POST /:schema/v4/create controllers.v4.ObjectController.create(schema:String) PATCH /:schema/v4/update/:identifier controllers.v4.ObjectController.update(schema:String, identifier:String) DELETE /:schema/v4/retire/:identifier controllers.v4.ObjectController.retire(schema:String, identifier:String) +PATCH /:schema/v4/:transition/:identifier controllers.v4.ObjectController.transition(schema: String, transition: String, identifier:String) # Collection V4 APIs POST /collection/v4/import/:collectionId controllers.v4.CollectionController.importCollection(collectionId:String) diff --git a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala index ab228612a..ebc0db08c 100644 --- a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala +++ b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala @@ -41,6 +41,16 @@ class DefinitionDTO(graphId: String, schemaName: String, version: String = "1.0" List() } + def getTransitionProps(): Map[String, AnyRef] = { + if (schemaValidator.getConfig.hasPath("transitions")) { + val transitions = schemaValidator.getConfig.getAnyRefList("transitions").asInstanceOf[util.List[Object]] + val transitionsMap = transitions.asScala.groupBy(x => x.asInstanceOf[java.util.Map[String, AnyRef]].get("api").asInstanceOf[String]).mapValues(t => t.toList.head) + transitionsMap + } + else + Map() + } + def fetchJsonProps(): List[String] = { val jsonProps = schemaValidator.getJsonProps.asScala jsonProps.toList diff --git a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala index daa00b3bc..d9705c3ec 100644 --- a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala +++ b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala @@ -36,6 +36,11 @@ object DefinitionNode { definition.getExternalProps() } + def getTransitionProps(graphId: String, version: String, schemaName: String, ocd: ObjectCategoryDefinition = ObjectCategoryDefinition())(implicit ec: ExecutionContext, oec: OntologyEngineContext): Map[String, AnyRef] = { + val definition = DefinitionFactory.getDefinition(graphId, schemaName, version, ocd) + definition.getTransitionProps() + } + def fetchJsonProps(graphId: String, version: String, schemaName: String, ocd: ObjectCategoryDefinition = ObjectCategoryDefinition())(implicit ec: ExecutionContext, oec: OntologyEngineContext): List[String] = { val definition = DefinitionFactory.getDefinition(graphId, schemaName, version, ocd) definition.fetchJsonProps() From 337a0a49b21ef461e7cf585d69f80dd1a4454131 Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Tue, 25 Jul 2023 10:43:08 +0530 Subject: [PATCH 3/7] Issue #KN-903 fix: object API demo feedback fixes 1. rename "api" with "action". 2. add required fields for transitions. 3. API response changes. 1. remove "node_id" 2. api.object.create to api.schema.create --- .../sunbird/content/actors/ObjectActor.scala | 33 +++++++++------ .../app/controllers/v4/ObjectController.scala | 10 ++--- .../content-service/app/utils/ApiId.scala | 7 ---- content-api/content-service/conf/routes | 10 ++--- .../sunbird/graph/schema/DefinitionDTO.scala | 2 +- schemas/crop/1.0/config.json | 42 ------------------- schemas/crop/1.0/schema.json | 39 ----------------- 7 files changed, 32 insertions(+), 111 deletions(-) delete mode 100644 schemas/crop/1.0/config.json delete mode 100644 schemas/crop/1.0/schema.json diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala index 9a44b398c..39fe21d35 100644 --- a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/ObjectActor.scala @@ -17,6 +17,7 @@ import java.util import javax.inject.Inject import scala.collection.JavaConverters import scala.collection.JavaConverters._ +import scala.collection.convert.ImplicitConversions.`map AsScala` import scala.concurrent.{ExecutionContext, Future} class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageService) extends BaseActor { @@ -37,24 +38,23 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer private def read(request: Request): Future[Response] = { val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava request.getRequest.put("fields", fields) - println("request "+ request) + val schemaName = request.getContext.getOrDefault("schemaName", "object").asInstanceOf[String] DataNode.read(request).map(node => { if (NodeUtil.isRetired(node)) ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name, "Object not found with identifier: " + node.getIdentifier) val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields,null, request.getContext.get("version").asInstanceOf[String]) - ResponseHandler.OK.put("object", metadata) + ResponseHandler.OK.put(schemaName, metadata) }) } @throws[Exception] private def create(request: Request): Future[Response] = { try { RequestUtil.restrictProperties(request) - DataNode.create(request).map(node => { - ResponseHandler.OK.put("identifier", node.getIdentifier).put("node_id", node.getIdentifier) - .put("versionKey", node.getMetadata.get("versionKey")) - }) } catch { case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") } + DataNode.create(request).map(node => { + ResponseHandler.OK.put("identifier", node.getIdentifier).put("versionKey", node.getMetadata.get("versionKey")) + }) } @throws[Exception] @@ -62,11 +62,9 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer if (StringUtils.isBlank(request.getRequest.getOrDefault("versionKey", "").asInstanceOf[String])) throw new ClientException("ERR_INVALID_REQUEST", "Please Provide Version Key!") try { RequestUtil.restrictProperties(request) - println("request "+ request) DataNode.update(request).map(node => { val identifier: String = node.getIdentifier.replace(".img", "") - ResponseHandler.OK.put("node_id", identifier).put("identifier", identifier) - .put("versionKey", node.getMetadata.get("versionKey")) + ResponseHandler.OK.put("identifier", identifier).put("versionKey", node.getMetadata.get("versionKey")) }) } catch { case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") @@ -78,8 +76,7 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer request.getRequest.put("status", "Retired") try { DataNode.update(request).map(node => { - ResponseHandler.OK.put("node_id", node.getIdentifier) - .put("identifier", node.getIdentifier) + ResponseHandler.OK.put("identifier", node.getIdentifier) }) } catch { case e: Exception => throw new ClientException("INVALID_OBJECT", "The schema does not exist for the provided object.") @@ -89,7 +86,7 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer private def handleDefault(request: Request): Future[Response] = { val req = new Request(request) val graph_id = req.getContext.getOrDefault("graph_id", "domain").asInstanceOf[String] - val schemaName = req.getContext.getOrDefault("schemaName", "framework").asInstanceOf[String] + val schemaName = req.getContext.getOrDefault("schemaName", "").asInstanceOf[String] val schemaVersion = req.getContext.getOrDefault("schemaVersion", "1.0").asInstanceOf[String] try { val transitionProps = DefinitionNode.getTransitionProps(graph_id, schemaVersion, schemaName) @@ -106,6 +103,18 @@ class ObjectActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSer throw new ClientException(ContentConstants.ERR_CONTENT_NOT_DRAFT, "Transition not allowed! "+ schemaName.capitalize +" Object status should be one of :" + fromStatus.toString()) } val toStatus = transitionData.get("to").asInstanceOf[String] + val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), request.getContext.get("version").asInstanceOf[String]) + val completeMetaData = metadata ++ request.getRequest + val requiredProps = transitionData.getOrDefault("required", new util.ArrayList[String]()).asInstanceOf[util.List[String]] + if(!completeMetaData.isEmpty && !requiredProps.isEmpty){ + val errors: util.List[String] = new util.ArrayList[String] + requiredProps.forEach(prop => { + if(!completeMetaData.contains(prop)) + errors.add(s"Required Metadata $prop not set") + }) + if (!errors.isEmpty) + throw new ClientException("CLIENT_ERROR", "Validation Errors.", errors) + } request.getRequest.put("status", toStatus) DataNode.update(request).map(updateNode => { ResponseHandler.OK.put("transition", s"Transition of the object is successful!") diff --git a/content-api/content-service/app/controllers/v4/ObjectController.scala b/content-api/content-service/app/controllers/v4/ObjectController.scala index 36ddd7d09..ae855c6c0 100644 --- a/content-api/content-service/app/controllers/v4/ObjectController.scala +++ b/content-api/content-service/app/controllers/v4/ObjectController.scala @@ -20,7 +20,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A app.putAll(Map("identifier" -> identifier, "mode" -> "read", "fields" -> fields.getOrElse("")).asJava) val readRequest = getRequest(app, headers, "readObject") setRequestContext(readRequest, version, schema.capitalize, schema) - getResult(ApiId.READ_OBJECT, objectActor, readRequest, version = apiVersion) + getResult(s"api.$schema.read", objectActor, readRequest, version = apiVersion) } def create(schema: String) = Action.async { implicit request => @@ -30,7 +30,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A content.putAll(headers) val createRequest = getRequest(content, headers, "createObject", true) setRequestContext(createRequest, version, schema.capitalize, schema) - getResult(ApiId.CREATE_OBJECT, objectActor, createRequest, version = apiVersion) + getResult(s"api.$schema.create", objectActor, createRequest, version = apiVersion) } def update(schema: String, identifier: String) = Action.async { implicit request => @@ -41,7 +41,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A val updateRequest = getRequest(content, headers, "updateObject") setRequestContext(updateRequest, version, schema.capitalize, schema) updateRequest.getContext.put("identifier", identifier); - getResult(ApiId.UPDATE_OBJECT, objectActor, updateRequest, version = apiVersion) + getResult(s"api.$schema.update", objectActor, updateRequest, version = apiVersion) } def retire(schema: String, identifier: String) = Action.async { implicit request => @@ -52,7 +52,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A val retireRequest = getRequest(content, headers, "retireObject") setRequestContext(retireRequest, version, schema.capitalize, schema) retireRequest.getContext.put("identifier", identifier); - getResult(ApiId.RETIRE_OBJECT, objectActor, retireRequest, version = apiVersion) + getResult(s"api.$schema.retire", objectActor, retireRequest, version = apiVersion) } def transition(schema: String, transition: String, identifier: String) = Action.async { implicit request => @@ -63,7 +63,7 @@ class ObjectController @Inject()(@Named(ActorNames.OBJECT_ACTOR) objectActor: A val transitionRequest = getRequest(content, headers, transition) setRequestContext(transitionRequest, version, schema.capitalize, schema) transitionRequest.getContext.put("identifier", identifier); - getResult(ApiId.TRANSITION_OBJECT, objectActor, transitionRequest, version = apiVersion) + getResult(s"api.$schema.transition", objectActor, transitionRequest, version = apiVersion) } diff --git a/content-api/content-service/app/utils/ApiId.scala b/content-api/content-service/app/utils/ApiId.scala index 20bd8550e..216253e49 100644 --- a/content-api/content-service/app/utils/ApiId.scala +++ b/content-api/content-service/app/utils/ApiId.scala @@ -97,13 +97,6 @@ object ApiId { val PUBLISH_EVENT_SET = "api.eventset.publish" val PUBLISH_EVENT = "api.event.publish" - //Object APIs - val READ_OBJECT = "api.object.read" - val CREATE_OBJECT = "api.object.create" - val UPDATE_OBJECT = "api.object.update" - val RETIRE_OBJECT = "api.object.retire" - val TRANSITION_OBJECT = "api.object.transition" - //Collection CSV APIs val IMPORT_CSV = "api.collection.import" val EXPORT_CSV = "api.collection.export" diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes index 4038c4ae4..65c7bcd27 100644 --- a/content-api/content-service/conf/routes +++ b/content-api/content-service/conf/routes @@ -128,11 +128,11 @@ DELETE /eventset/v4/discard/:identifier controllers.v4.EventSetControll DELETE /private/eventset/v4/retire/:identifier controllers.v4.EventSetController.retire(identifier:String) # Object v4 Api's -GET /:schema/v4/read/:identifier controllers.v4.ObjectController.read(schema:String, identifier:String, fields:Option[String]) -POST /:schema/v4/create controllers.v4.ObjectController.create(schema:String) -PATCH /:schema/v4/update/:identifier controllers.v4.ObjectController.update(schema:String, identifier:String) -DELETE /:schema/v4/retire/:identifier controllers.v4.ObjectController.retire(schema:String, identifier:String) -PATCH /:schema/v4/:transition/:identifier controllers.v4.ObjectController.transition(schema: String, transition: String, identifier:String) +GET /:schema/v1/read/:identifier controllers.v4.ObjectController.read(schema:String, identifier:String, fields:Option[String]) +POST /:schema/v1/create controllers.v4.ObjectController.create(schema:String) +PATCH /:schema/v1/update/:identifier controllers.v4.ObjectController.update(schema:String, identifier:String) +DELETE /:schema/v1/retire/:identifier controllers.v4.ObjectController.retire(schema:String, identifier:String) +POST /:schema/v1/:transition/:identifier controllers.v4.ObjectController.transition(schema: String, transition: String, identifier:String) # Collection V4 APIs POST /collection/v4/import/:collectionId controllers.v4.CollectionController.importCollection(collectionId:String) diff --git a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala index ebc0db08c..1d21b2bb0 100644 --- a/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala +++ b/ontology-engine/graph-engine_2.12/src/main/scala/org/sunbird/graph/schema/DefinitionDTO.scala @@ -44,7 +44,7 @@ class DefinitionDTO(graphId: String, schemaName: String, version: String = "1.0" def getTransitionProps(): Map[String, AnyRef] = { if (schemaValidator.getConfig.hasPath("transitions")) { val transitions = schemaValidator.getConfig.getAnyRefList("transitions").asInstanceOf[util.List[Object]] - val transitionsMap = transitions.asScala.groupBy(x => x.asInstanceOf[java.util.Map[String, AnyRef]].get("api").asInstanceOf[String]).mapValues(t => t.toList.head) + val transitionsMap = transitions.asScala.groupBy(x => x.asInstanceOf[java.util.Map[String, AnyRef]].get("action").asInstanceOf[String]).mapValues(t => t.toList.head) transitionsMap } else diff --git a/schemas/crop/1.0/config.json b/schemas/crop/1.0/config.json deleted file mode 100644 index 0bb952022..000000000 --- a/schemas/crop/1.0/config.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "restrictProps": { - "create" : [ - "status" - ], - "copy" : [ - "status" - ], - "update": [] - }, - "objectType": "Crop", - "external": { - }, - "relations": { - "concepts": { - "type": "associatedTo", - "direction": "out", - "objects": ["Concept"] - } - }, - "version": "enable", - "versionCheckMode": "ON", - "frameworkCategories": [], - "orgFrameworkTerms": [], - "targetFrameworkTerms": [], - "cacheEnabled": true, - "schema_restrict_api": false, - "transitions": [ - { - "name": "Review", - "api": "review", - "from": ["Draft", "FlagDraft"], - "to": "Review" - }, - { - "name": "Publish", - "api": "publish", - "from": ["Review"], - "to": "Live" - } - ] -} \ No newline at end of file diff --git a/schemas/crop/1.0/schema.json b/schemas/crop/1.0/schema.json deleted file mode 100644 index de07ab8e0..000000000 --- a/schemas/crop/1.0/schema.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$id": "crop-schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Crop", - "type": "object", - "required": [ - "name", - "code", - "status" - ], - "properties": { - "name": { - "type": "string", - "minLength": 5 - }, - "code": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "Draft", - "Review", - "Live", - "Retired" - ], - "default": "Draft" - }, - "createdOn": { - "type": "string" - }, - "lastUpdatedOn": { - "type": "string" - }, - "description": { - "type": "string" - } - } -} \ No newline at end of file From c2f7cbf8b9c63c8c021848044c60bcb5f0bd8552 Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Tue, 25 Jul 2023 21:41:54 +0530 Subject: [PATCH 4/7] Issue #KN-903 fix: Unit test issue fix --- .../content-service/test/controllers/v4/ObjectSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content-api/content-service/test/controllers/v4/ObjectSpec.scala b/content-api/content-service/test/controllers/v4/ObjectSpec.scala index a25d9a324..5ad61c1cb 100644 --- a/content-api/content-service/test/controllers/v4/ObjectSpec.scala +++ b/content-api/content-service/test/controllers/v4/ObjectSpec.scala @@ -13,7 +13,7 @@ class ObjectSpec extends BaseSpec { "Object controller" should { "return success response for read API" in { val controller = app.injector.instanceOf[controllers.v4.ObjectController] - val result = controller.read("crop","do_1234", None)(FakeRequest()) + val result = controller.read("content","do_1234", None)(FakeRequest()) isOK(result) status(result) must equalTo(OK) } From ef34b16c495271edb770b9daaf48fcab2c8c1e7d Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Wed, 2 Aug 2023 13:40:48 +0530 Subject: [PATCH 5/7] Issue #KN-903 feat: Schema APIs added --- .../sunbird/content/actors/SchemaActor.scala | 69 +++++++++ .../upload/mgr/SchemaUploadManager.scala | 133 ++++++++++++++++++ .../content/util/SchemaConstants.scala | 14 ++ .../app/controllers/v4/SchemaController.scala | 74 ++++++++++ .../app/modules/ContentModule.scala | 3 +- .../app/utils/ActorNames.scala | 1 + .../content-service/app/utils/ApiId.scala | 5 + content-api/content-service/conf/routes | 8 +- .../org/sunbird/graph/common/Identifier.java | 8 +- schemas/schema/1.0/config.json | 29 ++++ schemas/schema/1.0/schema.json | 59 ++++++++ 11 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala create mode 100644 content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/SchemaUploadManager.scala create mode 100644 content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala create mode 100644 content-api/content-service/app/controllers/v4/SchemaController.scala create mode 100644 schemas/schema/1.0/config.json create mode 100644 schemas/schema/1.0/schema.json diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala new file mode 100644 index 000000000..661e46e41 --- /dev/null +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala @@ -0,0 +1,69 @@ +package org.sunbird.content.actors + +import org.apache.commons.lang3.StringUtils +import org.sunbird.actor.core.BaseActor +import org.sunbird.common.{Platform, Slug} +import org.sunbird.common.dto.{Request, Response, ResponseHandler} +import org.sunbird.common.exception.ClientException +import org.sunbird.graph.OntologyEngineContext +import org.sunbird.graph.common.Identifier +import org.sunbird.graph.nodes.DataNode +import org.sunbird.graph.utils.NodeUtil +import org.sunbird.content.util.SchemaConstants +import org.sunbird.util.RequestUtil + +import java.util +import javax.inject.Inject +import scala.collection.JavaConverters +import scala.concurrent.{ExecutionContext, Future} +import org.sunbird.content.upload.mgr.SchemaUploadManager + +class SchemaActor @Inject()(implicit oec: OntologyEngineContext) extends BaseActor { + implicit val ec: ExecutionContext = getContext().dispatcher + private final val SCHEMA_SLUG_LIMIT: Int = if (Platform.config.hasPath("schema.slug_limit")) Platform.config.getInt("schema.slug_limit") else 3 + override def onReceive(request: Request): Future[Response] = { + request.getOperation match { + case "createSchema" => create(request) + case "readSchema" => read(request) + case "uploadSchema" => upload(request) + case _ => ERROR(request.getOperation) + } + } + @throws[Exception] + private def create(request: Request): Future[Response] = { + val name = request.getRequest.getOrDefault(SchemaConstants.NAME, "").asInstanceOf[String] + if (!request.getRequest.containsKey("name")) throw new ClientException("ERR_SCHEMA_NAME_REQUIRED", "Unique name is mandatory for Schema creation") + val slug = Slug.makeSlug(name) + request.getRequest.put("slug", slug) + RequestUtil.restrictProperties(request) + request.getRequest.put(SchemaConstants.IDENTIFIER, Identifier.getIdentifier(slug, Identifier.getUniqueIdFromTimestamp, SCHEMA_SLUG_LIMIT)) + DataNode.create(request).map(node => { + ResponseHandler.OK.put(SchemaConstants.IDENTIFIER, node.getIdentifier) + }) + } + + private def read(request: Request): Future[Response] = { + val fields: util.List[String] = JavaConverters.seqAsJavaListConverter(request.get("fields").asInstanceOf[String].split(",").filter(field => StringUtils.isNotBlank(field) && !StringUtils.equalsIgnoreCase(field, "null"))).asJava + request.getRequest.put("fields", fields) + val schemaId = request.get("identifier").asInstanceOf[String] + DataNode.read(request).map(node => { + if (null != node && StringUtils.equalsAnyIgnoreCase(node.getIdentifier, schemaId)) { + val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String]) + ResponseHandler.OK.put("schema", metadata) + } else throw new ClientException("ERR_INVALID_REQUEST", "Invalid Request. Please Provide Required Properties!") + }) + } + + def upload(request: Request): Future[Response] = { + val identifier: String = request.getContext.getOrDefault(SchemaConstants.IDENTIFIER, "").asInstanceOf[String] + val readReq = new Request(request) + readReq.put(SchemaConstants.IDENTIFIER, identifier) + readReq.put("fields", new util.ArrayList[String]) + DataNode.read(readReq).map(node => { + if (null != node & StringUtils.isNotBlank(node.getObjectType)) + request.getContext.put(SchemaConstants.SCHEMA, node.getObjectType.toLowerCase()) + SchemaUploadManager.upload(request, node) + }).flatMap(f => f) + } + +} diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/SchemaUploadManager.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/SchemaUploadManager.scala new file mode 100644 index 000000000..db439bb6a --- /dev/null +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/upload/mgr/SchemaUploadManager.scala @@ -0,0 +1,133 @@ +package org.sunbird.content.upload.mgr + +import org.apache.commons.lang3.StringUtils +import org.sunbird.cloudstore.StorageService +import org.sunbird.common.{Platform, Slug} +import org.sunbird.common.dto.{Request, Response, ResponseHandler} +import org.sunbird.common.exception.{ClientException, ResponseCode, ServerException} +import org.sunbird.graph.OntologyEngineContext +import org.sunbird.graph.dac.model.Node +import org.sunbird.graph.nodes.DataNode +import org.sunbird.telemetry.logger.TelemetryManager + +import java.io.File +import scala.collection.JavaConverters.mapAsJavaMap +import scala.concurrent.{ExecutionContext, Future} +import scala.collection.JavaConverters._ +import org.sunbird.cloud.storage.factory.{StorageConfig, StorageServiceFactory} +import org.sunbird.telemetry.util.LogTelemetryEventUtil +import java.util + +object SchemaUploadManager { + implicit val ss: StorageService = new StorageService + + private val SCHEMA_FOLDER = "schemas/local" + private val SCHEMA_VERSION_FOLDER = "1.0" + + private val storageType: String = if (Platform.config.hasPath("cloud_storage_type")) Platform.config.getString("cloud_storage_type") else "" + private val storageKey = Platform.config.getString("cloud_storage_key") + private val storageSecret = Platform.config.getString("cloud_storage_secret") +// TODO: endPoint defined to support "cephs3". Make code changes after cloud-store-sdk 2.11 support it. + val endPoint = if (Platform.config.hasPath("cloud_storage_endpoint")) Option(Platform.config.getString("cloud_storage_endpoint")) else None + val storageContainer = Platform.config.getString("cloud_storage_container") + + def getContainerName: String = { + if (Platform.config.hasPath("cloud_storage_container")) + Platform.config.getString("cloud_storage_container") + else + throw new ServerException("ERR_INVALID_CLOUD_STORAGE", "Cloud Storage Container name not configured.") + } + + def upload(request: Request, node: Node)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { + val identifier: String = node.getIdentifier + val slug: String = node.getMetadata.get("slug").asInstanceOf[String] + val file = request.getRequest.get("file").asInstanceOf[File] + val reqFilePath: String = request.getRequest.getOrDefault("filePath", "").asInstanceOf[String].replaceAll("^/+|/+$", "") + val filePath = if (StringUtils.isBlank(reqFilePath)) None else Option(reqFilePath) + val uploadFuture: Future[Map[String, AnyRef]] = uploadFile(identifier, node, file, filePath, slug) + uploadFuture.map(result => { + updateNode(request, node.getIdentifier, node.getObjectType, result) + }).flatMap(f => f) + } + + + def uploadFile(objectId: String, node: Node, uploadFile: File, filePath: Option[String], slug: String)(implicit ec: ExecutionContext): Future[Map[String, AnyRef]] = { + println("uploadFile objectId " + objectId + " node " + node + " uploadFile " + uploadFile + " filePath "+ filePath) + validateUploadRequest(objectId, node, uploadFile) + val result: Array[String] = uploadArtifactToCloud(uploadFile, slug, filePath) + Future { + Map("identifier" -> objectId, "artifactUrl" -> result(1), "downloadUrl" -> result(1), "cloudStorageKey" -> result(0), "s3Key" -> result(0), "size" -> getCloudStoredFileSize(result(0)).asInstanceOf[AnyRef]) + } + } + + def uploadArtifactToCloud(uploadedFile: File, identifier: String, filePath: Option[String] = None, slug: Option[Boolean] = Option(true)): Array[String] = { + var urlArray = new Array[String](2) + try { + val folder = if (filePath.isDefined) filePath.get + File.separator + Platform.getString(SCHEMA_FOLDER, "schemas/local") + File.separator + Slug.makeSlug(identifier, true) + File.separator + Platform.getString(SCHEMA_VERSION_FOLDER, "1.0") else Platform.getString(SCHEMA_FOLDER, "schemas/local") + File.separator + Slug.makeSlug(identifier, true) + File.separator + Platform.getString(SCHEMA_VERSION_FOLDER, "1.0") + val cloudService = StorageServiceFactory.getStorageService(StorageConfig(storageType, storageKey, storageSecret)) + val slugFile = Slug.createSlugFile(uploadedFile) + val objectKey = folder + "/" + slugFile.getName + + val url = cloudService.upload(getContainerName, slugFile.getAbsolutePath, objectKey, Option(false), Option(1), Option(2), None) + urlArray = Array[String](objectKey, url) + } catch { + case e: Exception => + TelemetryManager.error("Error while uploading the file.", e) + throw new ServerException("ERR_CONTENT_UPLOAD_FILE", "Error while uploading the File.", e) + } + urlArray + } + + def updateNode(request: Request, identifier: String, objectType: String, result: Map[String, AnyRef])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { + val updatedResult = result - "identifier" + val artifactUrl = updatedResult.getOrElse("artifactUrl", "").asInstanceOf[String] + if (StringUtils.isNotBlank(artifactUrl)) { + val updateReq = new Request(request) + updateReq.getContext().put("identifier", identifier) + updateReq.getRequest.putAll(mapAsJavaMap(updatedResult)) + println("updateReq "+ updateReq) + DataNode.update(updateReq).map(node => { + getUploadResponse(node) + }) + } else { + Future { + ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, "ERR_UPLOAD_FILE", "Something Went Wrong While Processing Your Request.") + } + } + } + + def getUploadResponse(node: Node)(implicit ec: ExecutionContext): Response = { + val id = node.getIdentifier.replace(".img", "") + val url = node.getMetadata.get("artifactUrl").asInstanceOf[String] + ResponseHandler.OK.put("identifier", id).put("artifactUrl", url).put("versionKey", node.getMetadata.get("versionKey")) + } + + private def validateUploadRequest(objectId: String, node: Node, data: AnyRef)(implicit ec: ExecutionContext): Unit = { + if (StringUtils.isBlank(objectId)) + throw new ClientException("ERR_INVALID_ID", "Please Provide Valid Identifier!") + if (null == node) + throw new ClientException("ERR_INVALID_NODE", "Please Provide Valid Node!") + if (null == data) + throw new ClientException("ERR_INVALID_DATA", "Please Provide Valid File Or File Url!") + data match { + case file: File => validateFile(file) + case _ => + } + } + + protected def getCloudStoredFileSize(key: String)(implicit ss: StorageService): Double = { + val size = 0 + if (StringUtils.isNotBlank(key)) try return ss.getObjectSize(key) + catch { + case e: Exception => + TelemetryManager.error("Error While getting the file size from Cloud Storage: " + key, e) + } + size + } + + private def validateFile(file: File): Unit = { + if (null == file || !file.exists()) + throw new ClientException("ERR_INVALID_FILE", "Please Provide Valid File!") + } + +} diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala new file mode 100644 index 000000000..825fe7106 --- /dev/null +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala @@ -0,0 +1,14 @@ +package org.sunbird.content.util + +object SchemaConstants { + + val SCHEMA: String = "schema" + val CREATE_SCHEMA: String = "createSchema" + val READ_SCHEMA: String = "readSchema" + val UPDATE_SCHEMA: String = "updateSchema" + val UPLOAD_SCHEMA: String = "uploadSchema" + val SCHEMA_VERSION = "1.0" + val IDENTIFIER: String = "identifier" + val FIELDS: String = "fields" + val NAME: String = "name" +} diff --git a/content-api/content-service/app/controllers/v4/SchemaController.scala b/content-api/content-service/app/controllers/v4/SchemaController.scala new file mode 100644 index 000000000..b7f25580f --- /dev/null +++ b/content-api/content-service/app/controllers/v4/SchemaController.scala @@ -0,0 +1,74 @@ +package controllers.v4 + +import akka.actor.{ActorRef, ActorSystem} +import controllers.BaseController +import org.sunbird.common.exception.ClientException +import org.sunbird.models.UploadParams +import play.api.mvc.{AnyContent, ControllerComponents, Request} +import utils.{ActorNames, ApiId} +import org.sunbird.content.util.SchemaConstants + +import java.io.File +import java.util +import javax.inject.{Inject, Named} +import scala.concurrent.ExecutionContext +import scala.collection.JavaConverters._ + +class SchemaController @Inject()(@Named(ActorNames.SCHEMA_ACTOR) schemaActor: ActorRef, cc: ControllerComponents)(implicit exec: ExecutionContext) extends BaseController(cc) { + + val objectType = "Schema" + val version = "1.0" + val apiVersion = "1.0" + + def create() = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val schema = body.getOrDefault(SchemaConstants.SCHEMA, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]] + schema.putAll(headers) + val createSchemaRequest = getRequest(schema, headers, SchemaConstants.CREATE_SCHEMA) + setRequestContext(createSchemaRequest, SchemaConstants.SCHEMA_VERSION, objectType, "schema") + getResult(ApiId.CREATE_SCHEMA, schemaActor, createSchemaRequest) + } + + def read(identifier: String, fields: Option[String]) = Action.async { implicit request => + val headers = commonHeaders() + val schema = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]] + schema.putAll(headers) + schema.putAll(Map(SchemaConstants.IDENTIFIER -> identifier, SchemaConstants.FIELDS -> fields.getOrElse("")).asJava) + val readSchemaRequest = getRequest(schema, headers, SchemaConstants.READ_SCHEMA) + setRequestContext(readSchemaRequest, SchemaConstants.SCHEMA_VERSION, objectType, "schema") + getResult(ApiId.READ_SCHEMA, schemaActor, readSchemaRequest) + } + + def upload(identifier: String, fileFormat: Option[String], validation: Option[String]) = Action.async { implicit request => + val headers = commonHeaders() + val content = requestSchemaFormData(identifier) + content.putAll(headers) + val uploadRequest = getRequest(content, headers, SchemaConstants.UPLOAD_SCHEMA) + setRequestContext(uploadRequest, SchemaConstants.SCHEMA_VERSION, objectType, "schema") + uploadRequest.getContext.putAll(Map("identifier" -> identifier, "params" -> UploadParams(fileFormat, validation.map(_.toBoolean))).asJava) + getResult(ApiId.UPDATE_SCHEMA, schemaActor, uploadRequest) + } + + private def requestSchemaFormData(identifier: String)(implicit request: Request[AnyContent]) = { + val reqMap = new util.HashMap[String, AnyRef]() + if (!request.body.asMultipartFormData.isEmpty) { + val multipartData = request.body.asMultipartFormData.get + if (null != multipartData.files && !multipartData.files.isEmpty) { + val file: File = new java.io.File(request.body.asMultipartFormData.get.files.head.filename) + val copiedFile: File = multipartData.files.head.ref.copyTo(file, false).toFile + reqMap.put("file", copiedFile) + println("isFile " + copiedFile.isFile) + println("getName " + copiedFile.getName) + println("getAbsolutePath " + copiedFile.getAbsolutePath) + } + } + if (null != reqMap.get("file").asInstanceOf[File]) { + println("reqMap " + reqMap) + reqMap + } else { + throw new ClientException("ERR_INVALID_DATA", "Please Provide Valid File Or File Url!") + } + } + +} diff --git a/content-api/content-service/app/modules/ContentModule.scala b/content-api/content-service/app/modules/ContentModule.scala index 1615e7f6f..f323dda9b 100644 --- a/content-api/content-service/app/modules/ContentModule.scala +++ b/content-api/content-service/app/modules/ContentModule.scala @@ -3,7 +3,7 @@ package modules import com.google.inject.AbstractModule import org.sunbird.channel.actors.ChannelActor import org.sunbird.collectioncsv.actors.CollectionCSVActor -import org.sunbird.content.actors.{AppActor, AssetActor, CategoryActor, CollectionActor, ContentActor, EventActor, EventSetActor, HealthActor, LicenseActor, ObjectActor} +import org.sunbird.content.actors.{AppActor, AssetActor, CategoryActor, CollectionActor, ContentActor, EventActor, EventSetActor, HealthActor, LicenseActor, ObjectActor, SchemaActor} import play.libs.akka.AkkaGuiceSupport import utils.ActorNames @@ -23,6 +23,7 @@ class ContentModule extends AbstractModule with AkkaGuiceSupport { bindActor(classOf[AssetActor], ActorNames.ASSET_ACTOR) bindActor(classOf[AppActor], ActorNames.APP_ACTOR) bindActor(classOf[ObjectActor], ActorNames.OBJECT_ACTOR) + bindActor(classOf[SchemaActor], ActorNames.SCHEMA_ACTOR) bindActor(classOf[CollectionCSVActor], ActorNames.COLLECTION_CSV_ACTOR) println("Initialized application actors...") // $COVERAGE-ON diff --git a/content-api/content-service/app/utils/ActorNames.scala b/content-api/content-service/app/utils/ActorNames.scala index 4f4fd1ea0..d4eaf3c2a 100644 --- a/content-api/content-service/app/utils/ActorNames.scala +++ b/content-api/content-service/app/utils/ActorNames.scala @@ -13,5 +13,6 @@ object ActorNames { final val ASSET_ACTOR = "assetActor" final val APP_ACTOR = "appActor" final val OBJECT_ACTOR = "objectActor" + final val SCHEMA_ACTOR = "SchemaActor" final val COLLECTION_CSV_ACTOR = "collectionCSVActor" } diff --git a/content-api/content-service/app/utils/ApiId.scala b/content-api/content-service/app/utils/ApiId.scala index 216253e49..a795199aa 100644 --- a/content-api/content-service/app/utils/ApiId.scala +++ b/content-api/content-service/app/utils/ApiId.scala @@ -102,4 +102,9 @@ object ApiId { val EXPORT_CSV = "api.collection.export" val RESERVE_DIAL_COLLECTION = "api.collection.dialcode.reserve" val RELEASE_DIAL_COLLECTION = "api.collection.dialcode.release" + + val CREATE_SCHEMA = "api.schema.create" + val READ_SCHEMA = "api.schema.read" + val UPDATE_SCHEMA = "api.schema.update" + val UPLOAD_SCHEMA = "api.schema.upload" } diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes index 65c7bcd27..e6984eef1 100644 --- a/content-api/content-service/conf/routes +++ b/content-api/content-service/conf/routes @@ -127,12 +127,18 @@ GET /eventset/v4/read/:identifier controllers.v4.EventSetControll DELETE /eventset/v4/discard/:identifier controllers.v4.EventSetController.discard(identifier:String) DELETE /private/eventset/v4/retire/:identifier controllers.v4.EventSetController.retire(identifier:String) +# Schema APIs +POST /schema/v1/create controllers.v4.SchemaController.create +GET /schema/v1/read/:identifier controllers.v4.SchemaController.read(identifier:String, fields:Option[String]) +# PATCH /schema/v1/update/:identifier controllers.v4.SchemaController.update(identifier:String) +POST /schema/v1/upload/:identifier controllers.v4.SchemaController.upload(identifier:String, fileFormat: Option[String], validation: Option[String]) + # Object v4 Api's GET /:schema/v1/read/:identifier controllers.v4.ObjectController.read(schema:String, identifier:String, fields:Option[String]) POST /:schema/v1/create controllers.v4.ObjectController.create(schema:String) PATCH /:schema/v1/update/:identifier controllers.v4.ObjectController.update(schema:String, identifier:String) DELETE /:schema/v1/retire/:identifier controllers.v4.ObjectController.retire(schema:String, identifier:String) -POST /:schema/v1/:transition/:identifier controllers.v4.ObjectController.transition(schema: String, transition: String, identifier:String) +POST /:schema/v1/:transition/:identifier controllers.v4.ObjectController.transition(schema: String, transition: String, identifier:String) # Collection V4 APIs POST /collection/v4/import/:collectionId controllers.v4.CollectionController.importCollection(collectionId:String) diff --git a/ontology-engine/graph-common/src/main/java/org/sunbird/graph/common/Identifier.java b/ontology-engine/graph-common/src/main/java/org/sunbird/graph/common/Identifier.java index 5b106ddaf..72e991e2c 100644 --- a/ontology-engine/graph-common/src/main/java/org/sunbird/graph/common/Identifier.java +++ b/ontology-engine/graph-common/src/main/java/org/sunbird/graph/common/Identifier.java @@ -32,12 +32,16 @@ public static String getUniqueIdFromTimestamp() { } public static String getIdentifier(String graphId, String id) { + return getIdentifier(graphId, id, 2); + } + + public static String getIdentifier(String graphId, String id, int length) { if (StringUtils.isBlank(graphId)) throw new ServerException("INVALID_", "Graph Id is required to generate an identifier"); String prefix = ""; - if (graphId.length() >= 2) - prefix = graphId.substring(0, 2); + if (graphId.length() >= length) + prefix = graphId.substring(0, length); else prefix = graphId; String identifier = prefix + "_" + id; diff --git a/schemas/schema/1.0/config.json b/schemas/schema/1.0/config.json new file mode 100644 index 000000000..1ed712fe1 --- /dev/null +++ b/schemas/schema/1.0/config.json @@ -0,0 +1,29 @@ +{ + "objectType": "Schema", + "restrictProps": { + "create" : [ + "status", "slug" + ], + "copy" : [ + "status" + ], + "update": [] + }, + "relations": { + }, + "version": "disable", + "versionCheckMode": "ON", + "frameworkCategories": [], + "orgFrameworkTerms": [], + "targetFrameworkTerms": [], + "edge": { + "properties": { + "license": "License" + } + }, + "copy": {}, + "cacheEnabled": true, + "searchProps": {}, + "variants": {}, + "schema_restrict_api": false +} \ No newline at end of file diff --git a/schemas/schema/1.0/schema.json b/schemas/schema/1.0/schema.json new file mode 100644 index 000000000..143870bef --- /dev/null +++ b/schemas/schema/1.0/schema.json @@ -0,0 +1,59 @@ +{ + "$id": "schema-object.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Schema", + "type": "object", + "required": [ + "name", + "slug" + ], + "properties": { + "name": { + "type": "string", + "minLength": 3, + "maxLength": 15 + }, + "slug": { + "type": "string" + }, + "createdOn": { + "type": "string" + }, + "lastUpdatedOn": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "Draft", + "Review", + "Live" + ], + "default": "Draft" + }, + "visibility": { + "type": "string", + "enum": [ + "Default", + "Parent" + ], + "default": "Default" + }, + "artifactUrl": { + "type": "string", + "format": "url" + }, + "description": { + "type": "string" + }, + "lastUpdatedBy": { + "type": "string" + }, + "lastPublishedBy": { + "type": "string" + }, + "lastPublishedOn": { + "type": "string" + } + } +} \ No newline at end of file From 24e85e3f3e44b961fee61e681a7ce0612ed7547d Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Wed, 2 Aug 2023 15:25:24 +0530 Subject: [PATCH 6/7] Issue #KN-903 test: unit tests added for Schema APIs --- .../content/actors/TestSchemaActor.scala | 69 +++++++++++++++++++ .../test/controllers/v4/SchemaSpec.scala | 19 +++++ .../test/modules/TestModule.scala | 1 + 3 files changed, 89 insertions(+) create mode 100644 content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestSchemaActor.scala create mode 100644 content-api/content-service/test/controllers/v4/SchemaSpec.scala diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestSchemaActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestSchemaActor.scala new file mode 100644 index 000000000..5c9f6bce0 --- /dev/null +++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestSchemaActor.scala @@ -0,0 +1,69 @@ +package org.sunbird.content.actors + +import akka.actor.Props +import org.scalamock.scalatest.MockFactory +import org.sunbird.cloudstore.StorageService +import org.sunbird.common.dto.Request +import org.sunbird.graph.dac.model.Node +import org.sunbird.graph.{GraphService, OntologyEngineContext} + +import java.util +import scala.collection.JavaConversions.mapAsJavaMap +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +class TestSchemaActor extends BaseSpec with MockFactory{ + + "SchemaActor" should "return failed response for 'unknown' operation" in { + implicit val ss = mock[StorageService] + implicit val oec: OntologyEngineContext = new OntologyEngineContext + testUnknownOperation(Props(new ObjectActor()), getRequest()) + } + + + it should "return success response for 'readSchema'" in { + implicit val oec: OntologyEngineContext = mock[OntologyEngineContext] + val graphDB = mock[GraphService] + (oec.graphService _).expects().returns(graphDB) + val node = getNode("Schema", None) + (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)) + implicit val ss = mock[StorageService] + val request = getRequest() + request.getContext.put("identifier", "emp_1234") + request.putAll(mapAsJavaMap(Map("identifier" -> "emp_1234", "fields" -> ""))) + request.setOperation("readSchema") + val response = callActor(request, Props(new SchemaActor())) + assert("successful".equals(response.getParams.getStatus)) + } + + private def getRequest(): Request = { + val request = new Request() + request.setContext(new util.HashMap[String, AnyRef]() { + { + put("graph_id", "domain") + put("version", "1.0") + put("objectType", "Schema") + put("schemaName", "schema") + } + }) + request.setObjectType("Schema") + request + } + + override def getNode(objectType: String, metadata: Option[util.Map[String, AnyRef]]): Node = { + val node = new Node("domain", "DATA_NODE", objectType) + node.setGraphId("domain") + val nodeMetadata = metadata.getOrElse(new util.HashMap[String, AnyRef]() { + { + put("name", "Schema Node") + put("code", "Schema-node") + put("status", "Draft") + } + }) + node.setMetadata(nodeMetadata) + node.setObjectType(objectType) + node.setIdentifier("emp_1234") + node + } + +} diff --git a/content-api/content-service/test/controllers/v4/SchemaSpec.scala b/content-api/content-service/test/controllers/v4/SchemaSpec.scala new file mode 100644 index 000000000..75f1415e3 --- /dev/null +++ b/content-api/content-service/test/controllers/v4/SchemaSpec.scala @@ -0,0 +1,19 @@ +package controllers.v4 + +import controllers.base.BaseSpec +import org.junit.runner.RunWith +import org.specs2.runner.JUnitRunner +import play.api.test.FakeRequest +import play.api.test.Helpers.{OK, defaultAwaitTimeout, status} + +class SchemaSpec extends BaseSpec { + + "Schema controller" should { + "return success response for read API" in { + val controller = app.injector.instanceOf[controllers.v4.SchemaController] + val result = controller.read("do_1234", None)(FakeRequest()) + isOK(result) + status(result) must equalTo(OK) + } + } +} \ No newline at end of file diff --git a/content-api/content-service/test/modules/TestModule.scala b/content-api/content-service/test/modules/TestModule.scala index 00d1a7acf..97b064e26 100644 --- a/content-api/content-service/test/modules/TestModule.scala +++ b/content-api/content-service/test/modules/TestModule.scala @@ -22,6 +22,7 @@ class TestModule extends AbstractModule with AkkaGuiceSupport { bindActor(classOf[TestActor], ActorNames.EVENT_SET_ACTOR) bindActor(classOf[TestActor], ActorNames.EVENT_ACTOR) bindActor(classOf[TestActor], ActorNames.OBJECT_ACTOR) + bindActor(classOf[TestActor], ActorNames.SCHEMA_ACTOR) bindActor(classOf[TestActor], ActorNames.COLLECTION_CSV_ACTOR) println("Test Module is initialized...") } From 9e8a29906706e72e976483b683619bbf4244ee52 Mon Sep 17 00:00:00 2001 From: Kartheek Palla Date: Fri, 4 Aug 2023 11:47:41 +0530 Subject: [PATCH 7/7] Issue #KN-903 test: Schema publish API added --- .../org/sunbird/content/actors/SchemaActor.scala | 9 +++++++++ .../org/sunbird/content/util/SchemaConstants.scala | 1 + .../app/controllers/v4/SchemaController.scala | 11 +++++++++++ content-api/content-service/app/utils/ApiId.scala | 1 + content-api/content-service/conf/routes | 1 + 5 files changed, 23 insertions(+) diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala index 661e46e41..a9225b816 100644 --- a/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/actors/SchemaActor.scala @@ -26,6 +26,7 @@ class SchemaActor @Inject()(implicit oec: OntologyEngineContext) extends BaseAct case "createSchema" => create(request) case "readSchema" => read(request) case "uploadSchema" => upload(request) + case "publishSchema" => publish(request) case _ => ERROR(request.getOperation) } } @@ -66,4 +67,12 @@ class SchemaActor @Inject()(implicit oec: OntologyEngineContext) extends BaseAct }).flatMap(f => f) } + @throws[Exception] + private def publish(request: Request): Future[Response] = { + request.getRequest.put("status", "Live") + DataNode.update(request).map(node => { + ResponseHandler.OK.put("publishStatus", s"Schema '${node.getIdentifier}' published Successfully!") + }) + } + } diff --git a/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala b/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala index 825fe7106..b2be61bc6 100644 --- a/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala +++ b/content-api/content-actors/src/main/scala/org/sunbird/content/util/SchemaConstants.scala @@ -7,6 +7,7 @@ object SchemaConstants { val READ_SCHEMA: String = "readSchema" val UPDATE_SCHEMA: String = "updateSchema" val UPLOAD_SCHEMA: String = "uploadSchema" + val PUBLISH_SCHEMA: String = "publishSchema" val SCHEMA_VERSION = "1.0" val IDENTIFIER: String = "identifier" val FIELDS: String = "fields" diff --git a/content-api/content-service/app/controllers/v4/SchemaController.scala b/content-api/content-service/app/controllers/v4/SchemaController.scala index b7f25580f..795d9fc9e 100644 --- a/content-api/content-service/app/controllers/v4/SchemaController.scala +++ b/content-api/content-service/app/controllers/v4/SchemaController.scala @@ -50,6 +50,17 @@ class SchemaController @Inject()(@Named(ActorNames.SCHEMA_ACTOR) schemaActor: Ac getResult(ApiId.UPDATE_SCHEMA, schemaActor, uploadRequest) } + def publish(schema: String, identifier: String) = Action.async { implicit request => + val headers = commonHeaders() + val body = requestBody() + val content = body.getOrDefault(schema, new java.util.HashMap()).asInstanceOf[java.util.Map[String, Object]] + content.putAll(headers) + val publishRequest = getRequest(content, headers, SchemaConstants.PUBLISH_SCHEMA) + setRequestContext(publishRequest, SchemaConstants.SCHEMA_VERSION, objectType, "schema") + publishRequest.getContext.put("identifier", identifier); + getResult(ApiId.PUBLISH_SCHEMA, schemaActor, publishRequest, version = apiVersion) + } + private def requestSchemaFormData(identifier: String)(implicit request: Request[AnyContent]) = { val reqMap = new util.HashMap[String, AnyRef]() if (!request.body.asMultipartFormData.isEmpty) { diff --git a/content-api/content-service/app/utils/ApiId.scala b/content-api/content-service/app/utils/ApiId.scala index a795199aa..fddd58d2c 100644 --- a/content-api/content-service/app/utils/ApiId.scala +++ b/content-api/content-service/app/utils/ApiId.scala @@ -107,4 +107,5 @@ object ApiId { val READ_SCHEMA = "api.schema.read" val UPDATE_SCHEMA = "api.schema.update" val UPLOAD_SCHEMA = "api.schema.upload" + val PUBLISH_SCHEMA = "api.schema.publish" } diff --git a/content-api/content-service/conf/routes b/content-api/content-service/conf/routes index e6984eef1..5e1432fa9 100644 --- a/content-api/content-service/conf/routes +++ b/content-api/content-service/conf/routes @@ -132,6 +132,7 @@ POST /schema/v1/create controllers.v4.SchemaController. GET /schema/v1/read/:identifier controllers.v4.SchemaController.read(identifier:String, fields:Option[String]) # PATCH /schema/v1/update/:identifier controllers.v4.SchemaController.update(identifier:String) POST /schema/v1/upload/:identifier controllers.v4.SchemaController.upload(identifier:String, fileFormat: Option[String], validation: Option[String]) +POST /schema/v1/publish/:identifier controllers.v4.SchemaController.publish(identifier:String) # Object v4 Api's GET /:schema/v1/read/:identifier controllers.v4.ObjectController.read(schema:String, identifier:String, fields:Option[String])