Repository: BlueBrain/nexus (scala)

Issue:
Fetching the project context during write operations should be done on the write pool
**Describe the bug**
When we create a project, we automatically create some resources (storage/resolver/view)
However when Nexus is using a PostgreSQL cluster, there may be some replication delays and fetching the context of the new project can fail and the creation of those default resources can't be completed.

**Expected behavior**
When fetching the context for a write operation, this should be done from the primary node

Pull request:
Fixes #5016

Patch:
diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContext.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContext.scala
index 65e56386b2..7aac74fbc5 100644
--- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContext.scala
+++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContext.scala
@@ -53,27 +53,36 @@ abstract class FetchContext { self =>
 object FetchContext {
 
   def apply(dam: ApiMappings, xas: Transactors, quotas: Quotas): FetchContext = {
-    def fetchProject(ref: ProjectRef) = {
+    def fetchProject(ref: ProjectRef, onWrite: Boolean) = {
       implicit val putId: Put[ProjectRef]      = ProjectState.serializer.putId
       implicit val getValue: Get[ProjectState] = ProjectState.serializer.getValue
+      val xa                                   = if (onWrite) xas.write else xas.read
       ScopedStateGet
         .latest[ProjectRef, ProjectState](Projects.entityType, ref, ref)
-        .transact(xas.read)
+        .transact(xa)
         .map(_.map(_.toResource(dam)))
     }
 
-    apply(
-      FetchActiveOrganization(xas).apply(_).void,
-      dam,
-      fetchProject,
-      quotas
-    )
+    val fetchActiveOrg: Label => IO[Unit] = FetchActiveOrganization(xas).apply(_).void
+    apply(fetchActiveOrg, dam, fetchProject, quotas)
   }
 
+  /**
+    * Constructs a fetch context
+    * @param fetchActiveOrg
+    *   fetches the org and makes sure it exists and is not deprecated
+    * @param dam
+    *   the default api mappings defined by Nexus
+    * @param fetchProject
+    *   fetches the project in read / write context. The write context is more consistent as it points to the primary
+    *   node while the read one can point to replicas and can suffer from replication delays
+    * @param quotas
+    *   the quotes
+    */
   def apply(
       fetchActiveOrg: Label => IO[Unit],
       dam: ApiMappings,
-      fetchProject: ProjectRef => IO[Option[ProjectResource]],
+      fetchProject: (ProjectRef, Boolean) => IO[Option[ProjectResource]],
       quotas: Quotas
   ): FetchContext =
     new FetchContext {
@@ -81,14 +90,14 @@ object FetchContext {
       override def defaultApiMappings: ApiMappings = dam
 
       override def onRead(ref: ProjectRef): IO[ProjectContext] =
-        fetchProject(ref).flatMap {
+        fetchProject(ref, false).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).flatMap {
+        fetchProject(ref, true).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))


Test patch:
diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContextSuite.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContextSuite.scala
index 7f989b4322..6224056b35 100644
--- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContextSuite.scala
+++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/projects/FetchContextSuite.scala
@@ -59,7 +59,7 @@ class FetchContextSuite extends NexusSuite {
   private def fetchContext(quotasResources: Boolean, quotasEvents: Boolean) = FetchContext(
     fetchActiveOrganization,
     ApiMappings.empty,
-    fetchProject,
+    (project: ProjectRef, _: Boolean) => fetchProject(project),
     quotas(quotasResources, quotasEvents)
   )