From 945d46f5d9f08856c980374e17ecb5627e747b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Guillot?= Date: Mon, 4 Nov 2024 20:31:47 +0100 Subject: [PATCH] fix!: `ProjectGraph` are now completely stored in DB (#258) --- apps/helm-chart/src/values.yaml | 11 ++ apps/server/project.json | 7 +- .../src/main/resources/application.yaml | 3 + libs/server/domain/build.gradle.kts | 2 +- .../server/domain/run/model/ProjectGraph.kt | 37 +++++-- .../server/domain/run/model/RunTest.kt | 27 ++++- libs/server/gateway/build.gradle.kts | 5 + .../infrastructure}/JacksonProducers.kt | 2 +- .../server/gateway/persistence/RunMapper.kt | 102 +++++++++-------- .../gateway/persistence/RunRepositoryImpl.kt | 8 +- .../persistence/RunRepositoryImplTest.kt | 27 ++++- libs/server/persistence/build.gradle.kts | 1 + .../persistence/PersistenceCodecProvider.kt | 26 +++++ .../persistence/codec/BsonReaderExtensions.kt | 45 ++++++++ .../persistence/codec/BsonWriterExtensions.kt | 38 +++++++ .../server/persistence/codec/ProjectCodec.kt | 64 +++++++++++ .../codec/ProjectConfigurationCodec.kt | 83 ++++++++++++++ .../persistence/codec/ProjectGraphCodec.kt | 104 ++++++++++++++++++ .../persistence/codec/ProjectMetadataCodec.kt | 71 ++++++++++++ .../server/persistence/entity/RunEntity.kt | 35 +++--- .../src/main/resources/db/change-log.yaml | 4 + ...0584515585-set-run-project-graph-null.yaml | 17 +++ .../repository/RunPanacheRepositoryTest.kt | 27 ++++- 23 files changed, 644 insertions(+), 102 deletions(-) rename {apps/server/src/main/kotlin/org/nxcloudce/server/technical/producer => libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/infrastructure}/JacksonProducers.kt (89%) create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/PersistenceCodecProvider.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonReaderExtensions.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonWriterExtensions.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectCodec.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectConfigurationCodec.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectGraphCodec.kt create mode 100644 libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectMetadataCodec.kt create mode 100644 libs/server/persistence/src/main/resources/db/change-log.yaml create mode 100644 libs/server/persistence/src/main/resources/db/changes/1730584515585-set-run-project-graph-null.yaml diff --git a/apps/helm-chart/src/values.yaml b/apps/helm-chart/src/values.yaml index ad9579a9..df11a365 100644 --- a/apps/helm-chart/src/values.yaml +++ b/apps/helm-chart/src/values.yaml @@ -253,6 +253,17 @@ mongodb: # MANDATORY: enable this "experimental" feature to create a secret for service binding enabled: true + initdbScripts: + grant-role-to-server.js: | + use server + db.grantRolesToUser( + "server", + [ + { role: "dbAdmin", db: "server" } + ], + { w: "majority", wtimeout: 5000 } + ); + ## Embedded NGINX configuration (Bitnami Helm Chart) nginx: # If `enabled`, Bitnami's chart is installed. diff --git a/apps/server/project.json b/apps/server/project.json index 59da070b..ab8f130d 100644 --- a/apps/server/project.json +++ b/apps/server/project.json @@ -14,7 +14,12 @@ "build-native-sources": { "executor": "@jnxplus/nx-gradle:run-task", "options": { - "task": ["build", "-Dquarkus.package.type=native-sources"] + "task": [ + "build", + "-Dquarkus.package.jar.enabled=false", + "-Dquarkus.native.enabled=true", + "-Dquarkus.native.sources-only=true" + ] }, "outputs": ["{projectRoot}/build/native-sources"] }, diff --git a/apps/server/src/main/resources/application.yaml b/apps/server/src/main/resources/application.yaml index 2d115ac8..e6189229 100644 --- a/apps/server/src/main/resources/application.yaml +++ b/apps/server/src/main/resources/application.yaml @@ -2,6 +2,9 @@ quarkus: http: port: 8080 root-path: "/nx-cloud" + liquibase-mongodb: + change-log: db/change-log.yaml + migrate-at-start: true mongodb: database: nx-cloud-ce smallrye-openapi: diff --git a/libs/server/domain/build.gradle.kts b/libs/server/domain/build.gradle.kts index 0bdb12f0..8dc420ba 100644 --- a/libs/server/domain/build.gradle.kts +++ b/libs/server/domain/build.gradle.kts @@ -54,7 +54,7 @@ tasks.withType { configure { excludeClassLoaders = listOf("*QuarkusClassLoader") - setDestinationFile(layout.buildDirectory.file("jacoco-quarkus.exec").get().asFile) + destinationFile = layout.buildDirectory.file("jacoco-quarkus.exec").get().asFile } tasks.jacocoTestReport { diff --git a/libs/server/domain/src/main/kotlin/org/nxcloudce/server/domain/run/model/ProjectGraph.kt b/libs/server/domain/src/main/kotlin/org/nxcloudce/server/domain/run/model/ProjectGraph.kt index efd9c5a0..f4abafe0 100644 --- a/libs/server/domain/src/main/kotlin/org/nxcloudce/server/domain/run/model/ProjectGraph.kt +++ b/libs/server/domain/src/main/kotlin/org/nxcloudce/server/domain/run/model/ProjectGraph.kt @@ -1,30 +1,45 @@ package org.nxcloudce.server.domain.run.model +/** + * Some type definitions can be found here: + * https://github.com/nrwl/nx/blob/master/packages/nx/src/config/project-graph.ts + * https://github.com/nrwl/nx/blob/master/packages/nx/src/config/workspace-json-project-json.ts + */ data class ProjectGraph( - val nodes: Map?, - val dependencies: Map>?, + val nodes: Map, + val dependencies: Map>, ) { data class Project( val type: String, val name: String, - val data: Data, + val data: ProjectConfiguration, ) { - data class Data( + data class ProjectConfiguration( val root: String, val sourceRoot: String?, - val metadata: Map?, - val targets: Map, + val targets: Map?, + val metadata: ProjectMetadata?, ) { - data class Target( + data class TargetConfiguration( val executor: String?, + val command: String?, + val outputs: Collection?, val dependsOn: Collection?, - val options: Map?, - val configurations: Any?, - val parallelism: Boolean?, val inputs: Collection?, - val outputs: Collection?, + val options: Any?, + val configurations: Map?, val defaultConfiguration: String?, val cache: Boolean?, + val parallelism: Boolean?, + val syncGenerators: Collection?, + // missing `metadata?` + ) + + data class ProjectMetadata( + val description: String?, + val technologies: Collection?, + val targetGroups: Map>?, + // missing `owners?` ) } } diff --git a/libs/server/domain/src/test/kotlin/org/nxcloudce/server/domain/run/model/RunTest.kt b/libs/server/domain/src/test/kotlin/org/nxcloudce/server/domain/run/model/RunTest.kt index a92d1088..2ff53c56 100644 --- a/libs/server/domain/src/test/kotlin/org/nxcloudce/server/domain/run/model/RunTest.kt +++ b/libs/server/domain/src/test/kotlin/org/nxcloudce/server/domain/run/model/RunTest.kt @@ -82,11 +82,32 @@ class RunTest { type = "application", name = "apps/server", data = - ProjectGraph.Project.Data( + ProjectGraph.Project.ProjectConfiguration( root = "root", sourceRoot = "root", - metadata = emptyMap(), - targets = emptyMap(), + metadata = + ProjectGraph.Project.ProjectConfiguration.ProjectMetadata( + description = null, + technologies = null, + targetGroups = null, + ), + targets = + mapOf( + "apps/server" to + ProjectGraph.Project.ProjectConfiguration.TargetConfiguration( + executor = "nx", + command = "nx build apps/server", + outputs = null, + dependsOn = null, + inputs = null, + options = null, + configurations = null, + defaultConfiguration = null, + cache = null, + parallelism = null, + syncGenerators = null, + ), + ), ), ), ), diff --git a/libs/server/gateway/build.gradle.kts b/libs/server/gateway/build.gradle.kts index 542e89b7..9904c21b 100644 --- a/libs/server/gateway/build.gradle.kts +++ b/libs/server/gateway/build.gradle.kts @@ -16,15 +16,20 @@ val javaVersion: String by project val quarkusPlatformGroupId: String by project val quarkusPlatformArtifactId: String by project val quarkusPlatformVersion: String by project +val jacksonDatatypeJsr310Version: String by project val ktlintVersion: String by project val atriumVersion: String by project val mockkVersion: String by project val quarkusMockkVersion: String by project dependencies { + implementation(kotlin("stdlib-jdk8")) + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation(enforcedPlatform("$quarkusPlatformGroupId:$quarkusPlatformArtifactId:$quarkusPlatformVersion")) implementation("io.quarkus:quarkus-kotlin") implementation("io.quarkus:quarkus-mongodb-panache-kotlin") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDatatypeJsr310Version") implementation(project(":libs:server:domain")) implementation(project(":libs:server:persistence")) diff --git a/apps/server/src/main/kotlin/org/nxcloudce/server/technical/producer/JacksonProducers.kt b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/infrastructure/JacksonProducers.kt similarity index 89% rename from apps/server/src/main/kotlin/org/nxcloudce/server/technical/producer/JacksonProducers.kt rename to libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/infrastructure/JacksonProducers.kt index bcb8e4ef..104b5c5e 100644 --- a/apps/server/src/main/kotlin/org/nxcloudce/server/technical/producer/JacksonProducers.kt +++ b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/infrastructure/JacksonProducers.kt @@ -1,4 +1,4 @@ -package org.nxcloudce.server.technical.producer +package org.nxcloudce.server.gateway.infrastructure import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule diff --git a/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunMapper.kt b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunMapper.kt index 7921f7bb..14aa12f6 100644 --- a/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunMapper.kt +++ b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunMapper.kt @@ -1,12 +1,13 @@ package org.nxcloudce.server.gateway.persistence +import com.fasterxml.jackson.databind.ObjectMapper import org.bson.types.ObjectId import org.nxcloudce.server.domain.run.model.* import org.nxcloudce.server.domain.run.usecase.EndRunRequest import org.nxcloudce.server.domain.workspace.model.WorkspaceId import org.nxcloudce.server.persistence.entity.RunEntity -fun RunEntity.toDomain(): Run = +fun RunEntity.toDomain(objectMapper: ObjectMapper): Run = Run { id = RunId(this@toDomain.id.toString()) workspaceId = WorkspaceId(this@toDomain.workspaceId.toString()) @@ -48,39 +49,36 @@ fun RunEntity.toDomain(): Run = projectGraph = this@toDomain.projectGraph?.let { projectGraph -> ProjectGraph( - nodes = emptyMap(), -// projectGraph.nodes.let { -// it.mapValues { (_, node) -> -// ProjectGraph.Project( -// type = node.type, -// name = node.name, -// data = -// ProjectGraph.Project.Data( -// root = node.data.root, -// sourceRoot = node.data.sourceRoot, -// metadata = null, -// targets = emptyMap(), -// // TODO: can't map those two fields for now -// // https://github.com/clementguillot/nx-cloud-ce/issues/118 -// // metadata = node.data.metadata, -// // targets = -// // node.data.targets.mapValues { (_, target) -> -// // ProjectGraph.Project.Data.Target( -// // executor = target.executor, -// // dependsOn = target.dependsOn, -// // options = target.options, -// // configurations = target.configurations, -// // parallelism = target.parallelism, -// // inputs = target.inputs, -// // outputs = target.outputs, -// // defaultConfiguration = target.defaultConfiguration, -// // cache = target.cache, -// // ) -// // }, -// ), -// ) -// } -// }, + projectGraph.nodes.let { + it.mapValues { (_, node) -> + ProjectGraph.Project( + type = node.type, + name = node.name, + data = + ProjectGraph.Project.ProjectConfiguration( + root = node.data.root, + sourceRoot = node.data.sourceRoot, + targets = + node.data.targets?.let { target -> + target.mapValues { (_, serializedTarget) -> + objectMapper.readValue( + serializedTarget, + ProjectGraph.Project.ProjectConfiguration.TargetConfiguration::class.java, + ) + } + }, + metadata = + node.data.metadata?.let { metadata -> + ProjectGraph.Project.ProjectConfiguration.ProjectMetadata( + description = metadata.description, + technologies = metadata.technologies, + targetGroups = metadata.targetGroups, + ) + }, + ), + ) + } + }, dependencies = projectGraph.dependencies.let { it.mapValues { (_, dependencies) -> @@ -102,6 +100,7 @@ fun RunEntity.toDomain(): Run = fun EndRunRequest.Run.toEntity( status: RunStatus, workspaceId: WorkspaceId, + objectMapper: ObjectMapper, ): RunEntity = RunEntity( id = null, @@ -145,34 +144,33 @@ fun EndRunRequest.Run.toEntity( projectGraph?.let { projectGraph -> RunEntity.ProjectGraph( nodes = - projectGraph.nodes!!.mapValues { (_, node) -> + projectGraph.nodes.mapValues { (_, node) -> RunEntity.ProjectGraph.Project( type = node.type, name = node.name, data = - RunEntity.ProjectGraph.Project.Data( + RunEntity.ProjectGraph.Project.ProjectConfiguration( root = node.data.root, sourceRoot = node.data.sourceRoot, -// metadata = node.data.metadata, -// targets = -// node.data.targets.mapValues { (_, target) -> -// RunEntity.ProjectGraph.Project.Data.Target( -// executor = target.executor, -// dependsOn = target.dependsOn, -// options = target.options, -// configurations = target.configurations, -// parallelism = target.parallelism, -// inputs = target.inputs, -// outputs = target.outputs, -// defaultConfiguration = target.defaultConfiguration, -// cache = target.cache, -// ) -// }, + targets = + node.data.targets?.let { + it.mapValues { (_, target) -> + objectMapper.writeValueAsString(target) + } + }, + metadata = + node.data.metadata?.let { + RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata( + description = it.description, + technologies = it.technologies, + targetGroups = it.targetGroups, + ) + }, ), ) }, dependencies = - projectGraph.dependencies!!.mapValues { (_, dependencies) -> + projectGraph.dependencies.mapValues { (_, dependencies) -> dependencies.map { dependency -> RunEntity.ProjectGraph.Dependency( source = dependency.source, diff --git a/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImpl.kt b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImpl.kt index c098327f..0bd7b456 100644 --- a/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImpl.kt +++ b/libs/server/gateway/src/main/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImpl.kt @@ -1,5 +1,6 @@ package org.nxcloudce.server.gateway.persistence +import com.fasterxml.jackson.databind.ObjectMapper import io.smallrye.mutiny.coroutines.asFlow import io.smallrye.mutiny.coroutines.awaitSuspending import jakarta.enterprise.context.ApplicationScoped @@ -17,19 +18,20 @@ import java.time.LocalDateTime @ApplicationScoped class RunRepositoryImpl( private val runPanacheRepository: RunPanacheRepository, + private val objectMapper: ObjectMapper, ) : RunRepository { override suspend fun create( run: EndRunRequest.Run, status: RunStatus, workspaceId: WorkspaceId, ): Run { - val entity = run.toEntity(status, workspaceId) + val entity = run.toEntity(status, workspaceId, objectMapper) - return runPanacheRepository.persist(entity).awaitSuspending().run { entity.toDomain() } + return runPanacheRepository.persist(entity).awaitSuspending().run { entity.toDomain(objectMapper) } } override fun findAllByCreationDateOlderThan(date: LocalDateTime): Flow = - runPanacheRepository.findAllByEndTimeLowerThan(date).asFlow().map { it.toDomain() } + runPanacheRepository.findAllByEndTimeLowerThan(date).asFlow().map { it.toDomain(objectMapper) } override suspend fun delete(run: Run) = runPanacheRepository.deleteById(ObjectId(run.id.value)).awaitSuspending() } diff --git a/libs/server/gateway/src/test/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImplTest.kt b/libs/server/gateway/src/test/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImplTest.kt index 7665ce92..89c14af0 100644 --- a/libs/server/gateway/src/test/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImplTest.kt +++ b/libs/server/gateway/src/test/kotlin/org/nxcloudce/server/gateway/persistence/RunRepositoryImplTest.kt @@ -78,11 +78,32 @@ class RunRepositoryImplTest { type = "application", name = "apps/server", data = - ProjectGraph.Project.Data( + ProjectGraph.Project.ProjectConfiguration( root = "root", sourceRoot = "root", - metadata = emptyMap(), - targets = emptyMap(), + metadata = + ProjectGraph.Project.ProjectConfiguration.ProjectMetadata( + description = null, + technologies = null, + targetGroups = null, + ), + targets = + mapOf( + "build" to + ProjectGraph.Project.ProjectConfiguration.TargetConfiguration( + executor = "@nx/angular:ng-packagr-lite", + command = null, + outputs = listOf("^build", "build"), + dependsOn = null, + inputs = listOf("production", "^production"), + options = null, + configurations = null, + defaultConfiguration = null, + cache = null, + parallelism = null, + syncGenerators = null, + ), + ), ), ), ), diff --git a/libs/server/persistence/build.gradle.kts b/libs/server/persistence/build.gradle.kts index 23e7ee0b..85958836 100644 --- a/libs/server/persistence/build.gradle.kts +++ b/libs/server/persistence/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(enforcedPlatform("$quarkusPlatformGroupId:$quarkusPlatformArtifactId:$quarkusPlatformVersion")) implementation("io.quarkus:quarkus-arc") implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-liquibase-mongodb") implementation("io.quarkus:quarkus-mongodb-panache-kotlin") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test") diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/PersistenceCodecProvider.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/PersistenceCodecProvider.kt new file mode 100644 index 00000000..7803d813 --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/PersistenceCodecProvider.kt @@ -0,0 +1,26 @@ +package org.nxcloudce.server.persistence + +import ProjectCodec +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistry +import org.nxcloudce.server.persistence.codec.ProjectConfigurationCodec +import org.nxcloudce.server.persistence.codec.ProjectGraphCodec +import org.nxcloudce.server.persistence.codec.ProjectMetadataCodec +import org.nxcloudce.server.persistence.entity.RunEntity + +@Suppress("unused") +class PersistenceCodecProvider : CodecProvider { + @Suppress("UNCHECKED_CAST") + override fun get( + clazz: Class, + registry: CodecRegistry, + ): Codec? = + when (clazz) { + RunEntity.ProjectGraph::class.java -> ProjectGraphCodec(registry) as Codec + RunEntity.ProjectGraph.Project::class.java -> ProjectCodec(registry) as Codec + RunEntity.ProjectGraph.Project.ProjectConfiguration::class.java -> ProjectConfigurationCodec(registry) as Codec + RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata::class.java -> ProjectMetadataCodec() as Codec + else -> null + } +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonReaderExtensions.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonReaderExtensions.kt new file mode 100644 index 00000000..d675d751 --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonReaderExtensions.kt @@ -0,0 +1,45 @@ +package org.nxcloudce.server.persistence.codec + +import org.bson.BsonReader +import org.bson.BsonType + +fun BsonReader.readStringArray(): List = + buildList { + readStartArray() + while (readBsonType() != BsonType.END_OF_DOCUMENT) { + add(readString()) + } + readEndArray() + } + +inline fun BsonReader.readRequiredField( + fieldName: String, + reader: BsonReader.() -> T, +): T { + readName() + return reader() +} + +inline fun BsonReader.readNullableField( + fieldName: String, + reader: BsonReader.() -> T, +): T? { + readName() + return when (currentBsonType) { + BsonType.NULL -> { + readNull() + null + } + else -> reader() + } +} + +inline fun BsonReader.readDocumentMap(valueReader: BsonReader.() -> T): Map = + buildMap { + readStartDocument() + while (readBsonType() != BsonType.END_OF_DOCUMENT) { + val key = readName() + put(key, valueReader()) + } + readEndDocument() + } diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonWriterExtensions.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonWriterExtensions.kt new file mode 100644 index 00000000..bff34a4f --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/BsonWriterExtensions.kt @@ -0,0 +1,38 @@ +package org.nxcloudce.server.persistence.codec + +import org.bson.BsonWriter + +inline fun BsonWriter.writeRequiredField( + fieldName: String, + writer: BsonWriter.() -> Unit, +) { + writeName(fieldName) + writer() +} + +fun BsonWriter.writeNullableField( + fieldName: String, + value: T?, + writer: BsonWriter.(T) -> Unit, +) { + writeName(fieldName) + value?.let { writer(it) } ?: writeNull() +} + +inline fun BsonWriter.writeArrayField(writer: BsonWriter.() -> Unit) { + writeStartArray() + writer() + writeEndArray() +} + +inline fun BsonWriter.writeDocumentMap( + map: Map, + crossinline valueWriter: BsonWriter.(T) -> Unit, +) { + writeStartDocument() + map.forEach { (key, value) -> + writeName(key) + valueWriter(value) + } + writeEndDocument() +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectCodec.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectCodec.kt new file mode 100644 index 00000000..639b7adc --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectCodec.kt @@ -0,0 +1,64 @@ +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecRegistry +import org.nxcloudce.server.persistence.codec.readRequiredField +import org.nxcloudce.server.persistence.codec.writeRequiredField +import org.nxcloudce.server.persistence.entity.RunEntity + +class ProjectCodec(private val registry: CodecRegistry) : Codec { + private val configurationCodec by lazy { + registry[RunEntity.ProjectGraph.Project.ProjectConfiguration::class.java] + } + + override fun encode( + writer: BsonWriter, + value: RunEntity.ProjectGraph.Project, + encoderContext: EncoderContext, + ) = writer.run { + writeStartDocument() + writeFields(value, encoderContext) + writeEndDocument() + } + + private fun BsonWriter.writeFields( + value: RunEntity.ProjectGraph.Project, + encoderContext: EncoderContext, + ) { + writeString("type", value.type) + writeString("name", value.name) + writeRequiredField("data") { + configurationCodec.encode(this, value.data, encoderContext) + } + } + + override fun decode( + reader: BsonReader, + decoderContext: DecoderContext, + ): RunEntity.ProjectGraph.Project = + reader.run { + readStartDocument() + val project = readProjectFields(decoderContext) + readEndDocument() + project + } + + private fun BsonReader.readProjectFields(decoderContext: DecoderContext): RunEntity.ProjectGraph.Project { + val type = readRequiredField("type") { readString() } + val name = readRequiredField("name") { readString() } + val data = + readRequiredField("data") { + configurationCodec.decode(this, decoderContext) + } + + return RunEntity.ProjectGraph.Project( + type = type, + name = name, + data = data, + ) + } + + override fun getEncoderClass(): Class = RunEntity.ProjectGraph.Project::class.java +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectConfigurationCodec.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectConfigurationCodec.kt new file mode 100644 index 00000000..192e7c0e --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectConfigurationCodec.kt @@ -0,0 +1,83 @@ +package org.nxcloudce.server.persistence.codec + +import org.bson.BsonReader +import org.bson.BsonType +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecRegistry +import org.nxcloudce.server.persistence.entity.RunEntity + +class ProjectConfigurationCodec(private val registry: CodecRegistry) : + Codec { + private val metadataCodec by lazy { + registry[RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata::class.java] + } + + override fun encode( + writer: BsonWriter, + value: RunEntity.ProjectGraph.Project.ProjectConfiguration, + encoderContext: EncoderContext, + ) = writer.run { + writeStartDocument() + writeFields(value, encoderContext) + writeEndDocument() + } + + private fun BsonWriter.writeFields( + value: RunEntity.ProjectGraph.Project.ProjectConfiguration, + encoderContext: EncoderContext, + ) { + writeString("root", value.root) + writeNullableField("sourceRoot", value.sourceRoot) { writeString(it) } + writeNullableField("targets", value.targets) { targets -> + writeStartDocument() + targets.forEach { (group, target) -> writeString(group, target) } + writeEndDocument() + } + writeNullableField("metadata", value.metadata) { metadata -> + metadataCodec.encode(this, metadata, encoderContext) + } + } + + override fun decode( + reader: BsonReader, + decoderContext: DecoderContext?, + ): RunEntity.ProjectGraph.Project.ProjectConfiguration = + reader.run { + readStartDocument() + val configuration = readConfigurationFields(decoderContext) + readEndDocument() + configuration + } + + private fun BsonReader.readConfigurationFields(decoderContext: DecoderContext?): RunEntity.ProjectGraph.Project.ProjectConfiguration { + val root = readRequiredField("root") { readString() } + val sourceRoot = readNullableField("sourceRoot") { readString() } + val targets = readNullableField("targets") { readTargetMappings() } + val metadata = + readNullableField("metadata") { + metadataCodec.decode(this, decoderContext) + } + + return RunEntity.ProjectGraph.Project.ProjectConfiguration( + root = root, + sourceRoot = sourceRoot, + targets = targets, + metadata = metadata, + ) + } + + private fun BsonReader.readTargetMappings(): Map = + buildMap { + readStartDocument() + while (readBsonType() != BsonType.END_OF_DOCUMENT) { + put(readName(), readString()) + } + readEndDocument() + } + + override fun getEncoderClass(): Class = + RunEntity.ProjectGraph.Project.ProjectConfiguration::class.java +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectGraphCodec.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectGraphCodec.kt new file mode 100644 index 00000000..e5a98870 --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectGraphCodec.kt @@ -0,0 +1,104 @@ +package org.nxcloudce.server.persistence.codec + +import org.bson.BsonReader +import org.bson.BsonType +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecRegistry +import org.nxcloudce.server.persistence.entity.RunEntity + +class ProjectGraphCodec(private val registry: CodecRegistry) : Codec { + private val projectCodec by lazy { + registry[RunEntity.ProjectGraph.Project::class.java] + } + + override fun encode( + writer: BsonWriter, + value: RunEntity.ProjectGraph, + encoderContext: EncoderContext, + ) = writer.run { + writeStartDocument() + writeFields(value, encoderContext) + writeEndDocument() + } + + private fun BsonWriter.writeFields( + value: RunEntity.ProjectGraph, + encoderContext: EncoderContext, + ) { + writeRequiredField("nodes") { + writeDocumentMap(value.nodes) { project -> + projectCodec.encode(this, project, encoderContext) + } + } + + writeRequiredField("dependencies") { + writeDocumentMap(value.dependencies) { deps -> + writeStartArray() + deps.forEach { writeDependency(it) } + writeEndArray() + } + } + } + + private fun BsonWriter.writeDependency(dependency: RunEntity.ProjectGraph.Dependency) { + writeStartDocument() + writeString("source", dependency.source) + writeString("target", dependency.target) + writeString("type", dependency.type) + writeEndDocument() + } + + override fun decode( + reader: BsonReader, + decoderContext: DecoderContext, + ): RunEntity.ProjectGraph = + reader.run { + readStartDocument() + val graph = readGraphFields(decoderContext) + readEndDocument() + graph + } + + private fun BsonReader.readGraphFields(decoderContext: DecoderContext): RunEntity.ProjectGraph { + val nodes = + readRequiredField("nodes") { + readDocumentMap { projectCodec.decode(this, decoderContext) } + } + + val dependencies = + readRequiredField("dependencies") { + readDocumentMap { readDependenciesList() } + } + + return RunEntity.ProjectGraph( + nodes = nodes, + dependencies = dependencies, + ) + } + + private fun BsonReader.readDependenciesList(): List = + buildList { + readStartArray() + while (readBsonType() != BsonType.END_OF_DOCUMENT) { + add(readDependency()) + } + readEndArray() + } + + private fun BsonReader.readDependency(): RunEntity.ProjectGraph.Dependency { + readStartDocument() + val dependency = + RunEntity.ProjectGraph.Dependency( + source = readRequiredField("source") { readString() }, + target = readRequiredField("target") { readString() }, + type = readRequiredField("type") { readString() }, + ) + readEndDocument() + return dependency + } + + override fun getEncoderClass(): Class = RunEntity.ProjectGraph::class.java +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectMetadataCodec.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectMetadataCodec.kt new file mode 100644 index 00000000..808f40da --- /dev/null +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/codec/ProjectMetadataCodec.kt @@ -0,0 +1,71 @@ +package org.nxcloudce.server.persistence.codec + +import org.bson.BsonReader +import org.bson.BsonType +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.nxcloudce.server.persistence.entity.RunEntity + +class ProjectMetadataCodec : Codec { + override fun encode( + writer: BsonWriter, + value: RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata, + encoderContext: EncoderContext, + ) = writer.run { + writeStartDocument() + writeFields(value) + writeEndDocument() + } + + private fun BsonWriter.writeFields(value: RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata) { + writeNullableField("description", value.description) { writeString(it) } + writeNullableField("technologies", value.technologies) { technologies -> + writeArrayField { technologies.forEach { writeString(it) } } + } + writeNullableField("targetGroups", value.targetGroups) { groups -> + writeStartDocument() + groups.forEach { (group, targets) -> + writeName(group) + writeArrayField { targets.forEach { writeString(it) } } + } + writeEndDocument() + } + } + + override fun decode( + reader: BsonReader, + decoderContext: DecoderContext, + ): RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata = + reader.run { + readStartDocument() + val metadata = readMetadataFields() + readEndDocument() + metadata + } + + private fun BsonReader.readMetadataFields(): RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata { + val description = readNullableField("description") { readString() } + val technologies = readNullableField("technologies") { readStringArray() } + val targetGroups = readNullableField("targetGroups") { readTargetGroups() } + return RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata( + description, + technologies, + targetGroups, + ) + } + + private fun BsonReader.readTargetGroups(): Map> = + buildMap { + readStartDocument() + while (readBsonType() != BsonType.END_OF_DOCUMENT) { + val groupName = readName() + put(groupName, readStringArray()) + } + readEndDocument() + } + + override fun getEncoderClass(): Class = + RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata::class.java +} diff --git a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/entity/RunEntity.kt b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/entity/RunEntity.kt index 0155fc97..34c2b7ab 100644 --- a/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/entity/RunEntity.kt +++ b/libs/server/persistence/src/main/kotlin/org/nxcloudce/server/persistence/entity/RunEntity.kt @@ -49,40 +49,31 @@ data class RunEntity( var platformName: String?, ) - // TODO: we should not use `val`, otherwise, values can't be late-initialized - // https://github.com/clementguillot/nx-cloud-ce/issues/118 @MongoEntity data class ProjectGraph( - val nodes: Map, - val dependencies: Map>, + var nodes: Map, + var dependencies: Map>, ) { @MongoEntity data class Project( var type: String, var name: String, - var data: Data, + var data: ProjectConfiguration, ) { @MongoEntity - data class Data( + data class ProjectConfiguration( var root: String, var sourceRoot: String?, - // TODO: can't implement those field due to missing custom codec - // https://github.com/clementguillot/nx-cloud-ce/issues/118 - // var metadata: Map?, - // var targets: Map, + var targets: Map?, + var metadata: Metadata?, ) { - // @MongoEntity - // data class Target( - // val executor: String?, - // val dependsOn: Collection?, - // val options: Map?, - // val configurations: Any?, - // val parallelism: Boolean?, - // val inputs: Collection?, - // val outputs: Collection?, - // val defaultConfiguration: String?, - // val cache: Boolean?, - // ) + @MongoEntity + data class Metadata( + var description: String?, + var technologies: Collection?, + var targetGroups: Map>?, + // missing `owners?` + ) } } diff --git a/libs/server/persistence/src/main/resources/db/change-log.yaml b/libs/server/persistence/src/main/resources/db/change-log.yaml new file mode 100644 index 00000000..a0dbe7f9 --- /dev/null +++ b/libs/server/persistence/src/main/resources/db/change-log.yaml @@ -0,0 +1,4 @@ +databaseChangeLog: + - include: + relativeToChangelogFile: true + file: changes/1730584515585-set-run-project-graph-null.yaml diff --git a/libs/server/persistence/src/main/resources/db/changes/1730584515585-set-run-project-graph-null.yaml b/libs/server/persistence/src/main/resources/db/changes/1730584515585-set-run-project-graph-null.yaml new file mode 100644 index 00000000..c81d6636 --- /dev/null +++ b/libs/server/persistence/src/main/resources/db/changes/1730584515585-set-run-project-graph-null.yaml @@ -0,0 +1,17 @@ +databaseChangeLog: + - changeSet: + id: 1730584515585-1 + author: clementguillot + changes: + - runCommand: + command: | + { + update: "run", + updates: [ + { + q: {}, + u: { $set: { projectGraph: null } }, + multi: true + } + ] + } diff --git a/libs/server/persistence/src/test/kotlin/org/nxcloudce/server/persistence/repository/RunPanacheRepositoryTest.kt b/libs/server/persistence/src/test/kotlin/org/nxcloudce/server/persistence/repository/RunPanacheRepositoryTest.kt index bd502042..85d17f36 100644 --- a/libs/server/persistence/src/test/kotlin/org/nxcloudce/server/persistence/repository/RunPanacheRepositoryTest.kt +++ b/libs/server/persistence/src/test/kotlin/org/nxcloudce/server/persistence/repository/RunPanacheRepositoryTest.kt @@ -88,16 +88,33 @@ class RunPanacheRepositoryTest { RunEntity.ProjectGraph( nodes = mapOf( - "node" to + "node1" to RunEntity.ProjectGraph.Project( type = "test type", name = "test name", data = - RunEntity.ProjectGraph.Project.Data( + RunEntity.ProjectGraph.Project.ProjectConfiguration( root = "root", - sourceRoot = "root", - // metadata = emptyMap(), - // targets = emptyMap(), + sourceRoot = "source root", + targets = mapOf("target" to "{target}"), + metadata = + RunEntity.ProjectGraph.Project.ProjectConfiguration.Metadata( + description = "description", + technologies = listOf("technologies"), + targetGroups = mapOf("target" to listOf("group")), + ), + ), + ), + "node2" to + RunEntity.ProjectGraph.Project( + type = "test type", + name = "test name", + data = + RunEntity.ProjectGraph.Project.ProjectConfiguration( + root = "root", + sourceRoot = null, + metadata = null, + targets = null, ), ), ),