develop #29
|
@ -4,11 +4,14 @@ object Constants {
|
|||
object ControllerPaths {
|
||||
const val COMPANY = "/api/company"
|
||||
const val FILE = "/api/file"
|
||||
const val GROUP = "/api/user/group"
|
||||
const val INVENTORY = "/api/inventory"
|
||||
const val MATERIAL = "/api/material"
|
||||
const val MATERIAL_TYPE = "/api/materialtype"
|
||||
const val MIX = "/api/recipe/mix"
|
||||
const val RECIPE = "/api/recipe"
|
||||
const val TOUCH_UP_KIT = "/api/touchupkit"
|
||||
const val USER = "/api/user"
|
||||
}
|
||||
|
||||
object FilePaths {
|
||||
|
@ -22,6 +25,7 @@ object Constants {
|
|||
|
||||
object ModelNames {
|
||||
const val COMPANY = "Company"
|
||||
const val GROUP = "Group"
|
||||
const val MATERIAL = "Material"
|
||||
const val MATERIAL_TYPE = "MaterialType"
|
||||
const val MIX = "Mix"
|
||||
|
@ -30,12 +34,14 @@ object Constants {
|
|||
const val RECIPE = "Recipe"
|
||||
const val RECIPE_STEP = "RecipeStep"
|
||||
const val TOUCH_UP_KIT = "TouchUpKit"
|
||||
const val USER = "User"
|
||||
}
|
||||
|
||||
object ValidationMessages {
|
||||
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 RANGE_OUTSIDE_PERCENTS = "Must be between 0 and 100"
|
||||
const val PASSWORD_TOO_SMALL = "Must contains at least 8 characters"
|
||||
}
|
||||
|
||||
object ValidationRegexes {
|
||||
|
|
|
@ -2,13 +2,12 @@ package dev.fyloz.colorrecipesexplorer.config.security
|
|||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
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.logic.users.JwtLogic
|
||||
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 io.jsonwebtoken.ExpiredJwtException
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
|
@ -40,7 +39,7 @@ class JwtAuthenticationFilter(
|
|||
}
|
||||
|
||||
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}...")
|
||||
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
||||
}
|
||||
|
@ -116,8 +115,8 @@ class JwtAuthorizationFilter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getAuthenticationToken(user: UserOutputDto) =
|
||||
UsernamePasswordAuthenticationToken(user.id, null, user.permissions.toAuthorities())
|
||||
private fun getAuthenticationToken(user: UserDto) =
|
||||
UsernamePasswordAuthenticationToken(user.id, null, user.authorities)
|
||||
|
||||
private fun getAuthenticationToken(userId: Long): UsernamePasswordAuthenticationToken? = try {
|
||||
val userDetails = userDetailsLogic.loadUserById(userId)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package dev.fyloz.colorrecipesexplorer.config.security
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||
import dev.fyloz.colorrecipesexplorer.emergencyMode
|
||||
import dev.fyloz.colorrecipesexplorer.logic.users.JwtLogic
|
||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserDetailsLogic
|
||||
import dev.fyloz.colorrecipesexplorer.logic.users.UserLogic
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||
import mu.KotlinLogging
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
|
@ -147,13 +147,14 @@ class SecurityConfig(
|
|||
with(securityProperties.root!!) {
|
||||
if (!userLogic.existsById(this.id)) {
|
||||
userLogic.save(
|
||||
User(
|
||||
UserDto(
|
||||
id = this.id,
|
||||
firstName = rootUserFirstName,
|
||||
lastName = rootUserLastName,
|
||||
group = null,
|
||||
password = passwordEncoder.encode(this.password),
|
||||
isSystemUser = true,
|
||||
permissions = mutableSetOf(Permission.ADMIN)
|
||||
permissions = listOf(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 dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||
import java.time.LocalDate
|
||||
import javax.validation.constraints.Max
|
||||
import javax.validation.constraints.Min
|
||||
|
@ -94,7 +93,7 @@ data class RecipeUpdateDto(
|
|||
data class RecipeGroupInformationDto(
|
||||
override val id: Long = 0L,
|
||||
|
||||
val group: Group,
|
||||
val group: GroupDto,
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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 {
|
||||
const val ID_IDENTIFIER_NAME = "id"
|
||||
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.config.annotations.LogicComponent
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
|
@ -30,7 +31,7 @@ class DefaultRecipeStepLogic(recipeStepService: RecipeStepService) :
|
|||
}
|
||||
|
||||
class InvalidGroupStepsPositionsException(
|
||||
val group: Group,
|
||||
val group: GroupDto,
|
||||
val exception: InvalidPositionsException
|
||||
) : RestException(
|
||||
"invalid-groupinformation-recipestep-position",
|
||||
|
@ -39,7 +40,7 @@ class InvalidGroupStepsPositionsException(
|
|||
"The position of steps for the group ${group.name} are invalid",
|
||||
mapOf(
|
||||
"group" to group.name,
|
||||
"groupId" to group.id!!,
|
||||
"groupId" to group.id,
|
||||
"invalidSteps" to exception.errors
|
||||
)
|
||||
) {
|
||||
|
|
|
@ -1,97 +1,80 @@
|
|||
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.logic.AbstractExternalNamedModelService
|
||||
import dev.fyloz.colorrecipesexplorer.logic.ExternalNamedModelService
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.stereotype.Service
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NoDefaultGroupException
|
||||
import dev.fyloz.colorrecipesexplorer.logic.BaseLogic
|
||||
import dev.fyloz.colorrecipesexplorer.logic.Logic
|
||||
import dev.fyloz.colorrecipesexplorer.service.GroupService
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.util.WebUtils
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import javax.transaction.Transactional
|
||||
|
||||
const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
||||
|
||||
interface GroupLogic :
|
||||
ExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository> {
|
||||
interface GroupLogic : Logic<GroupDto, GroupService> {
|
||||
/** 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]. */
|
||||
fun getRequestDefaultGroup(request: HttpServletRequest): Group
|
||||
fun getRequestDefaultGroup(request: HttpServletRequest): GroupDto
|
||||
|
||||
/** Sets the default group cookie for the given HTTP [response]. */
|
||||
fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse)
|
||||
fun setResponseDefaultGroup(id: Long, response: HttpServletResponse)
|
||||
}
|
||||
|
||||
@Service
|
||||
@Profile("!emergency")
|
||||
class DefaultGroupLogic(
|
||||
private val userLogic: UserLogic,
|
||||
groupRepository: GroupRepository
|
||||
) : AbstractExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository>(
|
||||
groupRepository
|
||||
),
|
||||
@LogicComponent
|
||||
class DefaultGroupLogic(service: GroupService, private val userLogic: UserLogic) :
|
||||
BaseLogic<GroupDto, GroupService>(service, Constants.ModelNames.GROUP),
|
||||
GroupLogic {
|
||||
override fun idNotFoundException(id: Long) = groupIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = groupIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = groupNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = groupNameAlreadyExistsException(name)
|
||||
override fun getUsersForGroup(id: Long) = userLogic.getAllByGroup(getById(id))
|
||||
|
||||
override fun Group.toOutput() = GroupOutputDto(
|
||||
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 {
|
||||
override fun getRequestDefaultGroup(request: HttpServletRequest): GroupDto {
|
||||
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
||||
?: throw NoDefaultGroupException()
|
||||
val defaultGroupUser = userLogic.getById(
|
||||
defaultGroupCookie.value.toLong(),
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = true
|
||||
isSystemUser = false,
|
||||
isDefaultGroupUser = true
|
||||
)
|
||||
return defaultGroupUser.group!!
|
||||
}
|
||||
|
||||
override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) {
|
||||
val group = getById(groupId)
|
||||
val defaultGroupUser = userLogic.getDefaultGroupUser(group)
|
||||
override fun setResponseDefaultGroup(id: Long, response: HttpServletResponse) {
|
||||
val defaultGroupUser = userLogic.getDefaultGroupUser(getById(id))
|
||||
response.addHeader(
|
||||
"Set-Cookie",
|
||||
"$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.module.kotlin.readValue
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||
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.dtos.UserDetails
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.UserDto
|
||||
import dev.fyloz.colorrecipesexplorer.utils.base64encode
|
||||
import dev.fyloz.colorrecipesexplorer.utils.toDate
|
||||
import io.jsonwebtoken.Jwts
|
||||
|
@ -23,10 +21,10 @@ interface JwtLogic {
|
|||
fun buildJwt(userDetails: UserDetails): String
|
||||
|
||||
/** 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. */
|
||||
fun parseJwt(jwt: String): UserOutputDto
|
||||
fun parseJwt(jwt: String): UserDto
|
||||
}
|
||||
|
||||
@Service
|
||||
|
@ -54,14 +52,14 @@ class DefaultJwtLogic(
|
|||
override fun buildJwt(userDetails: UserDetails) =
|
||||
buildJwt(userDetails.user)
|
||||
|
||||
override fun buildJwt(user: User): String =
|
||||
override fun buildJwt(user: UserDto): String =
|
||||
jwtBuilder
|
||||
.setSubject(user.id.toString())
|
||||
.setExpiration(getCurrentExpirationDate())
|
||||
.claim(jwtClaimUser, user.serialize())
|
||||
.compact()
|
||||
|
||||
override fun parseJwt(jwt: String): UserOutputDto =
|
||||
override fun parseJwt(jwt: String): UserDto =
|
||||
with(
|
||||
jwtParser.parseClaimsJws(jwt)
|
||||
.body.get(jwtClaimUser, String::class.java)
|
||||
|
@ -74,6 +72,6 @@ class DefaultJwtLogic(
|
|||
.plusSeconds(securityProperties.jwtDuration)
|
||||
.toDate()
|
||||
|
||||
private fun User.serialize(): String =
|
||||
objectMapper.writeValueAsString(this.toOutputDto())
|
||||
private fun UserDto.serialize(): String =
|
||||
objectMapper.writeValueAsString(this)
|
||||
}
|
||||
|
|
|
@ -4,18 +4,18 @@ import dev.fyloz.colorrecipesexplorer.SpringUserDetails
|
|||
import dev.fyloz.colorrecipesexplorer.SpringUserDetailsService
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||
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.model.account.Permission
|
||||
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.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
interface UserDetailsLogic : SpringUserDetailsService {
|
||||
/** Loads an [User] for the given [id]. */
|
||||
fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails
|
||||
fun loadUserById(id: Long, isDefaultGroupUser: Boolean = true): UserDetails
|
||||
}
|
||||
|
||||
@Service
|
||||
|
@ -25,17 +25,17 @@ class DefaultUserDetailsLogic(
|
|||
) : UserDetailsLogic {
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
try {
|
||||
return loadUserById(username.toLong(), true)
|
||||
return loadUserById(username.toLong(), false)
|
||||
} catch (ex: NotFoundException) {
|
||||
throw UsernameNotFoundException(username)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
||||
override fun loadUserById(id: Long, isDefaultGroupUser: Boolean): UserDetails {
|
||||
val user = userLogic.getById(
|
||||
id,
|
||||
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
||||
ignoreSystemUsers = false
|
||||
isSystemUser = true,
|
||||
isDefaultGroupUser = isDefaultGroupUser
|
||||
)
|
||||
return UserDetails(user)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class DefaultUserDetailsLogic(
|
|||
class EmergencyUserDetailsLogic(
|
||||
securityProperties: CreSecurityProperties
|
||||
) : UserDetailsLogic {
|
||||
private val users: Set<User>
|
||||
private val users: Set<UserDto>
|
||||
|
||||
init {
|
||||
if (securityProperties.root == null) {
|
||||
|
@ -56,20 +56,23 @@ class EmergencyUserDetailsLogic(
|
|||
users = setOf(
|
||||
// Add root user
|
||||
with(securityProperties.root!!) {
|
||||
user(
|
||||
UserDto(
|
||||
id = this.id,
|
||||
plainPassword = this.password,
|
||||
permissions = mutableSetOf(Permission.ADMIN)
|
||||
firstName = "Root",
|
||||
lastName = "User",
|
||||
group = null,
|
||||
password = this.password,
|
||||
permissions = listOf(Permission.ADMIN)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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 }
|
||||
?: throw UsernameNotFoundException(id.toString())
|
||||
|
||||
|
|
|
@ -1,189 +1,146 @@
|
|||
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.logic.AbstractExternalModelService
|
||||
import dev.fyloz.colorrecipesexplorer.logic.ExternalModelService
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
||||
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.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.Profile
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.web.util.WebUtils
|
||||
import java.time.LocalDateTime
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
interface UserLogic :
|
||||
ExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository> {
|
||||
/** Check if an [User] with the given [firstName] and [lastName] exists. */
|
||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
||||
interface UserLogic : Logic<UserDto, UserService> {
|
||||
/** Gets all users which have the given [group]. */
|
||||
fun getAllByGroup(group: GroupDto): Collection<UserDto>
|
||||
|
||||
/** Gets the user with the given [id]. */
|
||||
fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User
|
||||
|
||||
/** Gets all users which have the given [group]. */
|
||||
fun getByGroup(group: Group): Collection<User>
|
||||
fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean): UserDto
|
||||
|
||||
/** 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]. */
|
||||
fun saveDefaultGroupUser(group: Group)
|
||||
fun saveDefaultGroupUser(group: GroupDto)
|
||||
|
||||
/** Updates de given [entity]. **/
|
||||
fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User
|
||||
/** Saves the given [dto]. */
|
||||
fun save(dto: UserSaveDto): UserDto
|
||||
|
||||
/** Updates the last login time of the user with the given [userId]. */
|
||||
fun updateLastLoginTime(userId: Long, time: LocalDateTime = LocalDateTime.now()): User
|
||||
/** Updates the given [dto]. */
|
||||
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]. */
|
||||
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]. */
|
||||
fun addPermission(userId: Long, permission: Permission): User
|
||||
/** Adds the given [permission] to the user with the given [id]. */
|
||||
fun addPermission(id: Long, permission: Permission): UserDto
|
||||
|
||||
/** Removes the given [permission] from the user with the given [userId]. */
|
||||
fun removePermission(userId: Long, permission: Permission): User
|
||||
/** Removes the given [permission] from the user with the given [id]. */
|
||||
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)
|
||||
}
|
||||
|
||||
@Service
|
||||
@Profile("!emergency")
|
||||
@LogicComponent
|
||||
class DefaultUserLogic(
|
||||
userRepository: UserRepository,
|
||||
@Lazy val groupLogic: GroupLogic,
|
||||
) : AbstractExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository>(
|
||||
userRepository
|
||||
),
|
||||
UserLogic {
|
||||
override fun idNotFoundException(id: Long) = userIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = userIdAlreadyExistsException(id)
|
||||
service: UserService, @Lazy private val groupLogic: GroupLogic, @Lazy private val passwordEncoder: PasswordEncoder
|
||||
) : BaseLogic<UserDto, UserService>(service, Constants.ModelNames.USER), UserLogic {
|
||||
override fun getAll() = service.getAll(isSystemUser = false, isDefaultGroupUser = false)
|
||||
|
||||
override fun User.toOutput() = this.toOutputDto()
|
||||
override fun getAllByGroup(group: GroupDto) = service.getAllByGroup(group)
|
||||
|
||||
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
|
||||
repository.existsByFirstNameAndLastName(firstName, lastName)
|
||||
override fun getById(id: Long) = getById(id, isSystemUser = false, isDefaultGroupUser = false)
|
||||
override fun getById(id: Long, isSystemUser: Boolean, isDefaultGroupUser: Boolean) =
|
||||
service.getById(id, !isDefaultGroupUser, !isSystemUser) ?: throw notFoundException(value = id)
|
||||
|
||||
override fun getAll(): Collection<User> =
|
||||
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
|
||||
override fun getDefaultGroupUser(group: GroupDto) =
|
||||
service.getDefaultGroupUser(group) ?: throw notFoundException(identifierName = "groupId", value = group.id)
|
||||
|
||||
override fun getById(id: Long): User =
|
||||
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) {
|
||||
override fun saveDefaultGroupUser(group: GroupDto) {
|
||||
save(
|
||||
user(
|
||||
id = 1000000L + group.id!!,
|
||||
UserSaveDto(
|
||||
id = group.defaultGroupUserId,
|
||||
firstName = group.name,
|
||||
lastName = "User",
|
||||
plainPassword = group.name,
|
||||
group = group,
|
||||
password = group.name,
|
||||
groupId = group.id,
|
||||
permissions = listOf(),
|
||||
isDefaultGroupUser = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateLastLoginTime(userId: Long, time: LocalDateTime): User {
|
||||
val user = getById(userId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
|
||||
user.lastLoginTime = time
|
||||
override fun save(dto: UserSaveDto) = save(
|
||||
UserDto(
|
||||
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(
|
||||
user,
|
||||
ignoreDefaultGroupUsers = true,
|
||||
ignoreSystemUsers = false
|
||||
user.copy(
|
||||
firstName = dto.firstName,
|
||||
lastName = dto.lastName,
|
||||
group = if (dto.groupId != null) groupLogic.getById(dto.groupId) else null,
|
||||
permissions = dto.permissions
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun update(entity: UserUpdateDto): User {
|
||||
val persistedUser by lazy { getById(entity.id) }
|
||||
return update(with(entity) {
|
||||
User(
|
||||
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(dto: UserDto): UserDto {
|
||||
throwIfFirstNameAndLastNameAlreadyExists(dto.firstName, dto.lastName, dto.id)
|
||||
|
||||
return super.update(dto)
|
||||
}
|
||||
|
||||
override fun update(entity: User): User =
|
||||
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
|
||||
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 updateLastLoginTime(id: Long, time: LocalDateTime) = with(getById(id)) {
|
||||
update(this.copy(lastLoginTime = time))
|
||||
}
|
||||
|
||||
override fun updatePassword(id: Long, password: String): User {
|
||||
val persistedUser = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
return super.update(with(persistedUser) {
|
||||
user(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
plainPassword = password,
|
||||
isDefaultGroupUser,
|
||||
isSystemUser,
|
||||
group,
|
||||
permissions,
|
||||
lastLoginTime
|
||||
)
|
||||
})
|
||||
override fun updatePassword(id: Long, password: String) = with(getById(id)) {
|
||||
update(this.copy(password = passwordEncoder.encode(password)))
|
||||
}
|
||||
|
||||
override fun addPermission(userId: Long, permission: Permission): User =
|
||||
super.update(getById(userId).apply { permissions += permission })
|
||||
override fun addPermission(id: Long, permission: Permission) = with(getById(id)) {
|
||||
update(this.copy(permissions = this.permissions + permission))
|
||||
}
|
||||
|
||||
override fun removePermission(userId: Long, permission: Permission): User =
|
||||
super.update(getById(userId).apply { permissions -= permission })
|
||||
override fun removePermission(id: Long, permission: Permission) = with(getById(id)) {
|
||||
update(this.copy(permissions = this.permissions - permission))
|
||||
}
|
||||
|
||||
override fun logout(request: HttpServletRequest) {
|
||||
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
|
||||
val authorizationCookie = WebUtils.getCookie(request, authorizationCookieName)
|
||||
if (authorizationCookie != null) {
|
||||
val authorizationToken = authorizationCookie.value
|
||||
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(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
@Column(unique = true)
|
||||
val name: String
|
||||
|
|
|
@ -8,7 +8,7 @@ import javax.persistence.*
|
|||
data class Material(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
@Column(unique = true)
|
||||
val name: String,
|
||||
|
|
|
@ -8,7 +8,7 @@ import javax.persistence.*
|
|||
data class MaterialType(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long? = null,
|
||||
override val id: Long,
|
||||
|
||||
@Column(unique = true)
|
||||
val name: String = "",
|
||||
|
|
|
@ -7,9 +7,9 @@ import javax.persistence.*
|
|||
data class Mix(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
var location: String?,
|
||||
val location: String?,
|
||||
|
||||
@Column(name = "recipe_id")
|
||||
val recipeId: Long,
|
||||
|
|
|
@ -7,13 +7,13 @@ import javax.persistence.*
|
|||
data class MixMaterial(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "material_id")
|
||||
val material: Material,
|
||||
|
||||
var quantity: Float,
|
||||
val quantity: Float,
|
||||
|
||||
var position: Int
|
||||
val position: Int
|
||||
) : ModelEntity
|
|
@ -7,7 +7,7 @@ import javax.persistence.*
|
|||
data class MixType(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
val name: String,
|
||||
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
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 {
|
||||
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()
|
||||
}
|
||||
val id: Long
|
||||
}
|
|
@ -9,7 +9,7 @@ import javax.persistence.*
|
|||
data class Recipe(
|
||||
@Id
|
||||
@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]. */
|
||||
val name: String,
|
||||
|
@ -47,15 +47,15 @@ data class Recipe(
|
|||
data class RecipeGroupInformation(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
val group: Group,
|
||||
|
||||
var note: String?,
|
||||
val note: String?,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_group_information_id")
|
||||
var steps: List<RecipeStep>?
|
||||
val steps: List<RecipeStep>?
|
||||
) : ModelEntity
|
|
@ -7,7 +7,7 @@ import javax.persistence.*
|
|||
data class RecipeStep(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
val position: Int,
|
||||
|
||||
|
|
|
@ -1,134 +1,24 @@
|
|||
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 org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.http.HttpStatus
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotEmpty
|
||||
|
||||
@Entity
|
||||
@Table(name = "user_group")
|
||||
data class Group(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override var id: Long? = null,
|
||||
override val id: Long,
|
||||
|
||||
@Column(unique = true)
|
||||
override val name: String = "",
|
||||
val name: String,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
val permissions: MutableSet<Permission> = mutableSetOf(),
|
||||
) : NamedModelEntity {
|
||||
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"
|
||||
)
|
||||
val permissions: List<Permission>,
|
||||
) : ModelEntity
|
|
@ -1,20 +1,10 @@
|
|||
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 org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.Size
|
||||
|
||||
private const val VALIDATION_PASSWORD_LENGTH = "Must contains at least 8 characters"
|
||||
|
||||
@Entity
|
||||
@Table(name = "user")
|
||||
|
@ -23,210 +13,31 @@ data class User(
|
|||
override val id: Long,
|
||||
|
||||
@Column(name = "first_name")
|
||||
val firstName: String = "",
|
||||
val firstName: String,
|
||||
|
||||
@Column(name = "last_name")
|
||||
val lastName: String = "",
|
||||
val lastName: String,
|
||||
|
||||
val password: String = "",
|
||||
val password: String,
|
||||
|
||||
@Column(name = "default_group_user")
|
||||
val isDefaultGroupUser: Boolean = false,
|
||||
val isDefaultGroupUser: Boolean,
|
||||
|
||||
@Column(name = "system_user")
|
||||
val isSystemUser: Boolean = false,
|
||||
val isSystemUser: Boolean,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
@Fetch(FetchMode.SELECT)
|
||||
var group: Group? = null,
|
||||
val group: Group?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "user_permission", joinColumns = [JoinColumn(name = "user_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
val permissions: MutableSet<Permission> = mutableSetOf(),
|
||||
val permissions: List<Permission>,
|
||||
|
||||
@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?
|
||||
) : 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"
|
||||
)
|
||||
) : ModelEntity
|
|
@ -9,7 +9,7 @@ import javax.persistence.*
|
|||
data class TouchUpKit(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
val project: String,
|
||||
|
||||
|
@ -41,7 +41,7 @@ data class TouchUpKit(
|
|||
data class TouchUpKitProduct(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
override val id: Long,
|
||||
|
||||
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.User
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface UserRepository : JpaRepository<User, Long> {
|
||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
||||
|
||||
fun findByFirstNameAndLastName(firstName: String, lastName: String): User?
|
||||
/** Checks if a user with the given [firstName], [lastName] and a different [id] exists. */
|
||||
fun existsByFirstNameAndLastNameAndIdNot(firstName: String, lastName: String, id: Long): Boolean
|
||||
|
||||
/** 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 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
|
||||
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
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
|
||||
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.UserLogic
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
@ -13,30 +18,25 @@ import javax.servlet.http.HttpServletRequest
|
|||
import javax.servlet.http.HttpServletResponse
|
||||
import javax.validation.Valid
|
||||
|
||||
private const val USER_CONTROLLER_PATH = "api/user"
|
||||
private const val GROUP_CONTROLLER_PATH = "api/user/group"
|
||||
|
||||
@RestController
|
||||
@RequestMapping(USER_CONTROLLER_PATH)
|
||||
@RequestMapping(Constants.ControllerPaths.USER)
|
||||
@Profile("!emergency")
|
||||
class UserController(private val userLogic: UserLogic) {
|
||||
@GetMapping
|
||||
@PreAuthorizeViewUsers
|
||||
fun getAll() =
|
||||
ok(userLogic.getAllForOutput())
|
||||
ok(userLogic.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(userLogic.getByIdForOutput(id))
|
||||
ok(userLogic.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun save(@Valid @RequestBody user: UserSaveDto) =
|
||||
created<UserOutputDto>(USER_CONTROLLER_PATH) {
|
||||
with(userLogic) {
|
||||
save(user).toOutput()
|
||||
}
|
||||
created<UserDto>(Constants.ControllerPaths.USER) {
|
||||
userLogic.save(user)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
|
@ -78,7 +78,7 @@ class UserController(private val userLogic: UserLogic) {
|
|||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping(GROUP_CONTROLLER_PATH)
|
||||
@RequestMapping(Constants.ControllerPaths.GROUP)
|
||||
@Profile("!emergency")
|
||||
class GroupsController(
|
||||
private val groupLogic: GroupLogic,
|
||||
|
@ -87,20 +87,17 @@ class GroupsController(
|
|||
@GetMapping
|
||||
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
||||
fun getAll() =
|
||||
ok(groupLogic.getAllForOutput())
|
||||
ok(groupLogic.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(groupLogic.getByIdForOutput(id))
|
||||
ok(groupLogic.getById(id))
|
||||
|
||||
@GetMapping("{id}/users")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getUsersForGroup(@PathVariable id: Long) =
|
||||
ok(with(userLogic) {
|
||||
groupLogic.getUsersForGroup(id)
|
||||
.map { it.toOutput() }
|
||||
})
|
||||
ok(groupLogic.getUsersForGroup(id))
|
||||
|
||||
@PostMapping("default/{groupId}")
|
||||
@PreAuthorizeViewUsers
|
||||
|
@ -113,27 +110,25 @@ class GroupsController(
|
|||
@PreAuthorizeViewUsers
|
||||
fun getRequestDefaultGroup(request: HttpServletRequest) =
|
||||
ok(with(groupLogic) {
|
||||
getRequestDefaultGroup(request).toOutput()
|
||||
getRequestDefaultGroup(request)
|
||||
})
|
||||
|
||||
@GetMapping("currentuser")
|
||||
fun getCurrentGroupUser(request: HttpServletRequest) =
|
||||
ok(with(groupLogic.getRequestDefaultGroup(request)) {
|
||||
userLogic.getDefaultGroupUser(this).toOutputDto()
|
||||
userLogic.getDefaultGroupUser(this)
|
||||
})
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun save(@Valid @RequestBody group: GroupSaveDto) =
|
||||
created<GroupOutputDto>(GROUP_CONTROLLER_PATH) {
|
||||
with(groupLogic) {
|
||||
save(group).toOutput()
|
||||
}
|
||||
fun save(@Valid @RequestBody group: GroupDto) =
|
||||
created<GroupDto>(Constants.ControllerPaths.GROUP) {
|
||||
groupLogic.save(group)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun update(@Valid @RequestBody group: GroupUpdateDto) =
|
||||
fun update(@Valid @RequestBody group: GroupDto) =
|
||||
noContent {
|
||||
groupLogic.update(group)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.fyloz.colorrecipesexplorer.rest
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
||||
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.RestController
|
||||
|
||||
private const val INVENTORY_CONTROLLER_PATH = "api/inventory"
|
||||
|
||||
@RestController
|
||||
@RequestMapping(INVENTORY_CONTROLLER_PATH)
|
||||
@RequestMapping(Constants.ControllerPaths.INVENTORY)
|
||||
@Profile("!emergency")
|
||||
class InventoryController(
|
||||
private val inventoryLogic: InventoryLogic
|
||||
|
|
|
@ -44,19 +44,11 @@ fun fileCreated(basePath: String, producer: () -> String): ResponseEntity<String
|
|||
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. */
|
||||
@JvmName("createdDto")
|
||||
fun <T : EntityDto> created(controllerPath: String, body: T): ResponseEntity<T> =
|
||||
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. */
|
||||
@JvmName("createdDto")
|
||||
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 toDto(entity: Company) =
|
||||
CompanyDto(entity.id!!, entity.name)
|
||||
CompanyDto(entity.id, entity.name)
|
||||
|
||||
override fun toEntity(dto: CompanyDto) =
|
||||
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) =
|
||||
MaterialDto(
|
||||
entity.id!!,
|
||||
entity.id,
|
||||
entity.name,
|
||||
entity.inventoryQuantity,
|
||||
entity.isMixType,
|
||||
|
|
|
@ -37,7 +37,7 @@ class DefaultMaterialTypeService(repository: MaterialTypeRepository) :
|
|||
override fun isUsedByMaterial(id: Long) = repository.isUsedByMaterial(id)
|
||||
|
||||
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) =
|
||||
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 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) =
|
||||
MixMaterial(dto.id, materialService.toEntity(dto.material), dto.quantity, dto.position)
|
||||
|
|
|
@ -33,7 +33,7 @@ class DefaultMixService(
|
|||
|
||||
override fun toDto(entity: Mix) =
|
||||
MixDto(
|
||||
entity.id!!,
|
||||
entity.id,
|
||||
entity.location,
|
||||
entity.recipeId,
|
||||
mixTypeService.toDto(entity.mixType),
|
||||
|
|
|
@ -37,7 +37,7 @@ class DefaultMixTypeService(
|
|||
|
||||
override fun toDto(entity: MixType) =
|
||||
MixTypeDto(
|
||||
entity.id!!,
|
||||
entity.id,
|
||||
entity.name,
|
||||
materialTypeService.toDto(entity.materialType),
|
||||
if (entity.material != null) materialService.toDto(entity.material) else null
|
||||
|
|
|
@ -27,6 +27,7 @@ class DefaultRecipeService(
|
|||
private val companyService: CompanyService,
|
||||
private val mixService: MixService,
|
||||
private val recipeStepService: RecipeStepService,
|
||||
private val groupService: GroupService,
|
||||
private val configLogic: ConfigurationLogic
|
||||
) :
|
||||
BaseService<RecipeDto, Recipe, RecipeRepository>(repository), RecipeService {
|
||||
|
@ -39,7 +40,7 @@ class DefaultRecipeService(
|
|||
@Transactional
|
||||
override fun toDto(entity: Recipe) =
|
||||
RecipeDto(
|
||||
entity.id!!,
|
||||
entity.id,
|
||||
entity.name,
|
||||
entity.description,
|
||||
entity.color,
|
||||
|
@ -55,8 +56,8 @@ class DefaultRecipeService(
|
|||
|
||||
private fun groupInformationToDto(entity: RecipeGroupInformation) =
|
||||
RecipeGroupInformationDto(
|
||||
entity.id!!,
|
||||
entity.group,
|
||||
entity.id,
|
||||
groupService.toDto(entity.group),
|
||||
entity.note,
|
||||
entity.steps?.lazyMap(recipeStepService::toDto) ?: listOf()
|
||||
)
|
||||
|
@ -77,7 +78,12 @@ class DefaultRecipeService(
|
|||
)
|
||||
|
||||
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? =
|
||||
with(Period.parse(configLogic.getContent(ConfigurationType.RECIPE_APPROBATION_EXPIRATION))) {
|
||||
|
|
|
@ -11,7 +11,7 @@ interface RecipeStepService : Service<RecipeStepDto, RecipeStep, RecipeStepRepos
|
|||
class DefaultRecipeStepService(repository: RecipeStepRepository) :
|
||||
BaseService<RecipeStepDto, RecipeStep, RecipeStepRepository>(repository), RecipeStepService {
|
||||
override fun toDto(entity: RecipeStep) =
|
||||
RecipeStepDto(entity.id!!, entity.position, entity.message)
|
||||
RecipeStepDto(entity.id, entity.position, entity.message)
|
||||
|
||||
override fun toEntity(dto: RecipeStepDto) =
|
||||
RecipeStep(dto.id, dto.position, dto.message)
|
||||
|
|
|
@ -24,7 +24,7 @@ class DefaultTouchUpKitService(repository: TouchUpKitRepository, private val con
|
|||
|
||||
override fun toDto(entity: TouchUpKit) =
|
||||
TouchUpKitDto(
|
||||
entity.id!!,
|
||||
entity.id,
|
||||
entity.project,
|
||||
entity.buggy,
|
||||
entity.company,
|
||||
|
@ -39,7 +39,7 @@ class DefaultTouchUpKitService(repository: TouchUpKitRepository, private val con
|
|||
)
|
||||
|
||||
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) =
|
||||
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.readValue
|
||||
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.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.isAround
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.jackson.io.JacksonDeserializer
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.spyk
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class JwtLogicTest {
|
||||
class DefaultJwtLogicTest {
|
||||
private val objectMapper = jacksonObjectMapper()
|
||||
private val securityProperties = CreSecurityProperties().apply {
|
||||
jwtSecret = "XRRm7OflmFuCrOB2Xvmfsercih9DCKom"
|
||||
|
@ -34,12 +34,14 @@ class JwtLogicTest {
|
|||
|
||||
private val jwtService = spyk(DefaultJwtLogic(objectMapper, securityProperties))
|
||||
|
||||
private val user = user()
|
||||
private val userOutputDto = user.toOutputDto()
|
||||
private val user = UserDto(0L, "Unit test", "User", "", null, listOf())
|
||||
|
||||
// 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)
|
||||
.body.get(jwtClaimUser, String::class.java)
|
||||
|
||||
|
@ -47,27 +49,27 @@ class JwtLogicTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `buildJwt(userDetails) returns jwt string with valid user`() {
|
||||
fun buildJwt_userDetails_normalBehavior_returnsJwtStringWithValidUser() {
|
||||
val userDetails = UserDetails(user)
|
||||
|
||||
val builtJwt = jwtService.buildJwt(userDetails)
|
||||
|
||||
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
||||
assertEquals(user.toOutputDto(), parsedUser)
|
||||
assertEquals(user, parsedUser)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildJwt() returns jwt string with valid user`() {
|
||||
fun buildJwt_user_normalBehavior_returnsJwtStringWithValidUser() {
|
||||
val builtJwt = jwtService.buildJwt(user)
|
||||
|
||||
withParsedUserOutputDto(builtJwt) { parsedUser ->
|
||||
assertEquals(user.toOutputDto(), parsedUser)
|
||||
assertEquals(user, parsedUser)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildJwt() returns jwt string with valid subject`() {
|
||||
fun buildJwt_user_normalBehavior_returnsJwtStringWithValidSubject() {
|
||||
val builtJwt = jwtService.buildJwt(user)
|
||||
val jwtSubject = jwtParser.parseClaimsJws(builtJwt).body.subject
|
||||
|
||||
|
@ -75,7 +77,7 @@ class JwtLogicTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `buildJwt() returns jwt with valid expiration date`() {
|
||||
fun buildJwt_user_returnsJwtWithValidExpirationDate() {
|
||||
val jwtExpectedExpirationDate = Instant.now().plusSeconds(securityProperties.jwtDuration)
|
||||
|
||||
val builtJwt = jwtService.buildJwt(user)
|
||||
|
@ -89,10 +91,10 @@ class JwtLogicTest {
|
|||
// parseJwt()
|
||||
|
||||
@Test
|
||||
fun `parseJwt() returns expected user`() {
|
||||
fun parseJwt_normalBehavior_returnsExpectedUser() {
|
||||
val jwt = jwtService.buildJwt(user)
|
||||
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.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.logic.users.GroupLogic
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService
|
||||
import io.mockk.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
@ -23,7 +22,7 @@ class DefaultRecipeLogicTest {
|
|||
spyk(DefaultRecipeLogic(recipeServiceMock, companyLogicMock, recipeStepLogicMock, mixLogicMock, groupLogicMock))
|
||||
|
||||
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(
|
||||
1L,
|
||||
"Unit test recipe",
|
||||
|
@ -160,7 +159,7 @@ class DefaultRecipeLogicTest {
|
|||
|
||||
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())
|
||||
|
||||
// Act
|
||||
|
@ -189,7 +188,7 @@ class DefaultRecipeLogicTest {
|
|||
// Arrange
|
||||
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)
|
||||
|
||||
// Act
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.GroupDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeGroupInformationDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeStepService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
import io.mockk.*
|
||||
|
@ -28,7 +28,7 @@ class DefaultRecipeStepLogicTest {
|
|||
mockkObject(PositionUtils)
|
||||
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 groupInfo = RecipeGroupInformationDto(1L, group, "A note", steps)
|
||||
|
||||
|
@ -49,7 +49,7 @@ class DefaultRecipeStepLogicTest {
|
|||
mockkObject(PositionUtils)
|
||||
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 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 mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
||||
|
||||
class FileLogicTest {
|
||||
class DefaultFileLogicTest {
|
||||
private val fileCacheMock = mockk<FileCache> {
|
||||
every { setExists(any(), any()) } just runs
|
||||
}
|
Loading…
Reference in New Issue