Ré-implémentation des erreurs pour suivre un standard.

This commit is contained in:
FyloZ 2021-04-10 21:03:41 -04:00
parent 211fdb1895
commit 0321dd45f6
33 changed files with 898 additions and 528 deletions

View File

@ -1,7 +1,7 @@
package dev.fyloz.colorrecipesexplorer.config
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.Employee
import dev.fyloz.colorrecipesexplorer.model.EmployeeLoginRequest
import dev.fyloz.colorrecipesexplorer.model.EmployeePermission
@ -272,7 +272,7 @@ class JwtAuthorizationFilter(
private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken? = try {
val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), false)
UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities)
} catch (_: EntityNotFoundException) {
} catch (_: NotFoundException) {
null
}
}

View File

@ -1,9 +1,7 @@
package dev.fyloz.colorrecipesexplorer.exception
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
import org.springframework.context.annotation.Profile
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
@ -11,84 +9,84 @@ import org.springframework.validation.FieldError
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.ServletWebRequest
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
abstract class RestException(val exceptionMessage: String, val httpStatus: HttpStatus) :
RuntimeException(exceptionMessage) {
abstract fun buildBody(): RestExceptionBody
abstract class RestException(
val errorCode: String,
val title: String,
val status: HttpStatus,
val details: String,
val extensions: Map<String, Any> = mapOf()
) : RuntimeException(details) {
fun buildExceptionBody() = mapOf(
"type" to errorCode,
"title" to title,
"status" to status.value(),
"detail" to details,
@Suppress("unused")
open inner class RestExceptionBody(
val status: Int = httpStatus.value(),
@JsonProperty("message") val message: String = exceptionMessage
*extensions.map { it.key to it.value }.toTypedArray()
)
}
class EntityAlreadyExistsException(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
}
}
class NotFoundException(
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id"
) : RestException(
errorCode = "notfound-$errorCode-$identifierName",
title = title,
status = HttpStatus.NOT_FOUND,
details = details,
extensions = mapOf(
identifierName to identifierValue
)
)
class EntityNotFoundException(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
}
}
class AlreadyExistsException(
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id"
) : RestException(
errorCode = "exists-$errorCode-$identifierName",
title = title,
status = HttpStatus.CONFLICT,
details = details,
extensions = mapOf(
identifierName to identifierValue
)
)
class CannotDeleteEntityException(val value: Long) :
RestException(
"The entity with the given identifier could not be deleted because it is required by other entities",
HttpStatus.CONFLICT
) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val id = value
}
}
class SimdutWriteException(val material: Material) :
RestException(
"Could not write the SIMDUT file to disk",
HttpStatus.INTERNAL_SERVER_ERROR
) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = RestExceptionBody()
}
class LowQuantityException(val materialQuantity: MaterialQuantityDto) :
RestException(
"There is not enough of the given material in the inventory",
HttpStatus.CONFLICT
) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val material = materialQuantity.material
val quantity = materialQuantity.quantity
}
}
class LowQuantitiesException(val materialQuantities: Collection<MaterialQuantityDto>) :
RestException(
"There is not enough of one or more given materials in the inventory",
HttpStatus.CONFLICT
) {
@Suppress
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val lowQuantities = materialQuantities
}
}
class CannotDeleteException(
errorCode: String,
title: String,
details: String
) : RestException(
errorCode = "cannotdelete-$errorCode",
title = title,
status = HttpStatus.CONFLICT,
details = details
)
@ControllerAdvice
class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
@ExceptionHandler(RestException::class)
fun handleRestExceptions(exception: RestException, request: WebRequest): ResponseEntity<Any> {
return handleExceptionInternal(exception, exception.buildBody(), HttpHeaders(), exception.httpStatus, request)
val finalBody = exception.buildExceptionBody().toMutableMap()
finalBody["instance"] = (request as ServletWebRequest).request.requestURI
return handleExceptionInternal(
exception,
finalBody,
HttpHeaders(),
exception.status,
request
)
}
override fun handleMethodArgumentNotValid(

View File

@ -1,331 +0,0 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import java.time.LocalDateTime
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis"
private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis"
private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis"
private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis"
private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères"
@Entity
@Table(name = "employee")
data class Employee(
@Id
override val id: Long,
@Column(name = "first_name")
val firstName: String = "",
@Column(name = "last_name")
val lastName: String = "",
@JsonIgnore
val password: String = "",
@JsonIgnore
@Column(name = "default_group_user")
val isDefaultGroupUser: Boolean = false,
@JsonIgnore
@Column(name = "system_user")
val isSystemUser: Boolean = false,
@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<EmployeePermission> = mutableSetOf(),
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
) : Model {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
@get:JsonIgnore
val authorities: Set<GrantedAuthority>
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: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_PASSWORD_EMPTY_MESSAGE)
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
val password: String,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
) : EntityDto<Employee>
open class EmployeeUpdateDto(
@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_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission>?
) : EntityDto<Employee>
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis"
private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise"
@Entity
@Table(name = "employee_group")
data class EmployeeGroup(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override var id: Long? = null,
@Column(unique = true)
override val name: String = "",
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
@get:JsonProperty("explicitPermissions")
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
) : NamedModel {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = this.permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toSet()
}
open class EmployeeGroupSaveDto(
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
@field:Size(min = 3)
val name: String,
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
val permissions: MutableSet<EmployeePermission>
) : EntityDto<EmployeeGroup> {
override fun toEntity(): EmployeeGroup =
EmployeeGroup(null, name, permissions)
}
open class EmployeeGroupUpdateDto(
@field:NotNull(message = GROUP_ID_NULL_MESSAGE)
val id: Long,
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
@field:Size(min = 3)
val name: String,
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
val permissions: MutableSet<EmployeePermission>
) : EntityDto<EmployeeGroup> {
override fun toEntity(): EmployeeGroup =
EmployeeGroup(id, name, permissions)
}
data class EmployeeLoginRequest(val id: Long, val password: String)
enum class EmployeePermission(
val impliedPermissions: List<EmployeePermission> = listOf(),
val deprecated: Boolean = false
) {
VIEW_RECIPES,
VIEW_CATALOG,
VIEW_USERS,
PRINT_MIXES(listOf(VIEW_RECIPES)),
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)),
EDIT_MATERIALS(listOf(VIEW_CATALOG)),
EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)),
EDIT_COMPANIES(listOf(VIEW_CATALOG)),
EDIT_USERS(listOf(VIEW_USERS)),
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
REMOVE_RECIPES(listOf(EDIT_RECIPES)),
REMOVE_MATERIALS(listOf(EDIT_MATERIALS)),
REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)),
REMOVE_COMPANIES(listOf(EDIT_COMPANIES)),
REMOVE_USERS(listOf(EDIT_USERS)),
REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)),
ADMIN(
listOf(
EDIT_CATALOG,
REMOVE_RECIPES,
REMOVE_USERS,
REMOVE_CATALOG,
PRINT_MIXES,
ADD_TO_INVENTORY,
DEDUCT_FROM_INVENTORY
)
),
// deprecated permissions
VIEW_RECIPE(listOf(VIEW_RECIPES), true),
VIEW_MATERIAL(listOf(VIEW_CATALOG), true),
VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true),
VIEW_COMPANY(listOf(VIEW_CATALOG), true),
VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true),
VIEW_EMPLOYEE(listOf(VIEW_USERS), true),
VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true),
EDIT_RECIPE(listOf(EDIT_RECIPES), true),
EDIT_MATERIAL(listOf(EDIT_MATERIALS), true),
EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true),
EDIT_COMPANY(listOf(EDIT_COMPANIES), true),
EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true),
EDIT_EMPLOYEE(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true),
REMOVE_RECIPE(listOf(REMOVE_RECIPES), true),
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true),
REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true),
REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true),
REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true),
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
;
operator fun contains(permission: EmployeePermission): Boolean {
return permission == this || impliedPermissions.any { permission in it }
}
}
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
return mutableSetOf(this).apply {
impliedPermissions.forEach {
addAll(it.flat())
}
}
}
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
private fun EmployeePermission.toAuthority(): GrantedAuthority {
return SimpleGrantedAuthority(name)
}
// ==== 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<EmployeePermission> = mutableSetOf(),
lastLoginTime: LocalDateTime? = null,
op: Employee.() -> Unit = {}
) = Employee(
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<EmployeePermission> = 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<EmployeePermission> = mutableSetOf(),
op: EmployeeUpdateDto.() -> Unit = {}
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
fun employeeGroup(
id: Long? = null,
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroup.() -> Unit = {}
) = EmployeeGroup(id, name, permissions).apply(op)
fun employeeGroupSaveDto(
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroupSaveDto.() -> Unit = {}
) = EmployeeGroupSaveDto(name, permissions).apply(op)
fun employeeGroupUpdateDto(
id: Long = 0L,
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroupUpdateDto.() -> Unit = {}
) = EmployeeGroupUpdateDto(id, name, permissions).apply(op)

View File

@ -1,5 +1,8 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
import javax.persistence.*
import javax.validation.constraints.NotBlank
@ -55,3 +58,42 @@ fun companyUpdateDto(
name: String? = "name",
op: CompanyUpdateDto.() -> Unit = {}
) = CompanyUpdateDto(id, name).apply(op)
// ==== Exceptions ====
private const val COMPANY_NOT_FOUND_EXCEPTION_TITLE = "Company not found"
private const val COMPANY_ALREADY_EXISTS_EXCEPTION_TITLE = "Company already exists"
private const val COMPANY_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete company"
private const val COMPANY_EXCEPTION_ERROR_CODE = "company"
fun companyIdNotFoundException(id: Long) =
NotFoundException(
COMPANY_EXCEPTION_ERROR_CODE,
COMPANY_NOT_FOUND_EXCEPTION_TITLE,
"A company with the id $id could not be found",
id
)
fun companyNameNotFoundException(name: String) =
NotFoundException(
COMPANY_EXCEPTION_ERROR_CODE,
COMPANY_NOT_FOUND_EXCEPTION_TITLE,
"A company with the name $name could not be found",
name,
"name"
)
fun companyNameAlreadyExistsException(name: String) =
AlreadyExistsException(
COMPANY_EXCEPTION_ERROR_CODE,
COMPANY_ALREADY_EXISTS_EXCEPTION_TITLE,
"A company with the name $name already exists",
name,
"name"
)
fun cannotDeleteCompany(company: Company) =
CannotDeleteException(
COMPANY_EXCEPTION_ERROR_CODE,
COMPANY_CANNOT_DELETE_EXCEPTION_TITLE,
"Cannot delete the company ${company.name} because one or more recipes depends on it"
)

View File

@ -0,0 +1,191 @@
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
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import java.time.LocalDateTime
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis"
private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis"
private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis"
private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis"
private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères"
@Entity
@Table(name = "employee")
data class Employee(
@Id
override val id: Long,
@Column(name = "first_name")
val firstName: String = "",
@Column(name = "last_name")
val lastName: String = "",
@JsonIgnore
val password: String = "",
@JsonIgnore
@Column(name = "default_group_user")
val isDefaultGroupUser: Boolean = false,
@JsonIgnore
@Column(name = "system_user")
val isSystemUser: Boolean = false,
@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<EmployeePermission> = mutableSetOf(),
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
) : Model {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
@get:JsonIgnore
val authorities: Set<GrantedAuthority>
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: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_PASSWORD_EMPTY_MESSAGE)
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
val password: String,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
) : EntityDto<Employee>
open class EmployeeUpdateDto(
@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_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission>?
) : EntityDto<Employee>
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<EmployeePermission> = mutableSetOf(),
lastLoginTime: LocalDateTime? = null,
op: Employee.() -> Unit = {}
) = Employee(
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<EmployeePermission> = 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<EmployeePermission> = mutableSetOf(),
op: EmployeeUpdateDto.() -> Unit = {}
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
// ==== Exceptions ====
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee not found"
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already exists"
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
)
fun employeeIdAlreadyExistsException(id: Long) =
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"
)

