From bc9ace3ed6d276f3637dde0a0276b82420c4cc5f Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 22 Dec 2021 15:42:48 -0500 Subject: [PATCH 1/6] Add a mix initializer to find invalid mix materials positions and fix them --- build.gradle.kts | 24 +++--- docker-compose.yml | 8 +- .../config/ApplicationListeners.kt | 20 ++--- .../config/annotations/RequireDatabase.kt | 10 +++ .../initializers/AbstractInitializer.kt | 12 +++ .../initializers/MaterialTypeInitializer.kt | 57 ++++++++++++++ .../config/initializers/MixInitializer.kt | 78 +++++++++++++++++++ .../service/MaterialTypeService.kt | 30 +------ src/main/resources/application.properties | 2 +- src/main/resources/logback.xml | 9 ++- 10 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6fe585e..22e7087 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,12 +3,12 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile group = "dev.fyloz.colorrecipesexplorer" val kotlinVersion = "1.6.0" -val springBootVersion = "2.5.6" +val springBootVersion = "2.6.1" plugins { // Outer scope variables can't be accessed in the plugins section, so we have to redefine them here val kotlinVersion = "1.6.0" - val springBootVersion = "2.5.6" + val springBootVersion = "2.6.1" id("java") id("org.jetbrains.kotlin.jvm") version kotlinVersion @@ -28,18 +28,18 @@ repositories { dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom:${kotlinVersion}")) - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") - implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0") - implementation("javax.xml.bind:jaxb-api:2.3.0") + + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") + implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1") + implementation("io.github.microutils:kotlin-logging-jvm:2.1.21") implementation("io.jsonwebtoken:jjwt-api:0.11.2") implementation("io.jsonwebtoken:jjwt-impl:0.11.2") implementation("io.jsonwebtoken:jjwt-jackson:0.11.2") + implementation("javax.xml.bind:jaxb-api:2.3.0") implementation("org.apache.poi:poi-ooxml:4.1.0") implementation("org.apache.pdfbox:pdfbox:2.0.4") - implementation("org.apache.logging.log4j:log4j-api:2.15.0") - implementation("org.apache.logging.log4j:log4j-to-slf4j:2.15.0") - implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}") implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}") @@ -49,13 +49,13 @@ dependencies { implementation("org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}") implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}") - testImplementation("org.springframework:spring-test:5.3.13") - testImplementation("org.mockito:mockito-inline:3.11.2") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") testImplementation("io.mockk:mockk:1.12.0") + testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") + testImplementation("org.mockito:mockito-inline:3.11.2") + testImplementation("org.springframework:spring-test:5.3.13") testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}") - testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") runtimeOnly("com.h2database:h2:1.4.199") runtimeOnly("mysql:mysql-connector-java:8.0.22") diff --git a/docker-compose.yml b/docker-compose.yml index 9c03148..9de1c6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,16 @@ version: "3.1" services: - frontend: + cre.frontend: image: fyloz.dev:5443/color-recipes-explorer/frontend:latest ports: - - 4200:80 - database: + - "4200:80" + cre.database: image: mysql command: --default-authentication-plugin=mysql_native_password environment: MYSQL_ROOT_PASSWORD: "pass" MYSQL_DATABASE: "cre" ports: - - 3306:3306 + - "3306:3306" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt index 353ee71..2f92bf5 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt @@ -1,18 +1,16 @@ package dev.fyloz.colorrecipesexplorer.config +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.config.initializers.AbstractInitializer import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties -import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties import dev.fyloz.colorrecipesexplorer.emergencyMode import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.restartApplication -import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService import org.slf4j.Logger import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent -import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Profile import org.springframework.core.Ordered import org.springframework.core.annotation.Order import javax.annotation.PostConstruct @@ -20,15 +18,13 @@ import kotlin.concurrent.thread @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) -@Profile("!emergency") +@RequireDatabase class ApplicationReadyListener( - private val materialTypeService: MaterialTypeService, private val configurationService: ConfigurationService, - private val materialTypeProperties: MaterialTypeProperties, private val creProperties: CreProperties, private val logger: Logger -) : ApplicationListener { - override fun onApplicationEvent(event: ApplicationReadyEvent) { +) : AbstractInitializer() { + override fun initialize() { if (emergencyMode) { logger.error("Emergency mode is enabled, default material types will not be created") thread { @@ -40,15 +36,9 @@ class ApplicationReadyListener( } initDatabaseConfigurations() - initMaterialTypes() CRE_PROPERTIES = creProperties } - private fun initMaterialTypes() { - logger.info("Initializing system material types") - materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes) - } - private fun initDatabaseConfigurations() { configurationService.initializeProperties { !it.file } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt new file mode 100644 index 0000000..019d055 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt @@ -0,0 +1,10 @@ +package dev.fyloz.colorrecipesexplorer.config.annotations + +import org.springframework.context.annotation.Profile +import java.lang.annotation.Inherited + +@Profile("!emergency") +@Inherited +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class RequireDatabase \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt new file mode 100644 index 0000000..fbeaf56 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt @@ -0,0 +1,12 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.context.ApplicationListener + +abstract class AbstractInitializer : ApplicationListener { + override fun onApplicationEvent(event: ApplicationReadyEvent) { + initialize() + } + + abstract fun initialize() +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt new file mode 100644 index 0000000..1bb99d2 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt @@ -0,0 +1,57 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties +import dev.fyloz.colorrecipesexplorer.model.MaterialType +import dev.fyloz.colorrecipesexplorer.model.materialType +import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration + +@Configuration +@RequireDatabase +class MaterialTypeInitializer( + private val materialTypeService: MaterialTypeService, + private val materialTypeProperties: MaterialTypeProperties +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing material type initializer...") + ensureSystemMaterialTypesExists() + logger.debug("System material types are up to date!") + } + + private fun ensureSystemMaterialTypesExists() { + val systemTypes = materialTypeProperties.systemTypes.map { it.toMaterialType() } + val oldSystemTypes = materialTypeService.getAllSystemTypes().toMutableSet() + + fun saveOrUpdateSystemType(type: MaterialType) { + if (materialTypeService.existsByName(type.name)) { + with(materialTypeService.getByName(type.name)) { + if (!this.systemType) { + logger.info("Material type '${type.name}' already exists and will be flagged as a system type") + materialTypeService.update(this.copy(systemType = true)) + } else { + logger.debug("System material type '${type.name}' already exists") + } + } + } else { + logger.info("System material type '${type.name}' will be created") + materialTypeService.save(type) + } + } + + // Save new system types + systemTypes.forEach { + saveOrUpdateSystemType(it) + oldSystemTypes.removeIf { c -> c.name == it.name } + } + + // Remove old system types + oldSystemTypes.forEach { + logger.info("Material type '${it.name}' is not a system type anymore") + materialTypeService.update(materialType(it, newSystemType = false)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt new file mode 100644 index 0000000..12908cb --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt @@ -0,0 +1,78 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.model.Mix +import dev.fyloz.colorrecipesexplorer.model.MixMaterial +import dev.fyloz.colorrecipesexplorer.service.MixService +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration +import java.util.* + +@Configuration +@RequireDatabase +class MixInitializer( + private val mixService: MixService +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing mix initializer...") + fixAllPositions() + } + + private fun fixAllPositions() { + logger.debug("Validating mix materials positions...") + + mixService.getAll() + .filter { mix -> + mix.mixMaterials.any { it.position == 0 } + } + .forEach(this::fixMixPositions) + + logger.debug("Mix materials positions are valid!") + } + + private fun fixMixPositions(mix: Mix) { + val maxPosition = mix.mixMaterials.maxOf { it.position } + val nextPosition = maxPosition + 1 + + logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:") + + val invalidMixMaterials: Collection = with(mix.mixMaterials.filter { it.position == 0 }) { + if (nextPosition == 1 && this.size > 1) { + orderMixMaterials(this) + } else { + this + } + } + + val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, nextPosition) + val updatedMixMaterials = mergeMixMaterials(mix.mixMaterials, fixedMixMaterials) + + with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) { + mixService.update(this) + } + } + + private fun increaseMixMaterialsPosition(mixMaterials: Iterable, firstPosition: Int) = + mixMaterials + .mapIndexed { index, mixMaterial -> mixMaterial.copy(position = firstPosition + index) } + .onEach { + logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}") + } + + private fun mergeMixMaterials(mixMaterials: Iterable, updatedMixMaterials: Iterable) = + mixMaterials + .filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } } + .plus(updatedMixMaterials) + + private fun orderMixMaterials(mixMaterials: Collection) = + LinkedList(mixMaterials).apply { + while (this.peek().material.materialType?.usePercentages == true) { + // The first mix material can't use percents, so move it to the end of the queue + val pop = this.pop() + this.add(pop) + logger.debug("\tMaterial ${pop.material.id} (${pop.material.name}) uses percents, moving to the end of the queue") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt index 8d0ce96..2b7eec1 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt @@ -1,7 +1,5 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.validation.isNotNullAndNotBlank import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository @@ -21,9 +19,6 @@ interface MaterialTypeService : /** Gets all material types who are not a system type. */ fun getAllNonSystemType(): Collection - - /** Saves and update the system material types. */ - fun saveSystemTypes(systemTypeProperties: Collection) } @Service @@ -71,34 +66,11 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma throw materialTypePrefixAlreadyExistsException(entity.prefix) } - return super.update(entity) + return super.update(entity) } override fun delete(entity: MaterialType) { if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialTypeException(entity) super.delete(entity) } - - override fun saveSystemTypes(systemTypeProperties: Collection) { - val systemTypes = systemTypeProperties.map { it.toMaterialType() } - val oldSystemTypes = getAllSystemTypes().toMutableSet() - - fun saveOrUpdateSystemType(type: MaterialType) { - if (existsByName(type.name)) { - val persistedMaterialType = getByName(type.name) - update(materialType(type, newId = persistedMaterialType.id, newSystemType = true)) - } else { - save(type) - } - } - - // Save new system types - systemTypes.forEach { - saveOrUpdateSystemType(it) - oldSystemTypes.removeIf { c -> c.name == it.name } - } - - // Remove old system types - oldSystemTypes.forEach { update(materialType(it, newSystemType = false)) } - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 18852ab..1aa848b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -31,4 +31,4 @@ spring.jackson.deserialization.fail-on-null-for-primitives=true spring.jackson.default-property-inclusion=non_null spring.profiles.active=@spring.profiles.active@ -spring.datasource.continue-on-error=true +spring.sql.init.continue-on-error=true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index b5ccfc6..1ecd253 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -5,9 +5,12 @@ - %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable + %green(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable + + INFO + @@ -21,12 +24,12 @@ - %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n + %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n - + -- 2.40.1 From fe57745b638ee76e8e5058e70d2b3d5877970f8b Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 22 Dec 2021 16:09:45 -0500 Subject: [PATCH 2/6] Add a recipe initializer to find invalid steps positions and fix them --- .../initializers/AbstractInitializer.kt | 2 +- .../config/initializers/MixInitializer.kt | 17 ++--- .../config/initializers/RecipeInitializer.kt | 72 +++++++++++++++++++ .../colorrecipesexplorer/model/Recipe.kt | 5 +- .../colorrecipesexplorer/utils/Collections.kt | 30 +++++--- 5 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt index fbeaf56..82cb378 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt @@ -8,5 +8,5 @@ abstract class AbstractInitializer : ApplicationListener initialize() } - abstract fun initialize() + protected abstract fun initialize() } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt index 12908cb..0f0921f 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt @@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase import dev.fyloz.colorrecipesexplorer.model.Mix import dev.fyloz.colorrecipesexplorer.model.MixMaterial import dev.fyloz.colorrecipesexplorer.service.MixService +import dev.fyloz.colorrecipesexplorer.utils.merge import mu.KotlinLogging import org.springframework.context.annotation.Configuration import java.util.* @@ -24,9 +25,7 @@ class MixInitializer( logger.debug("Validating mix materials positions...") mixService.getAll() - .filter { mix -> - mix.mixMaterials.any { it.position == 0 } - } + .filter { mix -> mix.mixMaterials.any { it.position == 0 } } .forEach(this::fixMixPositions) logger.debug("Mix materials positions are valid!") @@ -34,20 +33,19 @@ class MixInitializer( private fun fixMixPositions(mix: Mix) { val maxPosition = mix.mixMaterials.maxOf { it.position } - val nextPosition = maxPosition + 1 logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:") val invalidMixMaterials: Collection = with(mix.mixMaterials.filter { it.position == 0 }) { - if (nextPosition == 1 && this.size > 1) { + if (maxPosition == 0 && this.size > 1) { orderMixMaterials(this) } else { this } } - val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, nextPosition) - val updatedMixMaterials = mergeMixMaterials(mix.mixMaterials, fixedMixMaterials) + val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, maxPosition + 1) + val updatedMixMaterials = mix.mixMaterials.merge(fixedMixMaterials) with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) { mixService.update(this) @@ -61,11 +59,6 @@ class MixInitializer( logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}") } - private fun mergeMixMaterials(mixMaterials: Iterable, updatedMixMaterials: Iterable) = - mixMaterials - .filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } } - .plus(updatedMixMaterials) - private fun orderMixMaterials(mixMaterials: Collection) = LinkedList(mixMaterials).apply { while (this.peek().material.materialType?.usePercentages == true) { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt new file mode 100644 index 0000000..4d49ab5 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt @@ -0,0 +1,72 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.model.Recipe +import dev.fyloz.colorrecipesexplorer.model.RecipeGroupInformation +import dev.fyloz.colorrecipesexplorer.model.RecipeStep +import dev.fyloz.colorrecipesexplorer.service.RecipeService +import dev.fyloz.colorrecipesexplorer.utils.merge +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration + +@Configuration +@RequireDatabase +class RecipeInitializer( + private val recipeService: RecipeService +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing recipe initializer...") + fixAllPositions() + } + + private fun fixAllPositions() { + logger.debug("Validating recipes steps positions...") + + recipeService.getAll() + .forEach(this::fixRecipePositions) + + logger.debug("Recipes steps positions are valid!") + } + + private fun fixRecipePositions(recipe: Recipe) { + val fixedGroupInformation = recipe.groupsInformation + .filter { it.steps != null } + .filter { groupInfo -> groupInfo.steps!!.any { it.position == 0 } } + .map { fixGroupInformationPositions(recipe, it) } + + val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation) + + with(recipe.copy(groupsInformation = updatedGroupInformation.toMutableSet())) { + recipeService.update(this) + } + } + + private fun fixGroupInformationPositions( + recipe: Recipe, + groupInformation: RecipeGroupInformation + ): RecipeGroupInformation { + val steps = groupInformation.steps!! + val maxPosition = steps.maxOf { it.position } + + logger.warn("Recipe ${recipe.id} (${recipe.name}) has invalid positions:") + + val invalidRecipeSteps = steps.filter { it.position == 0 } + val fixedRecipeSteps = increaseRecipeStepsPosition(groupInformation, invalidRecipeSteps, maxPosition + 1) + val updatedRecipeSteps = steps.merge(fixedRecipeSteps) + + return groupInformation.copy(steps = updatedRecipeSteps.toMutableSet()) + } + + private fun increaseRecipeStepsPosition( + groupInformation: RecipeGroupInformation, + recipeSteps: Iterable, + firstPosition: Int + ) = + recipeSteps + .mapIndexed { index, recipeStep -> recipeStep.copy(position = firstPosition + index) } + .onEach { + logger.info("\tPosition of step ${it.id} (group: ${groupInformation.group.name}) has been set to ${it.position}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index b0649a7..a78ba28 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -5,7 +5,6 @@ import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.account.group -import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -158,7 +157,7 @@ data class RecipeOutputDto( data class RecipeGroupInformation( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long?, + override val id: Long?, @ManyToOne @JoinColumn(name = "group_id") @@ -169,7 +168,7 @@ data class RecipeGroupInformation( @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) @JoinColumn(name = "recipe_group_information_id") var steps: MutableSet? -) +) : Model data class RecipeStepsDto( val groupId: Long, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt index a7a41ea..00b853c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt @@ -1,9 +1,11 @@ package dev.fyloz.colorrecipesexplorer.utils +import dev.fyloz.colorrecipesexplorer.model.Model + /** Returns a list containing the result of the given [transform] applied to each item of the [Iterable]. If the given [transform] throws, the [Throwable] will be passed to the given [throwableConsumer]. */ inline fun Iterable.mapMayThrow( - throwableConsumer: (E) -> Unit = {}, - transform: (T) -> R + throwableConsumer: (E) -> Unit = {}, + transform: (T) -> R ): List = this.mapNotNull { try { transform(it) @@ -19,17 +21,17 @@ inline fun Iterable.mapMayThrow( /** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */ inline fun Iterable.findDuplicated(keySelector: (T) -> K) = - this.groupBy(keySelector) - .filter { it.value.count() > 1 } - .map { it.key } + this.groupBy(keySelector) + .filter { it.value.count() > 1 } + .map { it.key } /** Check if the given [Iterable] has gaps between each items, using keys obtained from the given [keySelector]. */ inline fun Iterable.hasGaps(keySelector: (T) -> Int) = - this.map(keySelector) - .toIntArray() - .sorted() - .filterIndexed { index, it -> it != index + 1 } - .isNotEmpty() + this.map(keySelector) + .toIntArray() + .sorted() + .filterIndexed { index, it -> it != index + 1 } + .isNotEmpty() /** Clears and fills the given [MutableCollection] with the given [elements]. */ fun MutableCollection.setAll(elements: Collection) { @@ -40,6 +42,12 @@ fun MutableCollection.setAll(elements: Collection) { /** Removes and returns all elements of a [MutableCollection] matching the given [predicate]. */ inline fun MutableCollection.excludeAll(predicate: (T) -> Boolean): Iterable { val matching = this.filter(predicate) - this.removeAll(matching) + this.removeAll(matching.toSet()) return matching } + +/** Merge to [Model] [Iterable]s and prevent id duplication. */ +fun Iterable.merge(other: Iterable) = + this + .filter { model -> other.all { it.id != model.id } } + .plus(other) -- 2.40.1 From b18ed35571fe942e43f603f7debed8e307b30610 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 22 Dec 2021 16:21:44 -0500 Subject: [PATCH 3/6] Add logging when users login --- .../config/security/JwtFilters.kt | 5 +++++ .../config/security/SecurityConfig.kt | 15 ++++++++++----- .../rest/AccountControllers.kt | 1 + .../service/jobs/TouchUpKitRemover.kt | 14 +++++++++----- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt index d5b5023..b7c27a8 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt @@ -41,6 +41,7 @@ class JwtAuthenticationFilter( override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication { val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequest::class.java) + logger.debug("Login attempt for user ${loginRequest.id}...") return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password)) } @@ -53,6 +54,10 @@ class JwtAuthenticationFilter( val userDetails = auth.principal as UserDetails val token = jwtService.buildJwt(userDetails) + with(userDetails.user) { + logger.info("User ${this.id} (${this.firstName} ${this.lastName}) has logged in successfully") + } + response.addHeader("Access-Control-Expose-Headers", authorizationCookieName) response.addHeader(authorizationCookieName, "Bearer $token") response.addCookie(authorizationCookieName, "Bearer$token") { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/SecurityConfig.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/SecurityConfig.kt index ec68d49..f2a70e7 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/SecurityConfig.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/SecurityConfig.kt @@ -7,6 +7,8 @@ import dev.fyloz.colorrecipesexplorer.model.account.User import dev.fyloz.colorrecipesexplorer.service.users.JwtService import dev.fyloz.colorrecipesexplorer.service.users.UserDetailsService import dev.fyloz.colorrecipesexplorer.service.users.UserService +import mu.KLogger +import mu.KotlinLogging import org.slf4j.Logger import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean @@ -39,9 +41,10 @@ abstract class BaseSecurityConfig( private val userDetailsService: UserDetailsService, private val jwtService: JwtService, private val environment: Environment, - protected val logger: Logger, protected val securityProperties: CreSecurityProperties ) : WebSecurityConfigurerAdapter() { + protected abstract val logger: Logger + protected val passwordEncoder = BCryptPasswordEncoder() var debugMode = false @@ -119,9 +122,10 @@ class SecurityConfig( @Lazy private val userService: UserService, jwtService: JwtService, environment: Environment, - logger: Logger, securityProperties: CreSecurityProperties -) : BaseSecurityConfig(userDetailsService, jwtService, environment, logger, securityProperties) { +) : BaseSecurityConfig(userDetailsService, jwtService, environment, securityProperties) { + override val logger = KotlinLogging.logger {} + @PostConstruct fun initWebSecurity() { if (emergencyMode) { @@ -165,9 +169,10 @@ class EmergencySecurityConfig( userDetailsService: UserDetailsService, jwtService: JwtService, environment: Environment, - logger: Logger, securityProperties: CreSecurityProperties -) : BaseSecurityConfig(userDetailsService, jwtService, environment, logger, securityProperties) { +) : BaseSecurityConfig(userDetailsService, jwtService, environment, securityProperties) { + override val logger = KotlinLogging.logger {} + init { emergencyMode = true } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 42ab55e..75921be 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers import dev.fyloz.colorrecipesexplorer.model.account.* import dev.fyloz.colorrecipesexplorer.service.users.GroupService import dev.fyloz.colorrecipesexplorer.service.users.UserService +import mu.KotlinLogging import org.springframework.context.annotation.Profile import org.springframework.http.MediaType import org.springframework.security.access.prepost.PreAuthorize diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/jobs/TouchUpKitRemover.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/jobs/TouchUpKitRemover.kt index e8cffaf..5cde5bf 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/jobs/TouchUpKitRemover.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/jobs/TouchUpKitRemover.kt @@ -1,7 +1,7 @@ package dev.fyloz.colorrecipesexplorer.service.jobs import dev.fyloz.colorrecipesexplorer.service.TouchUpKitService -import org.slf4j.Logger +import mu.KotlinLogging import org.springframework.context.annotation.Profile import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component @@ -9,18 +9,22 @@ import org.springframework.stereotype.Component @Component @Profile("!emergency") class TouchUpKitRemover( - private val touchUpKitService: TouchUpKitService, - private val logger: Logger + private val touchUpKitService: TouchUpKitService ) { + private val logger = KotlinLogging.logger {} + @Scheduled(cron = "0 0 0 * * *") fun execute() { + logger.debug("Executing expired touch up kits removal job... ") removeExpiredKits() } private fun removeExpiredKits() { - logger.info("Removing expired touch up kits...") with(touchUpKitService.getAll().filter(touchUpKitService::isExpired)) { - this.forEach(touchUpKitService::delete) + this.forEach { + logger.debug("Removed expired touch up kit ${it.id} (${it.project} ${it.buggy})") + touchUpKitService.delete(it) + } logger.info("Removed ${this.size} expired touch up kits") } } -- 2.40.1 From c21a76316035835b7acc8b98573e8516f7ec8524 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 22 Dec 2021 16:26:23 -0500 Subject: [PATCH 4/6] CI/CD --- .drone.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index 131882b..5f87f63 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,6 +1,6 @@ --- global-variables: - release: &release ${DRONE_BRANCH##**/} + release: &release ${DRONE_TAG} environment: &environment JAVA_VERSION: 11 GRADLE_VERSION: 7.1 @@ -44,7 +44,8 @@ steps: commands: - echo -n "latest-release,$CRE_RELEASE" > .tags when: - branch: release/** + event: + - tag - name: containerize-dev image: plugins/docker @@ -74,7 +75,8 @@ steps: - CRE_VERSION=${DRONE_BRANCH##**/} repo: *docker-registry-repo when: - branch: release/** + event: + - tag - name: deploy image: alpine:latest @@ -107,12 +109,11 @@ steps: - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker pull $CRE_REGISTRY_IMAGE:$CRE_RELEASE" - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker run -d -p $CRE_PORT:9090 --name=$DEPLOY_CONTAINER_NAME -v $DEPLOY_DATA_VOLUME:/usr/bin/data -v $DEPLOY_CONFIG_VOLUME:/usr/bin/config -e spring_profiles_active=$DEPLOY_SPRING_PROFILES $CRE_REGISTRY_IMAGE:$CRE_RELEASE" when: - branch: release/** + event: + - tag trigger: branch: - - develop - - release/** - - master - - + - develop + event: + - tag \ No newline at end of file -- 2.40.1 From a178ef81c8d48faf75d1a9d19eae276626da17b4 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Thu, 23 Dec 2021 21:00:47 -0500 Subject: [PATCH 5/6] CI/CD --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 5f87f63..e300551 100644 --- a/.drone.yml +++ b/.drone.yml @@ -116,4 +116,4 @@ trigger: branch: - develop event: - - tag \ No newline at end of file + - tag -- 2.40.1 From fefdd508705542dc18ac12b8eed0688d372a4ce7 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Thu, 23 Dec 2021 21:01:31 -0500 Subject: [PATCH 6/6] CI/CD --- .drone.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index e300551..5b160cc 100644 --- a/.drone.yml +++ b/.drone.yml @@ -110,10 +110,4 @@ steps: - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker run -d -p $CRE_PORT:9090 --name=$DEPLOY_CONTAINER_NAME -v $DEPLOY_DATA_VOLUME:/usr/bin/data -v $DEPLOY_CONFIG_VOLUME:/usr/bin/config -e spring_profiles_active=$DEPLOY_SPRING_PROFILES $CRE_REGISTRY_IMAGE:$CRE_RELEASE" when: event: - - tag - -trigger: - branch: - - develop - event: - - tag + - tag \ No newline at end of file -- 2.40.1