This commit is contained in:
FyloZ 2021-03-09 14:08:57 -05:00
parent 50442d7ebc
commit ed8abaa456
22 changed files with 83 additions and 86 deletions

View File

@ -90,7 +90,11 @@ tasks.withType<JavaCompile> {
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "11"
kotlinOptions {
jvmTarget = "11"
useIR = true
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.contracts.ExperimentalContracts"
}
}
tasks.dokkaHtml {

View File

@ -13,12 +13,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
abstract class RestException(val httpStatus: HttpStatus) : RuntimeException() {
abstract val exceptionMessage: String
abstract class RestException(val exceptionMessage: String, val httpStatus: HttpStatus) : RuntimeException(exceptionMessage) {
abstract fun buildBody(): RestExceptionBody
override val message: String by lazy { exceptionMessage }
open inner class RestExceptionBody(val status: Int = httpStatus.value(), @JsonProperty("message") val message: String = exceptionMessage)
}

View File

@ -5,15 +5,32 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Model
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
class EntityAlreadyExistsException(modelType: Class<out Model>, val identifierType: IdentifierType, val identifierName: String?, val requestedId: Any) : ModelException(modelType) {
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
constructor(exception: EntityAlreadyExistsException) : this(exception.type, exception.identifierType, exception.identifierName, exception.requestedId)
class EntityAlreadyExistsException(
modelType: Class<out Model>,
val identifierType: IdentifierType,
val identifierName: String?,
val requestedId: Any
) : ModelException(modelType) {
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(
modelType,
identifierType,
identifierType.name,
requestedId
)
constructor(exception: EntityAlreadyExistsException) : this(
exception.type,
exception.identifierType,
exception.identifierName,
exception.requestedId
)
}
@ResponseStatus(HttpStatus.CONFLICT)
class EntityAlreadyExistsRestException(val value: Any) : RestException(HttpStatus.CONFLICT) {
override val exceptionMessage: String = "An entity with the given identifier already exists"
class EntityAlreadyExistsRestException(val value: Any) :
RestException("An entity with the given identifier already exists", HttpStatus.CONFLICT) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val id = value
}

View File

@ -5,14 +5,17 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Model
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
class EntityNotFoundException(modelType: Class<out Model>, val identifierType: IdentifierType, val identifierName: String, val requestedId: Any) : ModelException(modelType) {
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
}
class EntityNotFoundException(
modelType: Class<out Model>,
val identifierType: IdentifierType,
val requestedId: Any
) : ModelException(modelType)
@ResponseStatus(HttpStatus.NOT_FOUND)
class EntityNotFoundRestException(val value: Any) : RestException(HttpStatus.NOT_FOUND) {
override val exceptionMessage: String = "An entity could not be found with the given identifier"
class EntityNotFoundRestException(val value: Any) :
RestException("An entity could not be found with the given identifier", HttpStatus.NOT_FOUND) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val id = value
}

View File

@ -66,7 +66,7 @@ public class MixTypeJavaService extends AbstractJavaService<MixType, MixTypeRepo
public MixType getByMaterial(Material material) {
Optional<MixType> found = findOptional(repository.findByMaterial(material));
if (found.isEmpty())
throw new EntityNotFoundException(type, ModelException.IdentifierType.OTHER, MixTypeKt.IDENTIFIER_MATERIAL_NAME, material);
throw new EntityNotFoundException(type, ModelException.IdentifierType.OTHER, material);
return found.get();
}

View File

@ -2,20 +2,15 @@ package dev.fyloz.trial.colorrecipesexplorer
import dev.fyloz.trial.colorrecipesexplorer.config.properties.CREProperties
import dev.fyloz.trial.colorrecipesexplorer.config.properties.MaterialTypeProperties
import org.slf4j.Logger
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import javax.sql.DataSource
@SpringBootApplication(exclude = [LiquibaseAutoConfiguration::class])
@EnableConfigurationProperties(MaterialTypeProperties::class, CREProperties::class, DatabaseUpdaterProperties::class)
class ColorRecipesExplorerApplication
fun main(args: Array<String>) {
fun main() {
runApplication<ColorRecipesExplorerApplication>()
}

View File

@ -189,9 +189,9 @@ const val defaultGroupCookieName = "Default-Group"
val blacklistedJwtTokens = mutableListOf<String>()
class JwtAuthenticationFilter(
val authManager: AuthenticationManager,
val employeeService: EmployeeService,
val securityConfigurationProperties: SecurityConfigurationProperties
private val authManager: AuthenticationManager,
private val employeeService: EmployeeService,
private val securityConfigurationProperties: SecurityConfigurationProperties
) : UsernamePasswordAuthenticationFilter() {
private var debugMode = false
@ -238,8 +238,8 @@ class JwtAuthenticationFilter(
}
class JwtAuthorizationFilter(
val userDetailsService: EmployeeUserDetailsServiceImpl,
val securityConfigurationProperties: SecurityConfigurationProperties,
private val userDetailsService: EmployeeUserDetailsServiceImpl,
private val securityConfigurationProperties: SecurityConfigurationProperties,
authenticationManager: AuthenticationManager
) : BasicAuthenticationFilter(authenticationManager) {
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {

View File

@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.springframework.security.core.GrantedAuthority
@ -109,18 +110,15 @@ open class EmployeeUpdateDto(
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
val id: Long,
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String = "",
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String?,
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String = "",
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission> = mutableSetOf()
) : EntityDto<Employee> {
override fun toEntity(): Employee =
Employee(id, firstName, lastName, permissions = permissions.toMutableSet())
}
val permissions: Set<EmployeePermission>?
) : EntityDto<Employee>
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
@ -148,6 +146,7 @@ data class EmployeeGroup(
@JsonIgnore
val employees: MutableSet<Employee> = mutableSetOf()
) : NamedModel {
@JsonProperty("employeeCount")
fun getEmployeeCount() = employees.size
override fun equals(other: Any?): Boolean = other is EmployeeGroup && id == other.id && name == other.name
@ -313,16 +312,6 @@ fun employee(
lastLoginTime
).apply(op)
fun employee(
employee: Employee,
newId: Long? = null
) = with(employee) {
Employee(
newId
?: id, firstName, lastName, password, isDefaultGroupUser, isSystemUser, group, permissions, lastLoginTime
)
}
fun employeeSaveDto(
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
id: Long = 0L,
@ -350,12 +339,6 @@ fun employeeGroup(
op: EmployeeGroup.() -> Unit = {}
) = EmployeeGroup(id, name, permissions, employees).apply(op)
fun employeeGroup(
employeeGroup: EmployeeGroup,
newId: Long? = null,
newName: String? = null
) = with(employeeGroup) { EmployeeGroup(newId ?: id, newName ?: name, permissions, employees) }
fun employeeGroupSaveDto(
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),

View File

@ -9,7 +9,6 @@ import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
private const val MATERIAL_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val MATERIAL_NAME_NULL_MESSAGE = "Un nom est requis"
@ -98,6 +97,7 @@ data class InventoryMaterial(
@NotNull val lowQuantity: Boolean = false
)
@Suppress("unused")
fun Material.toInventoryMaterial(minimumQuantity: Float): InventoryMaterial {
Assert.notNull(id, "Cannot convert a material without id to an inventory material")
Assert.notNull(name, "Cannot convert a material without name to an inventory material")

View File

@ -11,7 +11,7 @@ interface NamedModel : Model {
interface EntityDto<out E> {
/** Converts the dto to an actual entity. */
fun toEntity(): E
fun toEntity(): E {
throw UnsupportedOperationException()
}
}
fun Collection<Model>.sortedById(): Collection<Model> = this.sortedBy { it.id }

View File

@ -50,6 +50,19 @@ data class Recipe(
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var steps: MutableCollection<RecipeStep>
) : Model {
constructor() : this(
null,
"name",
"description",
0,
null,
"remark",
"note",
company(),
mutableListOf(),
mutableListOf()
)
constructor(
id: Long,
name: String,

View File

@ -1,10 +1,8 @@
package dev.fyloz.trial.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import java.util.*
import javax.persistence.*
import javax.validation.constraints.NotNull
@Entity
@Table(name = "recipe_step")

View File

@ -3,7 +3,6 @@ package dev.fyloz.trial.colorrecipesexplorer.model.validation
import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.Payload
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.reflect.KClass
@ -15,8 +14,7 @@ private const val MESSAGE = "must be null or not blank"
@Constraint(validatedBy = [NullOrNotBlankValidator::class])
annotation class NullOrNotBlank(
val message: String = MESSAGE,
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
val groups: Array<KClass<*>> = []
)
class NullOrNotBlankValidator : ConstraintValidator<NullOrNotBlank, String> {
@ -42,5 +40,4 @@ fun isNotNullAndNotBlank(value: String?): Boolean {
return value != null && value.isNotBlank()
}
@ExperimentalContracts
fun String?.or(alternative: String): String = if (isNotNullAndNotBlank(this)) this else alternative
infix fun String?.or(alternative: String): String = if (isNotNullAndNotBlank(this)) this else alternative

View File

@ -3,7 +3,6 @@ package dev.fyloz.trial.colorrecipesexplorer.model.validation
import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.Payload
import kotlin.reflect.KClass
private const val MIN_SIZE = Long.MIN_VALUE
@ -17,8 +16,7 @@ annotation class NullOrSize(
val min: Long = MIN_SIZE,
val max: Long = MAX_SIZE,
val message: String = MESSAGE,
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
val groups: Array<KClass<*>> = []
)
class NullOrSizeValidator : ConstraintValidator<NullOrSize, Any> {

View File

@ -1,5 +1,5 @@
//package dev.fyloz.trial.colorrecipesexplorer.rest
//
package dev.fyloz.trial.colorrecipesexplorer.rest
//import dev.fyloz.trial.colorrecipesexplorer.model.InventoryMaterial
//import dev.fyloz.trial.colorrecipesexplorer.service.InventoryService
//import org.springframework.http.ResponseEntity

View File

@ -34,7 +34,7 @@ interface RestModelApiController<E : Model, S : EntityDto<E>, U : EntityDto<E>>
abstract class AbstractRestApiController<E, N : EntityDto<E>, U : EntityDto<E>, S : ExternalService<E, N, U, *>>(
val service: S,
protected val controllerPath: String
private val controllerPath: String
) :
RestApiController<E, N, U> {
protected abstract fun getEntityId(entity: E): Any?

View File

@ -1,12 +1,12 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import dev.fyloz.trial.colorrecipesexplorer.config.SecurityConfigurationProperties
import dev.fyloz.trial.colorrecipesexplorer.config.blacklistedJwtTokens
import dev.fyloz.trial.colorrecipesexplorer.config.defaultGroupCookieName
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsRestException
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundRestException
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.model.validation.or
import dev.fyloz.trial.colorrecipesexplorer.repository.EmployeeGroupRepository
import dev.fyloz.trial.colorrecipesexplorer.repository.EmployeeRepository
import org.springframework.beans.factory.annotation.Autowired
@ -170,13 +170,13 @@ class EmployeeServiceImpl(employeeRepository: EmployeeRepository, val passwordEn
return update(with(entity) {
Employee(
id = id,
firstName = if (firstName.isNotBlank()) firstName else persistedEmployee.firstName,
lastName = if (lastName.isNotBlank()) lastName else persistedEmployee.lastName,
firstName = firstName or persistedEmployee.firstName,
lastName = lastName or persistedEmployee.lastName,
password = persistedEmployee.password,
isDefaultGroupUser = false,
isSystemUser = false,
group = persistedEmployee.group,
permissions = if (permissions.isNotEmpty()) permissions.toMutableSet() else persistedEmployee.permissions,
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
lastLoginTime = persistedEmployee.lastLoginTime
)
})

View File

@ -1,20 +1,12 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import dev.fyloz.trial.colorrecipesexplorer.exception.TooLowQuantityException
import dev.fyloz.trial.colorrecipesexplorer.model.InventoryMaterial
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.dto.InventoryDto
import dev.fyloz.trial.colorrecipesexplorer.model.toInventoryMaterial
import org.springframework.stereotype.Service
const val minimumQuantity = 100.0f // TODO quantity stored in database
@Service
class InventoryService(val materialService: MaterialService) {
fun getAllMaterials(): Collection<InventoryMaterial> {
return materialService.getAllNotMixType().map { it.toInventoryMaterial(minimumQuantity) }
}
/**
* Use all materials in the given [mixes].
* @throws TooLowQuantityException When there is not enough stock for a material

View File

@ -105,8 +105,7 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
}
@ResponseStatus(HttpStatus.CONFLICT)
class CannotDeleteUsedMaterialTypeRestException : RestException(HttpStatus.CONFLICT) {
override val exceptionMessage: String = "Cannot delete a used material type"
class CannotDeleteUsedMaterialTypeRestException :
RestException("Cannot delete a used material type", HttpStatus.CONFLICT) {
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {}
}

View File

@ -19,7 +19,7 @@ interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpda
/** Gets all recipes with the given [company]. */
fun getAllByCompany(company: Company): Collection<Recipe>
/** Updates the public data of a recipe with the given [publicDateDto]. */
/** Updates the public data of a recipe with the given [publicDataDto]. */
fun updatePublicData(publicDataDto: RecipePublicDataDto)
/** Adds the given [mix] to the given [recipe]. */

View File

@ -11,7 +11,6 @@ import dev.fyloz.trial.colorrecipesexplorer.rest.RestApiController
import io.jsonwebtoken.lang.Assert
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.findByIdOrNull
import kotlin.contracts.ExperimentalContracts
/**
* A service implementing the basics CRUD operations for the given entities.
@ -144,6 +143,7 @@ interface ExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : Entity
NamedModelService<E, R>, ExternalModelService<E, S, U, R>
/** An [AbstractService] with the functionalities of a [ExternalService]. */
@Suppress("unused")
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>>(repository: R) :
AbstractService<E, R>(repository), ExternalService<E, S, U, R>

View File

@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
@Suppress("UNUSED_PARAMETER")
@Disabled
@DataJpaTest
class RecipeStepRepositoryTest @Autowired constructor(