Skip to content

Commit

Permalink
Add the ability to link a file while generating the id (#5088)
Browse files Browse the repository at this point in the history
* Add the ability to link a file while generating the id

* Fix compilation issue

---------

Co-authored-by: Simon Dumas <[email protected]>
  • Loading branch information
imsdu and Simon Dumas authored Aug 9, 2024
1 parent c5c9fcd commit 74a7835
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ class StoragePluginModule(priority: Int) extends ModuleDef {
jwsPayloadHelper: JWSPayloadHelper,
aclCheck: AclCheck,
files: Files,
schemeDirectives: DeltaSchemeDirectives,
indexingAction: AggregateIndexingAction,
shift: File.Shift,
baseUri: BaseUri,
Expand All @@ -312,8 +311,7 @@ class StoragePluginModule(priority: Int) extends ModuleDef {
aclCheck,
files,
jwsPayloadHelper,
indexingAction(_, _, _)(shift),
schemeDirectives
indexingAction(_, _, _)(shift)
)(baseUri, cr, ordering, showLocation)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,31 +237,23 @@ final class Files(
}.span("updateFileMetadata")

def linkFile(
id: FileId,
id: Option[IdSegment],
project: ProjectRef,
storageId: Option[IdSegment],
linkRequest: FileLinkRequest,
tag: Option[UserTag]
)(implicit caller: Caller): IO[FileResource] = {
for {
(iri, pc) <- id.expandIri(fetchContext.onCreate)
(storageRef, storage) <- fetchAndValidateActiveStorage(storageId, id.project, pc)
projectContext <- fetchContext.onCreate(project)
iri <- id.fold(generateId(projectContext)) { FileId.iriExpander(_, projectContext) }
(storageRef, storage) <- fetchAndValidateActiveStorage(storageId, project, projectContext)
s3Metadata <- fileOperations.link(storage, linkRequest.path)
filename <- IO.fromOption(linkRequest.path.lastSegment)(InvalidFilePath)
attr = FileAttributes.from(
FileDescription(filename, linkRequest.mediaType.orElse(s3Metadata.contentType), linkRequest.metadata),
s3Metadata.metadata
)
res <- eval(
CreateFile(
iri,
id.project,
storageRef,
storage.tpe,
attr,
caller.subject,
tag
)
)
res <- eval(CreateFile(iri, project, storageRef, storage.tpe, attr, caller.subject, tag))
} yield res
}.span("linkFile")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import ch.epfl.bluebrain.nexus.delta.sdk.{IndexingAction, IndexingMode}
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.circe.{CirceMarshalling, CirceUnmarshalling}
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.directives.{AuthDirectives, DeltaSchemeDirectives, ResponseToJsonLd}
import ch.epfl.bluebrain.nexus.delta.sdk.directives.{AuthDirectives, ResponseToJsonLd}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment}
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredDecoder
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.syntax.EncoderOps
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.routes.FileUriDirectives._
import io.circe.{Decoder, Encoder, Json}
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.jws.JWSPayloadHelper
Expand All @@ -35,8 +36,7 @@ final class DelegateFilesRoutes(
aclCheck: AclCheck,
files: Files,
jwsPayloadHelper: JWSPayloadHelper,
index: IndexingAction.Execute[File],
schemeDirectives: DeltaSchemeDirectives
index: IndexingAction.Execute[File]
)(implicit
baseUri: BaseUri,
cr: RemoteContextResolution,
Expand All @@ -46,8 +46,6 @@ final class DelegateFilesRoutes(
with CirceUnmarshalling
with CirceMarshalling { self =>

import schemeDirectives._

def routes: Route =
baseUriPrefix(baseUri.prefix) {
pathPrefix("delegate" / "files") {
Expand All @@ -56,15 +54,15 @@ final class DelegateFilesRoutes(
concat(
pathPrefix("validate") {
(pathEndOrSingleSlash & post) {
parameter("storage".as[IdSegment].?) { storageId =>
storageParam { storageId =>
entity(as[FileDescription]) { desc =>
emit(OK, validateFileDetails(project, storageId, desc).attemptNarrow[FileRejection])
}
}
}
},
(pathEndOrSingleSlash & post) {
(parameter("storage".as[IdSegment].?) & indexingMode) { (storageId, mode) =>
(storageParam & indexingMode) { (storageId, mode) =>
entity(as[Json]) { jwsPayload =>
emit(
Created,
Expand Down Expand Up @@ -97,9 +95,8 @@ final class DelegateFilesRoutes(
for {
originalPayload <- jwsPayloadHelper.verify(jwsPayload)
delegationResponse <- IO.fromEither(originalPayload.as[DelegationResponse])
fileId = FileId(delegationResponse.id, project)
request = FileLinkRequest(delegationResponse.path.path, delegationResponse.mediaType, delegationResponse.metadata)
fileResource <- files.linkFile(fileId, storageId, request, None)
fileResource <- files.linkFile(Some(delegationResponse.id), project, storageId, request, None)
_ <- index(project, fileResource, mode)
} yield fileResource

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ch.epfl.bluebrain.nexus.delta.plugins.storage.files.routes

import akka.http.scaladsl.server.Directives.parameter
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.QueryParamsUnmarshalling
import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegment

trait FileUriDirectives extends QueryParamsUnmarshalling {

def storageParam: Directive[Tuple1[Option[IdSegment]]] = parameter("storage".as[IdSegment].?)

}

object FileUriDirectives extends FileUriDirectives
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.{schemas, FileResourc
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.StoragesConfig.ShowFileLocation
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import FileUriDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk._
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.circe.CirceUnmarshalling
Expand All @@ -28,8 +29,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.model.routes.Tag
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredDecoder
import io.circe.{parser, Decoder}
Expand Down Expand Up @@ -74,9 +74,7 @@ final class FilesRoutes(
def index(m: IndexingMode): IO[FileResource] = io.flatTap(self.index(project, _, m))
}
concat(
(pathEndOrSingleSlash & post & noParameter("rev") & parameter(
"storage".as[IdSegment].?
) & indexingMode & tagParam) { (storage, mode, tag) =>
(pathEndOrSingleSlash & post & noRev & storageParam & indexingMode & tagParam) { (storage, mode, tag) =>
concat(
// Link a file without id segment
entity(as[LinkFileRequest]) { linkRequest =>
Expand Down Expand Up @@ -107,55 +105,54 @@ final class FilesRoutes(
concat(
(put & pathEndOrSingleSlash) {
concat(
parameters("rev".as[Int], "storage".as[IdSegment].?, "tag".as[UserTag].?) {
case (rev, storage, tag) =>
concat(
// Update a Link
entity(as[LinkFileRequest]) { linkRequest =>
(revParam & storageParam & tagParam) { case (rev, storage, tag) =>
concat(
// Update a Link
entity(as[LinkFileRequest]) { linkRequest =>
emit(
fileDescriptionFromRequest(linkRequest)
.flatMap { description =>
files
.updateLegacyLink(
fileId,
storage,
description,
linkRequest.path,
rev,
tag
)
.index(mode)
}
.attemptNarrow[FileRejection]
)
},
// Update a file
(requestEntityPresent & uploadRequest) { request =>
emit(
files
.update(fileId, storage, rev, request, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
},
// Update custom metadata
(requestEntityEmpty & extractFileMetadata & authorizeFor(project, Write)) {
case Some(FileCustomMetadata.empty) =>
emit(
fileDescriptionFromRequest(linkRequest)
.flatMap { description =>
files
.updateLegacyLink(
fileId,
storage,
description,
linkRequest.path,
rev,
tag
)
.index(mode)
}
.attemptNarrow[FileRejection]
IO.raiseError[FileResource](EmptyCustomMetadata).attemptNarrow[FileRejection]
)
},
// Update a file
(requestEntityPresent & uploadRequest) { request =>
case Some(metadata) =>
emit(
files
.update(fileId, storage, rev, request, tag)
.updateMetadata(fileId, rev, metadata, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
},
// Update custom metadata
(requestEntityEmpty & extractFileMetadata & authorizeFor(project, Write)) {
case Some(FileCustomMetadata.empty) =>
emit(
IO.raiseError[FileResource](EmptyCustomMetadata).attemptNarrow[FileRejection]
)
case Some(metadata) =>
emit(
files
.updateMetadata(fileId, rev, metadata, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
case None => reject
}
)
case None => reject
}
)
},
parameters("storage".as[IdSegment].?, "tag".as[UserTag].?) { case (storage, tag) =>
(storageParam & tagParam) { case (storage, tag) =>
concat(
// Link a file with id segment
entity(as[LinkFileRequest]) { linkRequest =>
Expand Down Expand Up @@ -185,7 +182,7 @@ final class FilesRoutes(
)
},
// Deprecate a file
(delete & parameter("rev".as[Int])) { rev =>
(delete & revParam) { rev =>
authorizeFor(project, Write).apply {
emit(
files
Expand Down Expand Up @@ -215,7 +212,7 @@ final class FilesRoutes(
)
},
// Tag a file
(post & parameter("rev".as[Int]) & pathEndOrSingleSlash) { rev =>
(post & revParam & pathEndOrSingleSlash) { rev =>
authorizeFor(project, Write).apply {
entity(as[Tag]) { case Tag(tagRev, tag) =>
emit(
Expand All @@ -226,7 +223,7 @@ final class FilesRoutes(
}
},
// Delete a tag
(tagLabel & delete & parameter("rev".as[Int]) & pathEndOrSingleSlash & authorizeFor(
(tagLabel & delete & revParam & pathEndOrSingleSlash & authorizeFor(
project,
Write
)) { (tag, rev) =>
Expand All @@ -240,7 +237,7 @@ final class FilesRoutes(
}
)
},
(pathPrefix("undeprecate") & put & parameter("rev".as[Int])) { rev =>
(pathPrefix("undeprecate") & put & revParam) { rev =>
authorizeFor(project, Write).apply {
emit(
files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import ch.epfl.bluebrain.nexus.delta.sdk.circe.CirceUnmarshalling
import ch.epfl.bluebrain.nexus.delta.sdk.directives.AuthDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.routes.FileUriDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import ch.epfl.bluebrain.nexus.delta.sdk.{IndexingAction, IndexingMode}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag

class LinkFilesRoutes(identities: Identities, aclCheck: AclCheck, files: Files, index: IndexingAction.Execute[File])(
implicit
Expand All @@ -28,6 +28,9 @@ class LinkFilesRoutes(identities: Identities, aclCheck: AclCheck, files: Files,
with CirceUnmarshalling {
self =>

private def onCreationDirective =
noRev & storageParam & tagParam & indexingMode & pathEndOrSingleSlash & entity(as[FileLinkRequest])

def routes: Route =
baseUriPrefix(baseUri.prefix) {
pathPrefix("link" / "files") {
Expand All @@ -37,24 +40,29 @@ class LinkFilesRoutes(identities: Identities, aclCheck: AclCheck, files: Files,
def index(m: IndexingMode): IO[FileResource] = io.flatTap(self.index(project, _, m))
}
concat(
// Link a file without an id segment
(onCreationDirective & post) { (storage, tag, mode, request) =>
emit(
Created,
files
.linkFile(None, project, storage, request, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
},
// Link a file with id segment
(put & idSegment & indexingMode & pathEndOrSingleSlash) { (id, mode) =>
(noParameter("rev") & parameters("storage".as[IdSegment].?, "tag".as[UserTag].?)) { (storage, tag) =>
entity(as[FileLinkRequest]) { request =>
val fileId = FileId(id, project)
emit(
Created,
files
.linkFile(fileId, storage, request, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
}
}
(idSegment & onCreationDirective & put) { (id, storage, tag, mode, request) =>
emit(
Created,
files
.linkFile(Some(id), project, storage, request, tag)
.index(mode)
.attemptNarrow[FileRejection]
)
},
// Update a linked file
(put & idSegment & indexingMode & pathEndOrSingleSlash) { (id, mode) =>
parameters("rev".as[Int], "storage".as[IdSegment].?, "tag".as[UserTag].?) { (rev, storage, tag) =>
(revParam & storageParam & tagParam) { (rev, storage, tag) =>
entity(as[FileLinkRequest]) { request =>
val fileId = FileId(id, project)
emit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ trait UriDirectives extends QueryParamsUnmarshalling {
}
}

def noRev: Directive0 = noParameter("rev")

/**
* Consumes a path Segment and parse it into an [[IdSegment]]
*/
Expand Down Expand Up @@ -175,6 +177,8 @@ trait UriDirectives extends QueryParamsUnmarshalling {
)
}

val revParam: Directive[Tuple1[Int]] = parameter("rev".as[Int])

/**
* Creates optional [[UserTag]] from `tag` query param.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FileProcessor private (
case CopySuccess(newPath) =>
val linkRequest = FileLinkRequest(newPath, attrs.mediaType, customMetadata)
files
.linkFile(fileId, None, linkRequest, e.tag)
.linkFile(Some(event.id), project, None, linkRequest, e.tag)
.as(ImportStatus.Success)
case CopySkipped => IO.pure(ImportStatus.Dropped)
}
Expand Down
Loading

0 comments on commit 74a7835

Please sign in to comment.