View File

@ -0,0 +1,129 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.exception.RestException
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.springframework.http.HttpStatus
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis"
private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise"
@Entity
@Table(name = "employee_group")
data class EmployeeGroup(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override var id: Long? = null,
@Column(unique = true)
override val name: String = "",
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
@get:JsonProperty("explicitPermissions")
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
) : NamedModel {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = this.permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toSet()
}
open class EmployeeGroupSaveDto(
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
@field:Size(min = 3)
val name: String,
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
val permissions: MutableSet<EmployeePermission>
) : EntityDto<EmployeeGroup> {
override fun toEntity(): EmployeeGroup =
EmployeeGroup(null, name, permissions)
}
open class EmployeeGroupUpdateDto(
@field:NotNull(message = GROUP_ID_NULL_MESSAGE)
val id: Long,
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
@field:Size(min = 3)
val name: String,
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
val permissions: MutableSet<EmployeePermission>
) : EntityDto<EmployeeGroup> {
override fun toEntity(): EmployeeGroup =
EmployeeGroup(id, name, permissions)
}
fun employeeGroup(
id: Long? = null,
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroup.() -> Unit = {}
) = EmployeeGroup(id, name, permissions).apply(op)
fun employeeGroupSaveDto(
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroupSaveDto.() -> Unit = {}
) = EmployeeGroupSaveDto(name, permissions).apply(op)
fun employeeGroupUpdateDto(
id: Long = 0L,
name: String = "name",
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeGroupUpdateDto.() -> Unit = {}
) = EmployeeGroupUpdateDto(id, name, permissions).apply(op)
// ==== Exceptions ====
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee group not found"
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee group already exists"
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employeegroup"
class NoDefaultGroupException : RestException(
"nodefaultgroup",
"No default group",
HttpStatus.NOT_FOUND,
"No default group cookie is defined in the current request"
)
fun employeeGroupIdNotFoundException(id: Long) =
NotFoundException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
"An employee group with the id $id could not be found",
id
)
fun employeeGroupNameNotFoundException(name: String) =
NotFoundException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
"An employee group with the name $name could not be found",
name,
"name"
)
fun employeeGroupNameAlreadyExistsException(name: String) =
AlreadyExistsException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
"An employee group with the name $name already exists",
name,
"name"
)

