Skip to content

Commit

Permalink
Do not index resolvers, list them from db and by project only (#5284)
Browse files Browse the repository at this point in the history
* Do not index resolvers, list them from db by project only

---------

Co-authored-by: Simon Dumas <[email protected]>
  • Loading branch information
imsdu and Simon Dumas authored Feb 20, 2025
1 parent b583f7b commit 352391a
Show file tree
Hide file tree
Showing 22 changed files with 111 additions and 421 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ 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.marshalling.{HttpResponseFields, OriginalSource, RdfMarshalling}
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults.searchResultsJsonLdEncoder
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment, IdSegmentRef, ResourceF}
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.resolvers.{read => Read, write => Write}
Expand All @@ -40,16 +42,13 @@ import io.circe.Json
* the resolvers module
* @param schemeDirectives
* directives related to orgs and projects
* @param indexAction
* the indexing action on write operations
*/
final class ResolversRoutes(
identities: Identities,
aclCheck: AclCheck,
resolvers: Resolvers,
multiResolution: MultiResolution,
schemeDirectives: DeltaSchemeDirectives,
indexAction: IndexingAction.Execute[Resolver]
schemeDirectives: DeltaSchemeDirectives
)(implicit
baseUri: BaseUri,
cr: RemoteContextResolution,
Expand Down Expand Up @@ -86,17 +85,22 @@ final class ResolversRoutes(
(baseUriPrefix(baseUri.prefix) & replaceUri("resolvers", schemas.resolvers)) {
pathPrefix("resolvers") {
extractCaller { implicit caller =>
(projectRef & indexingMode) { (project, indexingMode) =>
def index(resolver: ResolverResource): IO[Unit] =
indexAction(resolver.value.project, resolver, indexingMode)
val authorizeRead = authorizeFor(project, Read)
val authorizeWrite = authorizeFor(project, Write)
projectRef { project =>
val authorizeRead = authorizeFor(project, Read)
val authorizeWrite = authorizeFor(project, Write)
concat(
pathEndOrSingleSlash {
(get & authorizeRead) {
implicit val searchJsonLdEncoder: JsonLdEncoder[SearchResults[ResolverResource]] =
searchResultsJsonLdEncoder(Resolver.context)
emit(resolvers.list(project).widen[SearchResults[ResolverResource]])
}
},
pathEndOrSingleSlash {
// Create a resolver without an id segment
(post & noParameter("rev") & entity(as[Json])) { payload =>
authorizeWrite {
emitMetadata(Created, resolvers.create(project, payload).flatTap(index))
emitMetadata(Created, resolvers.create(project, payload))
}
}
},
Expand All @@ -109,17 +113,17 @@ final class ResolversRoutes(
(parameter("rev".as[Int].?) & pathEndOrSingleSlash & entity(as[Json])) {
case (None, payload) =>
// Create a resolver with an id segment
emitMetadata(Created, resolvers.create(resolver, project, payload).flatTap(index))
emitMetadata(Created, resolvers.create(resolver, project, payload))
case (Some(rev), payload) =>
// Update a resolver
emitMetadata(resolvers.update(resolver, project, rev, payload).flatTap(index))
emitMetadata(resolvers.update(resolver, project, rev, payload))
}
}
},
(delete & parameter("rev".as[Int])) { rev =>
authorizeWrite {
// Deprecate a resolver
emitMetadataOrReject(resolvers.deprecate(resolver, project, rev).flatTap(index))
emitMetadataOrReject(resolvers.deprecate(resolver, project, rev))
}
},
// Fetches a resolver
Expand Down Expand Up @@ -226,14 +230,13 @@ object ResolversRoutes {
aclCheck: AclCheck,
resolvers: Resolvers,
multiResolution: MultiResolution,
schemeDirectives: DeltaSchemeDirectives,
index: IndexingAction.Execute[Resolver]
schemeDirectives: DeltaSchemeDirectives
)(implicit
baseUri: BaseUri,
cr: RemoteContextResolution,
ordering: JsonKeyOrdering,
fusionConfig: FusionConfig
): Route =
new ResolversRoutes(identities, aclCheck, resolvers, multiResolution, schemeDirectives, index).routes
new ResolversRoutes(identities, aclCheck, resolvers, multiResolution, schemeDirectives).routes

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@ import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.routes.ResolversRoutes
import ch.epfl.bluebrain.nexus.delta.sdk.IndexingAction.AggregateIndexingAction
import ch.epfl.bluebrain.nexus.delta.sdk._
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaSchemeDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.fusion.FusionConfig
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.ServiceAccount
import ch.epfl.bluebrain.nexus.delta.sdk.model._
import ch.epfl.bluebrain.nexus.delta.sdk.model.metrics.ScopedEventMetricEncoder
import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ApiMappings
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers._
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.{Resolver, ResolverEvent}
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverEvent
import ch.epfl.bluebrain.nexus.delta.sdk.sse.SseEncoder
import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors
import izumi.distage.model.definition.{Id, ModuleDef}
Expand Down Expand Up @@ -70,8 +68,6 @@ object ResolversModule extends ModuleDef {
aclCheck: AclCheck,
resolvers: Resolvers,
schemeDirectives: DeltaSchemeDirectives,
indexingAction: AggregateIndexingAction,
shift: Resolver.Shift,
multiResolution: MultiResolution,
baseUri: BaseUri,
cr: RemoteContextResolution @Id("aggregate"),
Expand All @@ -83,8 +79,7 @@ object ResolversModule extends ModuleDef {
aclCheck,
resolvers,
multiResolution,
schemeDirectives,
indexingAction(_, _, _)(shift)
schemeDirectives
)(
baseUri,
cr,
Expand All @@ -95,17 +90,13 @@ object ResolversModule extends ModuleDef {

many[SseEncoder[_]].add { base: BaseUri => ResolverEvent.sseEncoder(base) }

many[ScopedEventMetricEncoder[_]].add { ResolverEvent.resolverEventMetricEncoder }

make[ResolverScopeInitialization].from { (resolvers: Resolvers, serviceAccount: ServiceAccount, config: AppConfig) =>
ResolverScopeInitialization(resolvers, serviceAccount, config.resolvers.defaults)
}
many[ScopeInitialization].ref[ResolverScopeInitialization]

many[ApiMappings].add(Resolvers.mappings)

many[ResourceToSchemaMappings].add(Resolvers.resourcesToSchemas)

many[MetadataContextValue].addEffect(MetadataContextValue.fromFile("contexts/resolvers-metadata.json"))

many[RemoteContextResolution].addEffect(
Expand All @@ -120,10 +111,4 @@ object ResolversModule extends ModuleDef {
many[PriorityRoute].add { (route: ResolversRoutes) =>
PriorityRoute(pluginsMaxPriority + 9, route.routes, requiresStrictEntity = true)
}

make[Resolver.Shift].from { (resolvers: Resolvers, base: BaseUri) =>
Resolver.shift(resolvers)(base)
}

many[ResourceShift[_, _, _]].ref[Resolver.Shift]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import cats.effect.IO
import ch.epfl.bluebrain.nexus.delta.kernel.utils.{UUIDF, UrlUtils}
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{contexts, nxv, schema, schemas}
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.directives.DeltaSchemeDirectives
Expand Down Expand Up @@ -121,9 +120,7 @@ class ResolversRoutesSpec extends BaseRouteSpec {
private lazy val multiResolution = MultiResolution(fetchContext, resolverResolution)

private lazy val routes =
Route.seal(
ResolversRoutes(identities, aclCheck, resolvers, multiResolution, groupDirectives, IndexingAction.noop)
)
Route.seal(ResolversRoutes(identities, aclCheck, resolvers, multiResolution, groupDirectives))

private def withId(id: String, payload: Json) =
payload.deepMerge(Json.obj("@id" -> id.asJson))
Expand Down Expand Up @@ -573,6 +570,21 @@ class ResolversRoutesSpec extends BaseRouteSpec {
}
}

"listing resolvers" should {
"succeed if the user has read access to the given project" in {
Get(s"/v1/resolvers/${project.ref}") ~> asBob ~> routes ~> check {
status shouldEqual StatusCodes.OK
response.asJson.asObject.value("_total").value shouldEqual Json.fromLong(3L)
}
}

"fail if the user has no read access to the given project" in {
Get(s"/v1/resolvers/${project.ref}") ~> routes ~> check {
response.shouldBeForbidden
}
}
}

val idResourceEncoded = UrlUtils.encode(resourceId.toString)
val idSchemaEncoded = UrlUtils.encode(schemaId.toString)
val unknownResourceEncoded = UrlUtils.encode((nxv + "xxx").toString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.ResourceF
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.Organization
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.Project
import ch.epfl.bluebrain.nexus.delta.sdk.realms.model.Realm
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.Resolver
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject
import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef.Latest
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef, ResourceRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ResourceRef}

/**
* Enumeration of the possible Search Parameters
Expand Down Expand Up @@ -156,38 +155,4 @@ object SearchParams {
)
}

/**
* Search parameters for resolvers
*
* @param project
* the option project of the resolver resources
* @param deprecated
* the optional deprecation status of resolver project resources
* @param rev
* the optional revision of the resolver resources
* @param createdBy
* the optional subject who created the resolver resource
* @param updatedBy
* the optional subject who updated the resolver
* @param types
* the types the resolver should contain
* @param filter
* the additional filter to select resolvers
*/
final case class ResolverSearchParams(
project: Option[ProjectRef] = None,
deprecated: Option[Boolean] = None,
rev: Option[Int] = None,
createdBy: Option[Subject] = None,
updatedBy: Option[Subject] = None,
types: Set[Iri] = Set(nxv.Resolver),
filter: Resolver => IO[Boolean]
) extends SearchParams[Resolver] {
override val schema: Option[ResourceRef] = Some(Latest(nxvschemas.resolvers))

override def matches(resource: ResourceF[Resolver]): IO[Boolean] =
super.matches(resource).map(_ && project.forall(_ == resource.value.project))

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resolvers

import cats.effect.IO
import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.kernel.search.Pagination
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.sdk.ResourceShifts
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdContent
import ch.epfl.bluebrain.nexus.delta.sdk.model.Fetch.Fetch
import ch.epfl.bluebrain.nexus.delta.sdk.model.ResourceF
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.ResultEntry
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchParams.ResolverSearchParams
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverResolution.{DeprecationCheck, ResolverResolutionResult}
Expand All @@ -19,10 +17,8 @@ import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.Resolver.{CrossProjectR
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverResolutionRejection._
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResourceResolutionReport.{ResolverFailedReport, ResolverReport, ResolverSuccessReport}
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.{Resolver, ResolverRejection, ResolverResolutionRejection, ResourceResolutionReport}
import ch.epfl.bluebrain.nexus.delta.sdk.{ResolverResource, ResourceShifts}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Identity, ProjectRef, ResourceRef}

import java.time.Instant
import scala.collection.immutable.VectorMap

/**
Expand Down Expand Up @@ -229,10 +225,6 @@ object ResolverResolution {

type ResolverResolutionResult[R] = (ResolverReport, Option[R])

private val resolverSearchParams = ResolverSearchParams(deprecated = Some(false), filter = _ => IO.pure(true))

private val resolverOrdering: Ordering[ResolverResource] = Ordering[Instant] on (r => r.createdAt)

/**
* Allows to check and exclude deprecated resources from the resolution
* @param enabled
Expand Down Expand Up @@ -264,17 +256,21 @@ object ResolverResolution {
extractTypes: R => Set[Iri],
readPermission: Permission,
deprecationCheck: DeprecationCheck[R]
) = new ResolverResolution(
checkAcls = (p: ProjectRef, identities: Set[Identity]) => aclCheck.authorizeFor(p, readPermission, identities),
listResolvers = (projectRef: ProjectRef) =>
resolvers
.list(projectRef, Pagination.OnePage, resolverSearchParams, resolverOrdering)
.map { r => r.results.map { r: ResultEntry[ResolverResource] => r.source.value }.toList },
fetchResolver = (id: Iri, projectRef: ProjectRef) => resolvers.fetchActiveResolver(id, projectRef),
fetch = fetch,
extractTypes,
deprecationCheck
)
) = {
def fetchActiveResolvers(project: ProjectRef) = resolvers
.list(project)
.map { r =>
r.results.mapFilter { r => Option.unless(r.source.deprecated)(r.source.value) }.toList
}
new ResolverResolution(
checkAcls = (p: ProjectRef, identities: Set[Identity]) => aclCheck.authorizeFor(p, readPermission, identities),
listResolvers = fetchActiveResolvers,
fetchResolver = (id: Iri, projectRef: ProjectRef) => resolvers.fetchActiveResolver(id, projectRef),
fetch = fetch,
extractTypes,
deprecationCheck
)
}

/**
* Resolution based on resolvers and reference exchanges
Expand Down
Loading

0 comments on commit 352391a

Please sign in to comment.