diff --git a/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.info b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.info new file mode 100644 index 0000000000..9b1745b97d --- /dev/null +++ b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.info @@ -0,0 +1,7 @@ +{ + "originalInternalFilename": "De6XyNL4H71-D9QxghOuOPJ.png.orig", + "internalFilename": "De6XyNL4H71-D9QxghOuOPJ.jp2", + "checksumOriginal": "710aa7c06f7a7293f4cab70855ab98e166762a6b3ab7b6bdb4f6750bfc7028ed", + "originalFilename": "dog.png", + "checksumDerivative": "a89a2522adefe4510016ebdf337d82d3af0eef2a5c008c8c76553db4adb64155" +} \ No newline at end of file diff --git a/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.jp2 b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.jp2 new file mode 100644 index 0000000000..4c74efcc2b Binary files /dev/null and b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.jp2 differ diff --git a/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.png.orig b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.png.orig new file mode 100644 index 0000000000..ce15c9bb2e Binary files /dev/null and b/integration/src/test/resources/sipi/testfiles/De6XyNL4H71-D9QxghOuOPJ.png.orig differ diff --git a/integration/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala b/integration/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala index 3a9d9bab44..179ad23799 100644 --- a/integration/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala @@ -31,6 +31,7 @@ import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtoc import org.knora.webapi.messages.util.rdf.JsonLDDocument import org.knora.webapi.messages.util.rdf.JsonLDUtil import org.knora.webapi.routing.UnsafeZioRun +import org.knora.webapi.testcontainers.SipiTestContainer import org.knora.webapi.testservices.FileToUpload import org.knora.webapi.testservices.TestClientService import org.knora.webapi.util.LogAspect @@ -82,6 +83,9 @@ abstract class ITKnoraLiveSpec config <- ZIO.service[AppConfig] } yield (router, config) + def copyFileToImageFolderInContainer(prefix: String, filename: String) = + UnsafeZioRun.runOrThrow(SipiTestContainer.copyFileToImageFolderInContainer(prefix, filename)) + /** * Create router and config by unsafe running them. */ diff --git a/integration/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala b/integration/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala index 9ef08dda7c..1cd59a8f75 100644 --- a/integration/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala @@ -5,7 +5,9 @@ package org.knora.webapi.it.v2 -import org.apache.pekko +import org.apache.pekko.http.scaladsl.model._ +import org.apache.pekko.http.scaladsl.model.headers.BasicHttpCredentials +import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshal import java.net.URLEncoder import java.nio.file.Paths @@ -30,10 +32,6 @@ import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.testservices.FileToUpload import org.knora.webapi.util.MutableTestIri -import pekko.http.scaladsl.model._ -import pekko.http.scaladsl.model.headers.BasicHttpCredentials -import pekko.http.scaladsl.unmarshalling.Unmarshal - /** * Tests interaction between Knora and Sipi using Knora API v2. */ @@ -505,6 +503,38 @@ class KnoraSipiIntegrationV2ITSpec assert(savedImage.internalFilename == uploadedFile.filename) } + "create a resource with a still image file that has already been ingested" in { + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.jp2") + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.info") + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.png.orig") + // Create the resource in the API. + val jsonLdEntity = UploadFileRequest + .make(fileType = FileType.StillImageFile(), internalFilename = "De6XyNL4H71-D9QxghOuOPJ.jp2") + .toJsonLd(className = Some("ThingPicture"), ontologyName = "anything") + val request = Post(s"$baseApiUrl/v2/resources", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> + addCredentials(BasicHttpCredentials(anythingUserEmail, password)) ~> + addHeader("X-Asset-Ingested", "true") + val responseJsonDoc: JsonLDDocument = getResponseJsonLD(request) + // Get the resource from the API. + val resIri = UnsafeZioRun.runOrThrow(responseJsonDoc.body.getRequiredIdValueAsKnoraDataIri).toString + val getRequest = Get(s"$baseApiUrl/v2/resources/${URLEncoder.encode(resIri, "UTF-8")}") + checkResponseOK(getRequest) + } + + "not create a resource with a still image file that has already been ingested if the header is not provided" in { + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.jp2") + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.info") + copyFileToImageFolderInContainer("0001", "De6XyNL4H71-D9QxghOuOPJ.png.orig") + // Create the resource in the API. + val jsonLdEntity = UploadFileRequest + .make(fileType = FileType.StillImageFile(), internalFilename = "De6XyNL4H71-D9QxghOuOPJ.jp2") + .toJsonLd(className = Some("ThingPicture"), ontologyName = "anything") + val request = Post(s"$baseApiUrl/v2/resources", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> + addCredentials(BasicHttpCredentials(anythingUserEmail, password)) // no X-Asset-Ingested header + val res = singleAwaitingRequest(request) + assert(res.status == StatusCodes.BadRequest) + } + "reject an image file with the wrong file extension" in { val exception = intercept[BadRequestException] { uploadToSipi( diff --git a/integration/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala b/integration/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala index b7e2c2330f..cce7711c34 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala @@ -5,7 +5,7 @@ package org.knora.webapi.responders.v2 -import org.apache.pekko +import org.apache.pekko.testkit.ImplicitSender import java.time.Instant import java.util.UUID @@ -26,6 +26,7 @@ import org.knora.webapi.messages.util.DatePrecisionYear import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.util.PermissionUtilADM import org.knora.webapi.messages.util.search.gravsearch.GravsearchParser +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState import org.knora.webapi.messages.v2.responder.resourcemessages._ import org.knora.webapi.messages.v2.responder.standoffmessages._ import org.knora.webapi.messages.v2.responder.valuemessages._ @@ -39,8 +40,6 @@ import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select import org.knora.webapi.util.MutableTestIri import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA -import pekko.testkit.ImplicitSender - /** * Tests [[ValuesResponderV2]]. */ @@ -457,7 +456,8 @@ class ValuesResponderV2Spec extends CoreSpec with ImplicitSender { val resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri val propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri val intVal = IntegerValueContentV2(ApiV2Complex, 4) - val duplicateValue = CreateValueV2(resourceIri, resourceClassIri, propertyIri, intVal) + val duplicateValue = + CreateValueV2(resourceIri, resourceClassIri, propertyIri, intVal, ingestState = AssetIngestState.AssetInTemp) val actual = UnsafeZioRun.run(ValuesResponderV2.createValueV2(duplicateValue, anythingUser1, randomUUID)) assertFailsWithA[DuplicateValueException](actual) diff --git a/integration/src/test/scala/org/knora/webapi/store/iiif/impl/SipiServiceMock.scala b/integration/src/test/scala/org/knora/webapi/store/iiif/impl/SipiServiceMock.scala index 503f814ae3..d105796e0b 100644 --- a/integration/src/test/scala/org/knora/webapi/store/iiif/impl/SipiServiceMock.scala +++ b/integration/src/test/scala/org/knora/webapi/store/iiif/impl/SipiServiceMock.scala @@ -11,6 +11,7 @@ import zio.nio.file.Path import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.sipimessages._ import org.knora.webapi.messages.v2.responder.SuccessResponseV2 +import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.service.Asset import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse import org.knora.webapi.store.iiif.api.SipiService @@ -27,7 +28,7 @@ case class SipiServiceMock() extends SipiService { */ private val FAILURE_FILENAME: String = "failure.jp2" - override def getFileMetadata(ignoredByMock: String): Task[FileMetadataSipiResponse] = + override def getFileMetadataFromTemp(filename: String): Task[FileMetadataSipiResponse] = ZIO.succeed( FileMetadataSipiResponse( originalFilename = Some("test2.tiff"), @@ -62,6 +63,9 @@ case class SipiServiceMock() extends SipiService { override def getStatus(): Task[IIIFServiceStatusResponse] = ZIO.succeed(IIIFServiceStatusOK) override def downloadAsset(asset: Asset, targetDir: Path, user: UserADM): Task[Option[Path]] = ??? + + override def getFileMetadata(filename: String, shortcode: KnoraProject.Shortcode): Task[FileMetadataSipiResponse] = + ??? } object SipiServiceMock { diff --git a/integration/src/test/scala/org/knora/webapi/testservices/TestClientService.scala b/integration/src/test/scala/org/knora/webapi/testservices/TestClientService.scala index 31f9f72a77..cb4ee3c467 100644 --- a/integration/src/test/scala/org/knora/webapi/testservices/TestClientService.scala +++ b/integration/src/test/scala/org/knora/webapi/testservices/TestClientService.scala @@ -148,6 +148,9 @@ final case class TestClientService(config: AppConfig, httpClient: CloseableHttpC .mapError(error => throw AssertionException(s"Got HTTP ${response.status.intValue}\n REQUEST: $request, \n RESPONSE: $error") ) + _ <- ZIO + .fail(AssertionException(s"Got HTTP ${response.status.intValue}\n REQUEST: $request, \n RESPONSE: $body")) + .when(response.status.isFailure()) } yield body /** diff --git a/sipi/scripts/sipi.init.lua b/sipi/scripts/sipi.init.lua index 051fa336b7..5890d8a0d6 100644 --- a/sipi/scripts/sipi.init.lua +++ b/sipi/scripts/sipi.init.lua @@ -96,11 +96,18 @@ function pre_flight(prefix, identifier, cookie) end log("pre_flight - filepath: " .. filepath, server.loglevel.LOG_DEBUG) + if prefix == "tmp" then log("pre_flight - always allow access to tmp folder", server.loglevel.LOG_DEBUG) return 'allow', filepath end + local token, error = auth_get_jwt_decoded() + if error == nil and token ~= nil and token["sub"] == "http://www.knora.org/ontology/knora-admin#SystemUser" then + log("pre_flight - always allow access for system user", server.loglevel.LOG_DEBUG) + return 'allow', filepath + end + local jwt_raw = auth_get_jwt_raw() local permission_info = get_permission_on_file(prefix, identifier, jwt_raw) if permission_info == nil then diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala index 2961af50f6..70596b2d46 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala @@ -34,6 +34,8 @@ import org.knora.webapi.messages.util.rdf.* import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2 import org.knora.webapi.messages.util.standoff.XMLUtil import org.knora.webapi.messages.v2.responder.* +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState.AssetInTemp import org.knora.webapi.messages.v2.responder.standoffmessages.MappingXMLtoStandoff import org.knora.webapi.messages.v2.responder.valuemessages.* import org.knora.webapi.slice.resourceinfo.domain.IriConverter @@ -656,10 +658,16 @@ case class CreateResourceV2( case class CreateResourceRequestV2( createResource: CreateResourceV2, requestingUser: UserADM, - apiRequestID: UUID + apiRequestID: UUID, + ingestState: AssetIngestState = AssetInTemp ) extends ResourcesResponderRequestV2 object CreateResourceRequestV2 { + sealed trait AssetIngestState + object AssetIngestState { + case object AssetIngested extends AssetIngestState + case object AssetInTemp extends AssetIngestState + } /** * Converts JSON-LD input to a [[CreateResourceRequestV2]]. @@ -667,12 +675,14 @@ object 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: UserADM + requestingUser: UserADM, + ingestState: AssetIngestState = AssetInTemp ): ZIO[IriConverter & SipiService & StringFormatter & MessageRelay, Throwable, CreateResourceRequestV2] = ZIO.serviceWithZIO[StringFormatter] { implicit stringFormatter => val validationFun: (String, => Nothing) => String = @@ -775,8 +785,12 @@ object CreateResourceRequestV2 { ) ) } + fileInfo <- ValueContentV2 + .getFileInfo(projectInfoResponse.project.shortcode, ingestState, valueJsonLDObject) + .option - valueContent <- ValueContentV2.fromJsonLdObject(valueJsonLDObject, requestingUser) + valueContent <- + ValueContentV2.fromJsonLdObject(ingestState, valueJsonLDObject, requestingUser, fileInfo) maybeCustomValueIri <- valueJsonLDObject.getIdValueAsKnoraDataIri .mapError(BadRequestException(_)) @@ -841,7 +855,8 @@ object CreateResourceRequestV2 { creationDate = creationDate ), requestingUser = maybeAttachedToUser.getOrElse(requestingUser), - apiRequestID = apiRequestID + apiRequestID = apiRequestID, + ingestState = ingestState ) } } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala index 9bcd3e283a..2465064ffc 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala @@ -12,6 +12,7 @@ import java.util.UUID import dsp.errors.AssertionException import dsp.errors.BadRequestException +import dsp.errors.NotFoundException import dsp.errors.NotImplementedException import dsp.valueobjects.Iri import dsp.valueobjects.IriErrorMessages @@ -37,10 +38,13 @@ import org.knora.webapi.messages.util.standoff.StandoffStringUtil import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2 import org.knora.webapi.messages.util.standoff.XMLUtil import org.knora.webapi.messages.v2.responder.* +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState.AssetInTemp import org.knora.webapi.messages.v2.responder.resourcemessages.ReadResourceV2 import org.knora.webapi.messages.v2.responder.standoffmessages.* import org.knora.webapi.routing.RouteUtilV2 import org.knora.webapi.routing.RouteUtilZ +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.resourceinfo.domain.IriConverter import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse import org.knora.webapi.store.iiif.api.SipiService @@ -583,6 +587,7 @@ case class ReadOtherValueV2( /** * Represents a Knora value to be created in an existing resource. * + * @param ingestState indicates the state of the file, either ingested or in temp folder * @param resourceIri the resource the new value should be attached to. * @param resourceClassIri the resource class that the client believes the resource belongs to. * @param propertyIri the property of the new value. If the client wants to create a link, this must be a link value property. @@ -601,7 +606,8 @@ case class CreateValueV2( valueIri: Option[SmartIri] = None, valueUUID: Option[UUID] = None, valueCreationDate: Option[Instant] = None, - permissions: Option[String] = None + permissions: Option[String] = None, + ingestState: AssetIngestState = AssetInTemp ) extends IOValueV2 /** @@ -612,11 +618,13 @@ object CreateValueV2 { /** * Converts JSON-LD input to a [[CreateValueV2]]. * + * @param ingestState indicates the state of the file, either ingested or in temp folder * @param jsonLdString JSON-LD input as String. * @param requestingUser the user making the request. * @return a case class instance representing the input. */ def fromJsonLd( + ingestState: AssetIngestState, jsonLdString: String, requestingUser: UserADM ): ZIO[SipiService & StringFormatter & IriConverter & MessageRelay, Throwable, CreateValueV2] = @@ -628,6 +636,8 @@ object CreateValueV2 { .mapError(BadRequestException(_)) .flatMap(RouteUtilZ.ensureIsKnoraResourceIri) + shortcode <- ZIO.fromOption(resourceIri.getProjectCode).orElseFail(NotFoundException("Shortcode not found.")) + // Get the resource class. resourceClassIri <- jsonLDDocument.body.getRequiredTypeAsKnoraApiV2ComplexTypeIri.mapError(BadRequestException(_)) @@ -637,7 +647,9 @@ object CreateValueV2 { jsonLDDocument.body.getRequiredResourcePropertyApiV2ComplexValue.mapError(BadRequestException(_)).flatMap { case (propertyIri: SmartIri, jsonLdObject: JsonLDObject) => for { - valueContent <- ValueContentV2.fromJsonLdObject(jsonLdObject, requestingUser) + fileInfo <- ValueContentV2.getFileInfo(shortcode, ingestState, jsonLdObject).option + valueContent <- + ValueContentV2.fromJsonLdObject(ingestState, jsonLdObject, requestingUser, fileInfo) // Get and validate the custom value IRI if provided. maybeCustomValueIri <- jsonLdObject.getIdValueAsKnoraDataIri @@ -646,7 +658,7 @@ object CreateValueV2 { definedNewIri.foreach( stringFormatter.validateCustomValueIri( _, - resourceIri.getProjectCode.get, + shortcode, resourceIri.getResourceID.get ) ) @@ -691,7 +703,8 @@ object CreateValueV2 { valueIri = maybeCustomValueIri, valueUUID = maybeCustomUUID, valueCreationDate = maybeCreationDate, - permissions = maybePermissions + permissions = maybePermissions, + ingestState = ingestState ) } } yield createValue @@ -752,13 +765,21 @@ object UpdateValueV2 { maybeNewIri: Option[SmartIri] ) = for { - valueContent <- ValueContentV2.fromJsonLdObject(jsonLDObject, requestingUser) maybePermissions <- ZIO.attempt { val validationFun: (String, => Nothing) => String = (s, errorFun) => Iri.toSparqlEncodedString(s).getOrElse(errorFun) jsonLDObject.maybeStringWithValidation(HasPermissions, validationFun) } + shortcode <- ZIO.fromOption(resourceIri.getProjectCode).orElseFail(NotFoundException("Shortcode not found.")) + fileInfo <- ValueContentV2.getFileInfo(shortcode, AssetIngestState.AssetInTemp, jsonLDObject).option + valueContent <- + ValueContentV2.fromJsonLdObject( + AssetIngestState.AssetInTemp, + jsonLDObject, + requestingUser, + fileInfo + ) } yield UpdateValueContentV2( resourceIri = resourceIri.toString, resourceClassIri = resourceClassIri, @@ -1041,14 +1062,17 @@ object ValueContentV2 { /** * Converts a JSON-LD object to a [[ValueContentV2]]. * + * @param ingestState indicates the state of the file, either ingested or in temp folder * @param jsonLdObject the JSON-LD object. * @param requestingUser the user making the request. * @return a [[ValueContentV2]]. */ def fromJsonLdObject( + ingestState: AssetIngestState, jsonLdObject: JsonLDObject, - requestingUser: UserADM - ): ZIO[SipiService & StringFormatter & MessageRelay, Throwable, ValueContentV2] = + requestingUser: UserADM, + fileInfo: Option[FileInfo] + ): ZIO[StringFormatter & MessageRelay, Throwable, ValueContentV2] = ZIO.serviceWithZIO[StringFormatter] { stringFormatter => for { valueType <- @@ -1069,17 +1093,67 @@ object ValueContentV2 { case UriValue => UriValueContentV2.fromJsonLdObject(jsonLdObject) case GeonameValue => GeonameValueContentV2.fromJsonLdObject(jsonLdObject) case ColorValue => ColorValueContentV2.fromJsonLdObject(jsonLdObject) - case StillImageFileValue => StillImageFileValueContentV2.fromJsonLdObject(jsonLdObject) - case DocumentFileValue => DocumentFileValueContentV2.fromJsonLdObject(jsonLdObject) - case TextFileValue => TextFileValueContentV2.fromJsonLdObject(jsonLdObject) - case AudioFileValue => AudioFileValueContentV2.fromJsonLdObject(jsonLdObject) - case MovingImageFileValue => MovingImageFileValueContentV2.fromJsonLdObject(jsonLdObject) - case ArchiveFileValue => ArchiveFileValueContentV2.fromJsonLdObject(jsonLdObject) - case other => ZIO.fail(NotImplementedException(s"Parsing of JSON-LD value type not implemented: $other")) + case StillImageFileValue => + for { + info <- + ZIO.fromOption(fileInfo).orElseFail(BadRequestException("No file info found for StillImageFileValue")) + content <- StillImageFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case DocumentFileValue => + for { + info <- + ZIO.fromOption(fileInfo).orElseFail(BadRequestException("No file info found for DocumentFileValue")) + content <- DocumentFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case TextFileValue => + for { + info <- ZIO.fromOption(fileInfo).orElseFail(BadRequestException("No file info found for TextFileValue")) + content <- TextFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case AudioFileValue => + for { + info <- + ZIO.fromOption(fileInfo).orElseFail(BadRequestException("No file info found for AudioFileValue")) + content <- AudioFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case MovingImageFileValue => + for { + info <- ZIO + .fromOption(fileInfo) + .orElseFail(BadRequestException("No file info found for MovingImageFileValue")) + content <- MovingImageFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case ArchiveFileValue => + for { + info <- + ZIO.fromOption(fileInfo).orElseFail(BadRequestException("No file info found for ArchiveFileValue")) + content <- ArchiveFileValueContentV2.fromJsonLdObject(jsonLdObject, info.filename, info.metadata) + } yield content + case other => ZIO.fail(NotImplementedException(s"Parsing of JSON-LD value type not implemented: $other")) } - } yield valueContent } + + final case class FileInfo(filename: IRI, metadata: FileMetadataSipiResponse) + + def getFileInfo( + shortcode: String, + ingestState: AssetIngestState, + jsonLdObject: JsonLDObject + ): ZIO[SipiService, Throwable, FileInfo] = + for { + internalFilename <- ZIO.attempt { + val validationFun: (IRI, => Nothing) => IRI = + (s, errorFun) => Iri.toSparqlEncodedString(s).getOrElse(errorFun) + jsonLdObject.requireStringWithValidation(FileValueHasFilename, validationFun) + } + metadata <- ingestState match { + case AssetIngestState.AssetIngested => + SipiService.getFileMetadata(internalFilename, Shortcode.unsafeFrom(shortcode)) + case AssetIngestState.AssetInTemp => SipiService.getFileMetadataFromTemp(internalFilename) + } + + } yield FileInfo(internalFilename, metadata) } /** @@ -2565,33 +2639,6 @@ case class FileValueV2( originalMimeType: Option[String] ) -/** - * Holds a [[FileValueV2]] and the metadata that Sipi returned about the file. - * - * @param fileValue a [[FileValueV2]]. - * @param sipiFileMetadata the metadata that Sipi returned about the file. - */ -case class FileValueWithSipiMetadata(fileValue: FileValueV2, sipiFileMetadata: FileMetadataSipiResponse) - -/** - * Constructs [[FileValueWithSipiMetadata]] objects based on JSON-LD input. - */ -object FileValueWithSipiMetadata { - def fromJsonLdObject( - jsonLDObject: JsonLDObject - ): ZIO[SipiService, Throwable, FileValueWithSipiMetadata] = - for { - // The submitted value provides only Sipi's internal filename for the file. - internalFilename <- ZIO.attempt { - val validationFun: (String, => Nothing) => String = - (s, errorFun) => Iri.toSparqlEncodedString(s).getOrElse(errorFun) - jsonLDObject.requireStringWithValidation(FileValueHasFilename, validationFun) - } - meta <- SipiService.getFileMetadata(s"/tmp/$internalFilename") - fileValue = FileValueV2(internalFilename, meta.internalMimeType, meta.originalFilename, meta.originalMimeType) - } yield FileValueWithSipiMetadata(fileValue, meta) -} - /** * A trait for case classes representing different types of file values. */ @@ -2702,16 +2749,18 @@ case class StillImageFileValueContentV2( */ object StillImageFileValueContentV2 { def fromJsonLdObject( - jsonLDObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, StillImageFileValueContentV2] = + jsonLDObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, StillImageFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLDObject) - comment <- JsonLDUtil.getComment(jsonLDObject) + comment <- JsonLDUtil.getComment(jsonLDObject) } yield StillImageFileValueContentV2( ontologySchema = ApiV2Complex, - fileValue = fileValueWithSipiMetadata.fileValue, - dimX = fileValueWithSipiMetadata.sipiFileMetadata.width.getOrElse(0), - dimY = fileValueWithSipiMetadata.sipiFileMetadata.height.getOrElse(0), + fileValue = + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + dimX = metadata.width.getOrElse(0), + dimY = metadata.height.getOrElse(0), comment = comment ) } @@ -2850,17 +2899,19 @@ case class ArchiveFileValueContentV2( */ object DocumentFileValueContentV2 { def fromJsonLdObject( - jsonLdObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, DocumentFileValueContentV2] = + jsonLdObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, DocumentFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLdObject) - comment <- JsonLDUtil.getComment(jsonLdObject) + comment <- JsonLDUtil.getComment(jsonLdObject) } yield DocumentFileValueContentV2( ontologySchema = ApiV2Complex, - fileValue = fileValueWithSipiMetadata.fileValue, - pageCount = fileValueWithSipiMetadata.sipiFileMetadata.numpages, - dimX = fileValueWithSipiMetadata.sipiFileMetadata.width, - dimY = fileValueWithSipiMetadata.sipiFileMetadata.height, + fileValue = + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + pageCount = metadata.numpages, + dimX = metadata.width, + dimY = metadata.height, comment ) } @@ -2870,12 +2921,17 @@ object DocumentFileValueContentV2 { */ object ArchiveFileValueContentV2 { def fromJsonLdObject( - jsonLdObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, ArchiveFileValueContentV2] = + jsonLdObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, ArchiveFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLdObject) - comment <- JsonLDUtil.getComment(jsonLdObject) - } yield ArchiveFileValueContentV2(ApiV2Complex, fileValueWithSipiMetadata.fileValue, comment) + comment <- JsonLDUtil.getComment(jsonLdObject) + } yield ArchiveFileValueContentV2( + ApiV2Complex, + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + comment + ) } /** @@ -2942,11 +2998,16 @@ case class TextFileValueContentV2( */ object TextFileValueContentV2 { def fromJsonLdObject( - jsonLDObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, TextFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLDObject) - comment <- JsonLDUtil.getComment(jsonLDObject) - } yield TextFileValueContentV2(ApiV2Complex, fileValueWithSipiMetadata.fileValue, comment) + jsonLDObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, TextFileValueContentV2] = for { + comment <- JsonLDUtil.getComment(jsonLDObject) + } yield TextFileValueContentV2( + ApiV2Complex, + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + comment + ) } /** @@ -3013,12 +3074,17 @@ case class AudioFileValueContentV2( */ object AudioFileValueContentV2 { def fromJsonLdObject( - jsonLDObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, AudioFileValueContentV2] = + jsonLDObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, AudioFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLDObject) - comment <- JsonLDUtil.getComment(jsonLDObject) - } yield AudioFileValueContentV2(ApiV2Complex, fileValueWithSipiMetadata.fileValue, comment) + comment <- JsonLDUtil.getComment(jsonLDObject) + } yield AudioFileValueContentV2( + ApiV2Complex, + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + comment + ) } /** @@ -3087,12 +3153,17 @@ case class MovingImageFileValueContentV2( */ object MovingImageFileValueContentV2 { def fromJsonLdObject( - jsonLDObject: JsonLDObject - ): ZIO[SipiService & StringFormatter, Throwable, MovingImageFileValueContentV2] = + jsonLDObject: JsonLDObject, + internalFilename: String, + metadata: FileMetadataSipiResponse + ): ZIO[StringFormatter, Throwable, MovingImageFileValueContentV2] = for { - fileValueWithSipiMetadata <- FileValueWithSipiMetadata.fromJsonLdObject(jsonLDObject) - comment <- JsonLDUtil.getComment(jsonLDObject) - } yield MovingImageFileValueContentV2(ApiV2Complex, fileValueWithSipiMetadata.fileValue, comment) + comment <- JsonLDUtil.getComment(jsonLDObject) + } yield MovingImageFileValueContentV2( + ApiV2Complex, + FileValueV2(internalFilename, metadata.internalMimeType, metadata.originalFilename, metadata.originalMimeType), + comment + ) } /** diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourceUtilV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourceUtilV2.scala index 6425c06c2e..103bafce00 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourceUtilV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourceUtilV2.scala @@ -108,7 +108,7 @@ trait ResourceUtilV2 { fileValues: Seq[FileValueContentV2], requestingUser: UserADM ): Task[T] - def doSipiPostUpdate[T <: UpdateResultInProject]( + final def doSipiPostUpdate[T <: UpdateResultInProject]( updateTask: Task[T], fileValue: FileValueContentV2, requestingUser: UserADM diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala index 0bfce732d2..e3c300096f 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala @@ -44,6 +44,7 @@ import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2 import org.knora.webapi.messages.v2.responder.SuccessResponseV2 import org.knora.webapi.messages.v2.responder.ontologymessages.OwlCardinality.* import org.knora.webapi.messages.v2.responder.ontologymessages.* +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState import org.knora.webapi.messages.v2.responder.resourcemessages.* import org.knora.webapi.messages.v2.responder.standoffmessages.GetMappingRequestV2 import org.knora.webapi.messages.v2.responder.standoffmessages.GetMappingResponseV2 @@ -369,14 +370,18 @@ final case class ResourcesResponderV2Live( ) } yield taskResult - // If the request includes file values, tell Sipi to move the files to permanent storage if the update - // succeeded, or to delete the temporary files if the update failed. - val fileValues = Seq(createResourceRequestV2.createResource) - .flatMap(_.flatValues) - .map(_.valueContent) - .filter(_.isInstanceOf[FileValueContentV2]) - .map(_.asInstanceOf[FileValueContentV2]) - resourceUtilV2.doSipiPostUpdate(triplestoreUpdateFuture, fileValues, createResourceRequestV2.requestingUser) + createResourceRequestV2.ingestState match { + case AssetIngestState.AssetIngested => triplestoreUpdateFuture + // If the request includes file values, tell Sipi to move the files to permanent storage if the update + // succeeded, or to delete the temporary files if the update failed. + case AssetIngestState.AssetInTemp => + val fileValues = Seq(createResourceRequestV2.createResource) + .flatMap(_.flatValues) + .map(_.valueContent) + .filter(_.isInstanceOf[FileValueContentV2]) + .map(_.asInstanceOf[FileValueContentV2]) + resourceUtilV2.doSipiPostUpdate(triplestoreUpdateFuture, fileValues, createResourceRequestV2.requestingUser) + } } /** diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala index 5fe9811db8..8bdf0ffa1d 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala @@ -26,6 +26,8 @@ import org.knora.webapi.messages.ValuesValidator import org.knora.webapi.messages.ValuesValidator.arkTimestampToInstant import org.knora.webapi.messages.ValuesValidator.xsdDateTimeStampToInstant import org.knora.webapi.messages.util.rdf.JsonLDUtil +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState.AssetInTemp +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState.AssetIngested import org.knora.webapi.messages.v2.responder.resourcemessages.* import org.knora.webapi.messages.v2.responder.valuemessages.* import org.knora.webapi.responders.v2.SearchResponderV2 @@ -99,7 +101,10 @@ final case class ResourcesRouteV2(appConfig: AppConfig)( requestDoc <- RouteUtilV2.parseJsonLd(jsonRequest) requestingUser <- Authenticator.getUserADM(requestContext) apiRequestId <- RouteUtilZ.randomUuid() - requestMessage <- CreateResourceRequestV2.fromJsonLd(requestDoc, apiRequestId, requestingUser) + header = "X-Asset-Ingested" + ingestState = if (requestContext.request.headers.exists(_.name == header)) AssetIngested + else AssetInTemp + requestMessage <- CreateResourceRequestV2.fromJsonLd(requestDoc, apiRequestId, requestingUser, ingestState) // check for each value which represents a file value if the file's MIME type is allowed _ <- checkMimeTypesForFileValueContents(requestMessage.createResource.flatValues) } yield requestMessage diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala index eb09dc23d4..72d4c0e61d 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala @@ -16,6 +16,8 @@ import org.knora.webapi.config.AppConfig import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.ValuesValidator +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState.* import org.knora.webapi.messages.v2.responder.resourcemessages.ResourcesGetRequestV2 import org.knora.webapi.messages.v2.responder.valuemessages.* import org.knora.webapi.responders.v2.ValuesResponderV2 @@ -83,7 +85,7 @@ final case class ValuesRouteV2()( for { requestingUser <- Authenticator.getUserADM(ctx) apiRequestId <- Random.nextUUID - valueToCreate <- CreateValueV2.fromJsonLd(jsonLdString, requestingUser) + valueToCreate <- CreateValueV2.fromJsonLd(AssetIngestState.AssetInTemp, jsonLdString, requestingUser) response <- ValuesResponderV2.createValueV2(valueToCreate, requestingUser, apiRequestId) } yield response, ctx diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala index f7db7e776a..4be4349138 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala @@ -14,6 +14,7 @@ import zio.nio.file.Path import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.sipimessages.* import org.knora.webapi.messages.v2.responder.SuccessResponseV2 +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.service.Asset import org.knora.webapi.store.iiif.errors.SipiException @@ -62,12 +63,21 @@ object FileMetadataSipiResponse { trait SipiService { /** - * Asks Sipi for metadata about a file, served from the 'knora.json' route. + * Asks Sipi for metadata about a file in the tmp folder, served from the 'knora.json' route. * - * @param filePath the path to the file. + * @param filename the path to the file. * @return a [[FileMetadataSipiResponse]] containing the requested metadata. */ - def getFileMetadata(filePath: String): Task[FileMetadataSipiResponse] + def getFileMetadataFromTemp(filename: String): Task[FileMetadataSipiResponse] + + /** + * Asks Sipi for metadata about a file in permanent location, served from the 'knora.json' route. + * + * @param filename the path to the file. + * @param shortcode the shortcode of the project. + * @return a [[FileMetadataSipiResponse]] containing the requested metadata. + */ + def getFileMetadata(filename: String, shortcode: Shortcode): Task[FileMetadataSipiResponse] /** * Asks Sipi to move a file from temporary storage to permanent storage. diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala index 1c17ed960d..85f50d1c30 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala @@ -19,6 +19,7 @@ import org.apache.http.config.SocketConfig import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClients import org.apache.http.impl.conn.PoolingHttpClientConnectionManager +import org.apache.http.message.BasicHeader import org.apache.http.message.BasicNameValuePair import org.apache.http.util.EntityUtils import spray.json.* @@ -35,9 +36,11 @@ import org.knora.webapi.config.AppConfig import org.knora.webapi.config.Sipi import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.sipimessages.* +import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.v2.responder.SuccessResponseV2 import org.knora.webapi.routing.Jwt import org.knora.webapi.routing.JwtService +import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.service.Asset import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse import org.knora.webapi.store.iiif.api.SipiService @@ -69,16 +72,32 @@ final case class SipiServiceLive( /** * Asks Sipi for metadata about a file, served from the 'knora.json' route. * - * @param filePath the path to the file. + * @param filename the file name * @return a [[FileMetadataSipiResponse]] containing the requested metadata. */ - override def getFileMetadata(filePath: String): Task[FileMetadataSipiResponse] = - doSipiRequest(new HttpGet(sipiConfig.internalBaseUrl + filePath + "/knora.json")) - .flatMap(bodyStr => - ZIO - .fromEither(bodyStr.fromJson[FileMetadataSipiResponse]) - .mapError(e => SipiException(s"Invalid response from Sipi: $e, $bodyStr")) - ) + override def getFileMetadataFromTemp(filename: String): Task[FileMetadataSipiResponse] = + getFileMetadataFromUrl(s"${sipiConfig.internalBaseUrl}/tmp/$filename/knora.json") + + /** + * Asks Sipi for metadata about a file in permanent location, served from the 'knora.json' route. + * + * @param filename the path to the file. + * @param shortcode the shortcode of the project. + * @return a [[FileMetadataSipiResponse]] containing the requested metadata. + */ + override def getFileMetadata(filename: String, shortcode: KnoraProject.Shortcode): Task[FileMetadataSipiResponse] = + getFileMetadataFromUrl(s"${sipiConfig.internalBaseUrl}/${shortcode.value}/$filename/knora.json") + + private def getFileMetadataFromUrl(url: String): Task[FileMetadataSipiResponse] = + for { + jwt <- jwtService.createJwt(KnoraSystemInstances.Users.SystemUser) + request = new HttpGet(url) + _ = request.addHeader(new BasicHeader("Authorization", s"Bearer ${jwt.jwtString}")) + bodyStr <- doSipiRequest(request) + res <- ZIO + .fromEither(bodyStr.fromJson[FileMetadataSipiResponse]) + .mapError(e => SipiException(s"Invalid response from Sipi: $e, $bodyStr")) + } yield res /** * Asks Sipi to move a file from temporary storage to permanent storage. diff --git a/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValuesV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValuesV2Spec.scala new file mode 100644 index 0000000000..ba04e8071c --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValuesV2Spec.scala @@ -0,0 +1,89 @@ +/* + * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.v2.responder.valuemessages + +import zio.* +import zio.nio.file.Path +import zio.test.Assertion.* +import zio.test.* + +import dsp.errors.AssertionException +import org.knora.webapi.messages.admin.responder.usersmessages.UserADM +import org.knora.webapi.messages.store.sipimessages.* +import org.knora.webapi.messages.util.rdf.JsonLDUtil +import org.knora.webapi.messages.v2.responder.SuccessResponseV2 +import org.knora.webapi.messages.v2.responder.resourcemessages.CreateResourceRequestV2.AssetIngestState +import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.service.Asset +import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse +import org.knora.webapi.store.iiif.api.SipiService + +object ValuesV2Spec extends ZIOSpecDefault { + private val json = + """{ + | "http://api.knora.org/ontology/knora-api/v2#fileValueHasFilename" : "filename" + | }""".stripMargin + + override def spec: Spec[TestEnvironment with Scope, Any] = + suite("ValuesV2")( + test("Expect file to be not ingested and in `tmp` folder") { + for { + ingested <- ValueContentV2 + .getFileInfo("0001", AssetIngestState.AssetIngested, JsonLDUtil.parseJsonLD(json).body) + .exit + temp <- ValueContentV2 + .getFileInfo("0001", AssetIngestState.AssetInTemp, JsonLDUtil.parseJsonLD(json).body) + .exit + } yield assert(ingested)(fails(isSubtype[AssertionException](anything))) && assert(temp)(succeeds(anything)) + }.provide(sipiServiceAllowMetadataInTemp), + test("Expect file to be already ingested and in project folder") { + for { + ingested <- ValueContentV2 + .getFileInfo("0001", AssetIngestState.AssetIngested, JsonLDUtil.parseJsonLD(json).body) + .exit + temp <- ValueContentV2 + .getFileInfo("0001", AssetIngestState.AssetInTemp, JsonLDUtil.parseJsonLD(json).body) + .exit + } yield assert(ingested)(succeeds(anything)) && assert(temp)(fails(isSubtype[AssertionException](anything))) + }.provide(sipiServiceAllowMetadataInProjectFolder) + ) + + private val response: FileMetadataSipiResponse = + FileMetadataSipiResponse(None, None, "", None, None, None, None, None) + + private def sipiServiceAllowMetadataInTemp: ULayer[SipiService] = ZLayer.succeed(new SipiService { + override def getFileMetadataFromTemp(filename: String): Task[FileMetadataSipiResponse] = + ZIO.succeed(response) + override def getFileMetadata(filename: String, shortcode: KnoraProject.Shortcode): Task[FileMetadataSipiResponse] = + ZIO.fail(AssertionException(filename)) + def moveTemporaryFileToPermanentStorage( + moveTemporaryFileToPermanentStorageRequestV2: MoveTemporaryFileToPermanentStorageRequest + ): Task[SuccessResponseV2] = ??? + def deleteTemporaryFile( + deleteTemporaryFileRequestV2: DeleteTemporaryFileRequest + ): Task[SuccessResponseV2] = ??? + def getTextFileRequest(textFileRequest: SipiGetTextFileRequest): Task[SipiGetTextFileResponse] = ??? + def getStatus(): Task[IIIFServiceStatusResponse] = ??? + def downloadAsset(asset: Asset, targetDir: Path, user: UserADM): Task[Option[Path]] = ??? + }) + + private def sipiServiceAllowMetadataInProjectFolder: ULayer[SipiService] = ZLayer.succeed(new SipiService { + override def getFileMetadata(filename: String, shortcode: KnoraProject.Shortcode): Task[FileMetadataSipiResponse] = + ZIO.succeed(response) + override def getFileMetadataFromTemp(filename: String): Task[FileMetadataSipiResponse] = + ZIO.fail(AssertionException(filename)) + def moveTemporaryFileToPermanentStorage( + moveTemporaryFileToPermanentStorageRequestV2: MoveTemporaryFileToPermanentStorageRequest + ): Task[SuccessResponseV2] = ??? + def deleteTemporaryFile( + deleteTemporaryFileRequestV2: DeleteTemporaryFileRequest + ): Task[SuccessResponseV2] = ??? + def getTextFileRequest(textFileRequest: SipiGetTextFileRequest): Task[SipiGetTextFileResponse] = ??? + def getStatus(): Task[IIIFServiceStatusResponse] = ??? + def downloadAsset(asset: Asset, targetDir: Path, user: UserADM): Task[Option[Path]] = ??? + }) + +}