Skip to content

Commit

Permalink
feat: Support json ld for create resource (#3420)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcin Procyk <[email protected]>
  • Loading branch information
seakayone and mpro7 authored Nov 12, 2024
1 parent 039dcb6 commit 419c74d
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 821 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1344,7 +1344,7 @@ class ResourcesRouteV2E2ESpec extends E2ESpec {
HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity),
) ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.anythingUser2.email, password))
val response = singleAwaitingRequest(request)
assert(response.status == StatusCodes.Forbidden, "should be forbidden")
assert(response.status == StatusCodes.BadRequest)
}

"create a resource containing escaped text" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ class KnoraSipiIntegrationV2ITSpec
val request = Post(s"$baseApiUrl/v2/resources", jsonLdHttpEntity(jsonLdEntity)) ~> addAuthorization

val response = singleAwaitingRequest(request)
assert(response.status == StatusCodes.NotFound)
assert(response.status == StatusCodes.BadRequest)
val body = Await.result(Unmarshal(response.entity).to[String], 1.seconds)
assert(
body.contains(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import dsp.valueobjects.Iri
import dsp.valueobjects.UuidUtil
import org.knora.webapi.*
import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.MessageRelay
import org.knora.webapi.core.RelayedMessage
import org.knora.webapi.messages.IriConversions.*
import org.knora.webapi.messages.OntologyConstants.*
Expand All @@ -36,11 +35,7 @@ import org.knora.webapi.slice.admin.api.model.Project
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.Permission
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.service.ProjectService
import org.knora.webapi.slice.admin.domain.service.UserService
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.store.iiif.api.SipiService
import org.knora.webapi.util.*

/**
* An abstract trait for messages that can be sent to `ResourcesResponderV2`.
Expand Down Expand Up @@ -651,201 +646,6 @@ case class CreateResourceRequestV2(
ingestState: AssetIngestState = AssetInTemp,
) extends ResourcesResponderRequestV2

object CreateResourceRequestV2 {

/**
* Converts JSON-LD input to a [[CreateResourceRequestV2]].
*
* @param jsonLDDocument the JSON-LD input.
* @param apiRequestID the UUID of the API request.
* @param requestingUser the user making the request.
* @param ingestState indicates the state of the file, either ingested or in temp folder
* @return a case class instance representing the input.
*/
def fromJsonLd(
jsonLDDocument: JsonLDDocument,
apiRequestID: UUID,
requestingUser: User,
ingestState: AssetIngestState = AssetInTemp,
): ZIO[
IriConverter & MessageRelay & ProjectService & SipiService & StringFormatter & UserService,
Throwable,
CreateResourceRequestV2,
] =
ZIO.serviceWithZIO[StringFormatter] { implicit stringFormatter =>
val validationFun: (String, => Nothing) => String =
(s, errorFun) => Iri.toSparqlEncodedString(s).getOrElse(errorFun)
for {
// Get the resource class.
resourceClassIri <-
jsonLDDocument.body.getRequiredTypeAsKnoraApiV2ComplexTypeIri.mapError(BadRequestException(_))

// Get the custom resource IRI if provided.
maybeCustomResourceIri <- jsonLDDocument.body.getIdValueAsKnoraDataIri.mapError(BadRequestException(_))

// Get the resource's rdfs:label.
label <-
ZIO.attempt(jsonLDDocument.body.requireStringWithValidation(Rdfs.Label, (s, _) => s))

// Get information about the project that the resource should be created in.
projectIri <- ZIO.attempt(
jsonLDDocument.body.requireIriInObject(
KnoraApiV2Complex.AttachedToProject,
stringFormatter.toSmartIriWithErr,
),
)
projectId <- ZIO.fromEither(ProjectIri.from(projectIri.toString)).mapError(BadRequestException.apply)
project <- ZIO
.serviceWithZIO[ProjectService](_.findById(projectId))
.someOrFail(NotFoundException(s"Project '$projectIri' not found"))

_ <- ZIO.attempt(maybeCustomResourceIri.foreach { iri =>
if (!iri.isKnoraResourceIri) {
throw BadRequestException(s"<$iri> is not a Knora resource IRI")
}

if (!iri.getProjectCode.contains(project.shortcode)) {
throw BadRequestException(s"The provided resource IRI does not contain the correct project code")
}
})

// Get the resource's permissions.
permissions <- ZIO.attempt(
jsonLDDocument.body
.maybeStringWithValidation(KnoraApiV2Complex.HasPermissions, validationFun),
)

// Get the user who should be indicated as the creator of the resource, if specified.

maybeAttachedToUserIri <- ZIO.attempt(
jsonLDDocument.body.maybeIriInObject(
KnoraApiV2Complex.AttachedToUser,
stringFormatter.toSmartIriWithErr,
),
)

maybeAttachedToUser <- ZIO.foreach(maybeAttachedToUserIri) { attachedToUserIri =>
UserUtilADM.switchToUser(
requestingUser,
attachedToUserIri.toString,
projectIri.toString,
)
}

creationDate <- ZIO.attempt(
jsonLDDocument.body.maybeDatatypeValueInObject(
key = KnoraApiV2Complex.CreationDate,
expectedDatatype = Xsd.DateTimeStamp.toSmartIri,
validationFun = (s, errorFun) => xsdDateTimeStampToInstant(s).getOrElse(errorFun),
),
)
// Get the resource's values.

propertyIriStrs <- ZIO.attempt(
jsonLDDocument.body.value.keySet --
Set(
JsonLDKeywords.ID,
JsonLDKeywords.TYPE,
Rdfs.Label,
KnoraApiV2Complex.AttachedToProject,
KnoraApiV2Complex.AttachedToUser,
KnoraApiV2Complex.HasPermissions,
KnoraApiV2Complex.CreationDate,
),
)

propertyValueFuturesMap <-
ZIO.attempt(
propertyIriStrs.map { propertyIriStr =>
val propertyIri: SmartIri =
propertyIriStr.toSmartIriWithErr(throw BadRequestException(s"Invalid property IRI: <$propertyIriStr>"))
val valuesArray: JsonLDArray = jsonLDDocument.body
.getRequiredArray(propertyIriStr)
.fold(e => throw BadRequestException(e), identity)

val valueFuturesSeq = ZIO.foreach(valuesArray.value) { valueJsonLD =>
for {
valueJsonLDObject <- valueJsonLD match {
case jsonLDObject: JsonLDObject => ZIO.succeed(jsonLDObject)
case _ =>
ZIO.fail(
BadRequestException(
s"Invalid JSON-LD as object of property <$propertyIriStr>",
),
)
}
fileInfo <- ValueContentV2.getFileInfo(project.getShortcode, ingestState, valueJsonLDObject)
valueContent <- ValueContentV2.fromJsonLdObject(valueJsonLDObject, requestingUser, fileInfo)

maybeCustomValueIri <- valueJsonLDObject.getIdValueAsKnoraDataIri
.mapError(BadRequestException(_))
maybeCustomValueUUID <- valueJsonLDObject
.getUuid(KnoraApiV2Complex.ValueHasUUID)
.mapError(BadRequestException(_))

// Get the value's creation date.
// TODO: creationDate for values is a bug, and will not be supported in future. Use valueCreationDate instead.
maybeCustomValueCreationDate <- ZIO
.attempt(
valueJsonLDObject
.maybeDatatypeValueInObject(
key = KnoraApiV2Complex.ValueCreationDate,
expectedDatatype = Xsd.DateTimeStamp.toSmartIri,
validationFun = (s, errorFun) =>
xsdDateTimeStampToInstant(s)
.getOrElse(errorFun),
),
)
.flatMap(it =>
if (it.isEmpty) {
ZIO.attempt(
valueJsonLDObject.maybeDatatypeValueInObject(
key = KnoraApiV2Complex.CreationDate,
expectedDatatype = Xsd.DateTimeStamp.toSmartIri,
validationFun = (s, errorFun) =>
xsdDateTimeStampToInstant(s)
.getOrElse(errorFun),
),
)
} else ZIO.succeed(it),
)

maybePermissions <- ZIO.attempt(
valueJsonLDObject.maybeStringWithValidation(
KnoraApiV2Complex.HasPermissions,
validationFun,
),
)
} yield CreateValueInNewResourceV2(
valueContent = valueContent,
customValueIri = maybeCustomValueIri,
customValueUUID = maybeCustomValueUUID,
customValueCreationDate = maybeCustomValueCreationDate,
permissions = maybePermissions,
)
}

propertyIri -> valueFuturesSeq
}.toMap,
)
propertyValuesMap <- ZioHelper.sequence(propertyValueFuturesMap)
} yield CreateResourceRequestV2(
createResource = CreateResourceV2(
resourceIri = maybeCustomResourceIri,
resourceClassIri = resourceClassIri,
label = label,
values = propertyValuesMap,
projectADM = project,
permissions = permissions,
creationDate = creationDate,
),
requestingUser = maybeAttachedToUser.getOrElse(requestingUser),
apiRequestID = apiRequestID,
ingestState = ingestState,
)
}
}

/**
* Represents a request to update a resource's metadata.
*
Expand Down
Loading

0 comments on commit 419c74d

Please sign in to comment.