View File

@ -0,0 +1,93 @@
package dev.fyloz.colorrecipesexplorer.model
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
enum class EmployeePermission(
val impliedPermissions: List<EmployeePermission> = listOf(),
val deprecated: Boolean = false
) {
VIEW_RECIPES,
VIEW_CATALOG,
VIEW_USERS,
PRINT_MIXES(listOf(VIEW_RECIPES)),
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)),
EDIT_MATERIALS(listOf(VIEW_CATALOG)),
EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)),
EDIT_COMPANIES(listOf(VIEW_CATALOG)),
EDIT_USERS(listOf(VIEW_USERS)),
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
REMOVE_RECIPES(listOf(EDIT_RECIPES)),
REMOVE_MATERIALS(listOf(EDIT_MATERIALS)),
REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)),
REMOVE_COMPANIES(listOf(EDIT_COMPANIES)),
REMOVE_USERS(listOf(EDIT_USERS)),
REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)),
ADMIN(
listOf(
EDIT_CATALOG,
REMOVE_RECIPES,
REMOVE_USERS,
REMOVE_CATALOG,
PRINT_MIXES,
ADD_TO_INVENTORY,
DEDUCT_FROM_INVENTORY
)
),
// deprecated permissions
VIEW_RECIPE(listOf(VIEW_RECIPES), true),
VIEW_MATERIAL(listOf(VIEW_CATALOG), true),
VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true),
VIEW_COMPANY(listOf(VIEW_CATALOG), true),
VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true),
VIEW_EMPLOYEE(listOf(VIEW_USERS), true),
VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true),
EDIT_RECIPE(listOf(EDIT_RECIPES), true),
EDIT_MATERIAL(listOf(EDIT_MATERIALS), true),
EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true),
EDIT_COMPANY(listOf(EDIT_COMPANIES), true),
EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true),
EDIT_EMPLOYEE(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true),
REMOVE_RECIPE(listOf(REMOVE_RECIPES), true),
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true),
REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true),
REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true),
REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true),
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
;
operator fun contains(permission: EmployeePermission): Boolean {
return permission == this || impliedPermissions.any { permission in it }
}
}
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
return mutableSetOf(this).apply {
impliedPermissions.forEach {
addAll(it.flat())
}
}
}
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
fun EmployeePermission.toAuthority(): GrantedAuthority {
return SimpleGrantedAuthority(name)
}

View File

@ -1,5 +1,8 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
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 org.springframework.web.multipart.MultipartFile
@ -119,3 +122,42 @@ fun materialQuantityDto(
quantity: Float,
op: MaterialQuantityDto.() -> Unit = {}
) = MaterialQuantityDto(materialId, quantity).apply(op)
// ==== Exceptions ====
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"
fun materialIdNotFoundException(id: Long) =
NotFoundException(
MATERIAL_EXCEPTION_ERROR_CODE,
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
"A material with the id $id could not be found",
id
)
fun materialNameNotFoundException(name: String) =
NotFoundException(
MATERIAL_EXCEPTION_ERROR_CODE,
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
"A material with the name $name could not be found",
name,
"name"
)
fun materialNameAlreadyExistsException(name: String) =
AlreadyExistsException(
MATERIAL_EXCEPTION_ERROR_CODE,
MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
"A material with the name $name already exists",
name,
"name"
)
fun cannotDeleteMaterialException(material: Material) =
CannotDeleteException(
MATERIAL_EXCEPTION_ERROR_CODE,
MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE,
"Cannot delete the material ${material.name} because one or more recipes depends on it"
)

