Skip to content

Commit

Permalink
Add the ability to link a file while generating the id
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Dumas committed Aug 8, 2024
1 parent c5c9fcd commit bf612ba
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 106 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 @@ -60,11 +60,6 @@ class HttpClient private (baseUrl: Uri, httpExt: HttpExt)(implicit
)(implicit um: FromEntityUnmarshaller[A]): IO[Assertion] =
requestAssert(POST, url, Some(body), identity, extraHeaders)(assertResponse)

def postJson(url: String, body: Json, identity: Identity, extraHeaders: Seq[HttpHeader] = jsonHeaders)(
assertResponse: (Json, HttpResponse) => Assertion
): IO[Assertion] =
requestAssert(POST, url, Some(body), identity, extraHeaders)(assertResponse)

def postIO[A](url: String, body: IO[Json], identity: Identity, extraHeaders: Seq[HttpHeader] = jsonHeaders)(
assertResponse: (A, HttpResponse) => Assertion
)(implicit um: FromEntityUnmarshaller[A]): IO[Assertion] = {
Expand Down
Loading

0 comments on commit bf612ba

Please sign in to comment.