diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala index 912b1fd738..48a69042ad 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala @@ -54,7 +54,27 @@ import java.util.UUID /** * Operations for handling files */ -trait Files { +final class Files( + formDataExtractor: FormDataExtractor, + log: FilesLog, + aclCheck: AclCheck, + fetchContext: FetchContext[FileRejection], + storages: Storages, + storagesStatistics: StoragesStatistics, + remoteDiskStorageClient: RemoteDiskStorageClient, + config: StorageTypeConfig +)(implicit + uuidF: UUIDF, + system: ClassicActorSystem +) { + + implicit private val kamonComponent: KamonMetricComponent = KamonMetricComponent(entityType.value) + + // format: off + private val testStorageRef = ResourceRef.Revision(iri"http://localhost/test", 1) + private val testStorageType = StorageType.DiskStorage + private val testAttributes = FileAttributes(UUID.randomUUID(), "http://localhost", Uri.Path.Empty, "", None, 0, ComputedDigest(DigestAlgorithm.default, "value"), Client) + // format: on /** * Create a new file where the id is self generated @@ -70,7 +90,16 @@ trait Files { storageId: Option[IdSegment], projectRef: ProjectRef, entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onCreate(projectRef) + iri <- generateId(pc) + _ <- test(CreateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, caller.subject)) + (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) + attributes <- extractFileAttributes(iri, entity, storage) + res <- eval(CreateFile(iri, projectRef, storageRef, storage.tpe, attributes, caller.subject)) + } yield res + }.span("createFile") /** * Create a new file with the provided id @@ -89,7 +118,16 @@ trait Files { storageId: Option[IdSegment], projectRef: ProjectRef, entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onCreate(projectRef) + iri <- expandIri(id, pc) + _ <- test(CreateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, caller.subject)) + (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) + attributes <- extractFileAttributes(iri, entity, storage) + res <- eval(CreateFile(iri, projectRef, storageRef, storage.tpe, attributes, caller.subject)) + } yield res + }.span("createFile") /** * Create a new file linking where the id is self generated @@ -111,7 +149,13 @@ trait Files { filename: Option[String], mediaType: Option[ContentType], path: Uri.Path - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onCreate(projectRef) + iri <- generateId(pc) + res <- createLink(iri, projectRef, pc, storageId, filename, mediaType, path) + } yield res + }.span("createLink") /** * Create a new file linking it from an existing file in a storage @@ -136,7 +180,13 @@ trait Files { filename: Option[String], mediaType: Option[ContentType], path: Uri.Path - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onCreate(projectRef) + iri <- expandIri(id, pc) + res <- createLink(iri, projectRef, pc, storageId, filename, mediaType, path) + } yield res + }.span("createLink") /** * Update an existing file @@ -158,7 +208,16 @@ trait Files { projectRef: ProjectRef, rev: Int, entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onModify(projectRef) + iri <- expandIri(id, pc) + _ <- test(UpdateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, rev, caller.subject)) + (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) + attributes <- extractFileAttributes(iri, entity, storage) + res <- eval(UpdateFile(iri, projectRef, storageRef, storage.tpe, attributes, rev, caller.subject)) + } yield res + }.span("updateFile") /** * Update a new file linking it from an existing file in a storage @@ -186,7 +245,20 @@ trait Files { mediaType: Option[ContentType], path: Uri.Path, rev: Int - )(implicit caller: Caller): IO[FileRejection, FileResource] + )(implicit caller: Caller): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onModify(projectRef) + iri <- expandIri(id, pc) + _ <- test(UpdateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, rev, caller.subject)) + (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) + resolvedFilename <- IO.fromOption(filename.orElse(path.lastSegment), InvalidFileLink(iri)) + description <- FileDescription(resolvedFilename, mediaType) + attributes <- LinkFile(storage, remoteDiskStorageClient, config) + .apply(path, description) + .mapError(LinkRejection(iri, storage.id, _)) + res <- eval(UpdateFile(iri, projectRef, storageRef, storage.tpe, attributes, rev, caller.subject)) + } yield res + }.span("updateLink") /** * Add a tag to an existing file @@ -208,7 +280,13 @@ trait Files { tag: UserTag, tagRev: Int, rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] + )(implicit subject: Subject): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onModify(projectRef) + iri <- expandIri(id, pc) + res <- eval(TagFile(iri, projectRef, tagRev, tag, rev, subject)) + } yield res + }.span("tagFile") /** * Delete a tag on an existing file. @@ -227,7 +305,13 @@ trait Files { projectRef: ProjectRef, tag: UserTag, rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] + )(implicit subject: Subject): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onModify(projectRef) + iri <- expandIri(id, pc) + res <- eval(DeleteFileTag(iri, projectRef, tag, rev, subject)) + } yield res + }.span("deleteFileTag") /** * Deprecate an existing file @@ -243,7 +327,13 @@ trait Files { id: IdSegment, projectRef: ProjectRef, rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] + )(implicit subject: Subject): IO[FileRejection, FileResource] = { + for { + pc <- fetchContext.onModify(projectRef) + iri <- expandIri(id, pc) + res <- eval(DeprecateFile(iri, projectRef, rev, subject)) + } yield res + }.span("deprecateFile") /** * Fetch the last version of a file content @@ -253,196 +343,7 @@ trait Files { * @param project * the project where the storage belongs */ - def fetchContent(id: IdSegmentRef, project: ProjectRef)(implicit caller: Caller): IO[FileRejection, FileResponse] - - /** - * Fetch the last version of a file - * - * @param id - * the identifier that will be expanded to the Iri of the file with its optional rev/tag - * @param project - * the project where the storage belongs - */ - def fetch(id: IdSegmentRef, project: ProjectRef): IO[FileRejection, FileResource] - - /** - * Starts a stream that attempts to update file attributes asynchronously for linked files in remote storages - * - * @param offset - * the offset to start from - * @return - */ - private[files] def attributesUpdateStream(offset: Offset): ElemStream[Unit] - - private[files] def updateAttributes(iri: Iri, project: ProjectRef): IO[FileRejection, Unit] -} - -final private class FilesImpl( - formDataExtractor: FormDataExtractor, - log: FilesLog, - aclCheck: AclCheck, - fetchContext: FetchContext[FileRejection], - storages: Storages, - storagesStatistics: StoragesStatistics, - remoteDiskStorageClient: RemoteDiskStorageClient, - config: StorageTypeConfig -)(implicit - uuidF: UUIDF, - system: ClassicActorSystem -) extends Files { - - implicit private val kamonComponent: KamonMetricComponent = KamonMetricComponent(entityType.value) - - private val logger: Logger = Logger[Files] - - // format: off - private val testStorageRef = ResourceRef.Revision(iri"http://localhost/test", 1) - private val testStorageType = StorageType.DiskStorage - private val testAttributes = FileAttributes(UUID.randomUUID(), "http://localhost", Uri.Path.Empty, "", None, 0, ComputedDigest(DigestAlgorithm.default, "value"), Client) - // format: on - - override def create( - storageId: Option[IdSegment], - projectRef: ProjectRef, - entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onCreate(projectRef) - iri <- generateId(pc) - _ <- test(CreateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, caller.subject)) - (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) - attributes <- extractFileAttributes(iri, entity, storage) - res <- eval(CreateFile(iri, projectRef, storageRef, storage.tpe, attributes, caller.subject)) - } yield res - }.span("createFile") - - override def create( - id: IdSegment, - storageId: Option[IdSegment], - projectRef: ProjectRef, - entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onCreate(projectRef) - iri <- expandIri(id, pc) - _ <- test(CreateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, caller.subject)) - (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) - attributes <- extractFileAttributes(iri, entity, storage) - res <- eval(CreateFile(iri, projectRef, storageRef, storage.tpe, attributes, caller.subject)) - } yield res - }.span("createFile") - - override def createLink( - storageId: Option[IdSegment], - projectRef: ProjectRef, - filename: Option[String], - mediaType: Option[ContentType], - path: Uri.Path - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onCreate(projectRef) - iri <- generateId(pc) - res <- createLink(iri, projectRef, pc, storageId, filename, mediaType, path) - } yield res - }.span("createLink") - - override def createLink( - id: IdSegment, - storageId: Option[IdSegment], - projectRef: ProjectRef, - filename: Option[String], - mediaType: Option[ContentType], - path: Uri.Path - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onCreate(projectRef) - iri <- expandIri(id, pc) - res <- createLink(iri, projectRef, pc, storageId, filename, mediaType, path) - } yield res - }.span("createLink") - - override def update( - id: IdSegment, - storageId: Option[IdSegment], - projectRef: ProjectRef, - rev: Int, - entity: HttpEntity - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onModify(projectRef) - iri <- expandIri(id, pc) - _ <- test(UpdateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, rev, caller.subject)) - (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) - attributes <- extractFileAttributes(iri, entity, storage) - res <- eval(UpdateFile(iri, projectRef, storageRef, storage.tpe, attributes, rev, caller.subject)) - } yield res - }.span("updateFile") - - override def updateLink( - id: IdSegment, - storageId: Option[IdSegment], - projectRef: ProjectRef, - filename: Option[String], - mediaType: Option[ContentType], - path: Uri.Path, - rev: Int - )(implicit caller: Caller): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onModify(projectRef) - iri <- expandIri(id, pc) - _ <- test(UpdateFile(iri, projectRef, testStorageRef, testStorageType, testAttributes, rev, caller.subject)) - (storageRef, storage) <- fetchActiveStorage(storageId, projectRef, pc) - resolvedFilename <- IO.fromOption(filename.orElse(path.lastSegment), InvalidFileLink(iri)) - description <- FileDescription(resolvedFilename, mediaType) - attributes <- LinkFile(storage, remoteDiskStorageClient, config) - .apply(path, description) - .mapError(LinkRejection(iri, storage.id, _)) - res <- eval(UpdateFile(iri, projectRef, storageRef, storage.tpe, attributes, rev, caller.subject)) - } yield res - }.span("updateLink") - - override def tag( - id: IdSegment, - projectRef: ProjectRef, - tag: UserTag, - tagRev: Int, - rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onModify(projectRef) - iri <- expandIri(id, pc) - res <- eval(TagFile(iri, projectRef, tagRev, tag, rev, subject)) - } yield res - }.span("tagFile") - - override def deleteTag( - id: IdSegment, - projectRef: ProjectRef, - tag: UserTag, - rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onModify(projectRef) - iri <- expandIri(id, pc) - res <- eval(DeleteFileTag(iri, projectRef, tag, rev, subject)) - } yield res - }.span("deleteFileTag") - - override def deprecate( - id: IdSegment, - projectRef: ProjectRef, - rev: Int - )(implicit subject: Subject): IO[FileRejection, FileResource] = { - for { - pc <- fetchContext.onModify(projectRef) - iri <- expandIri(id, pc) - res <- eval(DeprecateFile(iri, projectRef, rev, subject)) - } yield res - }.span("deprecateFile") - - override def fetchContent(id: IdSegmentRef, project: ProjectRef)(implicit - caller: Caller - ): IO[FileRejection, FileResponse] = { + def fetchContent(id: IdSegmentRef, project: ProjectRef)(implicit caller: Caller): IO[FileRejection, FileResponse] = { for { file <- fetch(id, project) attributes = file.value.attributes @@ -457,7 +358,15 @@ final private class FilesImpl( } yield FileResponse(attributes.filename, mediaType, attributes.bytes, s) }.span("fetchFileContent") - override def fetch(id: IdSegmentRef, project: ProjectRef): IO[FileRejection, FileResource] = { + /** + * Fetch the last version of a file + * + * @param id + * the identifier that will be expanded to the Iri of the file with its optional rev/tag + * @param project + * the project where the storage belongs + */ + def fetch(id: IdSegmentRef, project: ProjectRef): IO[FileRejection, FileResource] = { for { pc <- fetchContext.onRead(project) iri <- expandIri(id.value, pc) @@ -539,7 +448,13 @@ final private class FilesImpl( private def generateId(pc: ProjectContext)(implicit uuidF: UUIDF): UIO[Iri] = uuidF().map(uuid => pc.base.iri / uuid.toString) - override private[files] def attributesUpdateStream(offset: Offset): ElemStream[Unit] = { + /** + * Starts a stream that attempts to update file attributes asynchronously for linked files in remote storages + * @param offset + * the offset to start from + * @return + */ + private[files] def attributesUpdateStream(offset: Offset): ElemStream[Unit] = { for { // The stream will start only if remote storage is enabled retryStrategy <- Stream.iterable(config.remoteDisk).map { c => @@ -600,7 +515,7 @@ final private class FilesImpl( } yield stream } - override private[files] def updateAttributes(iri: Iri, project: ProjectRef): IO[FileRejection, Unit] = + private[files] def updateAttributes(iri: Iri, project: ProjectRef): IO[FileRejection, Unit] = for { f <- log.stateOr(project, iri, FileNotFound(iri, project)) storage <- storages @@ -628,6 +543,8 @@ final private class FilesImpl( object Files { + private val logger: Logger = Logger[Files] + /** * The file entity type. */ @@ -802,7 +719,7 @@ object Files { as: ActorSystem[Nothing] ): Files = { implicit val classicAs: ClassicActorSystem = as.classicSystem - new FilesImpl( + new Files( FormDataExtractor(config.mediaTypeDetector), ScopedEventLog(definition, config.eventLog, xas), aclCheck, diff --git a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/routes/FilesRoutesSpec.scala b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/routes/FilesRoutesSpec.scala index 70c360fa53..2d8cb6a800 100644 --- a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/routes/FilesRoutesSpec.scala +++ b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/routes/FilesRoutesSpec.scala @@ -5,42 +5,38 @@ import akka.http.scaladsl.model.ContentTypes.`text/plain(UTF-8)` import akka.http.scaladsl.model.MediaRanges._ import akka.http.scaladsl.model.MediaTypes.`text/html` import akka.http.scaladsl.model.headers.{Accept, Location, OAuth2BearerToken, RawHeader} -import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.server.Route import ch.epfl.bluebrain.nexus.delta.kernel.http.MediaTypeDetectorConfig import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.Digest.ComputedDigest import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.{FileAttributes, FileRejection} import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.routes.FilesRoutesSpec.fileMetadata -import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.{contexts => fileContexts, permissions, FileFixtures, FileResource, Files, FilesConfig} +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.{contexts => fileContexts, permissions, FileFixtures, Files, FilesConfig} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.{StorageRejection, StorageStatEntry, StorageType} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.remote.client.RemoteDiskStorageClient -import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{contexts => storageContexts, permissions => storagesPermissions, StorageFixtures, Storages, StoragesConfig} +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{contexts => storageContexts, permissions => storagesPermissions, StorageFixtures, Storages, StoragesConfig, StoragesStatistics} import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.RdfMediaTypes.`application/ld+json` import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{contexts, nxv} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution} -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder import ch.epfl.bluebrain.nexus.delta.sdk.IndexingAction import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclSimpleCheck import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.AclAddress import ch.epfl.bluebrain.nexus.delta.sdk.auth.{AuthTokenProvider, Credentials} -import ch.epfl.bluebrain.nexus.delta.sdk.directives.{DeltaSchemeDirectives, FileResponse} +import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaSchemeDirectives import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClient import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesDummy import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.{Caller, ServiceAccount} import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ -import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields -import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment, IdSegmentRef, ResourceUris} +import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, ResourceUris} import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.events import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContextDummy import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution -import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection import ch.epfl.bluebrain.nexus.delta.sdk.utils.{BaseRouteSpec, RouteFixtures} import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Anonymous, Authenticated, Group, Subject, User} -import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef, ResourceRef, Tag} -import ch.epfl.bluebrain.nexus.delta.sourcing.offset.Offset +import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef, ResourceRef} import ch.epfl.bluebrain.nexus.testkit._ import ch.epfl.bluebrain.nexus.testkit.bio.IOFromMap import io.circe.Json @@ -57,6 +53,8 @@ class FilesRoutesSpec import akka.actor.typed.scaladsl.adapter._ implicit val typedSystem: typed.ActorSystem[Nothing] = system.toTyped val httpClient: HttpClient = HttpClient()(httpClientConfig, system, s) + val authTokenProvider: AuthTokenProvider = AuthTokenProvider.anonymousForTest + val remoteDiskStorageClient = new RemoteDiskStorageClient(httpClient, authTokenProvider, Credentials.Anonymous) // TODO: sort out how we handle this in tests implicit override def rcr: RemoteContextResolution = @@ -72,6 +70,7 @@ class FilesRoutesSpec implicit private val caller: Caller = Caller(alice, Set(alice, Anonymous, Authenticated(realm), Group("group", realm))) + private val identities = IdentitiesDummy(caller) private val asAlice = addCredentials(OAuth2BearerToken("alice")) @@ -95,6 +94,9 @@ class FilesRoutesSpec private val stCfg = config.copy(disk = config.disk.copy(defaultMaxFileSize = 1000, allowedVolumes = Set(path))) + private val storagesStatistics: StoragesStatistics = + (_, _) => IO.pure { StorageStatEntry(0, 0) } + private val aclCheck = AclSimpleCheck().accepted lazy val storages: Storages = Storages( fetchContext.mapRejection(StorageRejection.ProjectContextRejection), @@ -105,65 +107,21 @@ class FilesRoutesSpec StoragesConfig(eventLogConfig, pagination, stCfg), ServiceAccount(User("nexus-sa", Label.unsafe("sa"))) ).accepted - - private def createFiles() = { - Files( - fetchContext.mapRejection(FileRejection.ProjectContextRejection), - aclCheck, - storages, - (_, _) => - IO.pure { - StorageStatEntry(0, 0) - }, - xas, - config, - FilesConfig(eventLogConfig, MediaTypeDetectorConfig.Empty), - new RemoteDiskStorageClient(httpClient, AuthTokenProvider.anonymousForTest, Credentials.Anonymous) - ) - } - - private lazy val routes = - createRoutes(createFiles()) - - // format: off - private def filesWithErrorInFileContents[E: JsonLdEncoder: HttpResponseFields](error: E): Files = { - new Files { - override def create(storageId: Option[IdSegment], projectRef: ProjectRef, entity: HttpEntity)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def create(id: IdSegment, storageId: Option[IdSegment], projectRef: ProjectRef, entity: HttpEntity)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def createLink(storageId: Option[IdSegment], projectRef: ProjectRef, filename: Option[String], mediaType: Option[ContentType], path: Uri.Path)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def createLink(id: IdSegment, storageId: Option[IdSegment], projectRef: ProjectRef, filename: Option[String], mediaType: Option[ContentType], path: Uri.Path)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def update(id: IdSegment, storageId: Option[IdSegment], projectRef: ProjectRef, rev: Int, entity: HttpEntity)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def updateLink(id: IdSegment, storageId: Option[IdSegment], projectRef: ProjectRef, filename: Option[String], mediaType: Option[ContentType], path: Uri.Path, rev: Int)(implicit caller: Caller): IO[FileRejection, FileResource] = ??? - override def tag(id: IdSegment, projectRef: ProjectRef, tag: Tag.UserTag, tagRev: Int, rev: Int)(implicit subject: Subject): IO[FileRejection, FileResource] = ??? - override def deleteTag(id: IdSegment, projectRef: ProjectRef, tag: Tag.UserTag, rev: Int)(implicit subject: Subject): IO[FileRejection, FileResource] = ??? - override def deprecate(id: IdSegment, projectRef: ProjectRef, rev: Int)(implicit subject: Subject): IO[FileRejection, FileResource] = ??? - override def fetchContent(id: IdSegmentRef, project: ProjectRef)(implicit caller: Caller): IO[FileRejection, FileResponse] = { - IO.pure( - FileResponse( - "file.name", - ContentTypes.`text/plain(UTF-8)`, - 1024, - IO.raiseError(error) - ) - ) - } - override def fetch(id: IdSegmentRef, project: ProjectRef): IO[FileRejection, FileResource] = ??? - override private[files] def attributesUpdateStream(offset: Offset) = ??? - override private[files] def updateAttributes(iri: Iri, project: ProjectRef) = ??? - } - } - // format: on - - private def createRoutes(files: Files) = Route.seal( - FilesRoutes( - stCfg, - IdentitiesDummy(caller), - aclCheck, - files, - DeltaSchemeDirectives(fetchContext, ioFromMap(uuid -> projectRef.organization), ioFromMap(uuid -> projectRef)), - IndexingAction.noop - ) + lazy val files: Files = Files( + fetchContext.mapRejection(FileRejection.ProjectContextRejection), + aclCheck, + storages, + storagesStatistics, + xas, + config, + FilesConfig(eventLogConfig, MediaTypeDetectorConfig.Empty), + remoteDiskStorageClient ) + private val groupDirectives = + DeltaSchemeDirectives(fetchContext, ioFromMap(uuid -> projectRef.organization), ioFromMap(uuid -> projectRef)) + + private lazy val routes = + Route.seal(FilesRoutes(stCfg, identities, aclCheck, files, groupDirectives, IndexingAction.noop)) private val diskIdRev = ResourceRef.Revision(dId, 1) private val s3IdRev = ResourceRef.Revision(s3Id, 2) @@ -515,17 +473,6 @@ class FilesRoutesSpec response.header[Location].value.uri shouldEqual Uri("https://bbp.epfl.ch/nexus/web/org/project/resources/file1") } } - - "fail when auth fails" in { - val mockRoutes = createRoutes(filesWithErrorInFileContents[ResourceRejection](ResourceRejection.BlankResourceId)) - Get("/v1/files/org/proj/file1") ~> Accept(`*/*`) ~> mockRoutes ~> check { - contentType.value shouldEqual `application/ld+json`.value - response.asJson shouldEqual jsonContentOf( - "/files/errors/blank-id.json" - ) - status shouldEqual StatusCodes.BadRequest - } - } } }