View File

@ -1,5 +1,8 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
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 org.hibernate.annotations.ColumnDefault
@ -101,3 +104,51 @@ fun materialTypeUpdateDto(
prefix: String? = null,
op: MaterialTypeUpdateDto.() -> Unit = {}
) = MaterialTypeUpdateDto(id, name, prefix).apply(op)
// ==== Exceptions ====
private const val MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE = "Material type not found"
private const val MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Material type already exists"
private const val MATERIAL_TYPE_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material type"
private const val MATERIAL_TYPE_EXCEPTION_ERROR_CODE = "materialtype"
fun materialTypeIdNotFoundException(id: Long) =
NotFoundException(
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE,
"A material type with the id $id could not be found",
id
)
fun materialTypeNameNotFoundException(name: String) =
NotFoundException(
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE,
"A material type with the name $name could not be found",
name,
"name"
)
fun materialTypeNameAlreadyExistsException(name: String) =
AlreadyExistsException(
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
"A material type with the name $name already exists",
name,
"name"
)
fun materialTypePrefixAlreadyExistsException(prefix: String) =
AlreadyExistsException(
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
"A material type with the prefix $prefix already exists",
prefix,
"prefix"
)
fun cannotDeleteMaterialTypeException(materialType: MaterialType) =
CannotDeleteException(
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
MATERIAL_TYPE_CANNOT_DELETE_EXCEPTION_TITLE,
"Cannot delete material type ${materialType.name} because one or more materials depends on it"
)

View File

@ -1,6 +1,8 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
import javax.persistence.*
import javax.validation.constraints.Min
@ -121,3 +123,23 @@ fun mixLocationDto(
location: String? = "location",
op: MixLocationDto.() -> Unit = {}
) = MixLocationDto(mixId, location).apply(op)
// ==== Exceptions ====
private const val MIX_NOT_FOUND_EXCEPTION_TITLE = "Mix not found"
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
)
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"
)

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotNull
@ -50,3 +51,15 @@ fun mixMaterialDto(
position: Int = 0,
op: MixMaterialDto.() -> Unit = {}
) = MixMaterialDto(materialId, quantity, position).apply(op)
// ==== Exceptions ====
private const val MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Mix material not found"
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
)

View File

@ -1,9 +1,12 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.exception.RestException
import org.springframework.http.HttpStatus
import javax.persistence.*
const val IDENTIFIER_MATERIAL_NAME = "material"
@Entity
@Table(name = "mix_type")
data class MixType(
@ -36,3 +39,54 @@ fun mixType(
name,
material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType)
).apply(op)
// ==== Exceptions ====
private const val MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE = "Mix type not found"
private const val MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix type already exists"
private const val MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix type"
private const val MIX_TYPE_EXCEPTION_ERROR_CODE = "mixtype"
class MixTypeNameAndMaterialTypeNotFoundException(name: String, materialType: MaterialType) :
RestException(
"notfound-mixtype-namematerialtype",
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
HttpStatus.NOT_FOUND,
"A mix type with the name $name and material type ${materialType.name} could not be found",
mapOf(
"name" to name,
"materialType" to materialType.name
)
)
fun mixTypeIdNotFoundException(id: Long) =
NotFoundException(
MIX_TYPE_EXCEPTION_ERROR_CODE,
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
"A mix type with the id $id could not be found",
id
)
fun mixTypeNameNotFoundException(name: String) =
NotFoundException(
MIX_TYPE_EXCEPTION_ERROR_CODE,
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
"A mix type with the name $name could not be found",
name,
"name"
)
fun mixTypeNameAlreadyExistsException(name: String) =
AlreadyExistsException(
MIX_TYPE_EXCEPTION_ERROR_CODE,
MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
"A mix type with the name $name already exists",
name,
"name"
)
fun cannotDeleteMixTypeException(mixType: MixType) =
CannotDeleteException(
MIX_TYPE_EXCEPTION_ERROR_CODE,
MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE,
"Cannot delete the mix type ${mixType.name} because one or more mixes depends on it"
)

View File

@ -1,8 +1,11 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
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 java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
@ -246,3 +249,27 @@ fun noteDto(
content: String? = "note",
op: NoteDto.() -> Unit = {}
) = NoteDto(groupId, content).apply(op)
// ==== Exceptions ====
private const val RECIPE_NOT_FOUND_EXCEPTION_TITLE = "Recipe not found"
private const val RECIPE_EXCEPTION_ERROR_CODE = "recipe"
class RecipeImageNotFoundException(id: Long, recipe: Recipe) :
RestException(
"notfound-recipeimage-id",
"Recipe image not found",
HttpStatus.NOT_FOUND,
"A recipe image with the id $id could no be found for the recipe ${recipe.name}",
mapOf(
"id" to id,
"recipe" to recipe.name
)
)
fun recipeIdNotFoundException(id: Long) =
NotFoundException(
RECIPE_EXCEPTION_ERROR_CODE,
RECIPE_NOT_FOUND_EXCEPTION_TITLE,
"A recipe with the id $id could not be found",
id
)

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.model
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import javax.persistence.*
@Entity
@ -21,3 +22,15 @@ fun recipeStep(
message: String = "message",
op: RecipeStep.() -> Unit = {}
) = RecipeStep(id, position, message).apply(op)
// ==== Exceptions ====
private const val RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE = "Recipe step not found"
private const val RECIPE_STEP_EXCEPTION_ERROR_CODE = "recipestep"
fun recipeStepIdNotFoundException(id: Long) =
NotFoundException(
RECIPE_STEP_EXCEPTION_ERROR_CODE,
RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE,
"A recipe step with the id $id could not be found",
id
)

