From ef27e57f47ef36e049881a815ad2875959dd3904 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 28 Apr 2021 17:50:05 -0400 Subject: [PATCH] Tout fonctionne! --- ...aLoader.kt => ApplicationReadyListener.kt} | 15 +- .../colorrecipesexplorer/model/Employee.kt | 224 +++++++++--------- .../model/EmployeeGroup.kt | 9 +- .../colorrecipesexplorer/model/Material.kt | 27 +-- .../fyloz/colorrecipesexplorer/model/Mix.kt | 159 +++++++------ .../colorrecipesexplorer/model/MixMaterial.kt | 77 +++--- .../colorrecipesexplorer/model/Recipe.kt | 41 ++-- .../rest/AccountControllers.kt | 106 +++++---- .../rest/CompanyController.kt | 4 +- .../rest/InventoryController.kt | 11 +- .../rest/MaterialController.kt | 55 ++--- .../rest/MaterialTypeController.kt | 4 +- .../rest/RecipeController.kt | 39 +-- .../colorrecipesexplorer/rest/RestUtils.kt | 3 + .../service/AccountService.kt | 184 +++++++------- .../service/CompanyService.kt | 19 +- .../service/MaterialService.kt | 41 +++- .../service/MaterialTypeService.kt | 6 +- .../service/MixMaterialService.kt | 101 ++++---- .../service/MixService.kt | 27 ++- .../service/RecipeService.kt | 32 ++- .../colorrecipesexplorer/service/Service.kt | 58 +++-- .../service/AbstractServiceTest.kt | 52 ++-- .../service/MaterialServiceTest.kt | 39 +-- .../service/RecipeServiceTest.kt | 4 +- 25 files changed, 722 insertions(+), 615 deletions(-) rename src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/{InitialDataLoader.kt => ApplicationReadyListener.kt} (58%) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/InitialDataLoader.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationReadyListener.kt similarity index 58% rename from src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/InitialDataLoader.kt rename to src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationReadyListener.kt index 0e1ca4b..f135661 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/InitialDataLoader.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationReadyListener.kt @@ -1,6 +1,8 @@ package dev.fyloz.colorrecipesexplorer.config +import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties +import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener @@ -10,10 +12,13 @@ import org.springframework.core.annotation.Order @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) -class InitialDataLoader( - private val materialTypeService: MaterialTypeService, - private val materialTypeProperties: MaterialTypeProperties +class ApplicationReadyListener( + private val materialTypeService: MaterialTypeService, + private val materialTypeProperties: MaterialTypeProperties, + private val creProperties: CreProperties ) : ApplicationListener { - override fun onApplicationEvent(event: ApplicationReadyEvent) = - materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes) + override fun onApplicationEvent(event: ApplicationReadyEvent) { + materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes) + CRE_PROPERTIES = creProperties + } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt index fa564eb..1b98d41 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt @@ -1,7 +1,5 @@ package dev.fyloz.colorrecipesexplorer.model -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonProperty import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank @@ -25,139 +23,143 @@ private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit co @Entity @Table(name = "employee") data class Employee( - @Id - override val id: Long, + @Id + override val id: Long, - @Column(name = "first_name") - val firstName: String = "", + @Column(name = "first_name") + val firstName: String = "", - @Column(name = "last_name") - val lastName: String = "", + @Column(name = "last_name") + val lastName: String = "", - @JsonIgnore - val password: String = "", + val password: String = "", - @JsonIgnore - @Column(name = "default_group_user") - val isDefaultGroupUser: Boolean = false, + @Column(name = "default_group_user") + val isDefaultGroupUser: Boolean = false, - @JsonIgnore - @Column(name = "system_user") - val isSystemUser: Boolean = false, + @Column(name = "system_user") + val isSystemUser: Boolean = false, - @ManyToOne - @JoinColumn(name = "group_id") - @Fetch(FetchMode.SELECT) - var group: EmployeeGroup? = null, + @ManyToOne + @JoinColumn(name = "group_id") + @Fetch(FetchMode.SELECT) + var group: EmployeeGroup? = null, - @Enumerated(EnumType.STRING) - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")]) - @Column(name = "permission") - @Fetch(FetchMode.SUBSELECT) - @get:JsonProperty("explicitPermissions") - val permissions: MutableSet = mutableSetOf(), + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")]) + @Column(name = "permission") + @Fetch(FetchMode.SUBSELECT) + val permissions: MutableSet = mutableSetOf(), - @Column(name = "last_login_time") - var lastLoginTime: LocalDateTime? = null + @Column(name = "last_login_time") + var lastLoginTime: LocalDateTime? = null ) : Model { - @get:JsonProperty("permissions") val flatPermissions: Set get() = permissions - .flatMap { it.flat() } - .filter { !it.deprecated } - .toMutableSet() - .apply { - if (group != null) this.addAll(group!!.flatPermissions) - } + .flatMap { it.flat() } + .filter { !it.deprecated } + .toMutableSet() + .apply { + if (group != null) this.addAll(group!!.flatPermissions) + } - @get:JsonIgnore val authorities: Set get() = flatPermissions.map { it.toAuthority() }.toMutableSet() } /** DTO for creating employees. Allows a [password] a [groupId]. */ open class EmployeeSaveDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, + @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) + val id: Long, - @field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String, + @field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String, - @field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String, + @field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) + val lastName: String, - @field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE) - @field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE) - val password: String, + @field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE) + @field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE) + val password: String, - val groupId: Long?, + val groupId: Long?, - @Enumerated(EnumType.STRING) - val permissions: MutableSet = mutableSetOf() + @Enumerated(EnumType.STRING) + val permissions: MutableSet = mutableSetOf() ) : EntityDto open class EmployeeUpdateDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, + @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) + val id: Long, - @field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String?, + @field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String?, - @field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String?, + @field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) + val lastName: String?, - val groupId: Long?, + val groupId: Long?, - @Enumerated(EnumType.STRING) - val permissions: Set? + @Enumerated(EnumType.STRING) + val permissions: Set? ) : EntityDto +data class EmployeeOutputDto( + override val id: Long, + val firstName: String, + val lastName: String, + val group: EmployeeGroup?, + val permissions: Set, + val explicitPermissions: Set, + val lastLoginTime: LocalDateTime? +) : Model + data class EmployeeLoginRequest(val id: Long, val password: String) // ==== DSL ==== fun employee( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - isDefaultGroupUser: Boolean = false, - isSystemUser: Boolean = false, - group: EmployeeGroup? = null, - permissions: MutableSet = mutableSetOf(), - lastLoginTime: LocalDateTime? = null, - op: Employee.() -> Unit = {} + passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + password: String = passwordEncoder.encode("password"), + isDefaultGroupUser: Boolean = false, + isSystemUser: Boolean = false, + group: EmployeeGroup? = null, + permissions: MutableSet = mutableSetOf(), + lastLoginTime: LocalDateTime? = null, + op: Employee.() -> Unit = {} ) = Employee( - id, - firstName, - lastName, - password, - isDefaultGroupUser, - isSystemUser, - group, - permissions, - lastLoginTime + id, + firstName, + lastName, + password, + isDefaultGroupUser, + isSystemUser, + group, + permissions, + lastLoginTime ).apply(op) fun employeeSaveDto( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeSaveDto.() -> Unit = {} + passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + password: String = passwordEncoder.encode("password"), + groupId: Long? = null, + permissions: MutableSet = mutableSetOf(), + op: EmployeeSaveDto.() -> Unit = {} ) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op) fun employeeUpdateDto( - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeUpdateDto.() -> Unit = {} + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + groupId: Long? = null, + permissions: MutableSet = mutableSetOf(), + op: EmployeeUpdateDto.() -> Unit = {} ) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) // ==== Exceptions ==== @@ -166,26 +168,26 @@ private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already ex private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee" fun employeeIdNotFoundException(id: Long) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee with the id $id could not be found", - id - ) + NotFoundException( + EMPLOYEE_EXCEPTION_ERROR_CODE, + EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, + "An employee with the id $id could not be found", + id + ) fun employeeIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the id $id already exists", - id - ) + AlreadyExistsException( + EMPLOYEE_EXCEPTION_ERROR_CODE, + EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, + "An employee with the id $id already exists", + id + ) fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the name '$firstName $lastName' already exists", - "$firstName $lastName", - "fullName" - ) + AlreadyExistsException( + EMPLOYEE_EXCEPTION_ERROR_CODE, + EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, + "An employee with the name '$firstName $lastName' already exists", + "$firstName $lastName", + "fullName" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt index a0d467e..a88e0e4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt @@ -31,10 +31,8 @@ data class EmployeeGroup( @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) @Column(name = "permission") @Fetch(FetchMode.SUBSELECT) - @get:JsonProperty("explicitPermissions") val permissions: MutableSet = mutableSetOf(), ) : NamedModel { - @get:JsonProperty("permissions") val flatPermissions: Set get() = this.permissions .flatMap { it.flat() } @@ -69,6 +67,13 @@ open class EmployeeGroupUpdateDto( EmployeeGroup(id, name, permissions) } +data class EmployeeGroupOutputDto( + override val id: Long, + val name: String, + val permissions: Set, + val explicitPermissions: Set +): Model + fun employeeGroup( id: Long? = null, name: String = "name", diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt index b337627..5dc53de 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt @@ -6,8 +6,11 @@ import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize +import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES +import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import org.springframework.web.multipart.MultipartFile -import java.net.URI +import java.net.URLEncoder +import java.nio.charset.StandardCharsets import javax.persistence.* import javax.validation.constraints.Min import javax.validation.constraints.NotBlank @@ -49,6 +52,8 @@ data class Material( @JsonIgnore @Transient get() = "$SIMDUT_FILES_PATH/$name.pdf" + + } open class MaterialSaveDto( @@ -81,13 +86,13 @@ open class MaterialUpdateDto( ) : EntityDto data class MaterialOutputDto( - val id: Long, + override val id: Long, val name: String, val inventoryQuantity: Float, val isMixType: Boolean, val materialType: MaterialType, val simdutUrl: String? -) +) : Model data class MaterialQuantityDto( @field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE) @@ -135,19 +140,6 @@ fun materialUpdateDto( op: MaterialUpdateDto.() -> Unit = {} ) = MaterialUpdateDto(id, name, inventoryQuantity, materialTypeId, simdutFile).apply(op) -fun materialOutputDto( - material: Material, - simdutUrl: String?, - op: MaterialOutputDto.() -> Unit = {} -) = MaterialOutputDto( - id = material.id!!, - name = material.name, - inventoryQuantity = material.inventoryQuantity, - isMixType = material.isMixType, - materialType = material.materialType!!, - simdutUrl = simdutUrl -).apply(op) - fun materialQuantityDto( materialId: Long, quantity: Float, @@ -155,7 +147,8 @@ fun materialQuantityDto( ) = MaterialQuantityDto(materialId, quantity).apply(op) // ==== Exceptions ==== -private const val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found" + private const +val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found" private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists" private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material" private const val MATERIAL_EXCEPTION_ERROR_CODE = "material" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt index 4dcb08f..e332b36 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt @@ -22,107 +22,116 @@ private const val MIX_DEDUCT_RATION_NEGATIVE_MESSAGE = "Le ratio doit être éga @Entity @Table(name = "mix") data class Mix( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - override val id: Long?, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override val id: Long?, - var location: String?, + var location: String?, - @JsonIgnore - @ManyToOne - @JoinColumn(name = "recipe_id") - val recipe: Recipe, + @JsonIgnore + @ManyToOne + @JoinColumn(name = "recipe_id") + val recipe: Recipe, - @ManyToOne - @JoinColumn(name = "mix_type_id") - var mixType: MixType, + @ManyToOne + @JoinColumn(name = "mix_type_id") + var mixType: MixType, - @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) - @JoinColumn(name = "mix_id") - var mixMaterials: MutableSet, + @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) + @JoinColumn(name = "mix_id") + var mixMaterials: MutableSet, ) : Model open class MixSaveDto( - @field:NotBlank(message = MIX_NAME_NULL_MESSAGE) - val name: String, + @field:NotBlank(message = MIX_NAME_NULL_MESSAGE) + val name: String, - @field:NotNull(message = MIX_RECIPE_NULL_MESSAGE) - val recipeId: Long, + @field:NotNull(message = MIX_RECIPE_NULL_MESSAGE) + val recipeId: Long, - @field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE) - val materialTypeId: Long, + @field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE) + val materialTypeId: Long, - val mixMaterials: Set? + val mixMaterials: Set? ) : EntityDto { override fun toEntity(): Mix = throw UnsupportedOperationException() } open class MixUpdateDto( - @field:NotNull(message = MIX_ID_NULL_MESSAGE) - val id: Long, + @field:NotNull(message = MIX_ID_NULL_MESSAGE) + val id: Long, - @field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE) - val name: String?, + @field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE) + val name: String?, - val materialTypeId: Long?, + val materialTypeId: Long?, - var mixMaterials: Set? + var mixMaterials: Set? ) : EntityDto { override fun toEntity(): Mix = throw UnsupportedOperationException() } -data class MixDeductDto( - @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) - val id: Long, +data class MixOutputDto( + val id: Long, + val location: String?, + val mixType: MixType, + val mixMaterials: Set +) - @field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE) - @field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE) - val ratio: Float +data class MixDeductDto( + @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) + val id: Long, + + @field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE) + @field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE) + val ratio: Float ) data class MixLocationDto( - @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) - val mixId: Long, + @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) + val mixId: Long, - val location: String? + val location: String? ) +//fun Mix.toOutput() = + // ==== DSL ==== fun mix( - id: Long? = null, - location: String? = "location", - recipe: Recipe = recipe(), - mixType: MixType = mixType(), - mixMaterials: MutableSet = mutableSetOf(), - op: Mix.() -> Unit = {} + id: Long? = null, + location: String? = "location", + recipe: Recipe = recipe(), + mixType: MixType = mixType(), + mixMaterials: MutableSet = mutableSetOf(), + op: Mix.() -> Unit = {} ) = Mix(id, location, recipe, mixType, mixMaterials).apply(op) fun mixSaveDto( - name: String = "name", - recipeId: Long = 0L, - materialTypeId: Long = 0L, - mixMaterials: Set? = setOf(), - op: MixSaveDto.() -> Unit = {} + name: String = "name", + recipeId: Long = 0L, + materialTypeId: Long = 0L, + mixMaterials: Set? = setOf(), + op: MixSaveDto.() -> Unit = {} ) = MixSaveDto(name, recipeId, materialTypeId, mixMaterials).apply(op) fun mixUpdateDto( - id: Long = 0L, - name: String? = "name", - materialTypeId: Long? = 0L, - mixMaterials: Set? = setOf(), - op: MixUpdateDto.() -> Unit = {} + id: Long = 0L, + name: String? = "name", + materialTypeId: Long? = 0L, + mixMaterials: Set? = setOf(), + op: MixUpdateDto.() -> Unit = {} ) = MixUpdateDto(id, name, materialTypeId, mixMaterials).apply(op) fun mixRatio( - id: Long = 0L, - ratio: Float = 1f, - op: MixDeductDto.() -> Unit = {} + id: Long = 0L, + ratio: Float = 1f, + op: MixDeductDto.() -> Unit = {} ) = MixDeductDto(id, ratio).apply(op) fun mixLocationDto( - mixId: Long = 0L, - location: String? = "location", - op: MixLocationDto.() -> Unit = {} + mixId: Long = 0L, + location: String? = "location", + op: MixLocationDto.() -> Unit = {} ) = MixLocationDto(mixId, location).apply(op) // ==== Exceptions ==== @@ -132,24 +141,24 @@ private const val MIX_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix" private const val MIX_EXCEPTION_ERROR_CODE = "mix" fun mixIdNotFoundException(id: Long) = - NotFoundException( - MIX_EXCEPTION_ERROR_CODE, - MIX_NOT_FOUND_EXCEPTION_TITLE, - "A mix with the id $id could not be found", - id - ) + NotFoundException( + MIX_EXCEPTION_ERROR_CODE, + MIX_NOT_FOUND_EXCEPTION_TITLE, + "A mix with the id $id could not be found", + id + ) fun mixIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - MIX_EXCEPTION_ERROR_CODE, - MIX_ALREADY_EXISTS_EXCEPTION_TITLE, - "A mix with the id $id already exists", - id - ) + AlreadyExistsException( + MIX_EXCEPTION_ERROR_CODE, + MIX_ALREADY_EXISTS_EXCEPTION_TITLE, + "A mix with the id $id already exists", + id + ) fun cannotDeleteMixException(mix: Mix) = - CannotDeleteException( - MIX_EXCEPTION_ERROR_CODE, - MIX_CANNOT_DELETE_EXCEPTION_TITLE, - "Cannot delete the mix ${mix.mixType.name} because one or more mixes depends on it" - ) + CannotDeleteException( + MIX_EXCEPTION_ERROR_CODE, + MIX_CANNOT_DELETE_EXCEPTION_TITLE, + "Cannot delete the mix ${mix.mixType.name} because one or more mixes depends on it" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt index 090a9bd..97f751c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt @@ -13,44 +13,51 @@ private const val MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE = "La quantité ne @Entity @Table(name = "mix_material") data class MixMaterial( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - override val id: Long?, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override val id: Long?, - @ManyToOne - @JoinColumn(name = "material_id") - val material: Material, + @ManyToOne + @JoinColumn(name = "material_id") + val material: Material, - var quantity: Float, + var quantity: Float, - var position: Int + var position: Int ) : Model +data class MixMaterialOutputDto( + val id: Long, + val material: MaterialOutputDto, + val quantity: Float, + val position: Int +) + data class MixMaterialDto( - @field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE) - val materialId: Long, + @field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE) + val materialId: Long, - @field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE) - @field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE) - val quantity: Float, + @field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE) + @field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE) + val quantity: Float, - val position: Int + val position: Int ) // ==== DSL ==== fun mixMaterial( - id: Long? = null, - material: Material = material(), - quantity: Float = 0f, - position: Int = 0, - op: MixMaterial.() -> Unit = {} + id: Long? = null, + material: Material = material(), + quantity: Float = 0f, + position: Int = 0, + op: MixMaterial.() -> Unit = {} ) = MixMaterial(id, material, quantity, position).apply(op) fun mixMaterialDto( - materialId: Long = 0L, - quantity: Float = 0f, - position: Int = 0, - op: MixMaterialDto.() -> Unit = {} + materialId: Long = 0L, + quantity: Float = 0f, + position: Int = 0, + op: MixMaterialDto.() -> Unit = {} ) = MixMaterialDto(materialId, quantity, position).apply(op) // ==== Exceptions ==== @@ -59,17 +66,17 @@ private const val MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix material al private const val MIX_MATERIAL_EXCEPTION_ERROR_CODE = "mixmaterial" fun mixMaterialIdNotFoundException(id: Long) = - NotFoundException( - MIX_MATERIAL_EXCEPTION_ERROR_CODE, - MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE, - "A mix material with the id $id could not be found", - id - ) + NotFoundException( + MIX_MATERIAL_EXCEPTION_ERROR_CODE, + MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE, + "A mix material with the id $id could not be found", + id + ) fun mixMaterialIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - MIX_MATERIAL_EXCEPTION_ERROR_CODE, - MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE, - "A mix material with the id $id already exists", - id - ) + AlreadyExistsException( + MIX_MATERIAL_EXCEPTION_ERROR_CODE, + MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE, + "A mix material with the id $id already exists", + id + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index dd933ac..367375a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -3,10 +3,12 @@ package dev.fyloz.colorrecipesexplorer.model import com.fasterxml.jackson.annotation.JsonIgnore import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize -import org.springframework.http.HttpStatus +import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES +import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH +import java.net.URLEncoder +import java.nio.charset.StandardCharsets import java.time.LocalDate import javax.persistence.* import javax.validation.constraints.* @@ -76,6 +78,14 @@ data class Recipe( fun groupInformationForGroup(groupId: Long) = groupsInformation.firstOrNull { it.group.id == groupId } + + fun imageUrl(name: String) = + "${CRE_PROPERTIES.deploymentUrl}$FILE_CONTROLLER_PATH?path=${ + URLEncoder.encode( + "${this.imagesDirectoryPath}/$name", + StandardCharsets.UTF_8 + ) + }" } open class RecipeSaveDto( @@ -143,7 +153,7 @@ open class RecipeUpdateDto( ) : EntityDto data class RecipeOutputDto( - val id: Long, + override val id: Long, val name: String, val description: String, val color: String, @@ -152,10 +162,10 @@ data class RecipeOutputDto( val approbationDate: LocalDate?, val remark: String?, val company: Company, - val mixes: Set, + val mixes: Set, val groupsInformation: Set, - val imagesUrls: Set -) + var imagesUrls: Set +) : Model @Entity @Table(name = "recipe_group_information") @@ -252,25 +262,6 @@ fun recipeUpdateDto( op: RecipeUpdateDto.() -> Unit = {} ) = RecipeUpdateDto(id, name, description, color, gloss, sample, approbationDate, remark, steps).apply(op) -fun recipeOutputDto( - recipe: Recipe, - imagesUrls: Set, - op: RecipeOutputDto.() -> Unit = {} -) = RecipeOutputDto( - recipe.id!!, - recipe.name, - recipe.description, - recipe.color, - recipe.gloss, - recipe.sample, - recipe.approbationDate, - recipe.remark, - recipe.company, - recipe.mixes.toSet(), - recipe.groupsInformation, - imagesUrls -).apply(op) - fun recipeGroupInformation( id: Long? = null, group: EmployeeGroup = employeeGroup(), diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 0776aa5..ea9efde 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -4,7 +4,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupServiceImpl +import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupService import dev.fyloz.colorrecipesexplorer.service.EmployeeService import org.springframework.http.MediaType import org.springframework.security.access.prepost.PreAuthorize @@ -23,52 +23,56 @@ class EmployeeController(private val employeeService: EmployeeService) { @GetMapping @PreAuthorizeViewUsers fun getAll() = - ok(employeeService.getAll()) + ok(employeeService.getAllForOutput()) @GetMapping("{id}") @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = - ok(employeeService.getById(id)) + ok(employeeService.getByIdForOutput(id)) @GetMapping("current") fun getCurrent(loggedInEmployee: Principal?) = - if (loggedInEmployee != null) - ok( - employeeService.getById( - loggedInEmployee.name.toLong(), - ignoreDefaultGroupUsers = false, - ignoreSystemUsers = false + if (loggedInEmployee != null) + ok( + with(employeeService) { + getById( + loggedInEmployee.name.toLong(), + ignoreDefaultGroupUsers = false, + ignoreSystemUsers = false + ).toOutput() + } ) - ) - else - forbidden() + else + forbidden() @PostMapping @PreAuthorizeEditUsers fun save(@Valid @RequestBody employee: EmployeeSaveDto) = - created(EMPLOYEE_CONTROLLER_PATH) { - employeeService.save(employee) - } + created(EMPLOYEE_CONTROLLER_PATH) { + with(employeeService) { + save(employee).toOutput() + } + } @PutMapping @PreAuthorizeEditUsers fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = - noContent { - employeeService.update(employee) - } + noContent { + employeeService.update(employee) + } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) @PreAuthorizeEditUsers fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = - noContent { - employeeService.updatePassword(id, password) - } + noContent { + employeeService.updatePassword(id, password) + } @PutMapping("{employeeId}/permissions/{permission}") @PreAuthorizeEditUsers fun addPermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable employeeId: Long, + @PathVariable permission: EmployeePermission ) = noContent { employeeService.addPermission(employeeId, permission) } @@ -76,8 +80,8 @@ class EmployeeController(private val employeeService: EmployeeService) { @DeleteMapping("{employeeId}/permissions/{permission}") @PreAuthorizeEditUsers fun removePermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable employeeId: Long, + @PathVariable permission: EmployeePermission ) = noContent { employeeService.removePermission(employeeId, permission) } @@ -85,59 +89,69 @@ class EmployeeController(private val employeeService: EmployeeService) { @DeleteMapping("{id}") @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = - employeeService.deleteById(id) + employeeService.deleteById(id) } @RestController @RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) -class GroupsController(private val groupService: EmployeeGroupServiceImpl) { +class GroupsController( + private val groupService: EmployeeGroupService, + private val employeeService: EmployeeService +) { @GetMapping @PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')") fun getAll() = - ok(groupService.getAll()) + ok(groupService.getAllForOutput()) @GetMapping("{id}") @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = - ok(groupService.getById(id)) + ok(groupService.getByIdForOutput(id)) @GetMapping("{id}/employees") @PreAuthorizeViewUsers fun getEmployeesForGroup(@PathVariable id: Long) = - ok(groupService.getEmployeesForGroup(id)) + ok(with(employeeService) { + groupService.getEmployeesForGroup(id) + .map { it.toOutput() } + }) @PostMapping("default/{groupId}") @PreAuthorizeViewUsers fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) = - noContent { - groupService.setResponseDefaultGroup(groupId, response) - } + noContent { + groupService.setResponseDefaultGroup(groupId, response) + } @GetMapping("default") @PreAuthorizeViewUsers fun getRequestDefaultGroup(request: HttpServletRequest) = - ok(groupService.getRequestDefaultGroup(request)) + ok(with(groupService) { + getRequestDefaultGroup(request).toOutput() + }) @PostMapping @PreAuthorizeEditUsers fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = - created(EMPLOYEE_GROUP_CONTROLLER_PATH) { - groupService.save(group) - } + created(EMPLOYEE_GROUP_CONTROLLER_PATH) { + with(groupService) { + save(group).toOutput() + } + } @PutMapping @PreAuthorizeEditUsers fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = - noContent { - groupService.update(group) - } + noContent { + groupService.update(group) + } @DeleteMapping("{id}") @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = - noContent { - groupService.deleteById(id) - } + noContent { + groupService.deleteById(id) + } } @RestController @@ -145,7 +159,7 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) { class LogoutController(private val employeeService: EmployeeService) { @GetMapping("logout") fun logout(request: HttpServletRequest) = - ok { - employeeService.logout(request) - } + ok { + employeeService.logout(request) + } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt index ef59303..e1acc00 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt @@ -17,11 +17,11 @@ private const val COMPANY_CONTROLLER_PATH = "api/company" class CompanyController(private val companyService: CompanyService) { @GetMapping fun getAll() = - ok(companyService.getAll()) + ok(companyService.getAllForOutput()) @GetMapping("{id}") fun getById(@PathVariable id: Long) = - ok(companyService.getById(id)) + ok(companyService.getByIdForOutput(id)) @PostMapping @PreAuthorize("hasAuthority('EDIT_COMPANIES')") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt index 832e14d..636c3af 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/InventoryController.kt @@ -15,21 +15,20 @@ private const val INVENTORY_CONTROLLER_PATH = "api/inventory" @RestController @RequestMapping(INVENTORY_CONTROLLER_PATH) class InventoryController( - private val inventoryService: InventoryService + private val inventoryService: InventoryService ) { @PutMapping("add") @PreAuthorize("hasAuthority('ADD_TO_INVENTORY')") - fun add(@RequestBody quantities: Collection): ResponseEntity> { - return ResponseEntity.ok(inventoryService.add(quantities)) - } + fun add(@RequestBody quantities: Collection) = + ok(inventoryService.add(quantities)) @PutMapping("deduct") @PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')") fun deduct(@RequestBody quantities: Collection) = - ok(inventoryService.deduct(quantities)) + ok(inventoryService.deduct(quantities)) @PutMapping("deduct/mix") @PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')") fun deduct(@RequestBody mixRatio: MixDeductDto) = - ok(inventoryService.deductMix(mixRatio)) + ok(inventoryService.deductMix(mixRatio)) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt index e9801f4..8e99b03 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt @@ -3,7 +3,6 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import dev.fyloz.colorrecipesexplorer.service.MaterialService import org.springframework.http.MediaType import org.springframework.http.ResponseEntity @@ -11,8 +10,6 @@ import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile import java.net.URI -import java.net.URLEncoder -import java.nio.charset.StandardCharsets import javax.validation.Valid private const val MATERIAL_CONTROLLER_PATH = "api/material" @@ -21,12 +18,11 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material" @RequestMapping(MATERIAL_CONTROLLER_PATH) @PreAuthorizeViewCatalog class MaterialController( - private val materialService: MaterialService, - private val creProperties: CreProperties + private val materialService: MaterialService ) { @GetMapping fun getAll() = - ok(materialService.getAll()) + ok(materialService.getAllForOutput()) @GetMapping("notmixtype") fun getAllNotMixType() = @@ -34,20 +30,22 @@ class MaterialController( @GetMapping("{id}") fun getById(@PathVariable id: Long) = - ok(materialService.getById(id)) + ok(materialService.getByIdForOutput(id)) @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @PreAuthorize("hasAuthority('EDIT_MATERIALS')") fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) = - created { - materialService.save( - materialSaveDto( - name = material.name, - inventoryQuantity = material.inventoryQuantity, - materialTypeId = material.materialTypeId, - simdutFile = simdutFile - ) - ) + created(MATERIAL_CONTROLLER_PATH) { + with(materialService) { + save( + materialSaveDto( + name = material.name, + inventoryQuantity = material.inventoryQuantity, + materialTypeId = material.materialTypeId, + simdutFile = simdutFile + ) + ).toOutput() + } } @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @@ -79,30 +77,5 @@ class MaterialController( @GetMapping("mix/update/{mixId}") fun getAllForMixUpdate(@PathVariable mixId: Long) = ok(materialService.getAllForMixUpdate(mixId)) - - private fun ok(material: Material) = - ok(material.toOutput()) - - private fun ok(materials: Collection) = - ok(materials.map { it.toOutput() }) - - private fun created(producer: () -> Material): ResponseEntity = with(producer().toOutput()) { - ResponseEntity - .created(URI.create("$MATERIAL_CONTROLLER_PATH/${this.id}")) - .body(this) - } - - private fun Material.toOutput() = materialOutputDto( - this, - if (materialService.hasSimdut(this)) this.simdutUrl() else null - ) - - private fun Material.simdutUrl() = - "${creProperties.deploymentUrl}$FILE_CONTROLLER_PATH?path=${ - URLEncoder.encode( - this.simdutFilePath, - StandardCharsets.UTF_8 - ) - }" } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt index 3991873..50e26f0 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt @@ -17,11 +17,11 @@ private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype" class MaterialTypeController(private val materialTypeService: MaterialTypeService) { @GetMapping fun getAll() = - ok(materialTypeService.getAll()) + ok(materialTypeService.getAllForOutput()) @GetMapping("{id}") fun getById(@PathVariable id: Long) = - ok(materialTypeService.getById(id)) + ok(materialTypeService.getByIdForOutput(id)) @PostMapping @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index d372c7a..8bf447e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -27,22 +27,23 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix" @PreAuthorizeViewRecipes class RecipeController( private val recipeService: RecipeService, - private val recipeImageService: RecipeImageService, - private val creProperties: CreProperties + private val recipeImageService: RecipeImageService ) { @GetMapping fun getAll() = - ok(recipeService.getAll()) + ok(recipeService.getAllForOutput()) @GetMapping("{id}") fun getById(@PathVariable id: Long) = - ok(recipeService.getById(id)) + ok(recipeService.getByIdForOutput(id)) @PostMapping @PreAuthorizeEditRecipes fun save(@Valid @RequestBody recipe: RecipeSaveDto) = - created(RECIPE_CONTROLLER_PATH) { - recipeService.save(recipe) + created(RECIPE_CONTROLLER_PATH) { + with(recipeService) { + save(recipe).toOutput() + } } @PutMapping @@ -66,7 +67,7 @@ class RecipeController( recipeService.deleteById(id) } - @PutMapping("{recipeId}/image", consumes = [MediaType.APPLICATION_OCTET_STREAM_VALUE]) + @PutMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @PreAuthorizeEditRecipes fun downloadImage(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity { recipeImageService.download(image, recipeService.getById(recipeId)) @@ -79,28 +80,6 @@ class RecipeController( noContent { recipeImageService.delete(recipeService.getById(recipeId), name) } - - private fun ok(recipe: Recipe) = - ok(recipe.toOutput()) - - private fun ok(recipes: Collection) = - ok(recipes.map { it.toOutput() }) - - private fun Recipe.toOutput() = - recipeOutputDto( - this, - recipeImageService.getAllImages(this) - .map { this.imageUrl(it) } - .toSet() - ) - - private fun Recipe.imageUrl(name: String) = - "${creProperties.deploymentUrl}$FILE_CONTROLLER_PATH?path=${ - URLEncoder.encode( - "${this.imagesDirectoryPath}/$name", - StandardCharsets.UTF_8 - ) - }" } @RestController @@ -109,7 +88,7 @@ class RecipeController( class MixController(private val mixService: MixService) { @GetMapping("{id}") fun getById(@PathVariable id: Long) = - ok(mixService.getById(id)) + ok(mixService.getByIdForOutput(id)) @PostMapping @PreAuthorizeEditRecipes diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt index 2f8fa7c..c7a82fc 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.rest +import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.model.Model import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus @@ -7,6 +8,8 @@ import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import java.net.URI +lateinit var CRE_PROPERTIES: CreProperties + /** Creates a HTTP OK [ResponseEntity] from the given [body]. */ fun ok(body: T): ResponseEntity = ResponseEntity.ok(body) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt index d3b81ba..8f08ed4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt @@ -20,7 +20,8 @@ import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import javax.transaction.Transactional -interface EmployeeService : ExternalModelService { +interface EmployeeService : + ExternalModelService { /** Check if an [Employee] with the given [firstName] and [lastName] exists. */ fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean @@ -56,7 +57,7 @@ interface EmployeeService : ExternalModelService { + ExternalNamedModelService { /** Gets all the employees of the group with the given [id]. */ fun getEmployeesForGroup(id: Long): Collection @@ -74,50 +75,62 @@ interface EmployeeUserDetailsService : UserDetailsService { @Service class EmployeeServiceImpl( - employeeRepository: EmployeeRepository, - @Lazy val groupService: EmployeeGroupService, - @Lazy val passwordEncoder: PasswordEncoder, -) : AbstractExternalModelService(employeeRepository), - EmployeeService { + employeeRepository: EmployeeRepository, + @Lazy val groupService: EmployeeGroupService, + @Lazy val passwordEncoder: PasswordEncoder, +) : AbstractExternalModelService( + employeeRepository +), + EmployeeService { override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id) + override fun Employee.toOutput() = EmployeeOutputDto( + this.id, + this.firstName, + this.lastName, + this.group, + this.flatPermissions, + this.permissions, + this.lastLoginTime + ) + override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean = - repository.existsByFirstNameAndLastName(firstName, lastName) + repository.existsByFirstNameAndLastName(firstName, lastName) override fun getAll(): Collection = - super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser } + super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser } override fun getById(id: Long): Employee = - getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) + getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee = - super.getById(id).apply { - if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser) - throw idNotFoundException(id) - } + super.getById(id).apply { + if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser) + throw idNotFoundException(id) + } override fun getByGroup(group: EmployeeGroup): Collection = - repository.findAllByGroup(group).filter { - !it.isSystemUser && !it.isDefaultGroupUser - } + repository.findAllByGroup(group).filter { + !it.isSystemUser && !it.isDefaultGroupUser + } override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee = - repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group) + repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group) override fun save(entity: EmployeeSaveDto): Employee = - save(with(entity) { - Employee( - id, - firstName, - lastName, - passwordEncoder.encode(password), - isDefaultGroupUser = false, - isSystemUser = false, - group = if (groupId != null) groupService.getById(groupId) else null, - permissions = permissions - ) - }) + save(with(entity) { + Employee( + id, + firstName, + lastName, + passwordEncoder.encode(password), + isDefaultGroupUser = false, + isSystemUser = false, + group = if (groupId != null) groupService.getById(groupId) else null, + permissions = permissions + ) + }) override fun save(entity: Employee): Employee { if (existsById(entity.id)) @@ -129,14 +142,14 @@ class EmployeeServiceImpl( override fun saveDefaultGroupEmployee(group: EmployeeGroup) { save( - employee( - id = 1000000L + group.id!!, - firstName = group.name, - lastName = "EmployeeModel", - password = passwordEncoder.encode(group.name), - group = group, - isDefaultGroupUser = true - ) + employee( + id = 1000000L + group.id!!, + firstName = group.name, + lastName = "EmployeeModel", + password = passwordEncoder.encode(group.name), + group = group, + isDefaultGroupUser = true + ) ) } @@ -144,9 +157,9 @@ class EmployeeServiceImpl( val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false) employee.lastLoginTime = time return update( - employee, - ignoreDefaultGroupUsers = true, - ignoreSystemUsers = false + employee, + ignoreDefaultGroupUsers = true, + ignoreSystemUsers = false ) } @@ -154,21 +167,21 @@ class EmployeeServiceImpl( val persistedEmployee by lazy { getById(entity.id) } return update(with(entity) { Employee( - id = id, - firstName = firstName or persistedEmployee.firstName, - lastName = lastName or persistedEmployee.lastName, - password = persistedEmployee.password, - isDefaultGroupUser = false, - isSystemUser = false, - group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group, - permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions, - lastLoginTime = persistedEmployee.lastLoginTime + id = id, + firstName = firstName or persistedEmployee.firstName, + lastName = lastName or persistedEmployee.lastName, + password = persistedEmployee.password, + isDefaultGroupUser = false, + isSystemUser = false, + group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group, + permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions, + lastLoginTime = persistedEmployee.lastLoginTime ) }) } override fun update(entity: Employee): Employee = - update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) + update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee { with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) { @@ -183,24 +196,24 @@ class EmployeeServiceImpl( val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) return super.update(with(persistedEmployee) { Employee( - id, - firstName, - lastName, - passwordEncoder.encode(password), - isDefaultGroupUser, - isSystemUser, - group, - permissions, - lastLoginTime + id, + firstName, + lastName, + passwordEncoder.encode(password), + isDefaultGroupUser, + isSystemUser, + group, + permissions, + lastLoginTime ) }) } override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions += permission }) + super.update(getById(employeeId).apply { permissions += permission }) override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions -= permission }) + super.update(getById(employeeId).apply { permissions -= permission }) override fun logout(request: HttpServletRequest) { val authorizationCookie = WebUtils.getCookie(request, "Authorization") @@ -217,20 +230,27 @@ const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans @Service class EmployeeGroupServiceImpl( - private val employeeService: EmployeeService, - employeeGroupRepository: EmployeeGroupRepository -) : AbstractExternalNamedModelService( - employeeGroupRepository + private val employeeService: EmployeeService, + employeeGroupRepository: EmployeeGroupRepository +) : AbstractExternalNamedModelService( + employeeGroupRepository ), - EmployeeGroupService { + EmployeeGroupService { override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id) override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name) override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name) + override fun EmployeeGroup.toOutput() = EmployeeGroupOutputDto( + this.id!!, + this.name, + this.permissions, + this.flatPermissions + ) + override fun existsByName(name: String): Boolean = repository.existsByName(name) override fun getEmployeesForGroup(id: Long): Collection = - employeeService.getByGroup(getById(id)) + employeeService.getByGroup(getById(id)) @Transactional override fun save(entity: EmployeeGroup): EmployeeGroup { @@ -243,9 +263,9 @@ class EmployeeGroupServiceImpl( val persistedGroup by lazy { getById(entity.id) } return update(with(entity) { EmployeeGroup( - entity.id, - if (name.isNotBlank()) entity.name else persistedGroup.name, - if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions + entity.id, + if (name.isNotBlank()) entity.name else persistedGroup.name, + if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions ) }) } @@ -258,11 +278,11 @@ class EmployeeGroupServiceImpl( override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup { val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName) - ?: throw NoDefaultGroupException() + ?: throw NoDefaultGroupException() val defaultGroupUser = employeeService.getById( - defaultGroupCookie.value.toLong(), - ignoreDefaultGroupUsers = false, - ignoreSystemUsers = true + defaultGroupCookie.value.toLong(), + ignoreDefaultGroupUsers = false, + ignoreSystemUsers = true ) return defaultGroupUser.group!! } @@ -271,17 +291,17 @@ class EmployeeGroupServiceImpl( val group = getById(groupId) val defaultGroupUser = employeeService.getDefaultGroupEmployee(group) response.addHeader( - "Set-Cookie", - "$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict" + "Set-Cookie", + "$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict" ) } } @Service class EmployeeUserDetailsServiceImpl( - private val employeeService: EmployeeService + private val employeeService: EmployeeService ) : - EmployeeUserDetailsService { + EmployeeUserDetailsService { override fun loadUserByUsername(username: String): UserDetails { try { return loadUserByEmployeeId(username.toLong(), true) @@ -294,9 +314,9 @@ class EmployeeUserDetailsServiceImpl( override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails { val employee = employeeService.getById( - employeeId, - ignoreDefaultGroupUsers = ignoreDefaultGroupUsers, - ignoreSystemUsers = false + employeeId, + ignoreDefaultGroupUsers = ignoreDefaultGroupUsers, + ignoreSystemUsers = false ) return User(employee.id.toString(), employee.password, employee.authorities) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyService.kt index 5e2d495..72a2f47 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyService.kt @@ -5,23 +5,28 @@ import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service -interface CompanyService : ExternalNamedModelService { +interface CompanyService : + ExternalNamedModelService { /** Checks if the given [company] is used by one or more recipes. */ fun isLinkedToRecipes(company: Company): Boolean } @Service class CompanyServiceImpl( - companyRepository: CompanyRepository, - @Lazy val recipeService: RecipeService + companyRepository: CompanyRepository, + @Lazy val recipeService: RecipeService ) : - AbstractExternalNamedModelService(companyRepository), - CompanyService { + AbstractExternalNamedModelService( + companyRepository + ), + CompanyService { override fun idNotFoundException(id: Long) = companyIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = companyIdAlreadyExistsException(id) override fun nameNotFoundException(name: String) = companyNameNotFoundException(name) override fun nameAlreadyExistsException(name: String) = companyNameAlreadyExistsException(name) + override fun Company.toOutput() = this + override fun isLinkedToRecipes(company: Company): Boolean = recipeService.existsByCompany(company) override fun update(entity: CompanyUpdateDto): Company { @@ -30,8 +35,8 @@ class CompanyServiceImpl( return update(with(entity) { company( - id = id, - name = if (name != null && name.isNotBlank()) name else persistedCompany.name + id = id, + name = if (name != null && name.isNotBlank()) name else persistedCompany.name ) }) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt index f40ae75..02b843d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt @@ -2,13 +2,17 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository +import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES +import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import dev.fyloz.colorrecipesexplorer.service.files.FileService import io.jsonwebtoken.lang.Assert import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service +import java.net.URLEncoder +import java.nio.charset.StandardCharsets interface MaterialService : - ExternalNamedModelService { + ExternalNamedModelService { /** Checks if a material with the given [materialType] exists. */ fun existsByMaterialType(materialType: MaterialType): Boolean @@ -16,13 +20,13 @@ interface MaterialService : fun hasSimdut(material: Material): Boolean /** Gets all materials that are not a mix type. */ - fun getAllNotMixType(): Collection + fun getAllNotMixType(): Collection /** Gets all materials available for the creation of a mix for the recipe with the given [recipeId], including normal materials and materials from [MixType]s included in the said recipe. */ - fun getAllForMixCreation(recipeId: Long): Collection + fun getAllForMixCreation(recipeId: Long): Collection /** Gets all materials available for updating the mix with the given [mixId], including normal materials and materials from [MixType]s included in the mix recipe, excluding the material of the [MixType] of the said mix. */ - fun getAllForMixUpdate(mixId: Long): Collection + fun getAllForMixUpdate(mixId: Long): Collection /** Updates the quantity of the given [material] with the given [factor] and returns the updated quantity. */ fun updateQuantity(material: Material, factor: Float): Float @@ -36,7 +40,7 @@ class MaterialServiceImpl( @Lazy val materialTypeService: MaterialTypeService, val fileService: FileService ) : - AbstractExternalNamedModelService( + AbstractExternalNamedModelService( materialRepository ), MaterialService { @@ -45,11 +49,28 @@ class MaterialServiceImpl( override fun nameNotFoundException(name: String) = materialNameNotFoundException(name) override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name) + override fun Material.toOutput(): MaterialOutputDto = + MaterialOutputDto( + id = this.id!!, + name = this.name, + inventoryQuantity = this.inventoryQuantity, + isMixType = this.isMixType, + materialType = this.materialType!!, + simdutUrl = if (fileService.exists(this.simdutFilePath)) + "${CRE_PROPERTIES.deploymentUrl}$FILE_CONTROLLER_PATH?path=${ + URLEncoder.encode( + this.simdutFilePath, + StandardCharsets.UTF_8 + ) + }" + else null + ) + override fun existsByMaterialType(materialType: MaterialType): Boolean = repository.existsByMaterialType(materialType) override fun hasSimdut(material: Material): Boolean = fileService.exists(material.simdutFilePath) - override fun getAllNotMixType(): Collection = getAll().filter { !it.isMixType } + override fun getAllNotMixType(): Collection = getAllForOutput().filter { !it.isMixType } override fun save(entity: MaterialSaveDto): Material = save(with(entity) { @@ -95,16 +116,16 @@ class MaterialServiceImpl( updatedQuantity } - override fun getAllForMixCreation(recipeId: Long): Collection { + override fun getAllForMixCreation(recipeId: Long): Collection { val recipesMixTypes = recipeService.getById(recipeId).mixTypes - return getAll() + return getAllForOutput() .filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } } } - override fun getAllForMixUpdate(mixId: Long): Collection { + override fun getAllForMixUpdate(mixId: Long): Collection { val mix = mixService.getById(mixId) val recipesMixTypes = mix.recipe.mixTypes - return getAll() + return getAllForOutput() .filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } } .filter { it.id != mix.mixType.material.id } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt index 3a9f108..e088ee4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt @@ -8,7 +8,7 @@ import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository import org.springframework.stereotype.Service interface MaterialTypeService : - ExternalNamedModelService { + ExternalNamedModelService { /** Checks if a material type with the given [prefix] exists. */ fun existsByPrefix(prefix: String): Boolean @@ -27,7 +27,7 @@ interface MaterialTypeService : @Service class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val materialService: MaterialService) : - AbstractExternalNamedModelService( + AbstractExternalNamedModelService( repository ), MaterialTypeService { override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id) @@ -35,6 +35,8 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name) override fun nameAlreadyExistsException(name: String) = materialTypeNameAlreadyExistsException(name) + override fun MaterialType.toOutput() = this + override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix) override fun isUsedByMaterial(materialType: MaterialType): Boolean = materialService.existsByMaterialType(materialType) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt index 636196d..b64cc12 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt @@ -28,31 +28,40 @@ interface MixMaterialService : ModelService * If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown. */ fun validateMixMaterials(mixMaterials: Set) + + fun MixMaterial.toOutput(): MixMaterialOutputDto } @Service class MixMaterialServiceImpl( - mixMaterialRepository: MixMaterialRepository, - @Lazy val materialService: MaterialService + mixMaterialRepository: MixMaterialRepository, + @Lazy val materialService: MaterialService ) : AbstractModelService(mixMaterialRepository), MixMaterialService { override fun idNotFoundException(id: Long) = mixMaterialIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = mixMaterialIdAlreadyExistsException(id) + override fun MixMaterial.toOutput() = MixMaterialOutputDto( + this.id!!, + with(materialService) { this@toOutput.material.toOutput() }, + this.quantity, + this.position + ) + override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material) override fun create(mixMaterials: Set): Set = - mixMaterials.map(::create).toSet() + mixMaterials.map(::create).toSet() override fun create(mixMaterial: MixMaterialDto): MixMaterial = - mixMaterial( - material = materialService.getById(mixMaterial.materialId), - quantity = mixMaterial.quantity, - position = mixMaterial.position - ) + mixMaterial( + material = materialService.getById(mixMaterial.materialId), + quantity = mixMaterial.quantity, + position = mixMaterial.position + ) override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) = - update(mixMaterial.apply { - this.quantity = quantity - }) + update(mixMaterial.apply { + this.quantity = quantity + }) override fun validateMixMaterials(mixMaterials: Set) { if (mixMaterials.isEmpty()) return @@ -63,17 +72,17 @@ class MixMaterialServiceImpl( // Check if the first mix material position is 1 fun isFirstMixMaterialPositionInvalid() = - sortedMixMaterials[0].position != 1 + sortedMixMaterials[0].position != 1 // Check if the first mix material is expressed in percents fun isFirstMixMaterialPercentages() = - sortedMixMaterials[0].material.materialType!!.usePercentages + sortedMixMaterials[0].material.materialType!!.usePercentages // Check if any positions is duplicated fun getDuplicatedPositionsErrors() = - sortedMixMaterials - .findDuplicated { it.position } - .map { duplicatedMixMaterialsPositions(it) } + sortedMixMaterials + .findDuplicated { it.position } + .map { duplicatedMixMaterialsPositions(it) } // Find all errors and throw if there is any if (isFirstMixMaterialPositionInvalid()) errors += invalidFirstMixMaterialPosition(sortedMixMaterials[0]) @@ -90,32 +99,32 @@ class MixMaterialServiceImpl( } class InvalidMixMaterialsPositionsError( - val type: String, - val details: String + val type: String, + val details: String ) class InvalidMixMaterialsPositionsException( - val errors: Set + val errors: Set ) : RestException( - "invalid-mixmaterial-position", - "Invalid mix materials positions", - HttpStatus.BAD_REQUEST, - "The position of mix materials are invalid", - mapOf( - "invalidMixMaterials" to errors - ) + "invalid-mixmaterial-position", + "Invalid mix materials positions", + HttpStatus.BAD_REQUEST, + "The position of mix materials are invalid", + mapOf( + "invalidMixMaterials" to errors + ) ) class InvalidFirstMixMaterial( - val mixMaterial: MixMaterial + val mixMaterial: MixMaterial ) : RestException( - "invalid-mixmaterial-first", - "Invalid first mix material", - HttpStatus.BAD_REQUEST, - "The first mix material is invalid because its material must not be expressed in percents", - mapOf( - "mixMaterial" to mixMaterial - ) + "invalid-mixmaterial-first", + "Invalid first mix material", + HttpStatus.BAD_REQUEST, + "The first mix material is invalid because its material must not be expressed in percents", + mapOf( + "mixMaterial" to mixMaterial + ) ) const val INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE = "first" @@ -123,20 +132,20 @@ const val DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE = "duplicated" const val GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE = "gap" private fun invalidFirstMixMaterialPosition(mixMaterial: MixMaterial) = - InvalidMixMaterialsPositionsError( - INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE, - "The position ${mixMaterial.position} is under the minimum of 1" - ) + InvalidMixMaterialsPositionsError( + INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE, + "The position ${mixMaterial.position} is under the minimum of 1" + ) private fun duplicatedMixMaterialsPositions(position: Int) = - InvalidMixMaterialsPositionsError( - DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE, - "The position $position is duplicated" - ) + InvalidMixMaterialsPositionsError( + DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE, + "The position $position is duplicated" + ) private fun gapBetweenStepsPositions() = - InvalidMixMaterialsPositionsError( - GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE, - "There is a gap between mix materials positions" - ) + InvalidMixMaterialsPositionsError( + GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE, + "There is a gap between mix materials positions" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt index 8416417..85411d7 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt @@ -7,7 +7,7 @@ import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import javax.transaction.Transactional -interface MixService : ExternalModelService { +interface MixService : ExternalModelService { /** Gets all mixes with the given [mixType]. */ fun getAllByMixType(mixType: MixType): Collection @@ -23,19 +23,30 @@ interface MixService : ExternalModelService(mixRepository), - MixService { + mixRepository: MixRepository, + @Lazy val recipeService: RecipeService, + @Lazy val materialTypeService: MaterialTypeService, + val mixMaterialService: MixMaterialService, + val mixTypeService: MixTypeService +) : AbstractExternalModelService(mixRepository), + MixService { override fun idNotFoundException(id: Long) = mixIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = mixIdAlreadyExistsException(id) override fun getAllByMixType(mixType: MixType): Collection = repository.findAllByMixType(mixType) override fun mixTypeIsShared(mixType: MixType): Boolean = getAllByMixType(mixType).count() > 1 + override fun Mix.toOutput() = MixOutputDto( + this.id!!, + this.location, + this.mixType, + this.mixMaterials.map { + with(mixMaterialService) { + return@with it.toOutput() + } + }.toSet() + ) + @Transactional override fun save(entity: MixSaveDto): Mix { val recipe = recipeService.getById(entity.recipeId) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index a7f7f68..709ac4d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -11,7 +11,8 @@ import org.springframework.web.multipart.MultipartFile import java.io.File import javax.transaction.Transactional -interface RecipeService : ExternalModelService { +interface RecipeService : + ExternalModelService { /** Checks if one or more recipes have the given [company]. */ fun existsByCompany(company: Company): Boolean @@ -34,13 +35,37 @@ class RecipeServiceImpl( val companyService: CompanyService, val mixService: MixService, val recipeStepService: RecipeStepService, - @Lazy val groupService: EmployeeGroupService + @Lazy val groupService: EmployeeGroupService, + val recipeImageService: RecipeImageService ) : - AbstractExternalModelService(recipeRepository), + AbstractExternalModelService( + recipeRepository + ), RecipeService { override fun idNotFoundException(id: Long) = recipeIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = recipeIdAlreadyExistsException(id) + override fun Recipe.toOutput() = RecipeOutputDto( + this.id!!, + this.name, + this.description, + this.color, + this.gloss, + this.sample, + this.approbationDate, + this.remark, + this.company, + this.mixes.map { + with(mixService) { + it.toOutput() + } + }.toSet(), + this.groupsInformation, + recipeImageService.getAllImages(this) + .map { this.imageUrl(it) } + .toSet() + ) + override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company) override fun getAllByCompany(company: Company): Collection = repository.findAllByCompany(company) @@ -157,7 +182,6 @@ const val RECIPE_IMAGE_EXTENSION = ".jpg" @Service class RecipeImageServiceImpl( - val recipeService: RecipeService, val fileService: FileService ) : RecipeImageService { override fun getAllImages(recipe: Recipe): Set { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt index ef45f8e..902a32c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/Service.kt @@ -62,7 +62,7 @@ abstract class AbstractService>(override val reposito } abstract class AbstractModelService>(repository: R) : - AbstractService(repository), ModelService { + AbstractService(repository), ModelService { protected abstract fun idNotFoundException(id: Long): NotFoundException protected abstract fun idAlreadyExistsException(id: Long): AlreadyExistsException @@ -83,7 +83,7 @@ abstract class AbstractModelService>(repos } override fun deleteById(id: Long) = - delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing + delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing protected fun assertId(id: Long?) { Assert.notNull(id, "${javaClass.simpleName}.update() was called with a null identifier") @@ -91,7 +91,7 @@ abstract class AbstractModelService>(repos } abstract class AbstractNamedModelService>(repository: R) : - AbstractModelService(repository), NamedModelService { + AbstractModelService(repository), NamedModelService { protected abstract fun nameNotFoundException(name: String): NotFoundException protected abstract fun nameAlreadyExistsException(name: String): AlreadyExistsException @@ -126,33 +126,57 @@ abstract class AbstractNamedModelService, U : EntityDto, R : JpaRepository> : Service { +interface ExternalService, U : EntityDto, O, R : JpaRepository> : Service { + /** Gets all entities mapped to their output model. */ + fun getAllForOutput(): Collection + /** Saves a given [entity]. */ fun save(entity: S): E = save(entity.toEntity()) /** Updates a given [entity]. */ - fun update(entity: U): E = update(entity.toEntity()) + fun update(entity: U): E + + /** Convert the given entity to its output model. */ + fun E.toOutput(): O } /** An [ExternalService] for entities implementing the [Model] interface. */ -interface ExternalModelService, U : EntityDto, R : JpaRepository> : - ModelService, ExternalService +interface ExternalModelService, U : EntityDto, O, R : JpaRepository> : + ModelService, ExternalService { + /** Gets the entity with the given [id] mapped to its output model. */ + fun getByIdForOutput(id: Long): O +} /** An [ExternalService] for entities implementing the [NamedModel] interface. */ -interface ExternalNamedModelService, U : EntityDto, R : JpaRepository> : - NamedModelService, ExternalModelService +interface ExternalNamedModelService, U : EntityDto, O, R : JpaRepository> : + NamedModelService, ExternalModelService /** An [AbstractService] with the functionalities of a [ExternalService]. */ @Suppress("unused") -abstract class AbstractExternalService, U : EntityDto, R : JpaRepository>(repository: R) : - AbstractService(repository), ExternalService +abstract class AbstractExternalService, U : EntityDto, O, R : JpaRepository>(repository: R) : + AbstractService(repository), ExternalService { + override fun getAllForOutput() = + getAll().map { it.toOutput() } +} /** An [AbstractModelService] with the functionalities of a [ExternalService]. */ -abstract class AbstractExternalModelService, U : EntityDto, R : JpaRepository>( - repository: R -) : AbstractModelService(repository), ExternalModelService +abstract class AbstractExternalModelService, U : EntityDto, O, R : JpaRepository>( + repository: R +) : AbstractModelService(repository), ExternalModelService { + override fun getAllForOutput() = + getAll().map { it.toOutput() } + + override fun getByIdForOutput(id: Long) = + getById(id).toOutput() +} /** An [AbstractNamedModelService] with the functionalities of a [ExternalService]. */ -abstract class AbstractExternalNamedModelService, U : EntityDto, R : NamedJpaRepository>( - repository: R -) : AbstractNamedModelService(repository), ExternalNamedModelService +abstract class AbstractExternalNamedModelService, U : EntityDto, O, R : NamedJpaRepository>( + repository: R +) : AbstractNamedModelService(repository), ExternalNamedModelService { + override fun getAllForOutput() = + getAll().map { it.toOutput() } + + override fun getByIdForOutput(id: Long) = + getById(id).toOutput() +} diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt index 0574fee..8d7f95c 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt @@ -27,8 +27,8 @@ abstract class AbstractServiceTest, R : JpaRepository protected val entityList: List get() = listOf( - entity, - anotherEntity + entity, + anotherEntity ) @AfterEach @@ -91,7 +91,7 @@ abstract class AbstractServiceTest, R : JpaRepository } abstract class AbstractModelServiceTest, R : JpaRepository> : - AbstractServiceTest1() { + AbstractServiceTest1() { // existsById() @@ -129,7 +129,7 @@ abstract class AbstractModelServiceTest, R : J whenever(repository.findById(entity.id!!)).doReturn(Optional.empty()) assertThrows { service.getById(entity.id!!) } - .assertErrorCode() + .assertErrorCode() } // save() @@ -139,7 +139,7 @@ abstract class AbstractModelServiceTest, R : J doReturn(true).whenever(service).existsById(entity.id!!) assertThrows { service.save(entity) } - .assertErrorCode() + .assertErrorCode() } // update() @@ -161,7 +161,7 @@ abstract class AbstractModelServiceTest, R : J doReturn(false).whenever(service).existsById(entity.id!!) assertThrows { service.update(entity) } - .assertErrorCode() + .assertErrorCode() } // deleteById() @@ -177,7 +177,7 @@ abstract class AbstractModelServiceTest, R : J } abstract class AbstractNamedModelServiceTest, R : NamedJpaRepository> : - AbstractModelServiceTest() { + AbstractModelServiceTest() { protected abstract val entityWithEntityName: E // existsByName() @@ -216,7 +216,7 @@ abstract class AbstractNamedModelServiceTest { service.getByName(entity.name) } - .assertErrorCode("name") + .assertErrorCode("name") } // save() @@ -226,7 +226,7 @@ abstract class AbstractNamedModelServiceTest { service.save(entity) } - .assertErrorCode("name") + .assertErrorCode("name") } // update() @@ -258,7 +258,7 @@ abstract class AbstractNamedModelServiceTest { service.update(entity) } - .assertErrorCode("name") + .assertErrorCode("name") } } @@ -269,8 +269,8 @@ interface ExternalModelServiceTest { // ==== IMPLEMENTATIONS FOR EXTERNAL SERVICES ==== // Lots of code duplication but I don't have a better solution for now -abstract class AbstractExternalModelServiceTest, U : EntityDto, S : ExternalModelService, R : JpaRepository> : - AbstractModelServiceTest(), ExternalModelServiceTest { +abstract class AbstractExternalModelServiceTest, U : EntityDto, S : ExternalModelService, R : JpaRepository> : + AbstractModelServiceTest(), ExternalModelServiceTest { protected abstract val entitySaveDto: N protected abstract val entityUpdateDto: U @@ -281,8 +281,8 @@ abstract class AbstractExternalModelServiceTest, U : } } -abstract class AbstractExternalNamedModelServiceTest, U : EntityDto, S : ExternalNamedModelService, R : NamedJpaRepository> : - AbstractNamedModelServiceTest(), ExternalModelServiceTest { +abstract class AbstractExternalNamedModelServiceTest, U : EntityDto, S : ExternalNamedModelService, R : NamedJpaRepository> : + AbstractNamedModelServiceTest(), ExternalModelServiceTest { protected abstract val entitySaveDto: N protected abstract val entityUpdateDto: U @@ -294,10 +294,10 @@ abstract class AbstractExternalNamedModelServiceTest> withBaseSaveDtoTest( - entity: E, - entitySaveDto: N, - service: ExternalService, - saveMockMatcher: () -> E = { entity }, - op: () -> Unit = {} + entity: E, + entitySaveDto: N, + service: ExternalService, + saveMockMatcher: () -> E = { entity }, + op: () -> Unit = {} ) { doReturn(entity).whenever(service).save(saveMockMatcher()) doReturn(entity).whenever(entitySaveDto).toEntity() @@ -329,11 +329,11 @@ fun > withBaseSaveDtoTest( } fun > withBaseUpdateDtoTest( - entity: E, - entityUpdateDto: U, - service: ExternalModelService, - updateMockMatcher: () -> E, - op: E.() -> Unit = {} + entity: E, + entityUpdateDto: U, + service: ExternalModelService, + updateMockMatcher: () -> E, + op: E.() -> Unit = {} ) { doAnswer { it.arguments[0] }.whenever(service).update(updateMockMatcher()) doReturn(entity).whenever(entityUpdateDto).toEntity() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt index 774c611..dc13ca3 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt @@ -14,16 +14,17 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class MaterialServiceTest : - AbstractExternalNamedModelServiceTest() { + AbstractExternalNamedModelServiceTest() { override val repository: MaterialRepository = mock() private val recipeService: RecipeService = mock() private val mixService: MixService = mock() private val materialTypeService: MaterialTypeService = mock() private val fileService: FileService = mock() override val service: MaterialService = - spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService)) + spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService)) override val entity: Material = material(id = 0L, name = "material") + private val entityOutput = materialOutputDto(entity) override val anotherEntity: Material = material(id = 1L, name = "another material") override val entityWithEntityName: Material = material(id = 2L, name = "material") override val entitySaveDto: MaterialSaveDto = spy(materialSaveDto()) @@ -83,15 +84,16 @@ class MaterialServiceTest : @Test fun `getAllNotMixType() returns a list containing every material that are not a mix type`() { - val mixTypeMaterial = material(name = "mix type material", isMixType = true) + val mixTypeMaterial = material(id = 1L, name = "mix type material", isMixType = true) + val mixTypeMaterialOutput = materialOutputDto(mixTypeMaterial) val materialList = listOf(entity, mixTypeMaterial) doReturn(materialList).whenever(service).getAll() val found = service.getAllNotMixType() - assertTrue(found.contains(entity)) - assertFalse(found.contains(mixTypeMaterial)) + assertTrue(found.contains(entityOutput)) + assertFalse(found.contains(mixTypeMaterialOutput)) } // save() @@ -101,7 +103,7 @@ class MaterialServiceTest : doReturn(true).whenever(service).existsByName(entity.name) assertThrows { service.save(entity) } - .assertErrorCode("name") + .assertErrorCode("name") } @Test @@ -133,7 +135,7 @@ class MaterialServiceTest : doReturn(entity).whenever(service).getById(material.id!!) assertThrows { service.update(material) } - .assertErrorCode("name") + .assertErrorCode("name") } @Test @@ -173,16 +175,16 @@ class MaterialServiceTest : val anotherMixTypeMaterial = material(id = 2L, isMixType = true) val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial) val recipe = - recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial)))) + recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial)))) whenever(recipeService.getById(recipe.id!!)).doReturn(recipe) doReturn(materials).whenever(service).getAll() val found = service.getAllForMixCreation(recipe.id!!) - assertTrue(normalMaterial in found) - assertTrue(mixTypeMaterial in found) - assertFalse(anotherMixTypeMaterial in found) + assertTrue(materialOutputDto(normalMaterial) in found) + assertTrue(materialOutputDto(mixTypeMaterial) in found) + assertFalse(materialOutputDto(anotherMixTypeMaterial) in found) } // getAllForMixUpdate() @@ -202,9 +204,9 @@ class MaterialServiceTest : val found = service.getAllForMixUpdate(mix.id!!) - assertTrue(normalMaterial in found) - assertTrue(mixTypeMaterial in found) - assertFalse(anotherMixTypeMaterial in found) + assertTrue(materialOutputDto(normalMaterial) in found) + assertTrue(materialOutputDto(mixTypeMaterial) in found) + assertFalse(materialOutputDto(anotherMixTypeMaterial) in found) } @@ -233,4 +235,13 @@ class MaterialServiceTest : test() } + + private fun materialOutputDto(material: Material) = MaterialOutputDto( + id = material.id!!, + name = material.name, + inventoryQuantity = material.inventoryQuantity, + isMixType = material.isMixType, + materialType = material.materialType!!, + simdutUrl = null + ) } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 584b62f..509d490 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -21,7 +21,7 @@ class RecipeServiceTest : private val groupService: EmployeeGroupService = mock() private val recipeStepService: RecipeStepService = mock() override val service: RecipeService = - spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService)) + spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock())) private val company: Company = company(id = 0L) override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company) @@ -168,7 +168,7 @@ private class RecipeImageServiceTestContext { every { write(any(), any(), any()) } just Runs every { delete(any()) } just Runs } - val recipeImageService = spyk(RecipeImageServiceImpl(mockk(), fileService)) + val recipeImageService = spyk(RecipeImageServiceImpl(fileService)) val recipe = spyk(recipe()) val recipeImagesIds = setOf(1L, 10L, 21L) val recipeImagesNames = recipeImagesIds.map { it.imageName }.toSet()