#25 Migrate users and groups to new logic
This commit is contained in:
parent
129fc4dcb9
commit
d0965d75a0
|
@ -4,11 +4,14 @@ object Constants {
|
||||||
object ControllerPaths {
|
object ControllerPaths {
|
||||||
const val COMPANY = "/api/company"
|
const val COMPANY = "/api/company"
|
||||||
const val FILE = "/api/file"
|
const val FILE = "/api/file"
|
||||||
|
const val GROUP = "/api/user/group"
|
||||||
|
const val INVENTORY = "/api/inventory"
|
||||||
const val MATERIAL = "/api/material"
|
const val MATERIAL = "/api/material"
|
||||||
const val MATERIAL_TYPE = "/api/materialtype"
|
const val MATERIAL_TYPE = "/api/materialtype"
|
||||||
const val MIX = "/api/recipe/mix"
|
const val MIX = "/api/recipe/mix"
|
||||||
const val RECIPE = "/api/recipe"
|
const val RECIPE = "/api/recipe"
|
||||||
const val TOUCH_UP_KIT = "/api/touchupkit"
|
const val TOUCH_UP_KIT = "/api/touchupkit"
|
||||||
|
const val USER = "/api/user"
|
||||||
}
|
}
|
||||||
|
|
||||||
object FilePaths {
|
object FilePaths {
|
||||||
|
@ -22,6 +25,7 @@ object Constants {
|
||||||
|
|
||||||
object ModelNames {
|
object ModelNames {
|
||||||
const val COMPANY = "Company"
|
const val COMPANY = "Company"
|
||||||
|
const val GROUP = "Group"
|
||||||
const val MATERIAL = "Material"
|
const val MATERIAL = "Material"
|
||||||
const val MATERIAL_TYPE = "MaterialType"
|
const val MATERIAL_TYPE = "MaterialType"
|
||||||
const val MIX = "Mix"
|
const val MIX = "Mix"
|
||||||
|
@ -30,12 +34,14 @@ object Constants {
|
||||||
const val RECIPE = "Recipe"
|
const val RECIPE = "Recipe"
|
||||||
const val RECIPE_STEP = "RecipeStep"
|
const val RECIPE_STEP = "RecipeStep"
|
||||||
const val TOUCH_UP_KIT = "TouchUpKit"
|
const val TOUCH_UP_KIT = "TouchUpKit"
|
||||||
|
const val USER = "User"
|
||||||
}
|
}
|
||||||
|
|
||||||
object ValidationMessages {
|
object ValidationMessages {
|
||||||
const val SIZE_GREATER_OR_EQUALS_ZERO = "Must be greater or equals to 0"
|
const val SIZE_GREATER_OR_EQUALS_ZERO = "Must be greater or equals to 0"
|
||||||
const val SIZE_GREATER_OR_EQUALS_ONE = "Must be greater or equals to 1"
|
const val SIZE_GREATER_OR_EQUALS_ONE = "Must be greater or equals to 1"
|
||||||
const val RANGE_OUTSIDE_PERCENTS = "Must be between 0 and 100"
|
const val RANGE_OUTSIDE_PERCENTS = "Must be between 0 and 100"
|
||||||
|
const val PASSWORD_TOO_SMALL = "Must contains at least 8 characters"
|
||||||
}
|
}
|
||||||
|
|
||||||
object ValidationRegexes {
|
object ValidationRegexes {
|
||||||
|
|
|
@ -2,13 +2,12 @@ package dev.fyloz.colorrecipesexplorer.config.security
|
||||||
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDetails
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserLoginRequestDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.JwtLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.JwtLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserDetailsLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.UserDetailsLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserDetails
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserLoginRequest
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserOutputDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.toAuthorities
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.addCookie
|
import dev.fyloz.colorrecipesexplorer.utils.addCookie
|
||||||
import io.jsonwebtoken.ExpiredJwtException
|
import io.jsonwebtoken.ExpiredJwtException
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
|
@ -40,7 +39,7 @@ class JwtAuthenticationFilter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication {
|
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication {
|
||||||
val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequest::class.java)
|
val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequestDto::class.java)
|
||||||
logger.debug("Login attempt for user ${loginRequest.id}...")
|
logger.debug("Login attempt for user ${loginRequest.id}...")
|
||||||
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
||||||
}
|
}
|
||||||
|
@ -116,8 +115,8 @@ class JwtAuthorizationFilter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAuthenticationToken(user: UserOutputDto) =
|
private fun getAuthenticationToken(user: UserDto) =
|
||||||
UsernamePasswordAuthenticationToken(user.id, null, user.permissions.toAuthorities())
|
UsernamePasswordAuthenticationToken(user.id, null, user.authorities)
|
||||||
|
|
||||||
private fun getAuthenticationToken(userId: Long): UsernamePasswordAuthenticationToken? = try {
|
private fun getAuthenticationToken(userId: Long): UsernamePasswordAuthenticationToken? = try {
|
||||||
val userDetails = userDetailsLogic.loadUserById(userId)
|
val userDetails = userDetailsLogic.loadUserById(userId)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.config.security
|
package dev.fyloz.colorrecipesexplorer.config.security
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.emergencyMode
|
import dev.fyloz.colorrecipesexplorer.emergencyMode
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.JwtLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.JwtLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserDetailsLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.UserDetailsLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
|
@ -147,13 +147,14 @@ class SecurityConfig(
|
||||||
with(securityProperties.root!!) {
|
with(securityProperties.root!!) {
|
||||||
if (!userLogic.existsById(this.id)) {
|
if (!userLogic.existsById(this.id)) {
|
||||||
userLogic.save(
|
userLogic.save(
|
||||||
User(
|
UserDto(
|
||||||
id = this.id,
|
id = this.id,
|
||||||
firstName = rootUserFirstName,
|
firstName = rootUserFirstName,
|
||||||
lastName = rootUserLastName,
|
lastName = rootUserLastName,
|
||||||
|
group = null,
|
||||||
password = passwordEncoder.encode(this.password),
|
password = passwordEncoder.encode(this.password),
|
||||||
isSystemUser = true,
|
permissions = listOf(Permission.ADMIN),
|
||||||
permissions = mutableSetOf(Permission.ADMIN)
|
isSystemUser = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.dtos
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.NotEmpty
|
||||||
|
|
||||||
|
data class GroupDto(
|
||||||
|
override val id: Long = 0L,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val permissions: List<Permission>,
|
||||||
|
|
||||||
|
val explicitPermissions: List<Permission> = listOf()
|
||||||
|
) : EntityDto {
|
||||||
|
@get:JsonIgnore
|
||||||
|
val defaultGroupUserId = getDefaultGroupUserId(id)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getDefaultGroupUserId(id: Long) = 1000000 + id
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package dev.fyloz.colorrecipesexplorer.dtos
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import dev.fyloz.colorrecipesexplorer.Constants
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.validation.constraints.Max
|
import javax.validation.constraints.Max
|
||||||
import javax.validation.constraints.Min
|
import javax.validation.constraints.Min
|
||||||
|
@ -94,7 +93,7 @@ data class RecipeUpdateDto(
|
||||||
data class RecipeGroupInformationDto(
|
data class RecipeGroupInformationDto(
|
||||||
override val id: Long = 0L,
|
override val id: Long = 0L,
|
||||||
|
|
||||||
val group: Group,
|
val group: GroupDto,
|
||||||
|
|
||||||
val note: String? = null,
|
val note: String? = null,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.dtos
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.SpringUserDetails
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.toAuthority
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
|
data class UserDto(
|
||||||
|
override val id: Long = 0L,
|
||||||
|
|
||||||
|
val firstName: String,
|
||||||
|
|
||||||
|
val lastName: String,
|
||||||
|
|
||||||
|
@field:JsonIgnore
|
||||||
|
val password: String = "",
|
||||||
|
|
||||||
|
val group: GroupDto?,
|
||||||
|
|
||||||
|
val permissions: List<Permission>,
|
||||||
|
|
||||||
|
val explicitPermissions: List<Permission> = listOf(),
|
||||||
|
|
||||||
|
val lastLoginTime: LocalDateTime? = null,
|
||||||
|
|
||||||
|
@field:JsonIgnore
|
||||||
|
val isDefaultGroupUser: Boolean = false,
|
||||||
|
|
||||||
|
@field:JsonIgnore
|
||||||
|
val isSystemUser: Boolean = false
|
||||||
|
) : EntityDto {
|
||||||
|
@get:JsonIgnore
|
||||||
|
val authorities
|
||||||
|
get() = permissions
|
||||||
|
.map { it.toAuthority() }
|
||||||
|
.toMutableSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UserSaveDto(
|
||||||
|
val id: Long = 0L,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val firstName: String,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val lastName: String,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
@field:Size(min = 8, message = Constants.ValidationMessages.PASSWORD_TOO_SMALL)
|
||||||
|
val password: String,
|
||||||
|
|
||||||
|
val groupId: Long?,
|
||||||
|
|
||||||
|
val permissions: List<Permission>,
|
||||||
|
|
||||||
|
// TODO WN: Test if working
|
||||||
|
// @JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@field:JsonIgnore
|
||||||
|
val isSystemUser: Boolean = false,
|
||||||
|
|
||||||
|
@field:JsonIgnore
|
||||||
|
val isDefaultGroupUser: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserUpdateDto(
|
||||||
|
val id: Long = 0L,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val firstName: String,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val lastName: String,
|
||||||
|
|
||||||
|
val groupId: Long?,
|
||||||
|
|
||||||
|
val permissions: List<Permission>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserLoginRequestDto(val id: Long, val password: String)
|
||||||
|
|
||||||
|
class UserDetails(val user: UserDto) : SpringUserDetails {
|
||||||
|
override fun getPassword() = user.password
|
||||||
|
override fun getUsername() = user.id.toString()
|
||||||
|
override fun getAuthorities() = user.authorities
|
||||||
|
|
||||||
|
override fun isAccountNonExpired() = true
|
||||||
|
override fun isAccountNonLocked() = true
|
||||||
|
override fun isCredentialsNonExpired() = true
|
||||||
|
override fun isEnabled() = true
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.exception
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
|
||||||
|
class NoDefaultGroupException : RestException(
|
||||||
|
"nodefaultgroup",
|
||||||
|
"No default group",
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"No default group cookie is defined in the current request"
|
||||||
|
)
|
|
@ -94,17 +94,6 @@ abstract class BaseLogic<D : EntityDto, S : Service<D, *, *>>(
|
||||||
details
|
details
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun loadRelations(dto: D, relationSelectors: Collection<(D) -> Iterable<*>>) {
|
|
||||||
relationSelectors.map { it(dto) }
|
|
||||||
.forEach {
|
|
||||||
if (it is LazyMapList<*, *>) {
|
|
||||||
it.initialize()
|
|
||||||
} else {
|
|
||||||
println("Can't load :(")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ID_IDENTIFIER_NAME = "id"
|
const val ID_IDENTIFIER_NAME = "id"
|
||||||
const val NAME_IDENTIFIER_NAME = "name"
|
const val NAME_IDENTIFIER_NAME = "name"
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.NamedModelEntity
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.NamedJpaRepository
|
|
||||||
import io.jsonwebtoken.lang.Assert
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service implementing the basics CRUD operations for the given entities.
|
|
||||||
*
|
|
||||||
* @param E The entity type
|
|
||||||
* @param R The entity repository type
|
|
||||||
*/
|
|
||||||
interface OldService<E, R : JpaRepository<E, *>> {
|
|
||||||
val repository: R
|
|
||||||
|
|
||||||
/** Gets all entities. */
|
|
||||||
fun getAll(): Collection<E>
|
|
||||||
|
|
||||||
/** Saves a given [entity]. */
|
|
||||||
fun save(entity: E): E
|
|
||||||
|
|
||||||
/** Updates a given [entity]. */
|
|
||||||
fun update(entity: E): E
|
|
||||||
|
|
||||||
/** Deletes a given [entity]. */
|
|
||||||
fun delete(entity: E)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A service for entities implementing the [ModelEntity] interface. This service add supports for numeric identifiers. */
|
|
||||||
interface ModelService<E : ModelEntity, R : JpaRepository<E, *>> : OldService<E, R> {
|
|
||||||
/** Checks if an entity with the given [id] exists. */
|
|
||||||
fun existsById(id: Long): Boolean
|
|
||||||
|
|
||||||
/** Gets the entity with the given [id]. */
|
|
||||||
fun getById(id: Long): E
|
|
||||||
|
|
||||||
/** Deletes the entity with the given [id]. */
|
|
||||||
fun deleteById(id: Long)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A service for entities implementing the [NamedModelEntity] interface. This service add supports for name identifiers. */
|
|
||||||
interface NamedModelService<E : NamedModelEntity, R : JpaRepository<E, *>> : ModelService<E, R> {
|
|
||||||
/** Checks if an entity with the given [name] exists. */
|
|
||||||
fun existsByName(name: String): Boolean
|
|
||||||
|
|
||||||
/** Gets the entity with the given [name]. */
|
|
||||||
fun getByName(name: String): E
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractService<E, R : JpaRepository<E, *>>(override val repository: R) : OldService<E, R> {
|
|
||||||
override fun getAll(): Collection<E> = repository.findAll()
|
|
||||||
override fun save(entity: E): E = repository.save(entity)
|
|
||||||
override fun update(entity: E): E = repository.save(entity)
|
|
||||||
override fun delete(entity: E) = repository.delete(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractModelService<E : ModelEntity, R : JpaRepository<E, Long>>(repository: R) :
|
|
||||||
AbstractService<E, R>(repository), ModelService<E, R> {
|
|
||||||
protected abstract fun idNotFoundException(id: Long): NotFoundException
|
|
||||||
protected abstract fun idAlreadyExistsException(id: Long): AlreadyExistsException
|
|
||||||
|
|
||||||
override fun existsById(id: Long): Boolean = repository.existsById(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 idAlreadyExistsException(entity.id!!)
|
|
||||||
return super.save(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(entity: E): E {
|
|
||||||
assertId(entity.id)
|
|
||||||
if (!existsById(entity.id!!))
|
|
||||||
throw idNotFoundException(entity.id!!)
|
|
||||||
return super.update(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deleteById(id: Long) =
|
|
||||||
delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing
|
|
||||||
|
|
||||||
protected fun assertId(id: Long?) {
|
|
||||||
Assert.notNull(id, "${javaClass.simpleName}.update() was called with a null identifier")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractNamedModelService<E : NamedModelEntity, 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 nameNotFoundException(name)
|
|
||||||
|
|
||||||
override fun save(entity: E): E {
|
|
||||||
if (existsByName(entity.name))
|
|
||||||
throw nameAlreadyExistsException(entity.name)
|
|
||||||
return super.save(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(entity: E): E {
|
|
||||||
assertId(entity.id)
|
|
||||||
assertName(entity.name)
|
|
||||||
with(repository.findByName(entity.name)) {
|
|
||||||
if (this != null && id != entity.id)
|
|
||||||
throw nameAlreadyExistsException(entity.name)
|
|
||||||
}
|
|
||||||
return super.update(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assertName(name: String) {
|
|
||||||
Assert.notNull(name, "${javaClass.simpleName}.update() was called with a null name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service that will receive *external* interactions, from the REST API, for example.
|
|
||||||
*
|
|
||||||
* @param E The entity type
|
|
||||||
* @param S The entity save DTO type
|
|
||||||
* @param U The entity update DTO type
|
|
||||||
*/
|
|
||||||
interface ExternalService<E, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> : OldService<E, R> {
|
|
||||||
/** Gets all entities mapped to their output model. */
|
|
||||||
fun getAllForOutput(): Collection<O>
|
|
||||||
|
|
||||||
/** Saves a given [entity]. */
|
|
||||||
fun save(entity: S): E = save(entity.toEntity())
|
|
||||||
|
|
||||||
/** Updates a given [entity]. */
|
|
||||||
fun update(entity: U): E
|
|
||||||
|
|
||||||
/** Convert the given entity to its output model. */
|
|
||||||
fun E.toOutput(): O
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An [ExternalService] for entities implementing the [ModelEntity] interface. */
|
|
||||||
interface ExternalModelService<E : ModelEntity, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> :
|
|
||||||
ModelService<E, R>, ExternalService<E, S, U, O, R> {
|
|
||||||
/** Gets the entity with the given [id] mapped to its output model. */
|
|
||||||
fun getByIdForOutput(id: Long): O
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An [ExternalService] for entities implementing the [NamedModelEntity] interface. */
|
|
||||||
interface ExternalNamedModelService<E : NamedModelEntity, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> :
|
|
||||||
NamedModelService<E, R>, ExternalModelService<E, S, U, O, R>
|
|
||||||
|
|
||||||
/** An [AbstractService] with the functionalities of a [ExternalService]. */
|
|
||||||
@Suppress("unused")
|
|
||||||
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>>(repository: R) :
|
|
||||||
AbstractService<E, R>(repository), ExternalService<E, S, U, O, R> {
|
|
||||||
override fun getAllForOutput() =
|
|
||||||
getAll().map { it.toOutput() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An [AbstractModelService] with the functionalities of a [ExternalService]. */
|
|
||||||
abstract class AbstractExternalModelService<E : ModelEntity, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, Long>>(
|
|
||||||
repository: R
|
|
||||||
) : AbstractModelService<E, R>(repository), ExternalModelService<E, S, U, O, R> {
|
|
||||||
override fun getAllForOutput() =
|
|
||||||
getAll().map { it.toOutput() }
|
|
||||||
|
|
||||||
override fun getByIdForOutput(id: Long) =
|
|
||||||
getById(id).toOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An [AbstractNamedModelService] with the functionalities of a [ExternalService]. */
|
|
||||||
abstract class AbstractExternalNamedModelService<E : NamedModelEntity, S : EntityDto<E>, U : EntityDto<E>, O, R : NamedJpaRepository<E>>(
|
|
||||||
repository: R
|
|
||||||
) : AbstractNamedModelService<E, R>(repository), ExternalNamedModelService<E, S, U, O, R> {
|
|
||||||
override fun getAllForOutput() =
|
|
||||||
getAll().map { it.toOutput() }
|
|
||||||
|
|
||||||
override fun getByIdForOutput(id: Long) =
|
|
||||||
getById(id).toOutput()
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.Constants
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||||
|
@ -30,7 +31,7 @@ class DefaultRecipeStepLogic(recipeStepService: RecipeStepService) :
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidGroupStepsPositionsException(
|
class InvalidGroupStepsPositionsException(
|
||||||
val group: Group,
|
val group: GroupDto,
|
||||||
val exception: InvalidPositionsException
|
val exception: InvalidPositionsException
|
||||||
) : RestException(
|
) : RestException(
|
||||||
"invalid-groupinformation-recipestep-position",
|
"invalid-groupinformation-recipestep-position",
|
||||||
|
@ -39,7 +40,7 @@ class InvalidGroupStepsPositionsException(
|
||||||
"The position of steps for the group ${group.name} are invalid",
|
"The position of steps for the group ${group.name} are invalid",
|
||||||
mapOf(
|
mapOf(
|
||||||
"group" to group.name,
|
"group" to group.name,
|
||||||
"groupId" to group.id!!,
|
"groupId" to group.id,
|
||||||
"invalidSteps" to exception.errors
|
"invalidSteps" to exception.errors
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,97 +1,80 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic.users
|
package dev.fyloz.colorrecipesexplorer.logic.users
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||||
import dev.fyloz.colorrecipesexplorer.config.security.defaultGroupCookieName
|
import dev.fyloz.colorrecipesexplorer.config.security.defaultGroupCookieName
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.AbstractExternalNamedModelService
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.ExternalNamedModelService
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
import dev.fyloz.colorrecipesexplorer.exception.NoDefaultGroupException
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
import dev.fyloz.colorrecipesexplorer.logic.BaseLogic
|
||||||
import org.springframework.context.annotation.Profile
|
import dev.fyloz.colorrecipesexplorer.logic.Logic
|
||||||
import org.springframework.stereotype.Service
|
import dev.fyloz.colorrecipesexplorer.service.GroupService
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import org.springframework.web.util.WebUtils
|
import org.springframework.web.util.WebUtils
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import javax.transaction.Transactional
|
|
||||||
|
|
||||||
const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
||||||
|
|
||||||
interface GroupLogic :
|
interface GroupLogic : Logic<GroupDto, GroupService> {
|
||||||
ExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository> {
|
|
||||||
/** Gets all the users of the group with the given [id]. */
|
/** Gets all the users of the group with the given [id]. */
|
||||||
fun getUsersForGroup(id: Long): Collection<User>
|
fun getUsersForGroup(id: Long): Collection<UserDto>
|
||||||
|
|
||||||
/** Gets the default group from a cookie in the given HTTP [request]. */
|
/** Gets the default group from a cookie in the given HTTP [request]. */
|
||||||
fun getRequestDefaultGroup(request: HttpServletRequest): Group
|
fun getRequestDefaultGroup(request: HttpServletRequest): GroupDto
|
||||||
|
|
||||||
/** Sets the default group cookie for the given HTTP [response]. */
|
/** Sets the default group cookie for the given HTTP [response]. */
|
||||||
fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse)
|
fun setResponseDefaultGroup(id: Long, response: HttpServletResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@LogicComponent
|
||||||
@Profile("!emergency")
|
class DefaultGroupLogic(service: GroupService, private val userLogic: UserLogic) :
|
||||||
class DefaultGroupLogic(
|
BaseLogic<GroupDto, GroupService>(service, Constants.ModelNames.GROUP),
|
||||||
private val userLogic: UserLogic,
|
|
||||||
groupRepository: GroupRepository
|
|
||||||
) : AbstractExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository>(
|
|
||||||
groupRepository
|
|
||||||
),
|
|
||||||
GroupLogic {
|
GroupLogic {
|
||||||
override fun idNotFoundException(id: Long) = groupIdNotFoundException(id)
|
override fun getUsersForGroup(id: Long) = userLogic.getAllByGroup(getById(id))
|
||||||
override fun idAlreadyExistsException(id: Long) = groupIdAlreadyExistsException(id)
|
|
||||||
override fun nameNotFoundException(name: String) = groupNameNotFoundException(name)
|
|
||||||
override fun nameAlreadyExistsException(name: String) = groupNameAlreadyExistsException(name)
|
|
||||||
|
|
||||||
override fun Group.toOutput() = GroupOutputDto(
|
override fun getRequestDefaultGroup(request: HttpServletRequest): GroupDto {
|
||||||
this.id!!,
|
|
||||||
this.name,
|
|
||||||
this.permissions,
|
|
||||||
this.flatPermissions
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun existsByName(name: String): Boolean = repository.existsByName(name)
|
|
||||||
override fun getUsersForGroup(id: Long): Collection<User> =
|
|
||||||
userLogic.getByGroup(getById(id))
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun save(entity: Group): Group {
|
|
||||||
return super<AbstractExternalNamedModelService>.save(entity).apply {
|
|
||||||
userLogic.saveDefaultGroupUser(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(entity: GroupUpdateDto): Group {
|
|
||||||
val persistedGroup by lazy { getById(entity.id) }
|
|
||||||
return update(with(entity) {
|
|
||||||
Group(
|
|
||||||
entity.id,
|
|
||||||
if (name.isNotBlank()) entity.name else persistedGroup.name,
|
|
||||||
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun delete(entity: Group) {
|
|
||||||
userLogic.delete(userLogic.getDefaultGroupUser(entity))
|
|
||||||
super.delete(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRequestDefaultGroup(request: HttpServletRequest): Group {
|
|
||||||
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
||||||
?: throw NoDefaultGroupException()
|
?: throw NoDefaultGroupException()
|
||||||
val defaultGroupUser = userLogic.getById(
|
val defaultGroupUser = userLogic.getById(
|
||||||
defaultGroupCookie.value.toLong(),
|
defaultGroupCookie.value.toLong(),
|
||||||
ignoreDefaultGroupUsers = false,
|
isSystemUser = false,
|
||||||
ignoreSystemUsers = true
|
isDefaultGroupUser = true
|
||||||
)
|
)
|
||||||
return defaultGroupUser.group!!
|
return defaultGroupUser.group!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) {
|
override fun setResponseDefaultGroup(id: Long, response: HttpServletResponse) {
|
||||||
val group = getById(groupId)
|
val defaultGroupUser = userLogic.getDefaultGroupUser(getById(id))
|
||||||
val defaultGroupUser = userLogic.getDefaultGroupUser(group)
|
|
||||||
response.addHeader(
|
response.addHeader(
|
||||||
"Set-Cookie",
|
"Set-Cookie",
|
||||||
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=$defaultGroupCookieMaxAge; Path=/api; HttpOnly; Secure; SameSite=strict"
|
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=$defaultGroupCookieMaxAge; Path=/api; HttpOnly; Secure; SameSite=strict"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
override fun save(dto: GroupDto): GroupDto {
|
||||||
|
throwIfNameAlreadyExists(dto.name)
|
||||||
|
|
||||||
|
return super.save(dto).also {
|
||||||
|
userLogic.saveDefaultGroupUser(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(dto: GroupDto): GroupDto {
|
||||||
|
throwIfNameAlreadyExists(dto.name, dto.id)
|
||||||
|
|
||||||
|
return super.update(dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long) {
|
||||||
|
userLogic.deleteById(GroupDto.getDefaultGroupUserId(id))
|
||||||
|
super.deleteById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun throwIfNameAlreadyExists(name: String, id: Long? = null) {
|
||||||
|
if (service.existsByName(name, id)) {
|
||||||
|
throw alreadyExistsException(value = name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,10 +3,8 @@ package dev.fyloz.colorrecipesexplorer.logic.users
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDetails
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserDetails
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserOutputDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.toOutputDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.base64encode
|
import dev.fyloz.colorrecipesexplorer.utils.base64encode
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.toDate
|
import dev.fyloz.colorrecipesexplorer.utils.toDate
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
|
@ -23,10 +21,10 @@ interface JwtLogic {
|
||||||
fun buildJwt(userDetails: UserDetails): String
|
fun buildJwt(userDetails: UserDetails): String
|
||||||
|
|
||||||
/** Build a JWT token for the given [user]. */
|
/** Build a JWT token for the given [user]. */
|
||||||
fun buildJwt(user: User): String
|
fun buildJwt(user: UserDto): String
|
||||||
|
|
||||||
/** Parses a user from the given [jwt] token. */
|
/** Parses a user from the given [jwt] token. */
|
||||||
fun parseJwt(jwt: String): UserOutputDto
|
fun parseJwt(jwt: String): UserDto
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -54,14 +52,14 @@ class DefaultJwtLogic(
|
||||||
override fun buildJwt(userDetails: UserDetails) =
|
override fun buildJwt(userDetails: UserDetails) =
|
||||||
buildJwt(userDetails.user)
|
buildJwt(userDetails.user)
|
||||||
|
|
||||||
override fun buildJwt(user: User): String =
|
override fun buildJwt(user: UserDto): String =
|
||||||
jwtBuilder
|
jwtBuilder
|
||||||
.setSubject(user.id.toString())
|
.setSubject(user.id.toString())
|
||||||
.setExpiration(getCurrentExpirationDate())
|
.setExpiration(getCurrentExpirationDate())
|
||||||
.claim(jwtClaimUser, user.serialize())
|
.claim(jwtClaimUser, user.serialize())
|
||||||
.compact()
|
.compact()
|
||||||
|
|
||||||
override fun parseJwt(jwt: String): UserOutputDto =
|
override fun parseJwt(jwt: String): UserDto =
|
||||||
with(
|
with(
|
||||||
jwtParser.parseClaimsJws(jwt)
|
jwtParser.parseClaimsJws(jwt)
|
||||||
.body.get(jwtClaimUser, String::class.java)
|
.body.get(jwtClaimUser, String::class.java)
|
||||||
|
@ -74,6 +72,6 @@ class DefaultJwtLogic(
|
||||||
.plusSeconds(securityProperties.jwtDuration)
|
.plusSeconds(securityProperties.jwtDuration)
|
||||||
.toDate()
|
.toDate()
|
||||||
|
|
||||||
private fun User.serialize(): String =
|
private fun UserDto.serialize(): String =
|
||||||
objectMapper.writeValueAsString(this.toOutputDto())
|
objectMapper.writeValueAsString(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,18 @@ import dev.fyloz.colorrecipesexplorer.SpringUserDetails
|
||||||
import dev.fyloz.colorrecipesexplorer.SpringUserDetailsService
|
import dev.fyloz.colorrecipesexplorer.SpringUserDetailsService
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDetails
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserDetails
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.user
|
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.context.annotation.Profile
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
interface UserDetailsLogic : SpringUserDetailsService {
|
interface UserDetailsLogic : SpringUserDetailsService {
|
||||||
/** Loads an [User] for the given [id]. */
|
/** Loads an [User] for the given [id]. */
|
||||||
fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails
|
fun loadUserById(id: Long, isDefaultGroupUser: Boolean = true): UserDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -25,17 +25,17 @@ class DefaultUserDetailsLogic(
|
||||||
) : UserDetailsLogic {
|
) : UserDetailsLogic {
|
||||||
override fun loadUserByUsername(username: String): UserDetails {
|
override fun loadUserByUsername(username: String): UserDetails {
|
||||||
try {
|
try {
|
||||||
return loadUserById(username.toLong(), true)
|
return loadUserById(username.toLong(), false)
|
||||||
} catch (ex: NotFoundException) {
|
} catch (ex: NotFoundException) {
|
||||||
throw UsernameNotFoundException(username)
|
throw UsernameNotFoundException(username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
override fun loadUserById(id: Long, isDefaultGroupUser: Boolean): UserDetails {
|
||||||
val user = userLogic.getById(
|
val user = userLogic.getById(
|
||||||
id,
|
id,
|
||||||
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
isSystemUser = true,
|
||||||
ignoreSystemUsers = false
|
isDefaultGroupUser = isDefaultGroupUser
|
||||||
)
|
)
|
||||||
return UserDetails(user)
|
return UserDetails(user)
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class DefaultUserDetailsLogic(
|
||||||
class EmergencyUserDetailsLogic(
|
class EmergencyUserDetailsLogic(
|
||||||
securityProperties: CreSecurityProperties
|
securityProperties: CreSecurityProperties
|
||||||
) : UserDetailsLogic {
|
) : UserDetailsLogic {
|
||||||
private val users: Set<User>
|
private val users: Set<UserDto>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (securityProperties.root == null) {
|
if (securityProperties.root == null) {
|
||||||
|
@ -56,20 +56,23 @@ class EmergencyUserDetailsLogic(
|
||||||
users = setOf(
|
users = setOf(
|
||||||
// Add root user
|
// Add root user
|
||||||
with(securityProperties.root!!) {
|
with(securityProperties.root!!) {
|
||||||
user(
|
UserDto(
|
||||||
id = this.id,
|
id = this.id,
|
||||||
plainPassword = this.password,
|
firstName = "Root",
|
||||||
permissions = mutableSetOf(Permission.ADMIN)
|
lastName = "User",
|
||||||
|
group = null,
|
||||||
|
password = this.password,
|
||||||
|
permissions = listOf(Permission.ADMIN)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadUserByUsername(username: String): SpringUserDetails {
|
override fun loadUserByUsername(username: String): SpringUserDetails {
|
||||||
return loadUserById(username.toLong(), true)
|
return loadUserById(username.toLong(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
override fun loadUserById(id: Long, isDefaultGroupUser: Boolean): UserDetails {
|
||||||
val user = users.firstOrNull { it.id == id }
|
val user = users.firstOrNull { it.id == id }
|
||||||
?: throw UsernameNotFoundException(id.toString())
|
?: throw UsernameNotFoundException(id.toString())
|
||||||
|
|
||||||
|
|
|
@ -1,189 +1,146 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic.users
|
package dev.fyloz.colorrecipesexplorer.logic.users
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.security.authorizationCookieName
|
||||||
import dev.fyloz.colorrecipesexplorer.config.security.blacklistedJwtTokens
|
import dev.fyloz.colorrecipesexplorer.config.security.blacklistedJwtTokens
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.AbstractExternalModelService
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.ExternalModelService
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
import dev.fyloz.colorrecipesexplorer.dtos.UserSaveDto
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
import dev.fyloz.colorrecipesexplorer.dtos.UserUpdateDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.BaseLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.Logic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import dev.fyloz.colorrecipesexplorer.service.UserService
|
||||||
import org.springframework.context.annotation.Lazy
|
import org.springframework.context.annotation.Lazy
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import org.springframework.web.util.WebUtils
|
import org.springframework.web.util.WebUtils
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
interface UserLogic :
|
interface UserLogic : Logic<UserDto, UserService> {
|
||||||
ExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository> {
|
/** Gets all users which have the given [group]. */
|
||||||
/** Check if an [User] with the given [firstName] and [lastName] exists. */
|
fun getAllByGroup(group: GroupDto): Collection<UserDto>
|
||||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
|
||||||
|
|
||||||
/** Gets the user with the given [id]. */
|
/** Gets the user with the given [id]. */
|
||||||
fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User
|
fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean): UserDto
|
||||||
|
|
||||||
/** Gets all users which have the given [group]. */
|
|
||||||
fun getByGroup(group: Group): Collection<User>
|
|
||||||
|
|
||||||
/** Gets the default user of the given [group]. */
|
/** Gets the default user of the given [group]. */
|
||||||
fun getDefaultGroupUser(group: Group): User
|
fun getDefaultGroupUser(group: GroupDto): UserDto
|
||||||
|
|
||||||
/** Save a default group user for the given [group]. */
|
/** Save a default group user for the given [group]. */
|
||||||
fun saveDefaultGroupUser(group: Group)
|
fun saveDefaultGroupUser(group: GroupDto)
|
||||||
|
|
||||||
/** Updates de given [entity]. **/
|
/** Saves the given [dto]. */
|
||||||
fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User
|
fun save(dto: UserSaveDto): UserDto
|
||||||
|
|
||||||
/** Updates the last login time of the user with the given [userId]. */
|
/** Updates the given [dto]. */
|
||||||
fun updateLastLoginTime(userId: Long, time: LocalDateTime = LocalDateTime.now()): User
|
fun update(dto: UserUpdateDto): UserDto
|
||||||
|
|
||||||
|
/** Updates the last login time of the user with the given [id]. */
|
||||||
|
fun updateLastLoginTime(id: Long, time: LocalDateTime = LocalDateTime.now()): UserDto
|
||||||
|
|
||||||
/** Updates the password of the user with the given [id]. */
|
/** Updates the password of the user with the given [id]. */
|
||||||
fun updatePassword(id: Long, password: String): User
|
fun updatePassword(id: Long, password: String): UserDto
|
||||||
|
|
||||||
/** Adds the given [permission] to the user with the given [userId]. */
|
/** Adds the given [permission] to the user with the given [id]. */
|
||||||
fun addPermission(userId: Long, permission: Permission): User
|
fun addPermission(id: Long, permission: Permission): UserDto
|
||||||
|
|
||||||
/** Removes the given [permission] from the user with the given [userId]. */
|
/** Removes the given [permission] from the user with the given [id]. */
|
||||||
fun removePermission(userId: Long, permission: Permission): User
|
fun removePermission(id: Long, permission: Permission): UserDto
|
||||||
|
|
||||||
/** Logout an user. Add the authorization token of the given [request] to the blacklisted tokens. */
|
/** Logout a user. Add the authorization token of the given [request] to the blacklisted tokens. */
|
||||||
fun logout(request: HttpServletRequest)
|
fun logout(request: HttpServletRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@LogicComponent
|
||||||
@Profile("!emergency")
|
|
||||||
class DefaultUserLogic(
|
class DefaultUserLogic(
|
||||||
userRepository: UserRepository,
|
service: UserService, @Lazy private val groupLogic: GroupLogic, @Lazy private val passwordEncoder: PasswordEncoder
|
||||||
@Lazy val groupLogic: GroupLogic,
|
) : BaseLogic<UserDto, UserService>(service, Constants.ModelNames.USER), UserLogic {
|
||||||
) : AbstractExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository>(
|
override fun getAll() = service.getAll(isSystemUser = false, isDefaultGroupUser = false)
|
||||||
userRepository
|
|
||||||
),
|
|
||||||
UserLogic {
|
|
||||||
override fun idNotFoundException(id: Long) = userIdNotFoundException(id)
|
|
||||||
override fun idAlreadyExistsException(id: Long) = userIdAlreadyExistsException(id)
|
|
||||||
|
|
||||||
override fun User.toOutput() = this.toOutputDto()
|
override fun getAllByGroup(group: GroupDto) = service.getAllByGroup(group)
|
||||||
|
|
||||||
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
|
override fun getById(id: Long) = getById(id, isSystemUser = false, isDefaultGroupUser = false)
|
||||||
repository.existsByFirstNameAndLastName(firstName, lastName)
|
override fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean) =
|
||||||
|
service.getById(id, !isDefaultGroupUser, !isSystemUser) ?: throw notFoundException(value = id)
|
||||||
|
|
||||||
override fun getAll(): Collection<User> =
|
override fun getDefaultGroupUser(group: GroupDto) =
|
||||||
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
|
service.getDefaultGroupUser(group) ?: throw notFoundException(identifierName = "groupId", value = group.id)
|
||||||
|
|
||||||
override fun getById(id: Long): User =
|
override fun saveDefaultGroupUser(group: GroupDto) {
|
||||||
getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
|
||||||
|
|
||||||
override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User =
|
|
||||||
super.getById(id).apply {
|
|
||||||
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
|
|
||||||
throw idNotFoundException(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getByGroup(group: Group): Collection<User> =
|
|
||||||
repository.findAllByGroup(group).filter {
|
|
||||||
!it.isSystemUser && !it.isDefaultGroupUser
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDefaultGroupUser(group: Group): User =
|
|
||||||
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
|
|
||||||
|
|
||||||
override fun save(entity: UserSaveDto): User =
|
|
||||||
save(with(entity) {
|
|
||||||
user(
|
|
||||||
id = id,
|
|
||||||
firstName = firstName,
|
|
||||||
lastName = lastName,
|
|
||||||
plainPassword = password,
|
|
||||||
isDefaultGroupUser = false,
|
|
||||||
isSystemUser = false,
|
|
||||||
group = if (groupId != null) groupLogic.getById(groupId) else null,
|
|
||||||
permissions = permissions
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
override fun save(entity: User): User {
|
|
||||||
if (existsById(entity.id))
|
|
||||||
throw userIdAlreadyExistsException(entity.id)
|
|
||||||
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
|
|
||||||
throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
|
||||||
return super<AbstractExternalModelService>.save(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveDefaultGroupUser(group: Group) {
|
|
||||||
save(
|
save(
|
||||||
user(
|
UserSaveDto(
|
||||||
id = 1000000L + group.id!!,
|
id = group.defaultGroupUserId,
|
||||||
firstName = group.name,
|
firstName = group.name,
|
||||||
lastName = "User",
|
lastName = "User",
|
||||||
plainPassword = group.name,
|
password = group.name,
|
||||||
group = group,
|
groupId = group.id,
|
||||||
|
permissions = listOf(),
|
||||||
isDefaultGroupUser = true
|
isDefaultGroupUser = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateLastLoginTime(userId: Long, time: LocalDateTime): User {
|
override fun save(dto: UserSaveDto) = save(
|
||||||
val user = getById(userId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
|
UserDto(
|
||||||
user.lastLoginTime = time
|
id = dto.id,
|
||||||
|
firstName = dto.firstName,
|
||||||
|
lastName = dto.lastName,
|
||||||
|
password = passwordEncoder.encode(dto.password),
|
||||||
|
group = if (dto.groupId != null) groupLogic.getById(dto.groupId) else null,
|
||||||
|
permissions = dto.permissions,
|
||||||
|
isSystemUser = dto.isSystemUser,
|
||||||
|
isDefaultGroupUser = dto.isDefaultGroupUser
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun save(dto: UserDto): UserDto {
|
||||||
|
throwIfIdAlreadyExists(dto.id)
|
||||||
|
throwIfFirstNameAndLastNameAlreadyExists(dto.firstName, dto.lastName)
|
||||||
|
|
||||||
|
return super.save(dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(dto: UserUpdateDto): UserDto {
|
||||||
|
val user = getById(dto.id, isSystemUser = false, isDefaultGroupUser = false)
|
||||||
|
|
||||||
return update(
|
return update(
|
||||||
user,
|
user.copy(
|
||||||
ignoreDefaultGroupUsers = true,
|
firstName = dto.firstName,
|
||||||
ignoreSystemUsers = false
|
lastName = dto.lastName,
|
||||||
|
group = if (dto.groupId != null) groupLogic.getById(dto.groupId) else null,
|
||||||
|
permissions = dto.permissions
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(entity: UserUpdateDto): User {
|
override fun update(dto: UserDto): UserDto {
|
||||||
val persistedUser by lazy { getById(entity.id) }
|
throwIfFirstNameAndLastNameAlreadyExists(dto.firstName, dto.lastName, dto.id)
|
||||||
return update(with(entity) {
|
|
||||||
User(
|
return super.update(dto)
|
||||||
id = id,
|
|
||||||
firstName = firstName ?: persistedUser.firstName,
|
|
||||||
lastName = lastName ?: persistedUser.lastName,
|
|
||||||
password = persistedUser.password,
|
|
||||||
isDefaultGroupUser = false,
|
|
||||||
isSystemUser = false,
|
|
||||||
group = if (entity.groupId != null) groupLogic.getById(entity.groupId) else persistedUser.group,
|
|
||||||
permissions = permissions?.toMutableSet() ?: persistedUser.permissions,
|
|
||||||
lastLoginTime = persistedUser.lastLoginTime
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(entity: User): User =
|
override fun updateLastLoginTime(id: Long, time: LocalDateTime) = with(getById(id)) {
|
||||||
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
update(this.copy(lastLoginTime = time))
|
||||||
|
|
||||||
override fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User {
|
|
||||||
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
|
|
||||||
if (this != null && id != entity.id)
|
|
||||||
throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.update(entity)
|
override fun updatePassword(id: Long, password: String) = with(getById(id)) {
|
||||||
|
update(this.copy(password = passwordEncoder.encode(password)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePassword(id: Long, password: String): User {
|
override fun addPermission(id: Long, permission: Permission) = with(getById(id)) {
|
||||||
val persistedUser = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
update(this.copy(permissions = this.permissions + permission))
|
||||||
return super.update(with(persistedUser) {
|
|
||||||
user(
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
plainPassword = password,
|
|
||||||
isDefaultGroupUser,
|
|
||||||
isSystemUser,
|
|
||||||
group,
|
|
||||||
permissions,
|
|
||||||
lastLoginTime
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addPermission(userId: Long, permission: Permission): User =
|
override fun removePermission(id: Long, permission: Permission) = with(getById(id)) {
|
||||||
super.update(getById(userId).apply { permissions += permission })
|
update(this.copy(permissions = this.permissions - permission))
|
||||||
|
}
|
||||||
override fun removePermission(userId: Long, permission: Permission): User =
|
|
||||||
super.update(getById(userId).apply { permissions -= permission })
|
|
||||||
|
|
||||||
override fun logout(request: HttpServletRequest) {
|
override fun logout(request: HttpServletRequest) {
|
||||||
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
|
val authorizationCookie = WebUtils.getCookie(request, authorizationCookieName)
|
||||||
if (authorizationCookie != null) {
|
if (authorizationCookie != null) {
|
||||||
val authorizationToken = authorizationCookie.value
|
val authorizationToken = authorizationCookie.value
|
||||||
if (authorizationToken != null && authorizationToken.startsWith("Bearer")) {
|
if (authorizationToken != null && authorizationToken.startsWith("Bearer")) {
|
||||||
|
@ -191,4 +148,22 @@ class DefaultUserLogic(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun throwIfIdAlreadyExists(id: Long) {
|
||||||
|
if (service.existsById(id)) {
|
||||||
|
throw alreadyExistsException(identifierName = ID_IDENTIFIER_NAME, value = id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun throwIfFirstNameAndLastNameAlreadyExists(firstName: String, lastName: String, id: Long? = null) {
|
||||||
|
if (service.existsByFirstNameAndLastName(firstName, lastName, id)) {
|
||||||
|
throw AlreadyExistsException(
|
||||||
|
typeNameLowerCase,
|
||||||
|
"$typeName already exists",
|
||||||
|
"A $typeNameLowerCase with the name '$firstName $lastName' already exists",
|
||||||
|
"$firstName $lastName",
|
||||||
|
"fullName"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ import javax.persistence.*
|
||||||
data class Company(
|
data class Company(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
val name: String
|
val name: String
|
||||||
|
|
|
@ -8,7 +8,7 @@ import javax.persistence.*
|
||||||
data class Material(
|
data class Material(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import javax.persistence.*
|
||||||
data class MaterialType(
|
data class MaterialType(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long? = null,
|
override val id: Long,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
|
|
|
@ -7,9 +7,9 @@ import javax.persistence.*
|
||||||
data class Mix(
|
data class Mix(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
var location: String?,
|
val location: String?,
|
||||||
|
|
||||||
@Column(name = "recipe_id")
|
@Column(name = "recipe_id")
|
||||||
val recipeId: Long,
|
val recipeId: Long,
|
||||||
|
|
|
@ -7,13 +7,13 @@ import javax.persistence.*
|
||||||
data class MixMaterial(
|
data class MixMaterial(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "material_id")
|
@JoinColumn(name = "material_id")
|
||||||
val material: Material,
|
val material: Material,
|
||||||
|
|
||||||
var quantity: Float,
|
val quantity: Float,
|
||||||
|
|
||||||
var position: Int
|
val position: Int
|
||||||
) : ModelEntity
|
) : ModelEntity
|
|
@ -7,7 +7,7 @@ import javax.persistence.*
|
||||||
data class MixType(
|
data class MixType(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model
|
package dev.fyloz.colorrecipesexplorer.model
|
||||||
|
|
||||||
/** Represents an entity, named differently to prevent conflicts with the JPA annotation. */
|
/** Represents an entity with an id, named differently to prevent conflicts with the JPA annotation. */
|
||||||
interface ModelEntity {
|
interface ModelEntity {
|
||||||
val id: Long?
|
val id: Long
|
||||||
}
|
|
||||||
|
|
||||||
interface NamedModelEntity : ModelEntity {
|
|
||||||
val name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EntityDto<out E> {
|
|
||||||
/** Converts the dto to an actual entity. */
|
|
||||||
fun toEntity(): E {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ import javax.persistence.*
|
||||||
data class Recipe(
|
data class Recipe(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@ -47,15 +47,15 @@ data class Recipe(
|
||||||
data class RecipeGroupInformation(
|
data class RecipeGroupInformation(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "group_id")
|
@JoinColumn(name = "group_id")
|
||||||
val group: Group,
|
val group: Group,
|
||||||
|
|
||||||
var note: String?,
|
val note: String?,
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||||
@JoinColumn(name = "recipe_group_information_id")
|
@JoinColumn(name = "recipe_group_information_id")
|
||||||
var steps: List<RecipeStep>?
|
val steps: List<RecipeStep>?
|
||||||
) : ModelEntity
|
) : ModelEntity
|
|
@ -7,7 +7,7 @@ import javax.persistence.*
|
||||||
data class RecipeStep(
|
data class RecipeStep(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
val position: Int,
|
val position: Int,
|
||||||
|
|
||||||
|
|
|
@ -1,134 +1,24 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model.account
|
package dev.fyloz.colorrecipesexplorer.model.account
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
||||||
import org.hibernate.annotations.Fetch
|
import org.hibernate.annotations.Fetch
|
||||||
import org.hibernate.annotations.FetchMode
|
import org.hibernate.annotations.FetchMode
|
||||||
import org.springframework.http.HttpStatus
|
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.NotBlank
|
|
||||||
import javax.validation.constraints.NotEmpty
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "user_group")
|
@Table(name = "user_group")
|
||||||
data class Group(
|
data class Group(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override var id: Long? = null,
|
override val id: Long,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
override val name: String = "",
|
val name: String,
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
|
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
|
||||||
@Column(name = "permission")
|
@Column(name = "permission")
|
||||||
@Fetch(FetchMode.SUBSELECT)
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
val permissions: MutableSet<Permission> = mutableSetOf(),
|
val permissions: List<Permission>,
|
||||||
) : NamedModelEntity {
|
) : ModelEntity
|
||||||
val flatPermissions: Set<Permission>
|
|
||||||
get() = this.permissions
|
|
||||||
.flatMap { it.flat() }
|
|
||||||
.filter { !it.deprecated }
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
open class GroupSaveDto(
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:NotEmpty
|
|
||||||
val permissions: MutableSet<Permission>
|
|
||||||
) : EntityDto<Group> {
|
|
||||||
override fun toEntity(): Group =
|
|
||||||
Group(null, name, permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
open class GroupUpdateDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:NotEmpty
|
|
||||||
val permissions: MutableSet<Permission>
|
|
||||||
) : EntityDto<Group> {
|
|
||||||
override fun toEntity(): Group =
|
|
||||||
Group(id, name, permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class GroupOutputDto(
|
|
||||||
override val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val permissions: Set<Permission>,
|
|
||||||
val explicitPermissions: Set<Permission>
|
|
||||||
): ModelEntity
|
|
||||||
|
|
||||||
fun group(
|
|
||||||
id: Long? = null,
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
op: Group.() -> Unit = {}
|
|
||||||
) = Group(id, name, permissions).apply(op)
|
|
||||||
|
|
||||||
fun groupSaveDto(
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
op: GroupSaveDto.() -> Unit = {}
|
|
||||||
) = GroupSaveDto(name, permissions).apply(op)
|
|
||||||
|
|
||||||
fun groupUpdateDto(
|
|
||||||
id: Long = 0L,
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
op: GroupUpdateDto.() -> Unit = {}
|
|
||||||
) = GroupUpdateDto(id, name, permissions).apply(op)
|
|
||||||
|
|
||||||
// ==== Exceptions ====
|
|
||||||
private const val GROUP_NOT_FOUND_EXCEPTION_TITLE = "Group not found"
|
|
||||||
private const val GROUP_ALREADY_EXISTS_EXCEPTION_TITLE = "Group already exists"
|
|
||||||
private const val GROUP_EXCEPTION_ERROR_CODE = "group"
|
|
||||||
|
|
||||||
class NoDefaultGroupException : RestException(
|
|
||||||
"nodefaultgroup",
|
|
||||||
"No default group",
|
|
||||||
HttpStatus.NOT_FOUND,
|
|
||||||
"No default group cookie is defined in the current request"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun groupIdNotFoundException(id: Long) =
|
|
||||||
NotFoundException(
|
|
||||||
GROUP_EXCEPTION_ERROR_CODE,
|
|
||||||
GROUP_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"A group with the id $id could not be found",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun groupNameNotFoundException(name: String) =
|
|
||||||
NotFoundException(
|
|
||||||
GROUP_EXCEPTION_ERROR_CODE,
|
|
||||||
GROUP_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"A group with the name $name could not be found",
|
|
||||||
name,
|
|
||||||
"name"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun groupIdAlreadyExistsException(id: Long) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
GROUP_EXCEPTION_ERROR_CODE,
|
|
||||||
GROUP_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"A group with the id $id already exists",
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun groupNameAlreadyExistsException(name: String) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
GROUP_EXCEPTION_ERROR_CODE,
|
|
||||||
GROUP_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"A group with the name $name already exists",
|
|
||||||
name,
|
|
||||||
"name"
|
|
||||||
)
|
|
|
@ -1,20 +1,10 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model.account
|
package dev.fyloz.colorrecipesexplorer.model.account
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.SpringUserDetails
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
||||||
import org.hibernate.annotations.Fetch
|
import org.hibernate.annotations.Fetch
|
||||||
import org.hibernate.annotations.FetchMode
|
import org.hibernate.annotations.FetchMode
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.NotBlank
|
|
||||||
import javax.validation.constraints.Size
|
|
||||||
|
|
||||||
private const val VALIDATION_PASSWORD_LENGTH = "Must contains at least 8 characters"
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "user")
|
@Table(name = "user")
|
||||||
|
@ -23,210 +13,31 @@ data class User(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
|
|
||||||
@Column(name = "first_name")
|
@Column(name = "first_name")
|
||||||
val firstName: String = "",
|
val firstName: String,
|
||||||
|
|
||||||
@Column(name = "last_name")
|
@Column(name = "last_name")
|
||||||
val lastName: String = "",
|
val lastName: String,
|
||||||
|
|
||||||
val password: String = "",
|
val password: String,
|
||||||
|
|
||||||
@Column(name = "default_group_user")
|
@Column(name = "default_group_user")
|
||||||
val isDefaultGroupUser: Boolean = false,
|
val isDefaultGroupUser: Boolean,
|
||||||
|
|
||||||
@Column(name = "system_user")
|
@Column(name = "system_user")
|
||||||
val isSystemUser: Boolean = false,
|
val isSystemUser: Boolean,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "group_id")
|
@JoinColumn(name = "group_id")
|
||||||
@Fetch(FetchMode.SELECT)
|
@Fetch(FetchMode.SELECT)
|
||||||
var group: Group? = null,
|
val group: Group?,
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@CollectionTable(name = "user_permission", joinColumns = [JoinColumn(name = "user_id")])
|
@CollectionTable(name = "user_permission", joinColumns = [JoinColumn(name = "user_id")])
|
||||||
@Column(name = "permission")
|
@Column(name = "permission")
|
||||||
@Fetch(FetchMode.SUBSELECT)
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
val permissions: MutableSet<Permission> = mutableSetOf(),
|
val permissions: List<Permission>,
|
||||||
|
|
||||||
@Column(name = "last_login_time")
|
@Column(name = "last_login_time")
|
||||||
var lastLoginTime: LocalDateTime? = null
|
|
||||||
) : ModelEntity {
|
|
||||||
val flatPermissions: Set<Permission>
|
|
||||||
get() = permissions
|
|
||||||
.flatMap { it.flat() }
|
|
||||||
.filter { !it.deprecated }
|
|
||||||
.toMutableSet()
|
|
||||||
.apply {
|
|
||||||
if (group != null) this.addAll(group!!.flatPermissions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class UserSaveDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val firstName: String,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val lastName: String,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
@field:Size(min = 8, message = VALIDATION_PASSWORD_LENGTH)
|
|
||||||
val password: String,
|
|
||||||
|
|
||||||
val groupId: Long?,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val permissions: MutableSet<Permission> = mutableSetOf()
|
|
||||||
) : EntityDto<User>
|
|
||||||
|
|
||||||
open class UserUpdateDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val firstName: String?,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val lastName: String?,
|
|
||||||
|
|
||||||
val groupId: Long?,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val permissions: Set<Permission>?
|
|
||||||
) : EntityDto<User>
|
|
||||||
|
|
||||||
data class UserOutputDto(
|
|
||||||
override val id: Long,
|
|
||||||
val firstName: String,
|
|
||||||
val lastName: String,
|
|
||||||
val group: Group?,
|
|
||||||
val permissions: Set<Permission>,
|
|
||||||
val explicitPermissions: Set<Permission>,
|
|
||||||
val lastLoginTime: LocalDateTime?
|
val lastLoginTime: LocalDateTime?
|
||||||
) : ModelEntity
|
) : ModelEntity
|
||||||
|
|
||||||
data class UserLoginRequest(val id: Long, val password: String)
|
|
||||||
|
|
||||||
data class UserDetails(val user: User) : SpringUserDetails {
|
|
||||||
override fun getPassword() = user.password
|
|
||||||
override fun getUsername() = user.id.toString()
|
|
||||||
override fun getAuthorities() = user.flatPermissions.toAuthorities()
|
|
||||||
|
|
||||||
override fun isAccountNonExpired() = true
|
|
||||||
override fun isAccountNonLocked() = true
|
|
||||||
override fun isCredentialsNonExpired() = true
|
|
||||||
override fun isEnabled() = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== DSL ====
|
|
||||||
fun user(
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
password: String = "password",
|
|
||||||
isDefaultGroupUser: Boolean = false,
|
|
||||||
isSystemUser: Boolean = false,
|
|
||||||
group: Group? = null,
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
lastLoginTime: LocalDateTime? = null,
|
|
||||||
op: User.() -> Unit = {}
|
|
||||||
) = User(
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
isDefaultGroupUser,
|
|
||||||
isSystemUser,
|
|
||||||
group,
|
|
||||||
permissions,
|
|
||||||
lastLoginTime
|
|
||||||
).apply(op)
|
|
||||||
|
|
||||||
fun user(
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
plainPassword: String = "password",
|
|
||||||
isDefaultGroupUser: Boolean = false,
|
|
||||||
isSystemUser: Boolean = false,
|
|
||||||
group: Group? = null,
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
lastLoginTime: LocalDateTime? = null,
|
|
||||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
|
||||||
op: User.() -> Unit = {}
|
|
||||||
) = User(
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
passwordEncoder.encode(plainPassword),
|
|
||||||
isDefaultGroupUser,
|
|
||||||
isSystemUser,
|
|
||||||
group,
|
|
||||||
permissions,
|
|
||||||
lastLoginTime
|
|
||||||
).apply(op)
|
|
||||||
|
|
||||||
fun userSaveDto(
|
|
||||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
password: String = passwordEncoder.encode("password"),
|
|
||||||
groupId: Long? = null,
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
op: UserSaveDto.() -> Unit = {}
|
|
||||||
) = UserSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
|
|
||||||
|
|
||||||
fun userUpdateDto(
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
groupId: Long? = null,
|
|
||||||
permissions: MutableSet<Permission> = mutableSetOf(),
|
|
||||||
op: UserUpdateDto.() -> Unit = {}
|
|
||||||
) = UserUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
|
|
||||||
|
|
||||||
// ==== Extensions ====
|
|
||||||
fun Set<Permission>.toAuthorities() =
|
|
||||||
this.map { it.toAuthority() }.toMutableSet()
|
|
||||||
|
|
||||||
fun User.toOutputDto() =
|
|
||||||
UserOutputDto(
|
|
||||||
this.id,
|
|
||||||
this.firstName,
|
|
||||||
this.lastName,
|
|
||||||
this.group,
|
|
||||||
this.flatPermissions,
|
|
||||||
this.permissions,
|
|
||||||
this.lastLoginTime
|
|
||||||
)
|
|
||||||
|
|
||||||
// ==== Exceptions ====
|
|
||||||
private const val USER_NOT_FOUND_EXCEPTION_TITLE = "User not found"
|
|
||||||
private const val USER_ALREADY_EXISTS_EXCEPTION_TITLE = "User already exists"
|
|
||||||
private const val USER_EXCEPTION_ERROR_CODE = "user"
|
|
||||||
|
|
||||||
fun userIdNotFoundException(id: Long) =
|
|
||||||
NotFoundException(
|
|
||||||
USER_EXCEPTION_ERROR_CODE,
|
|
||||||
USER_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"An user with the id $id could not be found",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun userIdAlreadyExistsException(id: Long) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
USER_EXCEPTION_ERROR_CODE,
|
|
||||||
USER_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"An user with the id $id already exists",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun userFullNameAlreadyExistsException(firstName: String, lastName: String) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
USER_EXCEPTION_ERROR_CODE,
|
|
||||||
USER_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"An user with the name '$firstName $lastName' already exists",
|
|
||||||
"$firstName $lastName",
|
|
||||||
"fullName"
|
|
||||||
)
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import javax.persistence.*
|
||||||
data class TouchUpKit(
|
data class TouchUpKit(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
val project: String,
|
val project: String,
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ data class TouchUpKit(
|
||||||
data class TouchUpKitProduct(
|
data class TouchUpKitProduct(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
override val id: Long?,
|
override val id: Long,
|
||||||
|
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,28 @@ package dev.fyloz.colorrecipesexplorer.repository
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface UserRepository : JpaRepository<User, Long> {
|
interface UserRepository : JpaRepository<User, Long> {
|
||||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
/** Checks if a user with the given [firstName], [lastName] and a different [id] exists. */
|
||||||
|
fun existsByFirstNameAndLastNameAndIdNot(firstName: String, lastName: String, id: Long): Boolean
|
||||||
fun findByFirstNameAndLastName(firstName: String, lastName: String): User?
|
|
||||||
|
|
||||||
|
/** Finds all users for the given [group]. */
|
||||||
|
@Query("SELECT u FROM User u WHERE u.group = :group AND u.isSystemUser IS FALSE AND u.isDefaultGroupUser IS FALSE")
|
||||||
fun findAllByGroup(group: Group): Collection<User>
|
fun findAllByGroup(group: Group): Collection<User>
|
||||||
|
|
||||||
fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: Group): User
|
/** Finds the user with the given [firstName] and [lastName]. */
|
||||||
|
fun findByFirstNameAndLastName(firstName: String, lastName: String): User?
|
||||||
|
|
||||||
|
/** Finds the default user for the given [group]. */
|
||||||
|
@Query("SELECT u FROM User u WHERE u.group = :group AND u.isDefaultGroupUser IS TRUE")
|
||||||
|
fun findDefaultGroupUser(group: Group): User?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface GroupRepository : NamedJpaRepository<Group>
|
interface GroupRepository : JpaRepository<Group, Long> {
|
||||||
|
/** Checks if a group with the given [name] and a different [id] exists. */
|
||||||
|
fun existsByNameAndIdNot(name: String, id: Long): Boolean
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.repository
|
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.NamedModelEntity
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
import org.springframework.data.repository.NoRepositoryBean
|
|
||||||
|
|
||||||
/** Adds support for entities using a name identifier. */
|
|
||||||
@NoRepositoryBean
|
|
||||||
interface NamedJpaRepository<E : NamedModelEntity> : JpaRepository<E, Long> {
|
|
||||||
/** Checks if an entity with the given [name]. */
|
|
||||||
fun existsByName(name: String): Boolean
|
|
||||||
|
|
||||||
/** Gets the entity with the given [name]. */
|
|
||||||
fun findByName(name: String): E?
|
|
||||||
|
|
||||||
/** Removes the entity with the given [name]. */
|
|
||||||
fun deleteByName(name: String)
|
|
||||||
}
|
|
|
@ -1,10 +1,15 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
|
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers
|
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserSaveDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserUpdateDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.context.annotation.Profile
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
@ -13,30 +18,25 @@ import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import javax.validation.Valid
|
import javax.validation.Valid
|
||||||
|
|
||||||
private const val USER_CONTROLLER_PATH = "api/user"
|
|
||||||
private const val GROUP_CONTROLLER_PATH = "api/user/group"
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(USER_CONTROLLER_PATH)
|
@RequestMapping(Constants.ControllerPaths.USER)
|
||||||
@Profile("!emergency")
|
@Profile("!emergency")
|
||||||
class UserController(private val userLogic: UserLogic) {
|
class UserController(private val userLogic: UserLogic) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getAll() =
|
fun getAll() =
|
||||||
ok(userLogic.getAllForOutput())
|
ok(userLogic.getAll())
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
ok(userLogic.getByIdForOutput(id))
|
ok(userLogic.getById(id))
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun save(@Valid @RequestBody user: UserSaveDto) =
|
fun save(@Valid @RequestBody user: UserSaveDto) =
|
||||||
created<UserOutputDto>(USER_CONTROLLER_PATH) {
|
created<UserDto>(Constants.ControllerPaths.USER) {
|
||||||
with(userLogic) {
|
userLogic.save(user)
|
||||||
save(user).toOutput()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
|
@ -78,7 +78,7 @@ class UserController(private val userLogic: UserLogic) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(GROUP_CONTROLLER_PATH)
|
@RequestMapping(Constants.ControllerPaths.GROUP)
|
||||||
@Profile("!emergency")
|
@Profile("!emergency")
|
||||||
class GroupsController(
|
class GroupsController(
|
||||||
private val groupLogic: GroupLogic,
|
private val groupLogic: GroupLogic,
|
||||||
|
@ -87,20 +87,17 @@ class GroupsController(
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
||||||
fun getAll() =
|
fun getAll() =
|
||||||
ok(groupLogic.getAllForOutput())
|
ok(groupLogic.getAll())
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
ok(groupLogic.getByIdForOutput(id))
|
ok(groupLogic.getById(id))
|
||||||
|
|
||||||
@GetMapping("{id}/users")
|
@GetMapping("{id}/users")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getUsersForGroup(@PathVariable id: Long) =
|
fun getUsersForGroup(@PathVariable id: Long) =
|
||||||
ok(with(userLogic) {
|
ok(groupLogic.getUsersForGroup(id))
|
||||||
groupLogic.getUsersForGroup(id)
|
|
||||||
.map { it.toOutput() }
|
|
||||||
})
|
|
||||||
|
|
||||||
@PostMapping("default/{groupId}")
|
@PostMapping("default/{groupId}")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
|
@ -113,27 +110,25 @@ class GroupsController(
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getRequestDefaultGroup(request: HttpServletRequest) =
|
fun getRequestDefaultGroup(request: HttpServletRequest) =
|
||||||
ok(with(groupLogic) {
|
ok(with(groupLogic) {
|
||||||
getRequestDefaultGroup(request).toOutput()
|
getRequestDefaultGroup(request)
|
||||||
})
|
})
|
||||||
|
|
||||||
@GetMapping("currentuser")
|
@GetMapping("currentuser")
|
||||||
fun getCurrentGroupUser(request: HttpServletRequest) =
|
fun getCurrentGroupUser(request: HttpServletRequest) =
|
||||||
ok(with(groupLogic.getRequestDefaultGroup(request)) {
|
ok(with(groupLogic.getRequestDefaultGroup(request)) {
|
||||||
userLogic.getDefaultGroupUser(this).toOutputDto()
|
userLogic.getDefaultGroupUser(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun save(@Valid @RequestBody group: GroupSaveDto) =
|
fun save(@Valid @RequestBody group: GroupDto) =
|
||||||
created<GroupOutputDto>(GROUP_CONTROLLER_PATH) {
|
created<GroupDto>(Constants.ControllerPaths.GROUP) {
|
||||||
with(groupLogic) {
|
groupLogic.save(group)
|
||||||
save(group).toOutput()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun update(@Valid @RequestBody group: GroupUpdateDto) =
|
fun update(@Valid @RequestBody group: GroupDto) =
|
||||||
noContent {
|
noContent {
|
||||||
groupLogic.update(group)
|
groupLogic.update(group)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.InventoryLogic
|
import dev.fyloz.colorrecipesexplorer.logic.InventoryLogic
|
||||||
|
@ -10,10 +11,8 @@ import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
private const val INVENTORY_CONTROLLER_PATH = "api/inventory"
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(INVENTORY_CONTROLLER_PATH)
|
@RequestMapping(Constants.ControllerPaths.INVENTORY)
|
||||||
@Profile("!emergency")
|
@Profile("!emergency")
|
||||||
class InventoryController(
|
class InventoryController(
|
||||||
private val inventoryLogic: InventoryLogic
|
private val inventoryLogic: InventoryLogic
|
||||||
|
|
|
@ -44,19 +44,11 @@ fun fileCreated(basePath: String, producer: () -> String): ResponseEntity<String
|
||||||
return ResponseEntity.created(URI.create(path)).body(fileName)
|
return ResponseEntity.created(URI.create(path)).body(fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
|
|
||||||
fun <T : ModelEntity> created(controllerPath: String, body: T): ResponseEntity<T> =
|
|
||||||
created(controllerPath, body, body.id!!)
|
|
||||||
|
|
||||||
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
|
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
|
||||||
@JvmName("createdDto")
|
@JvmName("createdDto")
|
||||||
fun <T : EntityDto> created(controllerPath: String, body: T): ResponseEntity<T> =
|
fun <T : EntityDto> created(controllerPath: String, body: T): ResponseEntity<T> =
|
||||||
created(controllerPath, body, body.id)
|
created(controllerPath, body, body.id)
|
||||||
|
|
||||||
/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */
|
|
||||||
fun <T : ModelEntity> created(controllerPath: String, producer: () -> T): ResponseEntity<T> =
|
|
||||||
created(controllerPath, producer())
|
|
||||||
|
|
||||||
/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */
|
/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */
|
||||||
@JvmName("createdDto")
|
@JvmName("createdDto")
|
||||||
fun <T : EntityDto> created(controllerPath: String, producer: () -> T): ResponseEntity<T> =
|
fun <T : EntityDto> created(controllerPath: String, producer: () -> T): ResponseEntity<T> =
|
||||||
|
|
|
@ -20,7 +20,7 @@ class DefaultCompanyService(repository: CompanyRepository) :
|
||||||
override fun isUsedByRecipe(id: Long) = repository.isUsedByRecipe(id)
|
override fun isUsedByRecipe(id: Long) = repository.isUsedByRecipe(id)
|
||||||
|
|
||||||
override fun toDto(entity: Company) =
|
override fun toDto(entity: Company) =
|
||||||
CompanyDto(entity.id!!, entity.name)
|
CompanyDto(entity.id, entity.name)
|
||||||
|
|
||||||
override fun toEntity(dto: CompanyDto) =
|
override fun toEntity(dto: CompanyDto) =
|
||||||
Company(dto.id, dto.name)
|
Company(dto.id, dto.name)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.flat
|
||||||
|
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
||||||
|
|
||||||
|
interface GroupService : Service<GroupDto, Group, GroupRepository> {
|
||||||
|
/** Checks if a group with the given [name] and a different [id] exists. */
|
||||||
|
fun existsByName(name: String, id: Long? = null): Boolean
|
||||||
|
|
||||||
|
/** Flatten the given the permissions of the given [group]. */
|
||||||
|
fun flattenPermissions(group: Group): List<Permission>
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceComponent
|
||||||
|
class DefaultGroupService(repository: GroupRepository) : BaseService<GroupDto, Group, GroupRepository>(repository),
|
||||||
|
GroupService {
|
||||||
|
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0L)
|
||||||
|
|
||||||
|
override fun toDto(entity: Group) =
|
||||||
|
GroupDto(entity.id, entity.name, flattenPermissions(entity), entity.permissions)
|
||||||
|
|
||||||
|
override fun toEntity(dto: GroupDto) =
|
||||||
|
Group(dto.id, dto.name, dto.permissions)
|
||||||
|
|
||||||
|
override fun flattenPermissions(group: Group) =
|
||||||
|
group.permissions.flatMap { it.flat() }.filter { !it.deprecated }
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ class DefaultMaterialService(
|
||||||
|
|
||||||
override fun toDto(entity: Material) =
|
override fun toDto(entity: Material) =
|
||||||
MaterialDto(
|
MaterialDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.name,
|
entity.name,
|
||||||
entity.inventoryQuantity,
|
entity.inventoryQuantity,
|
||||||
entity.isMixType,
|
entity.isMixType,
|
||||||
|
|
|
@ -37,7 +37,7 @@ class DefaultMaterialTypeService(repository: MaterialTypeRepository) :
|
||||||
override fun isUsedByMaterial(id: Long) = repository.isUsedByMaterial(id)
|
override fun isUsedByMaterial(id: Long) = repository.isUsedByMaterial(id)
|
||||||
|
|
||||||
override fun toDto(entity: MaterialType) =
|
override fun toDto(entity: MaterialType) =
|
||||||
MaterialTypeDto(entity.id!!, entity.name, entity.prefix, entity.usePercentages, entity.systemType)
|
MaterialTypeDto(entity.id, entity.name, entity.prefix, entity.usePercentages, entity.systemType)
|
||||||
|
|
||||||
override fun toEntity(dto: MaterialTypeDto) =
|
override fun toEntity(dto: MaterialTypeDto) =
|
||||||
MaterialType(dto.id, dto.name, dto.prefix, dto.usePercentages, dto.systemType)
|
MaterialType(dto.id, dto.name, dto.prefix, dto.usePercentages, dto.systemType)
|
||||||
|
|
|
@ -16,7 +16,7 @@ class DefaultMixMaterialService(repository: MixMaterialRepository, private val m
|
||||||
override fun existsByMaterialId(materialId: Long) = repository.existsByMaterialId(materialId)
|
override fun existsByMaterialId(materialId: Long) = repository.existsByMaterialId(materialId)
|
||||||
|
|
||||||
override fun toDto(entity: MixMaterial) =
|
override fun toDto(entity: MixMaterial) =
|
||||||
MixMaterialDto(entity.id!!, materialService.toDto(entity.material), entity.quantity, entity.position)
|
MixMaterialDto(entity.id, materialService.toDto(entity.material), entity.quantity, entity.position)
|
||||||
|
|
||||||
override fun toEntity(dto: MixMaterialDto) =
|
override fun toEntity(dto: MixMaterialDto) =
|
||||||
MixMaterial(dto.id, materialService.toEntity(dto.material), dto.quantity, dto.position)
|
MixMaterial(dto.id, materialService.toEntity(dto.material), dto.quantity, dto.position)
|
||||||
|
|
|
@ -33,7 +33,7 @@ class DefaultMixService(
|
||||||
|
|
||||||
override fun toDto(entity: Mix) =
|
override fun toDto(entity: Mix) =
|
||||||
MixDto(
|
MixDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.location,
|
entity.location,
|
||||||
entity.recipeId,
|
entity.recipeId,
|
||||||
mixTypeService.toDto(entity.mixType),
|
mixTypeService.toDto(entity.mixType),
|
||||||
|
|
|
@ -37,7 +37,7 @@ class DefaultMixTypeService(
|
||||||
|
|
||||||
override fun toDto(entity: MixType) =
|
override fun toDto(entity: MixType) =
|
||||||
MixTypeDto(
|
MixTypeDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.name,
|
entity.name,
|
||||||
materialTypeService.toDto(entity.materialType),
|
materialTypeService.toDto(entity.materialType),
|
||||||
if (entity.material != null) materialService.toDto(entity.material) else null
|
if (entity.material != null) materialService.toDto(entity.material) else null
|
||||||
|
|
|
@ -27,6 +27,7 @@ class DefaultRecipeService(
|
||||||
private val companyService: CompanyService,
|
private val companyService: CompanyService,
|
||||||
private val mixService: MixService,
|
private val mixService: MixService,
|
||||||
private val recipeStepService: RecipeStepService,
|
private val recipeStepService: RecipeStepService,
|
||||||
|
private val groupService: GroupService,
|
||||||
private val configLogic: ConfigurationLogic
|
private val configLogic: ConfigurationLogic
|
||||||
) :
|
) :
|
||||||
BaseService<RecipeDto, Recipe, RecipeRepository>(repository), RecipeService {
|
BaseService<RecipeDto, Recipe, RecipeRepository>(repository), RecipeService {
|
||||||
|
@ -39,7 +40,7 @@ class DefaultRecipeService(
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun toDto(entity: Recipe) =
|
override fun toDto(entity: Recipe) =
|
||||||
RecipeDto(
|
RecipeDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.name,
|
entity.name,
|
||||||
entity.description,
|
entity.description,
|
||||||
entity.color,
|
entity.color,
|
||||||
|
@ -55,8 +56,8 @@ class DefaultRecipeService(
|
||||||
|
|
||||||
private fun groupInformationToDto(entity: RecipeGroupInformation) =
|
private fun groupInformationToDto(entity: RecipeGroupInformation) =
|
||||||
RecipeGroupInformationDto(
|
RecipeGroupInformationDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.group,
|
groupService.toDto(entity.group),
|
||||||
entity.note,
|
entity.note,
|
||||||
entity.steps?.lazyMap(recipeStepService::toDto) ?: listOf()
|
entity.steps?.lazyMap(recipeStepService::toDto) ?: listOf()
|
||||||
)
|
)
|
||||||
|
@ -77,7 +78,12 @@ class DefaultRecipeService(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun groupInformationToEntity(dto: RecipeGroupInformationDto) =
|
private fun groupInformationToEntity(dto: RecipeGroupInformationDto) =
|
||||||
RecipeGroupInformation(dto.id, dto.group, dto.note, dto.steps.map(recipeStepService::toEntity))
|
RecipeGroupInformation(
|
||||||
|
dto.id,
|
||||||
|
groupService.toEntity(dto.group),
|
||||||
|
dto.note,
|
||||||
|
dto.steps.map(recipeStepService::toEntity)
|
||||||
|
)
|
||||||
|
|
||||||
private fun isApprobationExpired(recipe: Recipe): Boolean? =
|
private fun isApprobationExpired(recipe: Recipe): Boolean? =
|
||||||
with(Period.parse(configLogic.getContent(ConfigurationType.RECIPE_APPROBATION_EXPIRATION))) {
|
with(Period.parse(configLogic.getContent(ConfigurationType.RECIPE_APPROBATION_EXPIRATION))) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface RecipeStepService : Service<RecipeStepDto, RecipeStep, RecipeStepRepos
|
||||||
class DefaultRecipeStepService(repository: RecipeStepRepository) :
|
class DefaultRecipeStepService(repository: RecipeStepRepository) :
|
||||||
BaseService<RecipeStepDto, RecipeStep, RecipeStepRepository>(repository), RecipeStepService {
|
BaseService<RecipeStepDto, RecipeStep, RecipeStepRepository>(repository), RecipeStepService {
|
||||||
override fun toDto(entity: RecipeStep) =
|
override fun toDto(entity: RecipeStep) =
|
||||||
RecipeStepDto(entity.id!!, entity.position, entity.message)
|
RecipeStepDto(entity.id, entity.position, entity.message)
|
||||||
|
|
||||||
override fun toEntity(dto: RecipeStepDto) =
|
override fun toEntity(dto: RecipeStepDto) =
|
||||||
RecipeStep(dto.id, dto.position, dto.message)
|
RecipeStep(dto.id, dto.position, dto.message)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class DefaultTouchUpKitService(repository: TouchUpKitRepository, private val con
|
||||||
|
|
||||||
override fun toDto(entity: TouchUpKit) =
|
override fun toDto(entity: TouchUpKit) =
|
||||||
TouchUpKitDto(
|
TouchUpKitDto(
|
||||||
entity.id!!,
|
entity.id,
|
||||||
entity.project,
|
entity.project,
|
||||||
entity.buggy,
|
entity.buggy,
|
||||||
entity.company,
|
entity.company,
|
||||||
|
@ -39,7 +39,7 @@ class DefaultTouchUpKitService(repository: TouchUpKitRepository, private val con
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun touchUpKitProductToDto(entity: TouchUpKitProduct) =
|
private fun touchUpKitProductToDto(entity: TouchUpKitProduct) =
|
||||||
TouchUpKitProductDto(entity.id!!, entity.name, entity.description, entity.quantity, entity.ready)
|
TouchUpKitProductDto(entity.id, entity.name, entity.description, entity.quantity, entity.ready)
|
||||||
|
|
||||||
override fun toEntity(dto: TouchUpKitDto) =
|
override fun toEntity(dto: TouchUpKitDto) =
|
||||||
TouchUpKit(
|
TouchUpKit(
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.flat
|
||||||
|
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
|
||||||
|
interface UserService : Service<UserDto, User, UserRepository> {
|
||||||
|
/** Checks if a user with the given [firstName] and [lastName] exists. */
|
||||||
|
fun existsByFirstNameAndLastName(firstName: String, lastName: String, id: Long? = null): Boolean
|
||||||
|
|
||||||
|
/** Gets all users, depending on [isSystemUser] and [isDefaultGroupUser]. */
|
||||||
|
fun getAll(isSystemUser: Boolean, isDefaultGroupUser: Boolean): Collection<UserDto>
|
||||||
|
|
||||||
|
/** Gets all users for the given [group]. */
|
||||||
|
fun getAllByGroup(group: GroupDto): Collection<UserDto>
|
||||||
|
|
||||||
|
/** Finds the user with the given [id], depending on [isSystemUser] and [isDefaultGroupUser]. */
|
||||||
|
fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean): UserDto?
|
||||||
|
|
||||||
|
/** Finds the user with the given [firstName] and [lastName]. */
|
||||||
|
fun getByFirstNameAndLastName(firstName: String, lastName: String): UserDto?
|
||||||
|
|
||||||
|
/** Find the default user for the given [group]. */
|
||||||
|
fun getDefaultGroupUser(group: GroupDto): UserDto?
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceComponent
|
||||||
|
class DefaultUserService(repository: UserRepository, private val groupService: GroupService) :
|
||||||
|
BaseService<UserDto, User, UserRepository>(repository), UserService {
|
||||||
|
override fun existsByFirstNameAndLastName(firstName: String, lastName: String, id: Long?) =
|
||||||
|
repository.existsByFirstNameAndLastNameAndIdNot(firstName, lastName, id ?: 0L)
|
||||||
|
|
||||||
|
override fun getAll(isSystemUser: Boolean, isDefaultGroupUser: Boolean) =
|
||||||
|
repository.findAll()
|
||||||
|
.filter { isSystemUser || !it.isSystemUser }
|
||||||
|
.filter { isDefaultGroupUser || !it.isDefaultGroupUser }
|
||||||
|
.map(::toDto)
|
||||||
|
|
||||||
|
override fun getAllByGroup(group: GroupDto) =
|
||||||
|
repository.findAllByGroup(groupService.toEntity(group))
|
||||||
|
.map(::toDto)
|
||||||
|
|
||||||
|
override fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean): UserDto? {
|
||||||
|
val user = repository.findByIdOrNull(id) ?: return null
|
||||||
|
if ((!isSystemUser && user.isSystemUser) ||
|
||||||
|
!isDefaultGroupUser && user.isDefaultGroupUser
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDto(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getByFirstNameAndLastName(firstName: String, lastName: String): UserDto? {
|
||||||
|
val user = repository.findByFirstNameAndLastName(firstName, lastName)
|
||||||
|
return if (user != null) toDto(user) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultGroupUser(group: GroupDto): UserDto? {
|
||||||
|
val user = repository.findDefaultGroupUser(groupService.toEntity(group))
|
||||||
|
return if (user != null) toDto(user) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toDto(entity: User) = UserDto(
|
||||||
|
entity.id,
|
||||||
|
entity.firstName,
|
||||||
|
entity.lastName,
|
||||||
|
entity.password,
|
||||||
|
if (entity.group != null) groupService.toDto(entity.group) else null,
|
||||||
|
getFlattenPermissions(entity),
|
||||||
|
entity.permissions,
|
||||||
|
entity.lastLoginTime,
|
||||||
|
entity.isDefaultGroupUser,
|
||||||
|
entity.isSystemUser
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toEntity(dto: UserDto) = User(
|
||||||
|
dto.id,
|
||||||
|
dto.firstName,
|
||||||
|
dto.lastName,
|
||||||
|
dto.password,
|
||||||
|
dto.isDefaultGroupUser,
|
||||||
|
dto.isSystemUser,
|
||||||
|
if (dto.group != null) groupService.toEntity(dto.group) else null,
|
||||||
|
dto.explicitPermissions,
|
||||||
|
dto.lastLoginTime
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getFlattenPermissions(user: User): List<Permission> {
|
||||||
|
val perms = user.permissions.flatMap { it.flat() }.filter { !it.deprecated }
|
||||||
|
|
||||||
|
if (user.group != null) {
|
||||||
|
return perms + groupService.flattenPermissions(user.group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return perms
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,349 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.NamedModelEntity
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.NamedJpaRepository
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.AbstractServiceTest as AbstractServiceTest1
|
|
||||||
|
|
||||||
abstract class AbstractServiceTest<E, S : OldService<E, *>, R : JpaRepository<E, *>> {
|
|
||||||
protected abstract val repository: R
|
|
||||||
protected abstract val logic: S
|
|
||||||
|
|
||||||
protected abstract val entity: E
|
|
||||||
protected abstract val anotherEntity: E
|
|
||||||
|
|
||||||
protected val entityList: List<E>
|
|
||||||
get() = listOf(
|
|
||||||
entity,
|
|
||||||
anotherEntity
|
|
||||||
)
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
open fun afterEach() {
|
|
||||||
reset(repository, logic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAll()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getAll() returns all available entities`() {
|
|
||||||
whenever(repository.findAll()).doReturn(entityList)
|
|
||||||
|
|
||||||
val found = logic.getAll()
|
|
||||||
|
|
||||||
assertEquals(entityList, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getAll() returns empty list when there is no entities`() {
|
|
||||||
whenever(repository.findAll()).doReturn(listOf())
|
|
||||||
|
|
||||||
val found = logic.getAll()
|
|
||||||
|
|
||||||
assertTrue { found.isEmpty() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `save() saves in the repository and returns the saved value`() {
|
|
||||||
whenever(repository.save(entity)).doReturn(entity)
|
|
||||||
|
|
||||||
val found = logic.save(entity)
|
|
||||||
|
|
||||||
verify(repository).save(entity)
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `update() saves in the repository and returns the updated value`() {
|
|
||||||
whenever(repository.save(entity)).doReturn(entity)
|
|
||||||
|
|
||||||
val found = logic.update(entity)
|
|
||||||
|
|
||||||
verify(repository).save(entity)
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `delete() deletes in the repository`() {
|
|
||||||
logic.delete(entity)
|
|
||||||
|
|
||||||
verify(repository).delete(entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractModelServiceTest<E : ModelEntity, S : ModelService<E, *>, R : JpaRepository<E, Long>> :
|
|
||||||
AbstractServiceTest1<E, S, R>() {
|
|
||||||
|
|
||||||
// existsById()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `existsById() returns true when an entity with the given id exists in the repository`() {
|
|
||||||
whenever(repository.existsById(entity.id!!)).doReturn(true)
|
|
||||||
|
|
||||||
val found = logic.existsById(entity.id!!)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `existsById() returns false when no entity with the given id exists in the repository`() {
|
|
||||||
whenever(repository.existsById(entity.id!!)).doReturn(false)
|
|
||||||
|
|
||||||
val found = logic.existsById(entity.id!!)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getById()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getById() returns the entity with the given id from the repository`() {
|
|
||||||
whenever(repository.findById(entity.id!!)).doReturn(Optional.of(entity))
|
|
||||||
|
|
||||||
val found = logic.getById(entity.id!!)
|
|
||||||
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getById() throws NotFoundException when no entity with the given id exists in the repository`() {
|
|
||||||
whenever(repository.findById(entity.id!!)).doReturn(Optional.empty())
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> { logic.getById(entity.id!!) }
|
|
||||||
.assertErrorCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `save() throws AlreadyExistsException when an entity with the given id exists in the repository`() {
|
|
||||||
doReturn(true).whenever(logic).existsById(entity.id!!)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.save(entity) }
|
|
||||||
.assertErrorCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update() saves in the repository and returns the updated value`() {
|
|
||||||
whenever(repository.save(entity)).doReturn(entity)
|
|
||||||
doReturn(true).whenever(logic).existsById(entity.id!!)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
val found = logic.update(entity)
|
|
||||||
|
|
||||||
verify(repository).save(entity)
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
|
||||||
doReturn(false).whenever(logic).existsById(entity.id!!)
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> { logic.update(entity) }
|
|
||||||
.assertErrorCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteById()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `deleteById() deletes the entity with the given id in the repository`() {
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
logic.deleteById(entity.id!!)
|
|
||||||
|
|
||||||
verify(repository).delete(entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractNamedModelServiceTest<E : NamedModelEntity, S : NamedModelService<E, *>, R : NamedJpaRepository<E>> :
|
|
||||||
AbstractModelServiceTest<E, S, R>() {
|
|
||||||
protected abstract val entityWithEntityName: E
|
|
||||||
|
|
||||||
// existsByName()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `existsByName() returns true when an entity with the given name exists`() {
|
|
||||||
whenever(repository.existsByName(entity.name)).doReturn(true)
|
|
||||||
|
|
||||||
val found = logic.existsByName(entity.name)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `existsByName() returns false when no entity with the given name exists`() {
|
|
||||||
whenever(repository.existsByName(entity.name)).doReturn(false)
|
|
||||||
|
|
||||||
val found = logic.existsByName(entity.name)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getByName()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getByName() returns the entity with the given name`() {
|
|
||||||
whenever(repository.findByName(entity.name)).doReturn(entity)
|
|
||||||
|
|
||||||
val found = logic.getByName(entity.name)
|
|
||||||
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `getByName() throws NotFoundException when no entity with the given name exists`() {
|
|
||||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> { logic.getByName(entity.name) }
|
|
||||||
.assertErrorCode("name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `save() throws AlreadyExistsException when an entity with the given name exists`() {
|
|
||||||
doReturn(true).whenever(logic).existsByName(entity.name)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.save(entity) }
|
|
||||||
.assertErrorCode("name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update() saves in the repository and returns the updated value`() {
|
|
||||||
whenever(repository.save(entity)).doReturn(entity)
|
|
||||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
|
||||||
doReturn(true).whenever(logic).existsById(entity.id!!)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
val found = logic.update(entity)
|
|
||||||
|
|
||||||
verify(repository).save(entity)
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
|
||||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
|
||||||
doReturn(false).whenever(logic).existsById(entity.id!!)
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> { logic.update(entity) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
open fun `update() throws AlreadyExistsException when an entity with the updated name exists`() {
|
|
||||||
whenever(repository.findByName(entity.name)).doReturn(entityWithEntityName)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.update(entity) }
|
|
||||||
.assertErrorCode("name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExternalModelServiceTest {
|
|
||||||
fun `save(dto) calls and returns save() with the created entity`()
|
|
||||||
fun `update(dto) calls and returns update() with the created entity`()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== IMPLEMENTATIONS FOR EXTERNAL SERVICES ====
|
|
||||||
// Lots of code duplication but I don't have a better solution for now
|
|
||||||
abstract class AbstractExternalModelServiceTest<E : ModelEntity, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *, *>, R : JpaRepository<E, Long>> :
|
|
||||||
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
|
||||||
protected abstract val entitySaveDto: N
|
|
||||||
protected abstract val entityUpdateDto: U
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
override fun afterEach() {
|
|
||||||
reset(entitySaveDto, entityUpdateDto)
|
|
||||||
super.afterEach()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class AbstractExternalNamedModelServiceTest<E : NamedModelEntity, N : EntityDto<E>, U : EntityDto<E>, S : ExternalNamedModelService<E, N, U, *, *>, R : NamedJpaRepository<E>> :
|
|
||||||
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
|
||||||
protected abstract val entitySaveDto: N
|
|
||||||
protected abstract val entityUpdateDto: U
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
override fun afterEach() {
|
|
||||||
reset(entitySaveDto, entityUpdateDto)
|
|
||||||
super.afterEach()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NotFoundException.assertErrorCode(identifierName: String = "id") =
|
|
||||||
this.assertErrorCode("notfound", identifierName)
|
|
||||||
|
|
||||||
fun AlreadyExistsException.assertErrorCode(identifierName: String = "id") =
|
|
||||||
this.assertErrorCode("exists", identifierName)
|
|
||||||
|
|
||||||
fun RestException.assertErrorCode(type: String, identifierName: String) {
|
|
||||||
assertTrue {
|
|
||||||
this.errorCode.startsWith(type) &&
|
|
||||||
this.errorCode.endsWith(identifierName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RestException.assertErrorCode(errorCode: String) {
|
|
||||||
assertEquals(errorCode, this.errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <E : ModelEntity, N : EntityDto<E>> withBaseSaveDtoTest(
|
|
||||||
entity: E,
|
|
||||||
entitySaveDto: N,
|
|
||||||
service: ExternalService<E, N, *, *, *>,
|
|
||||||
saveMockMatcher: () -> E = { entity },
|
|
||||||
op: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
doReturn(entity).whenever(service).save(saveMockMatcher())
|
|
||||||
doReturn(entity).whenever(entitySaveDto).toEntity()
|
|
||||||
|
|
||||||
val found = service.save(entitySaveDto)
|
|
||||||
|
|
||||||
verify(service).save(saveMockMatcher())
|
|
||||||
assertEquals(entity, found)
|
|
||||||
|
|
||||||
op()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <E : ModelEntity, U : EntityDto<E>> withBaseUpdateDtoTest(
|
|
||||||
entity: E,
|
|
||||||
entityUpdateDto: U,
|
|
||||||
service: ExternalModelService<E, *, U, *, *>,
|
|
||||||
updateMockMatcher: () -> E,
|
|
||||||
op: E.() -> Unit = {}
|
|
||||||
) {
|
|
||||||
doAnswer { it.arguments[0] }.whenever(service).update(updateMockMatcher())
|
|
||||||
doReturn(entity).whenever(entityUpdateDto).toEntity()
|
|
||||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
|
||||||
doReturn(true).whenever(service).existsById(entity.id!!)
|
|
||||||
|
|
||||||
val found = service.update(entityUpdateDto)
|
|
||||||
|
|
||||||
verify(service).update(updateMockMatcher())
|
|
||||||
assertEquals(entity, found)
|
|
||||||
|
|
||||||
found.op()
|
|
||||||
}
|
|
|
@ -1,348 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.security.defaultGroupCookieName
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.*
|
|
||||||
import org.junit.jupiter.api.*
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
|
||||||
import java.util.*
|
|
||||||
import javax.servlet.http.Cookie
|
|
||||||
import javax.servlet.http.HttpServletRequest
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class UserLogicTest :
|
|
||||||
AbstractExternalModelServiceTest<User, UserSaveDto, UserUpdateDto, UserLogic, UserRepository>() {
|
|
||||||
private val passwordEncoder = BCryptPasswordEncoder()
|
|
||||||
|
|
||||||
override val entity: User = user(id = 0L, passwordEncoder = passwordEncoder)
|
|
||||||
override val anotherEntity: User = user(id = 1L, passwordEncoder = passwordEncoder)
|
|
||||||
private val entityDefaultGroupUser = user(id = 2L, isDefaultGroupUser = true, passwordEncoder = passwordEncoder)
|
|
||||||
private val entitySystemUser = user(id = 3L, isSystemUser = true, passwordEncoder = passwordEncoder)
|
|
||||||
private val group = group(id = 0L)
|
|
||||||
override val entitySaveDto: UserSaveDto = spy(userSaveDto(passwordEncoder, id = 0L))
|
|
||||||
override val entityUpdateDto: UserUpdateDto = spy(userUpdateDto(id = 0L))
|
|
||||||
|
|
||||||
override val repository: UserRepository = mock()
|
|
||||||
private val groupService: GroupLogic = mock()
|
|
||||||
override val logic: UserLogic = spy(DefaultUserLogic(repository, groupService))
|
|
||||||
|
|
||||||
private val entitySaveDtoUser = User(
|
|
||||||
entitySaveDto.id,
|
|
||||||
entitySaveDto.firstName,
|
|
||||||
entitySaveDto.lastName,
|
|
||||||
passwordEncoder.encode(entitySaveDto.password),
|
|
||||||
isDefaultGroupUser = false,
|
|
||||||
isSystemUser = false,
|
|
||||||
group = null,
|
|
||||||
permissions = entitySaveDto.permissions
|
|
||||||
)
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
override fun afterEach() {
|
|
||||||
reset(groupService)
|
|
||||||
super.afterEach()
|
|
||||||
}
|
|
||||||
|
|
||||||
// existsByFirstNameAndLastName()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `existsByFirstNameAndLastName() returns true when an user with the given first name and last name exists`() {
|
|
||||||
whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(true)
|
|
||||||
|
|
||||||
val found = logic.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `existsByFirstNameAndLastName() returns false when no user with the given first name and last name exists`() {
|
|
||||||
whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(false)
|
|
||||||
|
|
||||||
val found = logic.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getById()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getById() throws NotFoundException when the corresponding user is a default group user`() {
|
|
||||||
whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser))
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> {
|
|
||||||
logic.getById(
|
|
||||||
entityDefaultGroupUser.id,
|
|
||||||
ignoreDefaultGroupUsers = true,
|
|
||||||
ignoreSystemUsers = false
|
|
||||||
)
|
|
||||||
}.assertErrorCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getById() throws NotFoundException when the corresponding user is a system user`() {
|
|
||||||
whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser))
|
|
||||||
|
|
||||||
assertThrows<NotFoundException> {
|
|
||||||
logic.getById(
|
|
||||||
entitySystemUser.id,
|
|
||||||
ignoreDefaultGroupUsers = false,
|
|
||||||
ignoreSystemUsers = true
|
|
||||||
)
|
|
||||||
}.assertErrorCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getByGroup()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getByGroup() returns all the users with the given group from the repository`() {
|
|
||||||
whenever(repository.findAllByGroup(group)).doReturn(entityList)
|
|
||||||
|
|
||||||
val found = logic.getByGroup(group)
|
|
||||||
|
|
||||||
assertTrue(found.containsAll(entityList))
|
|
||||||
assertTrue(entityList.containsAll(found))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getByGroup() returns an empty list when there is no user with the given group in the repository`() {
|
|
||||||
whenever(repository.findAllByGroup(group)).doReturn(listOf())
|
|
||||||
|
|
||||||
val found = logic.getByGroup(group)
|
|
||||||
|
|
||||||
assertTrue(found.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultGroupUser()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getDefaultGroupUser() returns the default user of the given group from the repository`() {
|
|
||||||
whenever(repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)).doReturn(entityDefaultGroupUser)
|
|
||||||
|
|
||||||
val found = logic.getDefaultGroupUser(group)
|
|
||||||
|
|
||||||
assertEquals(entityDefaultGroupUser, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
override fun `save() saves in the repository and returns the saved value`() {
|
|
||||||
whenever(repository.save(entity)).doReturn(entity)
|
|
||||||
doReturn(false).whenever(repository).existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
|
||||||
|
|
||||||
val found = logic.save(entity)
|
|
||||||
|
|
||||||
verify(repository).save(entity)
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `save() throws AlreadyExistsException when firstName and lastName exists`() {
|
|
||||||
doReturn(true).whenever(repository).existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.save(entity) }
|
|
||||||
.assertErrorCode("fullName")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
|
||||||
withBaseSaveDtoTest(entity, entitySaveDto, logic, {
|
|
||||||
argThat {
|
|
||||||
this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `save(dto) calls and returns save() with the created user`() {
|
|
||||||
doReturn(entitySaveDtoUser).whenever(logic).save(any<User>())
|
|
||||||
|
|
||||||
val found = logic.save(entitySaveDto)
|
|
||||||
|
|
||||||
verify(logic).save(argThat<User> { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName })
|
|
||||||
assertEquals(entitySaveDtoUser, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
|
||||||
withBaseUpdateDtoTest(entity, entityUpdateDto, logic, { any() })
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `update() throws AlreadyExistsException when a different user with the given first name and last name exists`() {
|
|
||||||
whenever(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(
|
|
||||||
entityDefaultGroupUser
|
|
||||||
)
|
|
||||||
doReturn(entity).whenever(logic).getById(eq(entity.id), any(), any())
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> {
|
|
||||||
logic.update(
|
|
||||||
entity,
|
|
||||||
true,
|
|
||||||
ignoreSystemUsers = true
|
|
||||||
)
|
|
||||||
}.assertErrorCode("fullName")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class GroupLogicTest :
|
|
||||||
AbstractExternalNamedModelServiceTest<Group, GroupSaveDto, GroupUpdateDto, GroupLogic, GroupRepository>() {
|
|
||||||
private val userService: UserLogic = mock()
|
|
||||||
override val repository: GroupRepository = mock()
|
|
||||||
override val logic: DefaultGroupLogic = spy(DefaultGroupLogic(userService, repository))
|
|
||||||
|
|
||||||
override val entity: Group = group(id = 0L, name = "group")
|
|
||||||
override val anotherEntity: Group = group(id = 1L, name = "another group")
|
|
||||||
override val entitySaveDto: GroupSaveDto = spy(groupSaveDto(name = "group"))
|
|
||||||
override val entityUpdateDto: GroupUpdateDto = spy(groupUpdateDto(id = 0L, name = "group"))
|
|
||||||
override val entityWithEntityName: Group = group(id = 2L, name = entity.name)
|
|
||||||
|
|
||||||
private val groupUserId = 1000000L + entity.id!!
|
|
||||||
private val groupUser = user(passwordEncoder = BCryptPasswordEncoder(), id = groupUserId, group = entity)
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
override fun afterEach() {
|
|
||||||
reset(userService)
|
|
||||||
super.afterEach()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUsersForGroup()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getUsersForGroup() returns all users in the given group`() {
|
|
||||||
val group = group(id = 1L)
|
|
||||||
|
|
||||||
doReturn(group).whenever(logic).getById(group.id!!)
|
|
||||||
whenever(userService.getByGroup(group)).doReturn(listOf(groupUser))
|
|
||||||
|
|
||||||
val found = logic.getUsersForGroup(group.id!!)
|
|
||||||
|
|
||||||
assertTrue(found.contains(groupUser))
|
|
||||||
assertTrue(found.size == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getUsersForGroup() returns empty collection when the given group contains any user`() {
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
val found = logic.getUsersForGroup(entity.id!!)
|
|
||||||
|
|
||||||
assertTrue(found.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRequestDefaultGroup()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getRequestDefaultGroup() returns the group contained in the cookie of the HTTP request`() {
|
|
||||||
val cookies: Array<Cookie> = arrayOf(Cookie(defaultGroupCookieName, groupUserId.toString()))
|
|
||||||
val request: HttpServletRequest = mock()
|
|
||||||
|
|
||||||
whenever(request.cookies).doReturn(cookies)
|
|
||||||
whenever(userService.getById(eq(groupUserId), any(), any())).doReturn(groupUser)
|
|
||||||
|
|
||||||
val found = logic.getRequestDefaultGroup(request)
|
|
||||||
|
|
||||||
assertEquals(entity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getRequestDefaultGroup() throws NoDefaultGroupException when the HTTP request does not contains a cookie for the default group`() {
|
|
||||||
val request: HttpServletRequest = mock()
|
|
||||||
|
|
||||||
whenever(request.cookies).doReturn(arrayOf())
|
|
||||||
|
|
||||||
assertThrows<NoDefaultGroupException> { logic.getRequestDefaultGroup(request) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// setResponseDefaultGroup()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `setResponseDefaultGroup() the default group cookie has been added to the given HTTP response with the given group id`() {
|
|
||||||
val response = MockHttpServletResponse()
|
|
||||||
|
|
||||||
whenever(userService.getDefaultGroupUser(entity)).doReturn(groupUser)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
logic.setResponseDefaultGroup(entity.id!!, response)
|
|
||||||
val found = response.getCookie(defaultGroupCookieName)
|
|
||||||
|
|
||||||
assertNotNull(found)
|
|
||||||
assertEquals(defaultGroupCookieName, found.name)
|
|
||||||
assertEquals(groupUserId.toString(), found.value)
|
|
||||||
assertEquals(defaultGroupCookieMaxAge, found.maxAge)
|
|
||||||
assertTrue(found.isHttpOnly)
|
|
||||||
assertTrue(found.secure)
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
|
||||||
withBaseSaveDtoTest(entity, entitySaveDto, logic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
|
||||||
withBaseUpdateDtoTest(entity, entityUpdateDto, logic, { any() })
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class UserUserDetailsLogicTest {
|
|
||||||
private val userLogic: UserLogic = mock()
|
|
||||||
private val logic = spy(DefaultUserDetailsLogic(userLogic))
|
|
||||||
|
|
||||||
private val user = user(id = 0L)
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun beforeEach() {
|
|
||||||
reset(userLogic, logic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadUserByUsername()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `loadUserByUsername() calls loadUserByUserId() with the given username as an id`() {
|
|
||||||
whenever(userLogic.getById(eq(user.id), any(), any())).doReturn(user)
|
|
||||||
doReturn(UserDetails(user(id = user.id, plainPassword = user.password)))
|
|
||||||
.whenever(logic).loadUserById(user.id)
|
|
||||||
|
|
||||||
logic.loadUserByUsername(user.id.toString())
|
|
||||||
|
|
||||||
verify(logic).loadUserById(eq(user.id), any())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `loadUserByUsername() throws UsernameNotFoundException when no user with the given id exists`() {
|
|
||||||
whenever(userLogic.getById(eq(user.id), any(), any())).doThrow(
|
|
||||||
userIdNotFoundException(user.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertThrows<UsernameNotFoundException> { logic.loadUserByUsername(user.id.toString()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadUserByUserId
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `loadUserByUserId() returns an User corresponding to the user with the given id`() {
|
|
||||||
whenever(userLogic.getById(eq(user.id), any(), any())).doReturn(user)
|
|
||||||
|
|
||||||
val found = logic.loadUserById(user.id)
|
|
||||||
|
|
||||||
assertEquals(user.id, found.username.toLong())
|
|
||||||
assertEquals(user.password, found.password)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,23 +3,23 @@ package dev.fyloz.colorrecipesexplorer.logic
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDetails
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.DefaultJwtLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.DefaultJwtLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.jwtClaimUser
|
import dev.fyloz.colorrecipesexplorer.logic.users.jwtClaimUser
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserDetails
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.UserOutputDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.toOutputDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.user
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.base64encode
|
import dev.fyloz.colorrecipesexplorer.utils.base64encode
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.isAround
|
import dev.fyloz.colorrecipesexplorer.utils.isAround
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.jackson.io.JacksonDeserializer
|
import io.jsonwebtoken.jackson.io.JacksonDeserializer
|
||||||
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.spyk
|
import io.mockk.spyk
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class JwtLogicTest {
|
class DefaultJwtLogicTest {
|
||||||
private val objectMapper = jacksonObjectMapper()
|
private val objectMapper = jacksonObjectMapper()
|
||||||
private val securityProperties = CreSecurityProperties().apply {
|
private val securityProperties = CreSecurityProperties().apply {
|
||||||
jwtSecret = "XRRm7OflmFuCrOB2Xvmfsercih9DCKom"
|
jwtSecret = "XRRm7OflmFuCrOB2Xvmfsercih9DCKom"
|
||||||
|
@ -34,12 +34,14 @@ class JwtLogicTest {
|
||||||
|
|
||||||
private val jwtService = spyk(DefaultJwtLogic(objectMapper, securityProperties))
|
private val jwtService = spyk(DefaultJwtLogic(objectMapper, securityProperties))
|
||||||
|
|
||||||
private val user = user()
|
private val user = UserDto(0L, "Unit test", "User", "", null, listOf())
|
||||||
private val userOutputDto = user.toOutputDto()
|
|
||||||
|
|
||||||
// buildJwt()
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
private fun withParsedUserOutputDto(jwt: String, test: (UserOutputDto) -> Unit) {
|
private fun withParsedUserOutputDto(jwt: String, test: (UserDto) -> Unit) {
|
||||||
val serializedUser = jwtParser.parseClaimsJws(jwt)
|
val serializedUser = jwtParser.parseClaimsJws(jwt)
|
||||||
.body.get(jwtClaimUser, String::class.java)
|
.body.get(jwtClaimUser, String::class.java)
|
||||||
|
|
||||||
|
@ -47,27 +49,27 @@ class JwtLogicTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `buildJwt(userDetails) returns jwt string with valid user`() {
|
fun buildJwt_userDetails_normalBehavior_returnsJwtStringWithValidUser() {
|
||||||
val userDetails = UserDetails(user)
|
val userDetails = UserDetails(user)
|
||||||
|
|
||||||
val builtJwt = jwtService.buildJwt(userDetails)
|
val builtJwt = jwtService.buildJwt(userDetails)
|
||||||
|
|
||||||
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
||||||
assertEquals(user.toOutputDto(), parsedUser)
|
assertEquals(user, parsedUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `buildJwt() returns jwt string with valid user`() {
|
fun buildJwt_user_normalBehavior_returnsJwtStringWithValidUser() {
|
||||||
val builtJwt = jwtService.buildJwt(user)
|
val builtJwt = jwtService.buildJwt(user)
|
||||||
|
|
||||||
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
||||||
assertEquals(user.toOutputDto(), parsedUser)
|
assertEquals(user, parsedUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `buildJwt() returns jwt string with valid subject`() {
|
fun buildJwt_user_normalBehavior_returnsJwtStringWithValidSubject() {
|
||||||
val builtJwt = jwtService.buildJwt(user)
|
val builtJwt = jwtService.buildJwt(user)
|
||||||
val jwtSubject = jwtParser.parseClaimsJws(builtJwt).body.subject
|
val jwtSubject = jwtParser.parseClaimsJws(builtJwt).body.subject
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@ class JwtLogicTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `buildJwt() returns jwt with valid expiration date`() {
|
fun buildJwt_user_returnsJwtWithValidExpirationDate() {
|
||||||
val jwtExpectedExpirationDate = Instant.now().plusSeconds(securityProperties.jwtDuration)
|
val jwtExpectedExpirationDate = Instant.now().plusSeconds(securityProperties.jwtDuration)
|
||||||
|
|
||||||
val builtJwt = jwtService.buildJwt(user)
|
val builtJwt = jwtService.buildJwt(user)
|
||||||
|
@ -89,10 +91,10 @@ class JwtLogicTest {
|
||||||
// parseJwt()
|
// parseJwt()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `parseJwt() returns expected user`() {
|
fun parseJwt_normalBehavior_returnsExpectedUser() {
|
||||||
val jwt = jwtService.buildJwt(user)
|
val jwt = jwtService.buildJwt(user)
|
||||||
val parsedUser = jwtService.parseJwt(jwt)
|
val parsedUser = jwtService.parseJwt(jwt)
|
||||||
|
|
||||||
assertEquals(userOutputDto, parsedUser)
|
assertEquals(user, parsedUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,6 @@ package dev.fyloz.colorrecipesexplorer.logic
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.*
|
import dev.fyloz.colorrecipesexplorer.dtos.*
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
|
||||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService
|
import dev.fyloz.colorrecipesexplorer.service.RecipeService
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
@ -23,7 +22,7 @@ class DefaultRecipeLogicTest {
|
||||||
spyk(DefaultRecipeLogic(recipeServiceMock, companyLogicMock, recipeStepLogicMock, mixLogicMock, groupLogicMock))
|
spyk(DefaultRecipeLogic(recipeServiceMock, companyLogicMock, recipeStepLogicMock, mixLogicMock, groupLogicMock))
|
||||||
|
|
||||||
private val company = CompanyDto(1L, "Unit test company")
|
private val company = CompanyDto(1L, "Unit test company")
|
||||||
private val group = Group(1L, "Unit test group")
|
private val group = GroupDto(1L, "Unit test group", listOf())
|
||||||
private val recipe = RecipeDto(
|
private val recipe = RecipeDto(
|
||||||
1L,
|
1L,
|
||||||
"Unit test recipe",
|
"Unit test recipe",
|
||||||
|
@ -160,7 +159,7 @@ class DefaultRecipeLogicTest {
|
||||||
|
|
||||||
val expectedGroupInformation = RecipeGroupInformationDto(0L, group, "Unit test note", listOf())
|
val expectedGroupInformation = RecipeGroupInformationDto(0L, group, "Unit test note", listOf())
|
||||||
|
|
||||||
val groupNote = RecipeGroupNoteDto(group.id!!, expectedGroupInformation.note)
|
val groupNote = RecipeGroupNoteDto(group.id, expectedGroupInformation.note)
|
||||||
val dto = RecipePublicDataDto(recipe.id, listOf(groupNote), listOf())
|
val dto = RecipePublicDataDto(recipe.id, listOf(groupNote), listOf())
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -189,7 +188,7 @@ class DefaultRecipeLogicTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { mixLogicMock.updateLocations(any()) } just runs
|
every { mixLogicMock.updateLocations(any()) } just runs
|
||||||
|
|
||||||
val mixesLocation = listOf(MixLocationDto(group.id!!, "location"))
|
val mixesLocation = listOf(MixLocationDto(group.id, "location"))
|
||||||
val dto = RecipePublicDataDto(recipe.id, listOf(), mixesLocation)
|
val dto = RecipePublicDataDto(recipe.id, listOf(), mixesLocation)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
|
||||||
import dev.fyloz.colorrecipesexplorer.service.RecipeStepService
|
import dev.fyloz.colorrecipesexplorer.service.RecipeStepService
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
|
@ -28,7 +28,7 @@ class DefaultRecipeStepLogicTest {
|
||||||
mockkObject(PositionUtils)
|
mockkObject(PositionUtils)
|
||||||
every { PositionUtils.validate(any()) } just runs
|
every { PositionUtils.validate(any()) } just runs
|
||||||
|
|
||||||
val group = Group(1L, "Unit test group")
|
val group = GroupDto(1L, "Unit test group", listOf())
|
||||||
val steps = listOf(RecipeStepDto(1L, 1, "A message"))
|
val steps = listOf(RecipeStepDto(1L, 1, "A message"))
|
||||||
val groupInfo = RecipeGroupInformationDto(1L, group, "A note", steps)
|
val groupInfo = RecipeGroupInformationDto(1L, group, "A note", steps)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class DefaultRecipeStepLogicTest {
|
||||||
mockkObject(PositionUtils)
|
mockkObject(PositionUtils)
|
||||||
every { PositionUtils.validate(any()) } throws InvalidPositionsException(errors)
|
every { PositionUtils.validate(any()) } throws InvalidPositionsException(errors)
|
||||||
|
|
||||||
val group = Group(1L, "Unit test group")
|
val group = GroupDto(1L, "Unit test group", listOf())
|
||||||
val steps = listOf(RecipeStepDto(1L, 1, "A message"))
|
val steps = listOf(RecipeStepDto(1L, 1, "A message"))
|
||||||
val groupInfo = RecipeGroupInformationDto(1L, group, "A note", steps)
|
val groupInfo = RecipeGroupInformationDto(1L, group, "A note", steps)
|
||||||
|
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.configuration
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.PdfDocument
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource
|
|
||||||
import io.mockk.*
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.springframework.core.io.ByteArrayResource
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
//private class TouchUpKitServiceTestContext {
|
|
||||||
// val touchUpKitRepository = mockk<TouchUpKitRepository>()
|
|
||||||
// val fileService = mockk<WriteableFileLogic> {
|
|
||||||
// every { write(any<ByteArrayResource>(), any(), any()) } just Runs
|
|
||||||
// }
|
|
||||||
// val configService = mockk<ConfigurationLogic>(relaxed = true)
|
|
||||||
// val touchUpKitService = spyk(DefaultTouchUpKitLogic(fileService, configService, touchUpKitRepository))
|
|
||||||
// val pdfDocumentData = mockk<ByteArrayResource>()
|
|
||||||
// val pdfDocument = mockk<PdfDocument> {
|
|
||||||
// mockkStatic(PdfDocument::toByteArrayResource)
|
|
||||||
// mockkStatic(PdfDocument::toByteArrayResource)
|
|
||||||
// every { toByteArrayResource() } returns pdfDocumentData
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
class TouchUpKitLogicTest {
|
|
||||||
// private val job = "job"
|
|
||||||
//
|
|
||||||
// @AfterEach
|
|
||||||
// internal fun afterEach() {
|
|
||||||
// clearAllMocks()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // generateJobPdf()
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun `generateJobPdf() generates a valid PdfDocument for the given job`() {
|
|
||||||
// test {
|
|
||||||
// val generatedPdfDocument = touchUpKitService.generateJobPdf(job)
|
|
||||||
//
|
|
||||||
// setOf(0, 1).forEach {
|
|
||||||
// assertEquals(TOUCH_UP_TEXT_FR, generatedPdfDocument.containers[it].texts[0].text)
|
|
||||||
// assertEquals(TOUCH_UP_TEXT_EN, generatedPdfDocument.containers[it].texts[1].text)
|
|
||||||
// assertEquals(job, generatedPdfDocument.containers[it].texts[2].text)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // generateJobPdfResource()
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun `generateJobPdfResource() generates and returns a ByteArrayResource for the given job then cache it`() {
|
|
||||||
// test {
|
|
||||||
// every { touchUpKitService.generateJobPdf(any()) } returns pdfDocument
|
|
||||||
// with(touchUpKitService) {
|
|
||||||
// every { job.cachePdfDocument(pdfDocument) } just Runs
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val generatedResource = touchUpKitService.generateJobPdfResource(job)
|
|
||||||
//
|
|
||||||
// assertEquals(pdfDocumentData, generatedResource)
|
|
||||||
//
|
|
||||||
// verify {
|
|
||||||
// with(touchUpKitService) {
|
|
||||||
// job.cachePdfDocument(pdfDocument)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun `generateJobPdfResource() returns a cached ByteArrayResource from the FileService when caching is enabled and a cached file eixsts for the given job`() {
|
|
||||||
// test {
|
|
||||||
// enableCachePdf()
|
|
||||||
// every { fileService.exists(any()) } returns true
|
|
||||||
// every { fileService.read(any()) } returns pdfDocumentData
|
|
||||||
// every { configService.get(ConfigurationType.TOUCH_UP_KIT_CACHE_PDF) } returns configuration(
|
|
||||||
// ConfigurationType.TOUCH_UP_KIT_CACHE_PDF,
|
|
||||||
// "true"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// val redResource = touchUpKitService.generateJobPdfResource(job)
|
|
||||||
//
|
|
||||||
// assertEquals(pdfDocumentData, redResource)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // String.cachePdfDocument()
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun `cachePdfDocument() does nothing when caching is disabled`() {
|
|
||||||
// test {
|
|
||||||
// disableCachePdf()
|
|
||||||
//
|
|
||||||
// with(touchUpKitService) {
|
|
||||||
// job.cachePdfDocument(pdfDocument)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// verify(exactly = 0) {
|
|
||||||
// fileService.write(any<ByteArrayResource>(), any(), any())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun `cachePdfDocument() writes the given document to the FileService when cache is enabled`() {
|
|
||||||
// test {
|
|
||||||
// enableCachePdf()
|
|
||||||
//
|
|
||||||
// with(touchUpKitService) {
|
|
||||||
// job.cachePdfDocument(pdfDocument)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// verify {
|
|
||||||
// fileService.write(pdfDocumentData, any(), true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun TouchUpKitServiceTestContext.enableCachePdf() =
|
|
||||||
// this.setCachePdf(true)
|
|
||||||
//
|
|
||||||
// private fun TouchUpKitServiceTestContext.disableCachePdf() =
|
|
||||||
// this.setCachePdf(false)
|
|
||||||
//
|
|
||||||
// private fun TouchUpKitServiceTestContext.setCachePdf(enabled: Boolean) {
|
|
||||||
// every { configService.getContent(ConfigurationType.TOUCH_UP_KIT_CACHE_PDF) } returns enabled.toString()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun test(test: TouchUpKitServiceTestContext.() -> Unit) {
|
|
||||||
// TouchUpKitServiceTestContext().test()
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.logic.account
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.users.DefaultGroupLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.service.GroupService
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
|
||||||
|
class DefaultGroupLogicTest {
|
||||||
|
private val group = GroupDto(1L, "Unit test group", listOf())
|
||||||
|
private val user = UserDto(1L, "Unit test", "User", "asecurepassword", null, listOf())
|
||||||
|
|
||||||
|
private val groupServiceMock = mockk<GroupService> {
|
||||||
|
every { existsById(any()) } returns false
|
||||||
|
every { existsByName(any(), any()) } returns false
|
||||||
|
every { getAll() } returns listOf()
|
||||||
|
every { getById(any()) } returns group
|
||||||
|
every { save(any()) } returns group
|
||||||
|
every { deleteById(any()) } just runs
|
||||||
|
}
|
||||||
|
private val userLogicMock = mockk<UserLogic> {
|
||||||
|
every { getAllByGroup(any()) } returns listOf()
|
||||||
|
every { getById(any(), any(), any()) } returns user
|
||||||
|
every { getDefaultGroupUser(any()) } returns user
|
||||||
|
every { saveDefaultGroupUser(any()) } just runs
|
||||||
|
every { deleteById(any()) } just runs
|
||||||
|
}
|
||||||
|
|
||||||
|
private val groupLogic = spyk(DefaultGroupLogic(groupServiceMock, userLogicMock))
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getUsersForGroup_normalBehavior_callsGetAllByGroupInUserLogic() {
|
||||||
|
// Arrange
|
||||||
|
every { groupLogic.getById(any()) } returns group
|
||||||
|
|
||||||
|
// Act
|
||||||
|
groupLogic.getUsersForGroup(group.id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogicMock.getAllByGroup(group)
|
||||||
|
}
|
||||||
|
confirmVerified(userLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_nameAlreadyExists_throwsAlreadyExists() {
|
||||||
|
// Arrange
|
||||||
|
every { groupServiceMock.existsByName(any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { groupLogic.save(group) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_normalBehavior_throwsAlreadyExists() {
|
||||||
|
// Arrange
|
||||||
|
every { groupServiceMock.existsByName(any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { groupLogic.update(group) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteById_normalBehavior_callsDeleteByIdInUserLogicWithDefaultGroupUserId() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
groupLogic.deleteById(group.id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogicMock.deleteById(group.defaultGroupUserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.logic.account
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserSaveDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.UserUpdateDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.users.DefaultUserLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
|
import dev.fyloz.colorrecipesexplorer.service.UserService
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class DefaultUserLogicTest {
|
||||||
|
private val user = UserDto(1L, "Unit test", "User", "asecurepassword", null, listOf())
|
||||||
|
private val group = GroupDto(1L, "Unit test group", listOf())
|
||||||
|
|
||||||
|
private val userServiceMock = mockk<UserService> {
|
||||||
|
every { existsById(any()) } returns false
|
||||||
|
every { existsByFirstNameAndLastName(any(), any(), any()) } returns false
|
||||||
|
every { getAll(any(), any()) } returns listOf()
|
||||||
|
every { getAllByGroup(any()) } returns listOf()
|
||||||
|
every { getById(any(), any(), any()) } returns user
|
||||||
|
every { getByFirstNameAndLastName(any(), any()) } returns user
|
||||||
|
every { getDefaultGroupUser(any()) } returns user
|
||||||
|
}
|
||||||
|
private val groupLogicMock = mockk<GroupLogic> {
|
||||||
|
every { getById(any()) } returns group
|
||||||
|
}
|
||||||
|
private val passwordEncoderMock = mockk<PasswordEncoder> {
|
||||||
|
every { encode(any()) } answers { "encoded ${this.firstArg<String>()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val userLogic = spyk(DefaultUserLogic(userServiceMock, groupLogicMock, passwordEncoderMock))
|
||||||
|
|
||||||
|
private val userSaveDto = UserSaveDto(
|
||||||
|
user.id,
|
||||||
|
user.firstName,
|
||||||
|
user.lastName,
|
||||||
|
user.password,
|
||||||
|
null,
|
||||||
|
user.permissions,
|
||||||
|
user.isSystemUser,
|
||||||
|
user.isDefaultGroupUser
|
||||||
|
)
|
||||||
|
private val userUpdateDto = UserUpdateDto(user.id, user.firstName, user.lastName, null, listOf())
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAll_normalBehavior_callsGetAllInServiceWithSpecialUsersDisabled() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
userLogic.getAll()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userServiceMock.getAll(isSystemUser = false, isDefaultGroupUser = false)
|
||||||
|
}
|
||||||
|
confirmVerified(userServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllByGroup_normalBehavior_callsGetAllByGroupInService() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
userLogic.getAllByGroup(group)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userServiceMock.getAllByGroup(group)
|
||||||
|
}
|
||||||
|
confirmVerified(userServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getById_default_normalBehavior_callsGetByIdWithSpecialUsersDisabled() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
userLogic.getById(user.id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.getById(user.id, isSystemUser = false, isDefaultGroupUser = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getById_normalBehavior_callsGetByIdInService() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
userLogic.getById(user.id, isSystemUser = false, isDefaultGroupUser = true)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userServiceMock.getById(user.id, isSystemUser = false, isDefaultGroupUser = true)
|
||||||
|
}
|
||||||
|
confirmVerified(userServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getById_notFound_throwsNotFoundException() {
|
||||||
|
// Arrange
|
||||||
|
every { userServiceMock.getById(any(), any(), any()) } returns null
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<NotFoundException> { userLogic.getById(user.id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getDefaultGroupUser_normalBehavior_callsGetDefaultGroupUserInService() {
|
||||||
|
// Arrange
|
||||||
|
// Act
|
||||||
|
userLogic.getDefaultGroupUser(group)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userServiceMock.getDefaultGroupUser(group)
|
||||||
|
}
|
||||||
|
confirmVerified(userServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getDefaultGroupUser_notFound_throwsNotFoundException() {
|
||||||
|
// Arrange
|
||||||
|
every { userServiceMock.getDefaultGroupUser(any()) } returns null
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<NotFoundException> { userLogic.getDefaultGroupUser(group) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveDefaultGroupUser_normalBehavior_callsSaveWithValidSaveDto() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.save(any<UserSaveDto>()) } returns user
|
||||||
|
|
||||||
|
val expectedSaveDto = UserSaveDto(
|
||||||
|
group.defaultGroupUserId, group.name, "User", group.name, group.id, listOf(), isDefaultGroupUser = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.saveDefaultGroupUser(group)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.save(expectedSaveDto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_dto_normalBehavior_callsSaveWithValidUser() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.save(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
val expectedUser = user.copy(password = "encoded ${user.password}")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.save(userSaveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.save(expectedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Causes a stackoverflow because of a bug in mockk
|
||||||
|
// @Test
|
||||||
|
// fun save_normalBehavior_callsSaveInService() {
|
||||||
|
// // Arrange
|
||||||
|
// // Act
|
||||||
|
// userLogic.save(user)
|
||||||
|
//
|
||||||
|
// // Assert
|
||||||
|
// verify {
|
||||||
|
// userServiceMock.save(user)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_idAlreadyExists_throwsAlreadyExistsException() {
|
||||||
|
// Arrange
|
||||||
|
every { userServiceMock.existsById(any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { userLogic.save(user) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_fullNameAlreadyExists_throwsAlreadyExistsException() {
|
||||||
|
// Arrange
|
||||||
|
every { userServiceMock.existsByFirstNameAndLastName(any(), any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { userLogic.save(userSaveDto) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_dto_normalBehavior_callsUpdateWithValidUser() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.getById(any(), any(), any()) } returns user
|
||||||
|
every { userLogic.update(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.update(userUpdateDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.update(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_fullNameAlreadyExists_ThrowAlreadyExistsException() {
|
||||||
|
// Arrange
|
||||||
|
every { userServiceMock.existsByFirstNameAndLastName(any(), any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { userLogic.update(user) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateLastLoginTime_normalBehavior_callsUpdateWithUpdatedTime() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.getById(any()) } returns user
|
||||||
|
every { userLogic.update(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
val time = LocalDateTime.now()
|
||||||
|
val expectedUser = user.copy(lastLoginTime = time)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.updateLastLoginTime(user.id, time)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.update(expectedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updatePassword_normalBehavior_callsUpdateWithUpdatedTime() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.getById(any()) } returns user
|
||||||
|
every { userLogic.update(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
val updatedPassword = "updatedpassword"
|
||||||
|
val expectedUser = user.copy(password = "encoded $updatedPassword")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.updatePassword(user.id, updatedPassword)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.update(expectedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addPermission_normalBehavior_callsUpdateWithAddedPermission() {
|
||||||
|
// Arrange
|
||||||
|
every { userLogic.getById(any()) } returns user
|
||||||
|
every { userLogic.update(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
val addedPermission = Permission.VIEW_COMPANY
|
||||||
|
val expectedUser = user.copy(permissions = user.permissions + addedPermission)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.addPermission(user.id, addedPermission)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.update(expectedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun removePermission_normalBehavior_callsUpdateWithAddedPermission() {
|
||||||
|
// Arrange
|
||||||
|
val removedPermission = Permission.VIEW_COMPANY
|
||||||
|
val baseUser = user.copy(permissions = user.permissions + removedPermission)
|
||||||
|
|
||||||
|
every { userLogic.getById(any()) } returns baseUser
|
||||||
|
every { userLogic.update(any<UserDto>()) } returns user
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userLogic.removePermission(user.id, removedPermission)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
userLogic.update(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ private const val mockFilePath = "existingFile"
|
||||||
private val mockFilePathPath = Path.of(mockFilePath)
|
private val mockFilePathPath = Path.of(mockFilePath)
|
||||||
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
||||||
|
|
||||||
class FileLogicTest {
|
class DefaultFileLogicTest {
|
||||||
private val fileCacheMock = mockk<FileCache> {
|
private val fileCacheMock = mockk<FileCache> {
|
||||||
every { setExists(any(), any()) } just runs
|
every { setExists(any(), any()) } just runs
|
||||||
}
|
}
|
Loading…
Reference in New Issue