View File

@ -2,13 +2,11 @@ package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.model.validation.or
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
@ -77,11 +75,11 @@ interface EmployeeUserDetailsService : UserDetailsService {
@Service
class EmployeeServiceImpl(
employeeRepository: EmployeeRepository,
@Lazy val passwordEncoder: PasswordEncoder
@Lazy val groupService: EmployeeGroupService,
@Lazy val passwordEncoder: PasswordEncoder,
) : AbstractExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeRepository>(employeeRepository),
EmployeeService {
@Autowired
lateinit var groupService: EmployeeGroupServiceImpl
override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id)
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
repository.existsByFirstNameAndLastName(firstName, lastName)
@ -94,9 +92,8 @@ class EmployeeServiceImpl(
override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee =
super.getById(id).apply {
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser) throw EntityNotFoundException(
id
)
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
throw idNotFoundException(id)
}
override fun getByGroup(group: EmployeeGroup): Collection<Employee> =
@ -122,8 +119,10 @@ class EmployeeServiceImpl(
})
override fun save(entity: Employee): Employee {
if (existsById(entity.id))
throw employeeIdAlreadyExistsException(entity.id)
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
throw EntityAlreadyExistsException("${entity.firstName} ${entity.lastName}")
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
return super<AbstractExternalModelService>.save(entity)
}
@ -173,7 +172,7 @@ class EmployeeServiceImpl(
override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee {
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
if (this != null && id != entity.id)
throw EntityAlreadyExistsException("${entity.firstName} ${entity.lastName}")
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
}
return super<AbstractExternalModelService>.update(entity)
@ -219,11 +218,14 @@ const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
class EmployeeGroupServiceImpl(
val employeeService: EmployeeService,
employeeGroupRepository: EmployeeGroupRepository
) :
AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository>(
employeeGroupRepository
),
) : AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository>(
employeeGroupRepository
),
EmployeeGroupService {
override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id)
override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name)
override fun existsByName(name: String): Boolean = repository.existsByName(name)
override fun getEmployeesForGroup(id: Long): Collection<Employee> =
employeeService.getByGroup(getById(id))
@ -254,7 +256,7 @@ class EmployeeGroupServiceImpl(
override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup {
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
?: throw EntityNotFoundException("defaultGroup")
?: throw NoDefaultGroupException()
val defaultGroupUser = employeeService.getById(
defaultGroupCookie.value.toLong(),
ignoreDefaultGroupUsers = false,
@ -281,9 +283,9 @@ class EmployeeUserDetailsServiceImpl(
override fun loadUserByUsername(username: String): UserDetails {
try {
return loadUserByEmployeeId(username.toLong(), true)
} catch (ex: EntityNotFoundException) {
} catch (ex: NotFoundException) {
throw UsernameNotFoundException(username)
} catch (ex: EntityNotFoundException) {
} catch (ex: NotFoundException) {
throw UsernameNotFoundException(username)
}
}

View File

@ -1,10 +1,6 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
import dev.fyloz.colorrecipesexplorer.model.Company
import dev.fyloz.colorrecipesexplorer.model.CompanySaveDto
import dev.fyloz.colorrecipesexplorer.model.CompanyUpdateDto
import dev.fyloz.colorrecipesexplorer.model.company
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
@ -21,6 +17,10 @@ class CompanyServiceImpl(
) :
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository>(companyRepository),
CompanyService {
override fun idNotFoundException(id: Long) = companyIdNotFoundException(id)
override fun nameNotFoundException(name: String) = companyNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = companyNameAlreadyExistsException(name)
override fun isLinkedToRecipes(company: Company): Boolean = recipeService.existsByCompany(company)
override fun update(entity: CompanyUpdateDto): Company {
@ -35,8 +35,8 @@ class CompanyServiceImpl(
})
}
override fun deleteById(id: Long) {
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
super.deleteById(id)
override fun delete(entity: Company) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteCompany(entity)
super.delete(entity)
}
}

View File

@ -1,12 +1,9 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.colorrecipesexplorer.model.MixDeductDto
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
import dev.fyloz.colorrecipesexplorer.model.materialQuantityDto
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.utils.mapMayThrow
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import javax.transaction.Transactional
@ -66,16 +63,16 @@ class InventoryServiceImpl(
@Transactional
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
val thrown = mutableListOf<MaterialQuantityDto>()
val thrown = mutableListOf<NotEnoughInventoryException>()
val updatedQuantities =
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, LowQuantityException>(
{ thrown.add(it.materialQuantity) }
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
{ thrown.add(it) }
) {
materialQuantityDto(materialId = it.material, quantity = deduct(it))
}
if (thrown.isNotEmpty()) {
throw LowQuantitiesException(thrown)
throw MultiplesNotEnoughInventoryException(thrown)
}
return updatedQuantities
}
@ -85,7 +82,31 @@ class InventoryServiceImpl(
if (this.inventoryQuantity >= materialQuantity.quantity) {
materialService.updateQuantity(this, -materialQuantity.quantity)
} else {
throw LowQuantityException(materialQuantity)
throw NotEnoughInventoryException(materialQuantity.quantity, this)
}
}
}
class NotEnoughInventoryException(quantity: Float, material: Material) :
RestException(
"notenoughinventory",
"Not enough inventory",
HttpStatus.BAD_REQUEST,
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
mapOf(
"material" to material.name,
"requestQuantity" to quantity,
"availableQuantity" to material.inventoryQuantity
)
)
class MultiplesNotEnoughInventoryException(exceptions: List<NotEnoughInventoryException>) :
RestException(
"notenoughinventory-multiple",
"Not enough inventory",
HttpStatus.BAD_REQUEST,
"Cannot deduct requested quantities because there is no enough of them in inventory",
mapOf(
"lowQuantities" to exceptions.map { it.extensions }
)
)

View File

@ -1,6 +1,5 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
import dev.fyloz.colorrecipesexplorer.service.files.SimdutService
@ -47,6 +46,10 @@ class MaterialServiceImpl(
materialRepository
),
MaterialService {
override fun idNotFoundException(id: Long) = materialIdNotFoundException(id)
override fun nameNotFoundException(name: String) = materialNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name)
override fun existsByMaterialType(materialType: MaterialType): Boolean =
repository.existsByMaterialType(materialType)
@ -113,8 +116,8 @@ class MaterialServiceImpl(
Assert.notNull(material.name, "The persisted material with the id ${material.id} has a null name")
}
override fun deleteById(id: Long) {
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
super.deleteById(id)
override fun delete(entity: Material) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
super.delete(entity)
}
}

View File

@ -1,19 +1,10 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.model.MaterialType
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeSaveDto
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeUpdateDto
import dev.fyloz.colorrecipesexplorer.model.materialType
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.model.validation.isNotNullAndNotBlank
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ResponseStatus
import kotlin.contracts.ExperimentalContracts
interface MaterialTypeService :
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository> {
@ -38,6 +29,10 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository>(
repository
), MaterialTypeService {
override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id)
override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = materialTypeNameAlreadyExistsException(name)
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
materialService.existsByMaterialType(materialType)
@ -47,11 +42,10 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
override fun save(entity: MaterialType): MaterialType {
if (existsByPrefix(entity.prefix))
throw EntityAlreadyExistsException(entity.prefix)
throw materialTypePrefixAlreadyExistsException(entity.prefix)
return super<AbstractExternalNamedModelService>.save(entity)
}
@ExperimentalContracts
override fun update(entity: MaterialTypeUpdateDto): MaterialType {
val persistedMaterialType by lazy { getById(entity.id) }
@ -68,15 +62,15 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
override fun update(entity: MaterialType): MaterialType {
with(repository.findByPrefix(entity.prefix)) {
if (this != null && id != entity.id)
throw EntityAlreadyExistsException(entity.prefix)
throw materialTypePrefixAlreadyExistsException(entity.prefix)
}
return super<AbstractExternalNamedModelService>.update(entity)
}
override fun deleteById(id: Long) {
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
super.deleteById(id)
override fun delete(entity: MaterialType) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialTypeException(entity)
super.delete(entity)
}
override fun saveSystemTypes(systemTypeProperties: Collection<MaterialTypeProperties.MaterialTypeProperty>) {
@ -102,9 +96,3 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
oldSystemTypes.forEach { update(materialType(it, newSystemType = false)) }
}
}
@ResponseStatus(HttpStatus.CONFLICT)
class CannotDeleteUsedMaterialTypeRestException :
RestException("Cannot delete a used material type", HttpStatus.CONFLICT) {
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {}
}

View File

@ -1,9 +1,6 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
import dev.fyloz.colorrecipesexplorer.model.MixMaterialDto
import dev.fyloz.colorrecipesexplorer.model.mixMaterial
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
@ -27,6 +24,8 @@ class MixMaterialServiceImpl(
mixMaterialRepository: MixMaterialRepository,
@Lazy val materialService: MaterialService
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialService {
override fun idNotFoundException(id: Long) = mixMaterialIdNotFoundException(id)
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
override fun create(mixMaterials: Set<MixMaterialDto>): Set<MixMaterial> =
mixMaterials.map(::create).toSet()

View File

@ -1,6 +1,5 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
import org.springframework.context.annotation.Lazy
@ -30,6 +29,8 @@ class MixServiceImpl(
val mixTypeService: MixTypeService
) : AbstractModelService<Mix, MixRepository>(mixRepository),
MixService {
override fun idNotFoundException(id: Long) = mixIdNotFoundException(id)
override fun getAllByMixType(mixType: MixType): Collection<Mix> = repository.findAllByMixType(mixType)
override fun mixTypeIsShared(mixType: MixType): Boolean = getAllByMixType(mixType).count() > 1
@ -81,12 +82,8 @@ class MixServiceImpl(
@Transactional
override fun delete(entity: Mix) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixException(entity)
recipeService.removeMix(entity)
super.delete(entity)
}
override fun deleteById(id: Long) {
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
super.deleteById(id)
}
}

View File

@ -1,8 +1,5 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
import org.springframework.context.annotation.Lazy
@ -35,15 +32,19 @@ class MixTypeServiceImpl(
@Lazy val mixService: MixService
) :
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeService {
override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id)
override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name)
override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean =
repository.existsByNameAndMaterialType(name, materialType)
override fun getByMaterial(material: Material): MixType =
repository.findByMaterial(material) ?: throw EntityNotFoundException(material.name)
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType =
repository.findByNameAndMaterialType(name, materialType)
?: throw EntityNotFoundException("$name/${materialType.name}")
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
if (existsByNameAndMaterialType(name, materialType))
@ -53,7 +54,7 @@ class MixTypeServiceImpl(
override fun save(entity: MixType): MixType {
if (materialService.existsByName(entity.name))
throw EntityAlreadyExistsException(entity.name)
throw materialNameAlreadyExistsException(entity.name)
return super.save(entity)
}
@ -77,8 +78,8 @@ class MixTypeServiceImpl(
material.materialType = materialType
})
override fun deleteById(id: Long) {
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
super.deleteById(id)
override fun delete(entity: MixType) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity)
super.delete(entity)
}
}

View File

@ -1,6 +1,5 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.model.validation.or
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
@ -38,6 +37,8 @@ class RecipeServiceImpl(
) :
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
RecipeService {
override fun idNotFoundException(id: Long) = recipeIdNotFoundException(id)
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
@ -154,7 +155,7 @@ class RecipeImageServiceImpl(val recipeService: RecipeService, val fileService:
try {
fileService.readAsBytes(getPath(id, recipeId))
} catch (ex: NoSuchFileException) {
throw EntityNotFoundException("$recipeId/$id")
throw RecipeImageNotFoundException(id, recipeService.getById(recipeId))
}
override fun getAllIdsForRecipe(recipeId: Long): Collection<Long> {

View File

@ -1,6 +1,7 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.model.RecipeStep
import dev.fyloz.colorrecipesexplorer.model.recipeStepIdNotFoundException
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
import org.springframework.stereotype.Service
@ -9,4 +10,6 @@ interface RecipeStepService : ModelService<RecipeStep, RecipeStepRepository>
@Service
class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) :
AbstractModelService<RecipeStep, RecipeStepRepository>(recipeStepRepository),
RecipeStepService
RecipeStepService {
override fun idNotFoundException(id: Long) = recipeStepIdNotFoundException(id)
}

View File

@ -1,7 +1,7 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.EntityDto
import dev.fyloz.colorrecipesexplorer.model.Model
import dev.fyloz.colorrecipesexplorer.model.NamedModel
@ -66,19 +66,21 @@ abstract class AbstractService<E, R : JpaRepository<E, *>>(override val reposito
abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repository: R) :
AbstractService<E, R>(repository), ModelService<E, R> {
protected abstract fun idNotFoundException(id: Long): NotFoundException
override fun existsById(id: Long): Boolean = repository.existsById(id)
override fun getById(id: Long): E = repository.findByIdOrNull(id) ?: throw EntityNotFoundException(id)
override fun getById(id: Long): E = repository.findByIdOrNull(id) ?: throw idNotFoundException(id)
override fun save(entity: E): E {
if (entity.id != null && existsById(entity.id!!))
throw EntityAlreadyExistsException(entity.id!!)
throw idNotFoundException(entity.id!!)
return super.save(entity)
}
override fun update(entity: E): E {
assertId(entity.id)
if (!existsById(entity.id!!))
throw EntityNotFoundException(entity.id!!)
throw idNotFoundException(entity.id!!)
return super.update(entity)
}
@ -92,13 +94,16 @@ abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repos
abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<E>>(repository: R) :
AbstractModelService<E, R>(repository), NamedModelService<E, R> {
protected abstract fun nameNotFoundException(name: String): NotFoundException
protected abstract fun nameAlreadyExistsException(name: String): AlreadyExistsException
override fun existsByName(name: String): Boolean = repository.existsByName(name)
override fun getByName(name: String): E = repository.findByName(name) ?: throw EntityNotFoundException(name)
override fun getByName(name: String): E = repository.findByName(name) ?: throw nameNotFoundException(name)
override fun deleteByName(name: String) = repository.deleteByName(name)
override fun save(entity: E): E {
if (existsByName(entity.name))
throw EntityAlreadyExistsException(entity.name)
throw nameAlreadyExistsException(entity.name)
return super.save(entity)
}
@ -107,7 +112,7 @@ abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<
assertName(entity.name)
with(repository.findByName(entity.name)) {
if (this != null && id != entity.id)
throw EntityAlreadyExistsException(entity.name)
throw nameAlreadyExistsException(entity.name)
}
return super.update(entity)
}

View File

@ -1,10 +1,9 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.exception.SimdutWriteException
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.service.MaterialService
import org.slf4j.Logger
import org.springframework.context.annotation.Lazy
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.IOException
@ -57,3 +56,11 @@ class SimdutService(
fun getSimdutFileName(material: Material) =
material.id.toString()
}
class SimdutWriteException(material: Material) :
RestException(
"simdut-write",
"Could not write SIMDUT file",
HttpStatus.INTERNAL_SERVER_ERROR,
"Could not write the SIMDUT file for the material ${material.name} to the disk"
)

View File

@ -2,7 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.EntityDto
import dev.fyloz.colorrecipesexplorer.model.Model
import dev.fyloz.colorrecipesexplorer.model.NamedModel
@ -127,7 +127,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
open fun `getById() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
whenever(repository.findById(entity.id!!)).doReturn(Optional.empty())
val exception = assertThrows<EntityNotFoundException> { service.getById(entity.id!!) }
val exception = assertThrows<NotFoundException> { service.getById(entity.id!!) }
assertTrue(exception.value is Long)
assertEquals(entity.id, exception.value as Long)
}
@ -139,8 +139,8 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
doReturn(true).whenever(repository).existsById(entity.id!!)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertTrue(exception.value is Long)
assertEquals(entity.id, exception.value as Long)
assertTrue(exception.id is Long)
assertEquals(entity.id, exception.id as Long)
}
// update()
@ -161,7 +161,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
open fun `update() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
doReturn(false).whenever(service).existsById(entity.id!!)
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
val exception = assertThrows<NotFoundException> { service.update(entity) }
assertTrue(exception.value is Long)
assertEquals(entity.id, exception.value as Long)
}
@ -217,7 +217,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
open fun `getByName() throws EntityNotFoundException when no entity with the given name exists`() {
whenever(repository.findByName(entity.name)).doReturn(null)
val exception = assertThrows<EntityNotFoundException> { service.getByName(entity.name) }
val exception = assertThrows<NotFoundException> { service.getByName(entity.name) }
assertEquals(entity.name, exception.value)
}
@ -228,7 +228,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
doReturn(true).whenever(service).existsByName(entity.name)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertEquals(entity.name, exception.value)
assertEquals(entity.name, exception.id)
}
// update()
@ -251,7 +251,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
whenever(repository.findByName(entity.name)).doReturn(null)
doReturn(false).whenever(service).existsById(entity.id!!)
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
val exception = assertThrows<NotFoundException> { service.update(entity) }
assertTrue(exception.value is Long)
assertEquals(entity.id, exception.value as Long)
@ -263,7 +263,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
doReturn(entity).whenever(service).getById(entity.id!!)
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
assertEquals(entity.name, exception.value)
assertEquals(entity.name, exception.id)
}
// deleteByName()

View File

@ -3,7 +3,7 @@ package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
@ -76,7 +76,7 @@ class EmployeeServiceTest :
fun `getById() throws EntityNotFoundException when the corresponding employee is a default group user`() {
whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser))
val exception = assertThrows<EntityNotFoundException> {
val exception = assertThrows<NotFoundException> {
service.getById(
entityDefaultGroupUser.id,
ignoreDefaultGroupUsers = true,
@ -91,7 +91,7 @@ class EmployeeServiceTest :
fun `getById() throws EntityNotFoundException when the corresponding employee is a system user`() {
whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser))
val exception = assertThrows<EntityNotFoundException> {
val exception = assertThrows<NotFoundException> {
service.getById(
entitySystemUser.id,
ignoreDefaultGroupUsers = false,
@ -151,7 +151,7 @@ class EmployeeServiceTest :
doReturn(true).whenever(repository).existsByFirstNameAndLastName(entity.firstName, entity.lastName)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertEquals("${entity.firstName} ${entity.lastName}", exception.value)
assertEquals("${entity.firstName} ${entity.lastName}", exception.id)
}
@Test
@ -191,8 +191,8 @@ class EmployeeServiceTest :
ignoreSystemUsers = true
)
}
assertTrue(exception.value is String)
assertEquals("${entity.firstName} ${entity.lastName}", exception.value as String)
assertTrue(exception.id is String)
assertEquals("${entity.firstName} ${entity.lastName}", exception.id as String)
}
}
@ -262,7 +262,7 @@ class EmployeeGroupServiceTest :
whenever(request.cookies).doReturn(arrayOf())
val exception = assertThrows<EntityNotFoundException> { service.getRequestDefaultGroup(request) }
val exception = assertThrows<NotFoundException> { service.getRequestDefaultGroup(request) }
assertEquals("defaultGroup", exception.value)
}
@ -327,7 +327,7 @@ class EmployeeUserDetailsServiceTest {
@Test
fun `loadUserByUsername() throws UsernameNotFoundException when no employee with the given id exists`() {
whenever(employeeService.getById(eq(employee.id), any(), any())).doThrow(
EntityNotFoundException(
NotFoundException(
employee.id
)
)

View File

@ -2,7 +2,6 @@ package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.colorrecipesexplorer.model.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
@ -155,7 +154,7 @@ class InventoryServiceTest {
@Test
fun `deduct(materialQuantity) throws LowQuantityException when there is not enough inventory of the given material`() {
withGivenQuantities(0f, 1000f) {
val exception = assertThrows<LowQuantityException> { service.deduct(this) }
val exception = assertThrows<NotEnoughInventoryException> { service.deduct(this) }
assertEquals(this, exception.materialQuantity)
}
}

View File

@ -128,7 +128,7 @@ class MaterialServiceTest :
doReturn(true).whenever(service).existsByName(entity.name)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertEquals(entity.name, exception.value)
assertEquals(entity.name, exception.id)
}
@Test
@ -160,7 +160,7 @@ class MaterialServiceTest :
doReturn(entity).whenever(service).getById(material.id!!)
val exception = assertThrows<EntityAlreadyExistsException> { service.update(material) }
assertEquals(material.name, exception.value)
assertEquals(material.name, exception.id)
}
// updateQuantity()

View File

@ -2,7 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
import org.junit.jupiter.api.AfterEach
@ -110,7 +110,7 @@ class MaterialTypeServiceTest :
doReturn(true).whenever(service).existsByPrefix(entity.prefix)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertEquals(entity.prefix, exception.value)
assertEquals(entity.prefix, exception.id)
}
// update()
@ -138,7 +138,7 @@ class MaterialTypeServiceTest :
doReturn(false).whenever(service).existsById(entity.id!!)
doReturn(null).whenever(service).getById(entity.id!!)
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
val exception = assertThrows<NotFoundException> { service.update(entity) }
assertTrue(exception.value is Long)
assertEquals(entity.id, exception.value as Long)
}
@ -150,7 +150,7 @@ class MaterialTypeServiceTest :
doReturn(entity).whenever(service).getById(entity.id!!)
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
assertEquals(entity.name, exception.value)
assertEquals(entity.name, exception.id)
}
@Test
@ -160,7 +160,7 @@ class MaterialTypeServiceTest :
doReturn(entity).whenever(service).getById(entity.id!!)
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
assertEquals(entity.prefix, exception.value)
assertEquals(entity.prefix, exception.id)
}
// delete()

