Add a recipe initializer to find invalid steps positions and fix them
This commit is contained in:
parent
bc9ace3ed6
commit
fe57745b63
|
@ -8,5 +8,5 @@ abstract class AbstractInitializer : ApplicationListener<ApplicationReadyEvent>
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun initialize()
|
protected abstract fun initialize()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||||
import dev.fyloz.colorrecipesexplorer.model.Mix
|
import dev.fyloz.colorrecipesexplorer.model.Mix
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||||
import dev.fyloz.colorrecipesexplorer.service.MixService
|
import dev.fyloz.colorrecipesexplorer.service.MixService
|
||||||
|
import dev.fyloz.colorrecipesexplorer.utils.merge
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -24,9 +25,7 @@ class MixInitializer(
|
||||||
logger.debug("Validating mix materials positions...")
|
logger.debug("Validating mix materials positions...")
|
||||||
|
|
||||||
mixService.getAll()
|
mixService.getAll()
|
||||||
.filter { mix ->
|
.filter { mix -> mix.mixMaterials.any { it.position == 0 } }
|
||||||
mix.mixMaterials.any { it.position == 0 }
|
|
||||||
}
|
|
||||||
.forEach(this::fixMixPositions)
|
.forEach(this::fixMixPositions)
|
||||||
|
|
||||||
logger.debug("Mix materials positions are valid!")
|
logger.debug("Mix materials positions are valid!")
|
||||||
|
@ -34,20 +33,19 @@ class MixInitializer(
|
||||||
|
|
||||||
private fun fixMixPositions(mix: Mix) {
|
private fun fixMixPositions(mix: Mix) {
|
||||||
val maxPosition = mix.mixMaterials.maxOf { it.position }
|
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:")
|
logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:")
|
||||||
|
|
||||||
val invalidMixMaterials: Collection<MixMaterial> = with(mix.mixMaterials.filter { it.position == 0 }) {
|
val invalidMixMaterials: Collection<MixMaterial> = with(mix.mixMaterials.filter { it.position == 0 }) {
|
||||||
if (nextPosition == 1 && this.size > 1) {
|
if (maxPosition == 0 && this.size > 1) {
|
||||||
orderMixMaterials(this)
|
orderMixMaterials(this)
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, nextPosition)
|
val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, maxPosition + 1)
|
||||||
val updatedMixMaterials = mergeMixMaterials(mix.mixMaterials, fixedMixMaterials)
|
val updatedMixMaterials = mix.mixMaterials.merge(fixedMixMaterials)
|
||||||
|
|
||||||
with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) {
|
with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) {
|
||||||
mixService.update(this)
|
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}")
|
logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mergeMixMaterials(mixMaterials: Iterable<MixMaterial>, updatedMixMaterials: Iterable<MixMaterial>) =
|
|
||||||
mixMaterials
|
|
||||||
.filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } }
|
|
||||||
.plus(updatedMixMaterials)
|
|
||||||
|
|
||||||
private fun orderMixMaterials(mixMaterials: Collection<MixMaterial>) =
|
private fun orderMixMaterials(mixMaterials: Collection<MixMaterial>) =
|
||||||
LinkedList(mixMaterials).apply {
|
LinkedList(mixMaterials).apply {
|
||||||
while (this.peek().material.materialType?.usePercentages == true) {
|
while (this.peek().material.materialType?.usePercentages == true) {
|
||||||
|
|
|
@ -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<RecipeStep>,
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
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 dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
@ -158,7 +157,7 @@ data class RecipeOutputDto(
|
||||||
data class RecipeGroupInformation(
|
data class RecipeGroupInformation(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
val id: Long?,
|
override val id: Long?,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "group_id")
|
@JoinColumn(name = "group_id")
|
||||||
|
@ -169,7 +168,7 @@ data class RecipeGroupInformation(
|
||||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||||
@JoinColumn(name = "recipe_group_information_id")
|
@JoinColumn(name = "recipe_group_information_id")
|
||||||
var steps: MutableSet<RecipeStep>?
|
var steps: MutableSet<RecipeStep>?
|
||||||
)
|
) : Model
|
||||||
|
|
||||||
data class RecipeStepsDto(
|
data class RecipeStepsDto(
|
||||||
val groupId: Long,
|
val groupId: Long,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.utils
|
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]. */
|
/** 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 <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
||||||
throwableConsumer: (E) -> Unit = {},
|
throwableConsumer: (E) -> Unit = {},
|
||||||
transform: (T) -> R
|
transform: (T) -> R
|
||||||
): List<R> = this.mapNotNull {
|
): List<R> = this.mapNotNull {
|
||||||
try {
|
try {
|
||||||
transform(it)
|
transform(it)
|
||||||
|
@ -19,17 +21,17 @@ inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
||||||
|
|
||||||
/** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */
|
/** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */
|
||||||
inline fun <T, K> Iterable<T>.findDuplicated(keySelector: (T) -> K) =
|
inline fun <T, K> Iterable<T>.findDuplicated(keySelector: (T) -> K) =
|
||||||
this.groupBy(keySelector)
|
this.groupBy(keySelector)
|
||||||
.filter { it.value.count() > 1 }
|
.filter { it.value.count() > 1 }
|
||||||
.map { it.key }
|
.map { it.key }
|
||||||
|
|
||||||
/** Check if the given [Iterable] has gaps between each items, using keys obtained from the given [keySelector]. */
|
/** Check if the given [Iterable] has gaps between each items, using keys obtained from the given [keySelector]. */
|
||||||
inline fun <T> Iterable<T>.hasGaps(keySelector: (T) -> Int) =
|
inline fun <T> Iterable<T>.hasGaps(keySelector: (T) -> Int) =
|
||||||
this.map(keySelector)
|
this.map(keySelector)
|
||||||
.toIntArray()
|
.toIntArray()
|
||||||
.sorted()
|
.sorted()
|
||||||
.filterIndexed { index, it -> it != index + 1 }
|
.filterIndexed { index, it -> it != index + 1 }
|
||||||
.isNotEmpty()
|
.isNotEmpty()
|
||||||
|
|
||||||
/** Clears and fills the given [MutableCollection] with the given [elements]. */
|
/** Clears and fills the given [MutableCollection] with the given [elements]. */
|
||||||
fun <T> MutableCollection<T>.setAll(elements: Collection<T>) {
|
fun <T> MutableCollection<T>.setAll(elements: Collection<T>) {
|
||||||
|
@ -40,6 +42,12 @@ fun <T> MutableCollection<T>.setAll(elements: Collection<T>) {
|
||||||
/** Removes and returns all elements of a [MutableCollection] matching the given [predicate]. */
|
/** Removes and returns all elements of a [MutableCollection] matching the given [predicate]. */
|
||||||
inline fun <T> MutableCollection<T>.excludeAll(predicate: (T) -> Boolean): Iterable<T> {
|
inline fun <T> MutableCollection<T>.excludeAll(predicate: (T) -> Boolean): Iterable<T> {
|
||||||
val matching = this.filter(predicate)
|
val matching = this.filter(predicate)
|
||||||
this.removeAll(matching)
|
this.removeAll(matching.toSet())
|
||||||
return matching
|
return matching
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Merge to [Model] [Iterable]s and prevent id duplication. */
|
||||||
|
fun <T : Model> Iterable<T>.merge(other: Iterable<T>) =
|
||||||
|
this
|
||||||
|
.filter { model -> other.all { it.id != model.id } }
|
||||||
|
.plus(other)
|
||||||
|
|
Loading…
Reference in New Issue