Skip to content

Commit

Permalink
Refactor FetchContext + add resolver to import batch (#4756)
Browse files Browse the repository at this point in the history
* Refactor FetchContext + add resolver to import batch

---------

Co-authored-by: Simon Dumas <[email protected]>
  • Loading branch information
imsdu and Simon Dumas authored Feb 29, 2024
1 parent 9b2d5a2 commit 079d3df
Show file tree
Hide file tree
Showing 33 changed files with 310 additions and 227 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ch.epfl.bluebrain.nexus.delta.wiring

import cats.effect.{Clock, IO}
import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.Main.pluginsMaxPriority
import ch.epfl.bluebrain.nexus.delta.config.AppConfig
import ch.epfl.bluebrain.nexus.delta.kernel.utils.{ClasspathResourceLoader, UUIDF}
Expand All @@ -18,11 +17,9 @@ 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.organizations.Organizations
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.projects._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.WrappedOrganizationRejection
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, Project, ProjectEvent, ProjectHealer, ProjectsHealth}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model._
import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning
import ch.epfl.bluebrain.nexus.delta.sdk.quotas.Quotas
import ch.epfl.bluebrain.nexus.delta.sdk.sse.SseEncoder
Expand All @@ -49,7 +46,6 @@ object ProjectsModule extends ModuleDef {
make[Projects].fromEffect {
(
config: AppConfig,
organizations: Organizations,
scopeInitializer: ScopeInitializer,
mappings: ApiMappingsCollection,
xas: Transactors,
Expand All @@ -59,11 +55,7 @@ object ProjectsModule extends ModuleDef {
) =>
IO.pure(
ProjectsImpl(
organizations
.fetchActiveOrganization(_)
.adaptError { case e: OrganizationRejection =>
WrappedOrganizationRejection(e)
},
FetchActiveOrganization(xas),
ValidateProjectDeletion(xas, config.projects.deletion.enabled),
scopeInitializer,
mappings.merge,
Expand Down Expand Up @@ -97,8 +89,8 @@ object ProjectsModule extends ModuleDef {
ProjectProvisioning(acls, projects, config.automaticProvisioning, serviceAccount)
}

make[FetchContext].fromEffect { (organizations: Organizations, projects: Projects, quotas: Quotas) =>
IO.pure(FetchContext(organizations, projects, quotas))
make[FetchContext].from { (mappings: ApiMappingsCollection, xas: Transactors, quotas: Quotas) =>
FetchContext(mappings.merge, xas, quotas)
}

make[ProjectDeletionCoordinator].fromEffect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object ResolversModule extends ModuleDef {
ResolversImpl(
fetchContext,
resolverContextResolution,
config.resolvers,
config.resolvers.eventLog,
xas,
clock
)(api, uuidF)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import ch.epfl.bluebrain.nexus.delta.sdk.ScopeInitializer
import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.AclAddress
import ch.epfl.bluebrain.nexus.delta.sdk.generators.ProjectGen.defaultApiMappings
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.Organization
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection.{OrganizationIsDeprecated, OrganizationNotFound}
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.{projects => projectsPermissions, resources}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.Projects.FetchOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.WrappedOrganizationRejection
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.{ProjectsConfig, ProjectsImpl, ProjectsStatistics}
import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.{AutomaticProvisioningConfig, ProjectProvisioning}
Expand Down Expand Up @@ -59,11 +58,11 @@ class ProjectsRoutesSpec extends BaseRouteSpec with BeforeAndAfterAll {

private val ref = ProjectRef.unsafe("org1", "proj")

private def fetchOrg: FetchOrganization = {
private def fetchOrg: FetchActiveOrganization = {
case `org1` => IO.pure(Organization(org1, orgUuid, None))
case `usersOrg` => IO.pure(Organization(usersOrg, orgUuid, None))
case `org2` => IO.raiseError(WrappedOrganizationRejection(OrganizationIsDeprecated(org2)))
case other => IO.raiseError(WrappedOrganizationRejection(OrganizationNotFound(other)))
case `org2` => IO.raiseError(OrganizationIsDeprecated(org2))
case other => IO.raiseError(OrganizationNotFound(other))
}

private val provisioningConfig = AutomaticProvisioningConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 All @@ -26,7 +27,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverType.{CrossProj
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.Resource
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.Schema
import ch.epfl.bluebrain.nexus.delta.sdk.utils.BaseRouteSpec
import ch.epfl.bluebrain.nexus.delta.sdk.{Defaults, IndexingAction}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Anonymous, Authenticated, Group, Subject}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef.{Latest, Revision}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef, ResourceRef}
Expand Down Expand Up @@ -87,12 +87,10 @@ class ResolversRoutesSpec extends BaseRouteSpec {
case _ => IO.none
}

private val defaults = Defaults("resolverName", "resolverDescription")

private lazy val resolvers = ResolversImpl(
fetchContext,
resolverContextResolution,
ResolversConfig(eventLogConfig, defaults),
eventLogConfig,
xas,
clock
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ch.epfl.bluebrain.nexus.delta.sdk.organizations

import cats.effect.IO
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection._
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.{Organization, OrganizationState}
import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label
import ch.epfl.bluebrain.nexus.delta.sourcing.state.GlobalStateGet
import doobie.implicits._
import doobie.{Get, Put}

trait FetchActiveOrganization {

def apply(org: Label): IO[Organization]

}

object FetchActiveOrganization {

implicit val getId: Put[Label] = OrganizationState.serializer.putId
implicit val getValue: Get[OrganizationState] = OrganizationState.serializer.getValue

def apply(xas: Transactors): FetchActiveOrganization = (org: Label) =>
GlobalStateGet[Label, OrganizationState](Organizations.entityType, org)
.transact(xas.read)
.flatMap {
case None => IO.raiseError(OrganizationNotFound(org))
case Some(o) if o.deprecated => IO.raiseError(OrganizationIsDeprecated(org))
case Some(o) => IO.pure(o.toResource.value)
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package ch.epfl.bluebrain.nexus.delta.sdk.projects

import cats.effect.IO
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.sdk.ProjectResource
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.Organizations
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext, ProjectRejection}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext, ProjectState}
import ch.epfl.bluebrain.nexus.delta.sdk.quotas.Quotas
import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.state.ScopedStateGet
import doobie.implicits._
import doobie.{Get, Put}

/**
* Define the rules to fetch project context for read and write operations
Expand Down Expand Up @@ -49,48 +52,55 @@ abstract class FetchContext { self =>

object FetchContext {

/**
* Create a fetch context instance from an [[Organizations]], [[Projects]] and [[Quotas]] instances
*/
def apply(organizations: Organizations, projects: Projects, quotas: Quotas): FetchContext =
def apply(dam: ApiMappings, xas: Transactors, quotas: Quotas): FetchContext = {
def fetchProject(ref: ProjectRef) = {
implicit val putId: Put[ProjectRef] = ProjectState.serializer.putId
implicit val getValue: Get[ProjectState] = ProjectState.serializer.getValue
ScopedStateGet
.latest[ProjectRef, ProjectState](Projects.entityType, ref, ref)
.transact(xas.read)
.map(_.map(_.toResource(dam)))
}

apply(
organizations.fetchActiveOrganization(_).void,
projects.defaultApiMappings,
projects.fetch,
FetchActiveOrganization(xas).apply(_).void,
dam,
fetchProject,
quotas
)
}

def apply(
fetchActiveOrganization: Label => IO[Unit],
fetchActiveOrg: Label => IO[Unit],
dam: ApiMappings,
fetchProject: ProjectRef => IO[ProjectResource],
fetchProject: ProjectRef => IO[Option[ProjectResource]],
quotas: Quotas
): FetchContext =
new FetchContext {

override def defaultApiMappings: ApiMappings = dam

override def onRead(ref: ProjectRef): IO[ProjectContext] =
fetchProject(ref).attemptNarrow[ProjectRejection].flatMap {
case Left(rejection) => IO.raiseError(rejection)
case Right(project) if project.value.markedForDeletion => IO.raiseError(ProjectIsMarkedForDeletion(ref))
case Right(project) => IO.pure(project.value.context)
fetchProject(ref).flatMap {
case None => IO.raiseError(ProjectNotFound(ref))
case Some(project) if project.value.markedForDeletion => IO.raiseError(ProjectIsMarkedForDeletion(ref))
case Some(project) => IO.pure(project.value.context)
}

private def onWrite(ref: ProjectRef) =
fetchProject(ref).attemptNarrow[ProjectRejection].flatMap {
case Left(rejection) => IO.raiseError(rejection)
case Right(project) if project.value.markedForDeletion => IO.raiseError(ProjectIsMarkedForDeletion(ref))
case Right(project) if project.deprecated => IO.raiseError(ProjectIsDeprecated(ref))
case Right(project) => IO.pure(project.value.context)
fetchProject(ref).flatMap {
case None => IO.raiseError(ProjectNotFound(ref))
case Some(project) if project.value.markedForDeletion => IO.raiseError(ProjectIsMarkedForDeletion(ref))
case Some(project) if project.deprecated => IO.raiseError(ProjectIsDeprecated(ref))
case Some(project) => IO.pure(project.value.context)
}

override def onCreate(ref: ProjectRef)(implicit subject: Subject): IO[ProjectContext] =
quotas.reachedForResources(ref, subject) >> onModify(ref)

override def onModify(ref: ProjectRef)(implicit subject: Subject): IO[ProjectContext] =
for {
_ <- fetchActiveOrganization(ref.organization)
_ <- fetchActiveOrg(ref.organization)
_ <- quotas.reachedForEvents(ref, subject)
context <- onWrite(ref)
} yield context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package ch.epfl.bluebrain.nexus.delta.sdk.projects

import cats.effect.{Clock, IO}
import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.kernel.search.Pagination.FromPagination
import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.sdk.ProjectResource
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchParams.ProjectSearchParams
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults.UnscoredSearchResults
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, ResourceUris}
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.Organizations
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.{Organization, OrganizationRejection}
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectCommand._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectEvent._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.{IncorrectRev, ProjectAlreadyExists, ProjectIsDeprecated, ProjectIsMarkedForDeletion, ProjectIsNotDeprecated, ProjectNotFound, WrappedOrganizationRejection}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.{IncorrectRev, ProjectAlreadyExists, ProjectIsDeprecated, ProjectIsMarkedForDeletion, ProjectIsNotDeprecated, ProjectNotFound}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model._
import ch.epfl.bluebrain.nexus.delta.sdk.syntax._
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{ElemStream, EntityType, Label, ProjectRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{ElemStream, EntityType, ProjectRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.offset.Offset
import ch.epfl.bluebrain.nexus.delta.sourcing.{ScopedEntityDefinition, StateMachine}
import fs2.Stream
Expand Down Expand Up @@ -156,8 +154,6 @@ trait Projects {

object Projects {

type FetchOrganization = Label => IO[Organization]

/**
* The projects entity type.
*/
Expand Down Expand Up @@ -190,31 +186,16 @@ object Projects {
// format: on
}

private[delta] def evaluate(
orgs: Organizations,
validateDeletion: ValidateProjectDeletion,
clock: Clock[IO]
)(state: Option[ProjectState], command: ProjectCommand)(implicit
base: BaseUri,
uuidF: UUIDF
): IO[ProjectEvent] = {
val f: FetchOrganization = label =>
orgs
.fetchActiveOrganization(label)
.adaptError { case o: OrganizationRejection => WrappedOrganizationRejection(o) }
evaluate(f, validateDeletion, clock)(state, command)
}

private[sdk] def evaluate(
fetchAndValidateOrg: FetchOrganization,
fetchActiveOrg: FetchActiveOrganization,
validateDeletion: ValidateProjectDeletion,
clock: Clock[IO]
)(state: Option[ProjectState], command: ProjectCommand)(implicit base: BaseUri, uuidF: UUIDF): IO[ProjectEvent] = {

def create(c: CreateProject): IO[ProjectCreated] = state match {
case None =>
for {
org <- fetchAndValidateOrg(c.ref.organization)
org <- fetchActiveOrg(c.ref.organization)
uuid <- uuidF()
now <- clock.realTimeInstant
} yield ProjectCreated(c.ref, uuid, org.uuid, c.fields, now, c.subject)
Expand All @@ -232,7 +213,7 @@ object Projects {
case Some(s) if s.markedForDeletion =>
IO.raiseError(ProjectIsMarkedForDeletion(c.ref))
case Some(s) =>
fetchAndValidateOrg(c.ref.organization) >>
fetchActiveOrg(c.ref.organization) >>
clock.realTimeInstant.map(
ProjectUpdated(c.ref, s.uuid, s.organizationUuid, s.rev + 1, c.fields, _, c.subject)
)
Expand All @@ -250,7 +231,7 @@ object Projects {
IO.raiseError(ProjectIsMarkedForDeletion(c.ref))
case Some(s) =>
// format: off
fetchAndValidateOrg(c.ref.organization) >>
fetchActiveOrg(c.ref.organization) >>
clock.realTimeInstant.map(ProjectDeprecated(s.label, s.uuid,s.organizationLabel, s.organizationUuid,s.rev + 1, _, c.subject))
// format: on
}
Expand All @@ -267,7 +248,7 @@ object Projects {
IO.raiseError(ProjectIsMarkedForDeletion(c.ref))
case Some(s) =>
// format: off
fetchAndValidateOrg(c.ref.organization) >>
fetchActiveOrg(c.ref.organization) >>
clock.realTimeInstant.map(ProjectUndeprecated(s.label, s.uuid, s.organizationLabel, s.organizationUuid, s.rev + 1, _, c.subject))
// format: on
}
Expand Down Expand Up @@ -299,14 +280,14 @@ object Projects {
/**
* Entity definition for [[Projects]]
*/
def definition(fetchAndValidateOrg: FetchOrganization, validateDeletion: ValidateProjectDeletion, clock: Clock[IO])(
def definition(fetchActiveOrg: FetchActiveOrganization, validateDeletion: ValidateProjectDeletion, clock: Clock[IO])(
implicit
base: BaseUri,
uuidF: UUIDF
): ScopedEntityDefinition[ProjectRef, ProjectState, ProjectCommand, ProjectEvent, ProjectRejection] =
ScopedEntityDefinition.untagged(
entityType,
StateMachine(None, evaluate(fetchAndValidateOrg, validateDeletion, clock)(_, _), next),
StateMachine(None, evaluate(fetchActiveOrg, validateDeletion, clock)(_, _), next),
ProjectEvent.serializer,
ProjectState.serializer,
_ => None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF
import ch.epfl.bluebrain.nexus.delta.sdk._
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.{SearchParams, SearchResults}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.Projects.{entityType, FetchOrganization}
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization
import ch.epfl.bluebrain.nexus.delta.sdk.projects.Projects.entityType
import ch.epfl.bluebrain.nexus.delta.sdk.projects.ProjectsImpl.{logger, ProjectsLog}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectCommand._
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection._
Expand Down Expand Up @@ -109,7 +110,7 @@ object ProjectsImpl {
* Constructs a [[Projects]] instance.
*/
final def apply(
fetchAndValidateOrg: FetchOrganization,
fetchActiveOrg: FetchActiveOrganization,
validateDeletion: ValidateProjectDeletion,
scopeInitializer: ScopeInitializer,
defaultApiMappings: ApiMappings,
Expand All @@ -121,7 +122,7 @@ object ProjectsImpl {
uuidF: UUIDF
): Projects =
new ProjectsImpl(
ScopedEventLog(Projects.definition(fetchAndValidateOrg, validateDeletion, clock), config, xas),
ScopedEventLog(Projects.definition(fetchActiveOrg, validateDeletion, clock), config, xas),
scopeInitializer,
defaultApiMappings
)
Expand Down
Loading

0 comments on commit 079d3df

Please sign in to comment.