View File

@ -2,7 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
import org.junit.jupiter.api.AfterEach
@ -57,7 +57,7 @@ class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService
fun `getByMaterial() throws EntityNotFoundException when no mix type with the given material exists`() {
whenever(repository.findByMaterial(material)).doReturn(null)
val exception = assertThrows<EntityNotFoundException> { service.getByMaterial(material) }
val exception = assertThrows<NotFoundException> { service.getByMaterial(material) }
assertEquals(material.name, exception.value)
}
@ -104,7 +104,7 @@ class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService
whenever(materialService.existsByName(entity.name)).doReturn(true)
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
assertEquals(entity.name, exception.value)
assertEquals(entity.name, exception.id)
}
// saveForNameAndMaterialType()

View File

@ -1,7 +1,7 @@
package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
import dev.fyloz.colorrecipesexplorer.service.files.FileService
@ -198,7 +198,7 @@ class RecipeImageServiceTest {
whenever(fileService.readAsBytes(imagePath)).doAnswer { throw NoSuchFileException(imagePath) }
val exception =
assertThrows<EntityNotFoundException> { service.getByIdForRecipe(imageId, recipeId) }
assertThrows<NotFoundException> { service.getByIdForRecipe(imageId, recipeId) }
assertEquals("$recipeId/$imageId", exception.value)
}