From a59bad7a7abe6f1f4dae9ad95909faf6bfe9c69b Mon Sep 17 00:00:00 2001 From: FyloZ Date: Tue, 4 May 2021 12:04:02 -0400 Subject: [PATCH 1/6] =?UTF-8?q?Ajout=20des=20conflits=20de=20nom=20entre?= =?UTF-8?q?=20les=20recettes=20d'une=20banni=C3=A8re=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/RestException.kt | 99 +++++++++---------- .../colorrecipesexplorer/model/Recipe.kt | 15 +++ .../repository/RecipeRepository.kt | 3 + .../service/RecipeService.kt | 28 +++++- .../service/RecipeServiceTest.kt | 43 +++++++- 5 files changed, 133 insertions(+), 55 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/exception/RestException.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/exception/RestException.kt index 2732f61..1bc62b7 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/exception/RestException.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/exception/RestException.kt @@ -12,63 +12,62 @@ import org.springframework.web.context.request.WebRequest import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler abstract class RestException( - val errorCode: String, - val title: String, - val status: HttpStatus, - val details: String, - val extensions: Map = mapOf() + val errorCode: String, + val title: String, + val status: HttpStatus, + val details: String, + val extensions: Map = mapOf() ) : RuntimeException(details) { fun buildExceptionBody() = mapOf( - "type" to errorCode, - "title" to title, - "status" to status.value(), - "detail" to details, + "type" to errorCode, + "title" to title, + "status" to status.value(), + "detail" to details, - *extensions.map { it.key to it.value }.toTypedArray() + *extensions.map { it.key to it.value }.toTypedArray() ) } class NotFoundException( - errorCode: String, - title: String, - details: String, - identifierValue: Any, - identifierName: String = "id" + errorCode: String, + title: String, + details: String, + identifierValue: Any, + identifierName: String = "id" ) : RestException( - errorCode = "notfound-$errorCode-$identifierName", - title = title, - status = HttpStatus.NOT_FOUND, - details = details, - extensions = mapOf( - identifierName to identifierValue - ) + errorCode = "notfound-$errorCode-$identifierName", + title = title, + status = HttpStatus.NOT_FOUND, + details = details, + extensions = mapOf( + identifierName to identifierValue + ) ) class AlreadyExistsException( - errorCode: String, - title: String, - details: String, - identifierValue: Any, - identifierName: String = "id" + errorCode: String, + title: String, + details: String, + identifierValue: Any, + identifierName: String = "id", + extensions: MutableMap = mutableMapOf() ) : RestException( - errorCode = "exists-$errorCode-$identifierName", - title = title, - status = HttpStatus.CONFLICT, - details = details, - extensions = mapOf( - identifierName to identifierValue - ) + errorCode = "exists-$errorCode-$identifierName", + title = title, + status = HttpStatus.CONFLICT, + details = details, + extensions = extensions.apply { this[identifierName] = identifierValue }.toMap() ) class CannotDeleteException( - errorCode: String, - title: String, - details: String + errorCode: String, + title: String, + details: String ) : RestException( - errorCode = "cannotdelete-$errorCode", - title = title, - status = HttpStatus.CONFLICT, - details = details + errorCode = "cannotdelete-$errorCode", + title = title, + status = HttpStatus.CONFLICT, + details = details ) @ControllerAdvice @@ -79,19 +78,19 @@ class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() { finalBody["instance"] = (request as ServletWebRequest).request.requestURI return handleExceptionInternal( - exception, - finalBody, - HttpHeaders(), - exception.status, - request + exception, + finalBody, + HttpHeaders(), + exception.status, + request ) } override fun handleMethodArgumentNotValid( - ex: MethodArgumentNotValidException, - headers: HttpHeaders, - status: HttpStatus, - request: WebRequest + ex: MethodArgumentNotValidException, + headers: HttpHeaders, + status: HttpStatus, + request: WebRequest ): ResponseEntity { val errors = hashMapOf() ex.bindingResult.allErrors.forEach { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index 367375a..75c0287 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -288,6 +288,8 @@ private const val RECIPE_NOT_FOUND_EXCEPTION_TITLE = "Recipe not found" private const val RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe already exists" private const val RECIPE_EXCEPTION_ERROR_CODE = "recipe" +sealed class RecipeException + fun recipeIdNotFoundException(id: Long) = NotFoundException( RECIPE_EXCEPTION_ERROR_CODE, @@ -303,3 +305,16 @@ fun recipeIdAlreadyExistsException(id: Long) = "A recipe with the id $id already exists", id ) + +fun recipeNameAlreadyExistsForCompanyException(name: String, company: Company) = + AlreadyExistsException( + "${RECIPE_EXCEPTION_ERROR_CODE}-company", + RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE, + "A recipe with the name $name already exists for the company ${company.name}", + name, + "name", + mutableMapOf( + "company" to company.name, + "companyId" to company.id!! + ) + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt index c3b9e4b..a7284f7 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt @@ -8,6 +8,9 @@ interface RecipeRepository : JpaRepository { /** Checks if one or more recipes have the given [company]. */ fun existsByCompany(company: Company): Boolean + /** Checks if a recipe exists with the given [name] and [company]. */ + fun existsByNameAndCompany(name: String, company: Company): Boolean + /** Gets all recipes with the given [company]. */ fun findAllByCompany(company: Company): Collection } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 4ace03b..b170e81 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -16,6 +16,9 @@ interface RecipeService : /** Checks if one or more recipes have the given [company]. */ fun existsByCompany(company: Company): Boolean + /** Checks if a recipe exists with the given [name] and [company]. */ + fun existsByNameAndCompany(name: String, company: Company): Boolean + /** Gets all recipes with the given [company]. */ fun getAllByCompany(company: Company): Collection @@ -67,10 +70,18 @@ class RecipeServiceImpl( ) override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company) + override fun existsByNameAndCompany(name: String, company: Company) = + repository.existsByNameAndCompany(name, company) + override fun getAllByCompany(company: Company): Collection = repository.findAllByCompany(company) override fun save(entity: RecipeSaveDto): Recipe { - // TODO checks if name is unique in the scope of the [company] + val company = companyService.getById(entity.companyId) + + if (existsByNameAndCompany(entity.name, company)) { + throw recipeNameAlreadyExistsForCompanyException(entity.name, company) + } + return save(with(entity) { recipe( name = name, @@ -80,14 +91,23 @@ class RecipeServiceImpl( sample = sample, approbationDate = approbationDate, remark = remark ?: "", - company = companyService.getById(companyId) + company = company ) }) } @Transactional override fun update(entity: RecipeUpdateDto): Recipe { - val persistedRecipe by lazy { getById(entity.id) } + val persistedRecipe = getById(entity.id) + val name = entity.name + val company = persistedRecipe.company + + if (name != null && + name != persistedRecipe.name && + existsByNameAndCompany(name, company) + ) { + throw recipeNameAlreadyExistsForCompanyException(name, company) + } return update(with(entity) { recipe( @@ -99,7 +119,7 @@ class RecipeServiceImpl( sample = sample ?: persistedRecipe.sample, approbationDate = approbationDate ?: persistedRecipe.approbationDate, remark = remark or persistedRecipe.remark, - company = persistedRecipe.company, + company = company, mixes = persistedRecipe.mixes, groupsInformation = updateGroupsInformation(persistedRecipe, entity) ) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 98dd0f4..859f4d9 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -1,12 +1,14 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.* +import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.springframework.mock.web.MockMultipartFile import org.springframework.web.multipart.MultipartFile import java.io.File @@ -56,6 +58,19 @@ class RecipeServiceTest : assertFalse(found) } + // existsByNameAndCompany() + + @Test + fun `existsByNameAndCompany() returns if a recipe exists for the given name and company in the repository`() { + setOf(true, false).forEach { + whenever(repository.existsByNameAndCompany(entity.name, company)).doReturn(it) + + val exists = service.existsByNameAndCompany(entity.name, company) + + assertEquals(it, exists) + } + } + // getAllByCompany() @Test @@ -73,14 +88,40 @@ class RecipeServiceTest : @Test override fun `save(dto) calls and returns save() with the created entity`() { whenever(companyService.getById(company.id!!)).doReturn(company) + doReturn(false).whenever(service).existsByNameAndCompany(entity.name, company) withBaseSaveDtoTest(entity, entitySaveDto, service, { argThat { this.id == null && this.color == color } }) } + @Test + fun `save(dto) throw AlreadyExistsException when a recipe with the given name and company exists in the repository`() { + whenever(companyService.getById(company.id!!)).doReturn(company) + doReturn(true).whenever(service).existsByNameAndCompany(entity.name, company) + + with(assertThrows { service.save(entitySaveDto) }) { + this.assertErrorCode("company-name") + } + } + // update() @Test - override fun `update(dto) calls and returns update() with the created entity`() = + override fun `update(dto) calls and returns update() with the created entity`() { + doReturn(false).whenever(service).existsByNameAndCompany(entity.name, company) withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) + } + + @Test + fun `update(dto) throws AlreadyExistsException when a recipe exists for the given name and company`() { + val name = "another recipe" + + doReturn(entity).whenever(service).getById(entity.id!!) + doReturn(true).whenever(service).existsByNameAndCompany(name, company) + doReturn(name).whenever(entityUpdateDto).name + + with(assertThrows { service.update(entityUpdateDto) }) { + this.assertErrorCode("company-name") + } + } // updatePublicData() From 8f761a4be43c7f041ce38e433fad5858ee453a32 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 5 May 2021 23:49:12 -0400 Subject: [PATCH 2/6] Employee -> User --- .../config/WebSecurityConfig.kt | 70 +++--- .../colorrecipesexplorer/model/Employee.kt | 193 ---------------- .../model/EmployeeGroup.kt | 141 ------------ .../colorrecipesexplorer/model/Recipe.kt | 6 +- .../model/account/Group.kt | 143 ++++++++++++ .../Permission.kt} | 14 +- .../model/account/User.kt | 195 ++++++++++++++++ .../repository/AccountRepository.kt | 14 +- .../rest/AccountControllers.kt | 82 +++---- .../service/AccountService.kt | 216 +++++++++--------- .../service/RecipeService.kt | 5 +- .../service/RecipeStepService.kt | 5 +- .../service/AccountsServiceTest.kt | 154 ++++++------- .../service/RecipeServiceTest.kt | 9 +- .../service/RecipeStepServiceTest.kt | 3 +- 15 files changed, 630 insertions(+), 620 deletions(-) delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt rename src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/{EmployeePermission.kt => account/Permission.kt} (88%) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt index 0d4834c..1fb70ea 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt @@ -2,13 +2,13 @@ package dev.fyloz.colorrecipesexplorer.config import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.Employee -import dev.fyloz.colorrecipesexplorer.model.EmployeeLoginRequest -import dev.fyloz.colorrecipesexplorer.model.EmployeePermission -import dev.fyloz.colorrecipesexplorer.service.EmployeeService -import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl -import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsService -import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsServiceImpl +import dev.fyloz.colorrecipesexplorer.model.account.UserLoginRequest +import dev.fyloz.colorrecipesexplorer.model.account.Permission +import dev.fyloz.colorrecipesexplorer.model.account.User +import dev.fyloz.colorrecipesexplorer.service.UserService +import dev.fyloz.colorrecipesexplorer.service.UserServiceImpl +import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsService +import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsServiceImpl import io.jsonwebtoken.ExpiredJwtException import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm @@ -31,7 +31,7 @@ import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.Authentication import org.springframework.security.core.AuthenticationException import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.User as SpringUser import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter @@ -52,11 +52,11 @@ import javax.servlet.http.HttpServletResponse @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableConfigurationProperties(SecurityConfigurationProperties::class) class WebSecurityConfig( - val securityConfigurationProperties: SecurityConfigurationProperties, - @Lazy val userDetailsService: EmployeeUserDetailsServiceImpl, - @Lazy val employeeService: EmployeeServiceImpl, - val environment: Environment, - val logger: Logger + val securityConfigurationProperties: SecurityConfigurationProperties, + @Lazy val userDetailsService: CreUserDetailsServiceImpl, + @Lazy val userService: UserServiceImpl, + val environment: Environment, + val logger: Logger ) : WebSecurityConfigurerAdapter() { var debugMode = false @@ -95,15 +95,15 @@ class WebSecurityConfig( credentials: SecurityConfigurationProperties.SystemUserCredentials?, firstName: String, lastName: String, - permissions: List + permissions: List ) { Assert.notNull(credentials, "No root user has been defined.") credentials!! Assert.notNull(credentials.id, "The root user has no identifier defined.") Assert.notNull(credentials.password, "The root user has no password defined.") - if (!employeeService.existsById(credentials.id!!)) { - employeeService.save( - Employee( + if (!userService.existsById(credentials.id!!)) { + userService.save( + User( id = credentials.id!!, firstName = firstName, lastName = lastName, @@ -115,7 +115,7 @@ class WebSecurityConfig( } } - createUser(securityConfigurationProperties.root, "Root", "User", listOf(EmployeePermission.ADMIN)) + createUser(securityConfigurationProperties.root, "Root", "User", listOf(Permission.ADMIN)) debugMode = "debug" in environment.activeProfiles if (debugMode) logger.warn("Debug mode is enabled, security will be disabled!") } @@ -128,7 +128,7 @@ class WebSecurityConfig( .addFilter( JwtAuthenticationFilter( authenticationManager(), - employeeService, + userService, securityConfigurationProperties ) ) @@ -145,7 +145,7 @@ class WebSecurityConfig( http.authorizeRequests() .antMatchers("/api/login").permitAll() .antMatchers("/api/logout").authenticated() - .antMatchers("/api/employee/current").authenticated() + .antMatchers("/api/user/current").authenticated() .anyRequest().authenticated() } else { http @@ -171,9 +171,9 @@ const val defaultGroupCookieName = "Default-Group" val blacklistedJwtTokens = mutableListOf() class JwtAuthenticationFilter( - private val authManager: AuthenticationManager, - private val employeeService: EmployeeService, - private val securityConfigurationProperties: SecurityConfigurationProperties + private val authManager: AuthenticationManager, + private val userService: UserService, + private val securityConfigurationProperties: SecurityConfigurationProperties ) : UsernamePasswordAuthenticationFilter() { private var debugMode = false @@ -183,7 +183,7 @@ class JwtAuthenticationFilter( } override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication { - val loginRequest = jacksonObjectMapper().readValue(request.inputStream, EmployeeLoginRequest::class.java) + val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequest::class.java) return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password)) } @@ -197,12 +197,12 @@ class JwtAuthenticationFilter( val jwtDuration = securityConfigurationProperties.jwtDuration Assert.notNull(jwtSecret, "No JWT secret has been defined.") Assert.notNull(jwtDuration, "No JWT duration has been defined.") - val employeeId = (authResult.principal as User).username - employeeService.updateLastLoginTime(employeeId.toLong()) + val userId = (authResult.principal as SpringUser).username + userService.updateLastLoginTime(userId.toLong()) val expirationMs = System.currentTimeMillis() + jwtDuration!! val expirationDate = Date(expirationMs) val token = Jwts.builder() - .setSubject(employeeId) + .setSubject(userId) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray()) .compact() @@ -220,9 +220,9 @@ class JwtAuthenticationFilter( } class JwtAuthorizationFilter( - private val userDetailsService: EmployeeUserDetailsService, - private val securityConfigurationProperties: SecurityConfigurationProperties, - authenticationManager: AuthenticationManager + private val userDetailsService: CreUserDetailsService, + private val securityConfigurationProperties: SecurityConfigurationProperties, + authenticationManager: AuthenticationManager ) : BasicAuthenticationFilter(authenticationManager) { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { fun tryLoginFromBearer(): Boolean { @@ -259,20 +259,20 @@ class JwtAuthorizationFilter( val jwtSecret = securityConfigurationProperties.jwtSecret Assert.notNull(jwtSecret, "No JWT secret has been defined.") return try { - val employeeId = Jwts.parser() + val userId = Jwts.parser() .setSigningKey(jwtSecret!!.toByteArray()) .parseClaimsJws(token.replace("Bearer", "")) .body .subject - if (employeeId != null) getAuthenticationToken(employeeId) else null + if (userId != null) getAuthenticationToken(userId) else null } catch (_: ExpiredJwtException) { null } } - private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken? = try { - val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), false) - UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities) + private fun getAuthenticationToken(userId: String): UsernamePasswordAuthenticationToken? = try { + val userDetails = userDetailsService.loadUserById(userId.toLong(), false) + UsernamePasswordAuthenticationToken(userDetails.username, null, userDetails.authorities) } catch (_: NotFoundException) { null } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt deleted file mode 100644 index 1b98d41..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt +++ /dev/null @@ -1,193 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.model - -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank -import org.hibernate.annotations.Fetch -import org.hibernate.annotations.FetchMode -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -import org.springframework.security.crypto.password.PasswordEncoder -import java.time.LocalDateTime -import javax.persistence.* -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size - -private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis" -private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis" -private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis" -private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis" -private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères" - -@Entity -@Table(name = "employee") -data class Employee( - @Id - override val id: Long, - - @Column(name = "first_name") - val firstName: String = "", - - @Column(name = "last_name") - val lastName: String = "", - - val password: String = "", - - @Column(name = "default_group_user") - val isDefaultGroupUser: Boolean = false, - - @Column(name = "system_user") - val isSystemUser: Boolean = false, - - @ManyToOne - @JoinColumn(name = "group_id") - @Fetch(FetchMode.SELECT) - var group: EmployeeGroup? = null, - - @Enumerated(EnumType.STRING) - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")]) - @Column(name = "permission") - @Fetch(FetchMode.SUBSELECT) - val permissions: MutableSet = mutableSetOf(), - - @Column(name = "last_login_time") - var lastLoginTime: LocalDateTime? = null -) : Model { - val flatPermissions: Set - get() = permissions - .flatMap { it.flat() } - .filter { !it.deprecated } - .toMutableSet() - .apply { - if (group != null) this.addAll(group!!.flatPermissions) - } - - val authorities: Set - get() = flatPermissions.map { it.toAuthority() }.toMutableSet() -} - -/** DTO for creating employees. Allows a [password] a [groupId]. */ -open class EmployeeSaveDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, - - @field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String, - - @field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String, - - @field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE) - @field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE) - val password: String, - - val groupId: Long?, - - @Enumerated(EnumType.STRING) - val permissions: MutableSet = mutableSetOf() -) : EntityDto - -open class EmployeeUpdateDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, - - @field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String?, - - @field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String?, - - val groupId: Long?, - - @Enumerated(EnumType.STRING) - val permissions: Set? -) : EntityDto - -data class EmployeeOutputDto( - override val id: Long, - val firstName: String, - val lastName: String, - val group: EmployeeGroup?, - val permissions: Set, - val explicitPermissions: Set, - val lastLoginTime: LocalDateTime? -) : Model - -data class EmployeeLoginRequest(val id: Long, val password: String) - -// ==== DSL ==== -fun employee( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - isDefaultGroupUser: Boolean = false, - isSystemUser: Boolean = false, - group: EmployeeGroup? = null, - permissions: MutableSet = mutableSetOf(), - lastLoginTime: LocalDateTime? = null, - op: Employee.() -> Unit = {} -) = Employee( - id, - firstName, - lastName, - password, - isDefaultGroupUser, - isSystemUser, - group, - permissions, - lastLoginTime -).apply(op) - -fun employeeSaveDto( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeSaveDto.() -> Unit = {} -) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op) - -fun employeeUpdateDto( - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeUpdateDto.() -> Unit = {} -) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) - -// ==== Exceptions ==== -private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee not found" -private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already exists" -private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee" - -fun employeeIdNotFoundException(id: Long) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee with the id $id could not be found", - id - ) - -fun employeeIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the id $id already exists", - id - ) - -fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the name '$firstName $lastName' already exists", - "$firstName $lastName", - "fullName" - ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt deleted file mode 100644 index a88e0e4..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt +++ /dev/null @@ -1,141 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.model - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.exception.RestException -import org.hibernate.annotations.Fetch -import org.hibernate.annotations.FetchMode -import org.springframework.http.HttpStatus -import javax.persistence.* -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size - -private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis" -private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise" - -@Entity -@Table(name = "employee_group") -data class EmployeeGroup( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - override var id: Long? = null, - - @Column(unique = true) - override val name: String = "", - - @Enumerated(EnumType.STRING) - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) - @Column(name = "permission") - @Fetch(FetchMode.SUBSELECT) - val permissions: MutableSet = mutableSetOf(), -) : NamedModel { - val flatPermissions: Set - get() = this.permissions - .flatMap { it.flat() } - .filter { !it.deprecated } - .toSet() -} - -open class EmployeeGroupSaveDto( - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) - val name: String, - - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) - val permissions: MutableSet -) : EntityDto { - override fun toEntity(): EmployeeGroup = - EmployeeGroup(null, name, permissions) -} - -open class EmployeeGroupUpdateDto( - @field:NotNull(message = GROUP_ID_NULL_MESSAGE) - val id: Long, - - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) - val name: String, - - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) - val permissions: MutableSet -) : EntityDto { - override fun toEntity(): EmployeeGroup = - EmployeeGroup(id, name, permissions) -} - -data class EmployeeGroupOutputDto( - override val id: Long, - val name: String, - val permissions: Set, - val explicitPermissions: Set -): Model - -fun employeeGroup( - id: Long? = null, - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroup.() -> Unit = {} -) = EmployeeGroup(id, name, permissions).apply(op) - -fun employeeGroupSaveDto( - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroupSaveDto.() -> Unit = {} -) = EmployeeGroupSaveDto(name, permissions).apply(op) - -fun employeeGroupUpdateDto( - id: Long = 0L, - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroupUpdateDto.() -> Unit = {} -) = EmployeeGroupUpdateDto(id, name, permissions).apply(op) - -// ==== Exceptions ==== -private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee group not found" -private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee group already exists" -private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employeegroup" - -class NoDefaultGroupException : RestException( - "nodefaultgroup", - "No default group", - HttpStatus.NOT_FOUND, - "No default group cookie is defined in the current request" -) - -fun employeeGroupIdNotFoundException(id: Long) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee group with the id $id could not be found", - id - ) - -fun employeeGroupNameNotFoundException(name: String) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee group with the name $name could not be found", - name, - "name" - ) - -fun employeeGroupIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee group with the id $id already exists", - id, - ) - -fun employeeGroupNameAlreadyExistsException(name: String) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee group with the name $name already exists", - name, - "name" - ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index 75c0287..cf0aad3 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -3,6 +3,8 @@ package dev.fyloz.colorrecipesexplorer.model import com.fasterxml.jackson.annotation.JsonIgnore import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.model.account.Group +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES @@ -176,7 +178,7 @@ data class RecipeGroupInformation( @ManyToOne @JoinColumn(name = "group_id") - val group: EmployeeGroup, + val group: Group, var note: String?, @@ -264,7 +266,7 @@ fun recipeUpdateDto( fun recipeGroupInformation( id: Long? = null, - group: EmployeeGroup = employeeGroup(), + group: Group = group(), note: String? = null, steps: MutableSet? = mutableSetOf(), op: RecipeGroupInformation.() -> Unit = {} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt new file mode 100644 index 0000000..4bc229d --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt @@ -0,0 +1,143 @@ +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.EntityDto +import dev.fyloz.colorrecipesexplorer.model.Model +import dev.fyloz.colorrecipesexplorer.model.NamedModel +import org.hibernate.annotations.Fetch +import org.hibernate.annotations.FetchMode +import org.springframework.http.HttpStatus +import javax.persistence.* +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull +import javax.validation.constraints.Size + +private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis" +private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis" +private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise" + +@Entity +@Table(name = "user_group") +data class Group( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override var id: Long? = null, + + @Column(unique = true) + override val name: String = "", + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) + @Column(name = "permission") + @Fetch(FetchMode.SUBSELECT) + val permissions: MutableSet = mutableSetOf(), +) : NamedModel { + val flatPermissions: Set + get() = this.permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toSet() +} + +open class GroupSaveDto( + @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) + @field:Size(min = 3) + val name: String, + + @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + val permissions: MutableSet +) : EntityDto { + override fun toEntity(): Group = + Group(null, name, permissions) +} + +open class GroupUpdateDto( + @field:NotNull(message = GROUP_ID_NULL_MESSAGE) + val id: Long, + + @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) + @field:Size(min = 3) + val name: String, + + @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + val permissions: MutableSet +) : EntityDto { + override fun toEntity(): Group = + Group(id, name, permissions) +} + +data class GroupOutputDto( + override val id: Long, + val name: String, + val permissions: Set, + val explicitPermissions: Set +): Model + +fun group( + id: Long? = null, + name: String = "name", + permissions: MutableSet = mutableSetOf(), + op: Group.() -> Unit = {} +) = Group(id, name, permissions).apply(op) + +fun groupSaveDto( + name: String = "name", + permissions: MutableSet = mutableSetOf(), + op: GroupSaveDto.() -> Unit = {} +) = GroupSaveDto(name, permissions).apply(op) + +fun groupUpdateDto( + id: Long = 0L, + name: String = "name", + permissions: MutableSet = 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" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt similarity index 88% rename from src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt rename to src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt index 5124243..333f8fb 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt @@ -1,10 +1,10 @@ -package dev.fyloz.colorrecipesexplorer.model +package dev.fyloz.colorrecipesexplorer.model.account import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority -enum class EmployeePermission( - val impliedPermissions: List = listOf(), +enum class Permission( + val impliedPermissions: List = listOf(), val deprecated: Boolean = false ) { READ_FILE, @@ -80,12 +80,12 @@ enum class EmployeePermission( SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true), ; - operator fun contains(permission: EmployeePermission): Boolean { + operator fun contains(permission: Permission): Boolean { return permission == this || impliedPermissions.any { permission in it } } } -fun EmployeePermission.flat(): Iterable { +fun Permission.flat(): Iterable { return mutableSetOf(this).apply { impliedPermissions.forEach { addAll(it.flat()) @@ -93,7 +93,7 @@ fun EmployeePermission.flat(): Iterable { } } -/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */ -fun EmployeePermission.toAuthority(): GrantedAuthority { +/** Converts the given [Permission] to a [GrantedAuthority]. */ +fun Permission.toAuthority(): GrantedAuthority { return SimpleGrantedAuthority(name) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt new file mode 100644 index 0000000..c6e2b58 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt @@ -0,0 +1,195 @@ +package dev.fyloz.colorrecipesexplorer.model.account + +import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException +import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.model.EntityDto +import dev.fyloz.colorrecipesexplorer.model.Model +import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank +import org.hibernate.annotations.Fetch +import org.hibernate.annotations.FetchMode +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import java.time.LocalDateTime +import javax.persistence.* +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull +import javax.validation.constraints.Size + +private const val USER_ID_NULL_MESSAGE = "Un numéro d'utilisateur est requis" +private const val USER_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis" +private const val USER_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis" +private const val USER_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis" +private const val USER_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères" + +@Entity +@Table(name = "user") +data class User( + @Id + override val id: Long, + + @Column(name = "first_name") + val firstName: String = "", + + @Column(name = "last_name") + val lastName: String = "", + + val password: String = "", + + @Column(name = "default_group_user") + val isDefaultGroupUser: Boolean = false, + + @Column(name = "system_user") + val isSystemUser: Boolean = false, + + @ManyToOne + @JoinColumn(name = "group_id") + @Fetch(FetchMode.SELECT) + var group: Group? = null, + + @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 = mutableSetOf(), + + @Column(name = "last_login_time") + var lastLoginTime: LocalDateTime? = null +) : Model { + val flatPermissions: Set + get() = permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toMutableSet() + .apply { + if (group != null) this.addAll(group!!.flatPermissions) + } + + val authorities: Set + get() = flatPermissions.map { it.toAuthority() }.toMutableSet() +} + +/** DTO for creating users. Allows a [password] a [groupId]. */ +open class UserSaveDto( + @field:NotNull(message = USER_ID_NULL_MESSAGE) + val id: Long, + + @field:NotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String, + + @field:NotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + val lastName: String, + + @field:NotBlank(message = USER_PASSWORD_EMPTY_MESSAGE) + @field:Size(min = 8, message = USER_PASSWORD_TOO_SHORT_MESSAGE) + val password: String, + + val groupId: Long?, + + @Enumerated(EnumType.STRING) + val permissions: MutableSet = mutableSetOf() +) : EntityDto + +open class UserUpdateDto( + @field:NotNull(message = USER_ID_NULL_MESSAGE) + val id: Long, + + @field:NullOrNotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String?, + + @field:NullOrNotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + val lastName: String?, + + val groupId: Long?, + + @Enumerated(EnumType.STRING) + val permissions: Set? +) : EntityDto + +data class UserOutputDto( + override val id: Long, + val firstName: String, + val lastName: String, + val group: Group?, + val permissions: Set, + val explicitPermissions: Set, + val lastLoginTime: LocalDateTime? +) : Model + +data class UserLoginRequest(val id: Long, val password: String) + +// ==== DSL ==== +fun user( + passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + password: String = passwordEncoder.encode("password"), + isDefaultGroupUser: Boolean = false, + isSystemUser: Boolean = false, + group: Group? = null, + permissions: MutableSet = mutableSetOf(), + lastLoginTime: LocalDateTime? = null, + op: User.() -> Unit = {} +) = User( + id, + firstName, + lastName, + password, + 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 = 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 = mutableSetOf(), + op: UserUpdateDto.() -> Unit = {} +) = UserUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) + +// ==== 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" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt index b1f6099..d2548ea 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt @@ -1,20 +1,20 @@ package dev.fyloz.colorrecipesexplorer.repository -import dev.fyloz.colorrecipesexplorer.model.Employee -import dev.fyloz.colorrecipesexplorer.model.EmployeeGroup +import dev.fyloz.colorrecipesexplorer.model.account.Group +import dev.fyloz.colorrecipesexplorer.model.account.User import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface EmployeeRepository : JpaRepository { +interface UserRepository : JpaRepository { fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean - fun findByFirstNameAndLastName(firstName: String, lastName: String): Employee? + fun findByFirstNameAndLastName(firstName: String, lastName: String): User? - fun findAllByGroup(group: EmployeeGroup): Collection + fun findAllByGroup(group: Group): Collection - fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: EmployeeGroup): Employee + fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: Group): User } @Repository -interface EmployeeGroupRepository : NamedJpaRepository +interface GroupRepository : NamedJpaRepository diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index ea9efde..62f6d03 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -3,9 +3,9 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupService -import dev.fyloz.colorrecipesexplorer.service.EmployeeService +import dev.fyloz.colorrecipesexplorer.model.account.* +import dev.fyloz.colorrecipesexplorer.service.UserService +import dev.fyloz.colorrecipesexplorer.service.GroupService import org.springframework.http.MediaType import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* @@ -14,29 +14,29 @@ import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import javax.validation.Valid -private const val EMPLOYEE_CONTROLLER_PATH = "api/employee" -private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group" +private const val USER_CONTROLLER_PATH = "api/user" +private const val GROUP_CONTROLLER_PATH = "api/user/group" @RestController -@RequestMapping(EMPLOYEE_CONTROLLER_PATH) -class EmployeeController(private val employeeService: EmployeeService) { +@RequestMapping(USER_CONTROLLER_PATH) +class UserController(private val userService: UserService) { @GetMapping @PreAuthorizeViewUsers fun getAll() = - ok(employeeService.getAllForOutput()) + ok(userService.getAllForOutput()) @GetMapping("{id}") @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = - ok(employeeService.getByIdForOutput(id)) + ok(userService.getByIdForOutput(id)) @GetMapping("current") - fun getCurrent(loggedInEmployee: Principal?) = - if (loggedInEmployee != null) + fun getCurrent(loggedInUser: Principal?) = + if (loggedInUser != null) ok( - with(employeeService) { + with(userService) { getById( - loggedInEmployee.name.toLong(), + loggedInUser.name.toLong(), ignoreDefaultGroupUsers = false, ignoreSystemUsers = false ).toOutput() @@ -47,56 +47,56 @@ class EmployeeController(private val employeeService: EmployeeService) { @PostMapping @PreAuthorizeEditUsers - fun save(@Valid @RequestBody employee: EmployeeSaveDto) = - created(EMPLOYEE_CONTROLLER_PATH) { - with(employeeService) { - save(employee).toOutput() + fun save(@Valid @RequestBody user: UserSaveDto) = + created(USER_CONTROLLER_PATH) { + with(userService) { + save(user).toOutput() } } @PutMapping @PreAuthorizeEditUsers - fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = + fun update(@Valid @RequestBody user: UserUpdateDto) = noContent { - employeeService.update(employee) + userService.update(user) } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) @PreAuthorizeEditUsers fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = noContent { - employeeService.updatePassword(id, password) + userService.updatePassword(id, password) } - @PutMapping("{employeeId}/permissions/{permission}") + @PutMapping("{userId}/permissions/{permission}") @PreAuthorizeEditUsers fun addPermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable userId: Long, + @PathVariable permission: Permission ) = noContent { - employeeService.addPermission(employeeId, permission) + userService.addPermission(userId, permission) } - @DeleteMapping("{employeeId}/permissions/{permission}") + @DeleteMapping("{userId}/permissions/{permission}") @PreAuthorizeEditUsers fun removePermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable userId: Long, + @PathVariable permission: Permission ) = noContent { - employeeService.removePermission(employeeId, permission) + userService.removePermission(userId, permission) } @DeleteMapping("{id}") @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = - employeeService.deleteById(id) + userService.deleteById(id) } @RestController -@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) +@RequestMapping(GROUP_CONTROLLER_PATH) class GroupsController( - private val groupService: EmployeeGroupService, - private val employeeService: EmployeeService + private val groupService: GroupService, + private val userService: UserService ) { @GetMapping @PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')") @@ -108,11 +108,11 @@ class GroupsController( fun getById(@PathVariable id: Long) = ok(groupService.getByIdForOutput(id)) - @GetMapping("{id}/employees") + @GetMapping("{id}/users") @PreAuthorizeViewUsers - fun getEmployeesForGroup(@PathVariable id: Long) = - ok(with(employeeService) { - groupService.getEmployeesForGroup(id) + fun getUsersForGroup(@PathVariable id: Long) = + ok(with(userService) { + groupService.getUsersForGroup(id) .map { it.toOutput() } }) @@ -132,8 +132,8 @@ class GroupsController( @PostMapping @PreAuthorizeEditUsers - fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = - created(EMPLOYEE_GROUP_CONTROLLER_PATH) { + fun save(@Valid @RequestBody group: GroupSaveDto) = + created(GROUP_CONTROLLER_PATH) { with(groupService) { save(group).toOutput() } @@ -141,7 +141,7 @@ class GroupsController( @PutMapping @PreAuthorizeEditUsers - fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = + fun update(@Valid @RequestBody group: GroupUpdateDto) = noContent { groupService.update(group) } @@ -156,10 +156,10 @@ class GroupsController( @RestController @RequestMapping("api") -class LogoutController(private val employeeService: EmployeeService) { +class LogoutController(private val userService: UserService) { @GetMapping("logout") fun logout(request: HttpServletRequest) = ok { - employeeService.logout(request) + userService.logout(request) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt index 8f08ed4..5d6bd11 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt @@ -3,12 +3,11 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.* import dev.fyloz.colorrecipesexplorer.model.validation.or -import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository -import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository +import dev.fyloz.colorrecipesexplorer.repository.UserRepository +import dev.fyloz.colorrecipesexplorer.repository.GroupRepository import org.springframework.context.annotation.Lazy -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException @@ -19,73 +18,74 @@ import java.time.LocalDateTime import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import javax.transaction.Transactional +import org.springframework.security.core.userdetails.User as SpringUser -interface EmployeeService : - ExternalModelService { - /** Check if an [Employee] with the given [firstName] and [lastName] exists. */ +interface UserService : + ExternalModelService { + /** Check if an [User] with the given [firstName] and [lastName] exists. */ fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean - /** Gets the employee with the given [id]. */ - fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee + /** Gets the user with the given [id]. */ + fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User - /** Gets all employees which have the given [group]. */ - fun getByGroup(group: EmployeeGroup): Collection + /** Gets all users which have the given [group]. */ + fun getByGroup(group: Group): Collection /** Gets the default user of the given [group]. */ - fun getDefaultGroupEmployee(group: EmployeeGroup): Employee + fun getDefaultGroupUser(group: Group): User - /** Save a default group employee for the given [group]. */ - fun saveDefaultGroupEmployee(group: EmployeeGroup) + /** Save a default group user for the given [group]. */ + fun saveDefaultGroupUser(group: Group) /** Updates de given [entity]. **/ - fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee + fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User - /** Updates the last login time of the employee with the given [employeeId]. */ - fun updateLastLoginTime(employeeId: Long, time: LocalDateTime = LocalDateTime.now()): Employee + /** Updates the last login time of the user with the given [userId]. */ + fun updateLastLoginTime(userId: Long, time: LocalDateTime = LocalDateTime.now()): User - /** Updates the password of the employee with the given [id]. */ - fun updatePassword(id: Long, password: String): Employee + /** Updates the password of the user with the given [id]. */ + fun updatePassword(id: Long, password: String): User - /** Adds the given [permission] to the employee with the given [employeeId]. */ - fun addPermission(employeeId: Long, permission: EmployeePermission): Employee + /** Adds the given [permission] to the user with the given [userId]. */ + fun addPermission(userId: Long, permission: Permission): User - /** Removes the given [permission] from the employee with the given [employeeId]. */ - fun removePermission(employeeId: Long, permission: EmployeePermission): Employee + /** Removes the given [permission] from the user with the given [userId]. */ + fun removePermission(userId: Long, permission: Permission): User /** Logout an user. Add the authorization token of the given [request] to the blacklisted tokens. */ fun logout(request: HttpServletRequest) } -interface EmployeeGroupService : - ExternalNamedModelService { - /** Gets all the employees of the group with the given [id]. */ - fun getEmployeesForGroup(id: Long): Collection +interface GroupService : + ExternalNamedModelService { + /** Gets all the users of the group with the given [id]. */ + fun getUsersForGroup(id: Long): Collection /** Gets the default group from a cookie in the given HTTP [request]. */ - fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup + fun getRequestDefaultGroup(request: HttpServletRequest): Group /** Sets the default group cookie for the given HTTP [response]. */ fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) } -interface EmployeeUserDetailsService : UserDetailsService { - /** Loads an [User] for the given [employeeId]. */ - fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails +interface CreUserDetailsService : UserDetailsService { + /** Loads an [User] for the given [id]. */ + fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails } @Service -class EmployeeServiceImpl( - employeeRepository: EmployeeRepository, - @Lazy val groupService: EmployeeGroupService, +class UserServiceImpl( + userRepository: UserRepository, + @Lazy val groupService: GroupService, @Lazy val passwordEncoder: PasswordEncoder, -) : AbstractExternalModelService( - employeeRepository +) : AbstractExternalModelService( + userRepository ), - EmployeeService { - override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id) + UserService { + override fun idNotFoundException(id: Long) = userIdNotFoundException(id) + override fun idAlreadyExistsException(id: Long) = userIdAlreadyExistsException(id) - override fun Employee.toOutput() = EmployeeOutputDto( + override fun User.toOutput() = UserOutputDto( this.id, this.firstName, this.lastName, @@ -98,29 +98,29 @@ class EmployeeServiceImpl( override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean = repository.existsByFirstNameAndLastName(firstName, lastName) - override fun getAll(): Collection = + override fun getAll(): Collection = super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser } - override fun getById(id: Long): Employee = + override fun getById(id: Long): User = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee = + 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: EmployeeGroup): Collection = + override fun getByGroup(group: Group): Collection = repository.findAllByGroup(group).filter { !it.isSystemUser && !it.isDefaultGroupUser } - override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee = + override fun getDefaultGroupUser(group: Group): User = repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group) - override fun save(entity: EmployeeSaveDto): Employee = + override fun save(entity: UserSaveDto): User = save(with(entity) { - Employee( + User( id, firstName, lastName, @@ -132,20 +132,20 @@ class EmployeeServiceImpl( ) }) - override fun save(entity: Employee): Employee { + override fun save(entity: User): User { if (existsById(entity.id)) - throw employeeIdAlreadyExistsException(entity.id) + throw userIdAlreadyExistsException(entity.id) if (existsByFirstNameAndLastName(entity.firstName, entity.lastName)) - throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName) + throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName) return super.save(entity) } - override fun saveDefaultGroupEmployee(group: EmployeeGroup) { + override fun saveDefaultGroupUser(group: Group) { save( - employee( + user( id = 1000000L + group.id!!, firstName = group.name, - lastName = "EmployeeModel", + lastName = "User", password = passwordEncoder.encode(group.name), group = group, isDefaultGroupUser = true @@ -153,49 +153,49 @@ class EmployeeServiceImpl( ) } - override fun updateLastLoginTime(employeeId: Long, time: LocalDateTime): Employee { - val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false) - employee.lastLoginTime = time + override fun updateLastLoginTime(userId: Long, time: LocalDateTime): User { + val user = getById(userId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false) + user.lastLoginTime = time return update( - employee, + user, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false ) } - override fun update(entity: EmployeeUpdateDto): Employee { - val persistedEmployee by lazy { getById(entity.id) } + override fun update(entity: UserUpdateDto): User { + val persistedUser by lazy { getById(entity.id) } return update(with(entity) { - Employee( + User( id = id, - firstName = firstName or persistedEmployee.firstName, - lastName = lastName or persistedEmployee.lastName, - password = persistedEmployee.password, + firstName = firstName or persistedUser.firstName, + lastName = lastName or persistedUser.lastName, + password = persistedUser.password, isDefaultGroupUser = false, isSystemUser = false, - group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group, - permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions, - lastLoginTime = persistedEmployee.lastLoginTime + group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedUser.group, + permissions = permissions?.toMutableSet() ?: persistedUser.permissions, + lastLoginTime = persistedUser.lastLoginTime ) }) } - override fun update(entity: Employee): Employee = + override fun update(entity: User): User = update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee { + override fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User { with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) { if (this != null && id != entity.id) - throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName) + throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName) } - return super.update(entity) + return super.update(entity) } - override fun updatePassword(id: Long, password: String): Employee { - val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - return super.update(with(persistedEmployee) { - Employee( + 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, @@ -209,11 +209,11 @@ class EmployeeServiceImpl( }) } - override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions += permission }) + override fun addPermission(userId: Long, permission: Permission): User = + super.update(getById(userId).apply { permissions += permission }) - override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions -= permission }) + override fun removePermission(userId: Long, permission: Permission): User = + super.update(getById(userId).apply { permissions -= permission }) override fun logout(request: HttpServletRequest) { val authorizationCookie = WebUtils.getCookie(request, "Authorization") @@ -229,19 +229,19 @@ class EmployeeServiceImpl( const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans @Service -class EmployeeGroupServiceImpl( - private val employeeService: EmployeeService, - employeeGroupRepository: EmployeeGroupRepository -) : AbstractExternalNamedModelService( - employeeGroupRepository +class GroupServiceImpl( + private val userService: UserService, + groupRepository: GroupRepository +) : AbstractExternalNamedModelService( + groupRepository ), - EmployeeGroupService { - override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id) - override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name) - override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name) + GroupService { + 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 EmployeeGroup.toOutput() = EmployeeGroupOutputDto( + override fun Group.toOutput() = GroupOutputDto( this.id!!, this.name, this.permissions, @@ -249,20 +249,20 @@ class EmployeeGroupServiceImpl( ) override fun existsByName(name: String): Boolean = repository.existsByName(name) - override fun getEmployeesForGroup(id: Long): Collection = - employeeService.getByGroup(getById(id)) + override fun getUsersForGroup(id: Long): Collection = + userService.getByGroup(getById(id)) @Transactional - override fun save(entity: EmployeeGroup): EmployeeGroup { + override fun save(entity: Group): Group { return super.save(entity).apply { - employeeService.saveDefaultGroupEmployee(this) + userService.saveDefaultGroupUser(this) } } - override fun update(entity: EmployeeGroupUpdateDto): EmployeeGroup { + override fun update(entity: GroupUpdateDto): Group { val persistedGroup by lazy { getById(entity.id) } return update(with(entity) { - EmployeeGroup( + Group( entity.id, if (name.isNotBlank()) entity.name else persistedGroup.name, if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions @@ -271,15 +271,15 @@ class EmployeeGroupServiceImpl( } @Transactional - override fun delete(entity: EmployeeGroup) { - employeeService.delete(employeeService.getDefaultGroupEmployee(entity)) + override fun delete(entity: Group) { + userService.delete(userService.getDefaultGroupUser(entity)) super.delete(entity) } - override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup { + override fun getRequestDefaultGroup(request: HttpServletRequest): Group { val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName) ?: throw NoDefaultGroupException() - val defaultGroupUser = employeeService.getById( + val defaultGroupUser = userService.getById( defaultGroupCookie.value.toLong(), ignoreDefaultGroupUsers = false, ignoreSystemUsers = true @@ -289,7 +289,7 @@ class EmployeeGroupServiceImpl( override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) { val group = getById(groupId) - val defaultGroupUser = employeeService.getDefaultGroupEmployee(group) + val defaultGroupUser = userService.getDefaultGroupUser(group) response.addHeader( "Set-Cookie", "$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict" @@ -298,13 +298,13 @@ class EmployeeGroupServiceImpl( } @Service -class EmployeeUserDetailsServiceImpl( - private val employeeService: EmployeeService +class CreUserDetailsServiceImpl( + private val userService: UserService ) : - EmployeeUserDetailsService { + CreUserDetailsService { override fun loadUserByUsername(username: String): UserDetails { try { - return loadUserByEmployeeId(username.toLong(), true) + return loadUserById(username.toLong(), true) } catch (ex: NotFoundException) { throw UsernameNotFoundException(username) } catch (ex: NotFoundException) { @@ -312,12 +312,12 @@ class EmployeeUserDetailsServiceImpl( } } - override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails { - val employee = employeeService.getById( - employeeId, + override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails { + val user = userService.getById( + id, ignoreDefaultGroupUsers = ignoreDefaultGroupUsers, ignoreSystemUsers = false ) - return User(employee.id.toString(), employee.password, employee.authorities) + return SpringUser(user.id.toString(), user.password, user.authorities) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index b170e81..7f0a1cc 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -1,6 +1,7 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.validation.or import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService @@ -38,7 +39,7 @@ class RecipeServiceImpl( val companyService: CompanyService, val mixService: MixService, val recipeStepService: RecipeStepService, - @Lazy val groupService: EmployeeGroupService, + @Lazy val groupService: GroupService, val recipeImageService: RecipeImageService ) : AbstractExternalModelService( @@ -157,7 +158,7 @@ class RecipeServiceImpl( if (publicDataDto.notes != null) { val recipe = getById(publicDataDto.recipeId) - fun noteForGroup(group: EmployeeGroup) = + fun noteForGroup(group: Group) = publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content // Notes diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index a72fc76..2475ed3 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import dev.fyloz.colorrecipesexplorer.utils.findDuplicated import dev.fyloz.colorrecipesexplorer.utils.hasGaps @@ -81,8 +82,8 @@ class InvalidStepsPositionsException( ) class InvalidGroupStepsPositionsException( - val group: EmployeeGroup, - val exception: InvalidStepsPositionsException + val group: Group, + val exception: InvalidStepsPositionsException ) : RestException( "invalid-groupinformation-recipestep-position", "Invalid steps positions", diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt index a844d91..ba55ec5 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt @@ -4,15 +4,14 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository -import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository +import dev.fyloz.colorrecipesexplorer.model.account.* +import dev.fyloz.colorrecipesexplorer.repository.UserRepository +import dev.fyloz.colorrecipesexplorer.repository.GroupRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import java.util.* @@ -22,24 +21,25 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue +import org.springframework.security.core.userdetails.User as SpringUser -class EmployeeServiceTest : - AbstractExternalModelServiceTest() { +class UserServiceTest : + AbstractExternalModelServiceTest() { private val passwordEncoder = BCryptPasswordEncoder() - override val entity: Employee = employee(passwordEncoder, id = 0L) - override val anotherEntity: Employee = employee(passwordEncoder, id = 1L) - private val entityDefaultGroupUser = employee(passwordEncoder, id = 2L, isDefaultGroupUser = true) - private val entitySystemUser = employee(passwordEncoder, id = 3L, isSystemUser = true) - private val group = employeeGroup(id = 0L) - override val entitySaveDto: EmployeeSaveDto = spy(employeeSaveDto(passwordEncoder, id = 0L)) - override val entityUpdateDto: EmployeeUpdateDto = spy(employeeUpdateDto(id = 0L)) + override val entity: User = user(passwordEncoder, id = 0L) + override val anotherEntity: User = user(passwordEncoder, id = 1L) + private val entityDefaultGroupUser = user(passwordEncoder, id = 2L, isDefaultGroupUser = true) + private val entitySystemUser = user(passwordEncoder, id = 3L, isSystemUser = true) + 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: EmployeeRepository = mock() - private val employeeGroupService: EmployeeGroupService = mock() - override val service: EmployeeService = spy(EmployeeServiceImpl(repository, employeeGroupService, passwordEncoder)) + override val repository: UserRepository = mock() + private val groupService: GroupService = mock() + override val service: UserService = spy(UserServiceImpl(repository, groupService, passwordEncoder)) - private val entitySaveDtoEmployee = Employee( + private val entitySaveDtoUser = User( entitySaveDto.id, entitySaveDto.firstName, entitySaveDto.lastName, @@ -52,14 +52,14 @@ class EmployeeServiceTest : @AfterEach override fun afterEach() { - reset(employeeGroupService) + reset(groupService) super.afterEach() } // existsByFirstNameAndLastName() @Test - fun `existsByFirstNameAndLastName() returns true when an employee with the given first name and last name exists`() { + 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 = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName) @@ -68,7 +68,7 @@ class EmployeeServiceTest : } @Test - fun `existsByFirstNameAndLastName() returns false when no employee with the given first name and last name exists`() { + 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 = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName) @@ -79,7 +79,7 @@ class EmployeeServiceTest : // getById() @Test - fun `getById() throws NotFoundException when the corresponding employee is a default group user`() { + fun `getById() throws NotFoundException when the corresponding user is a default group user`() { whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser)) assertThrows { @@ -92,7 +92,7 @@ class EmployeeServiceTest : } @Test - fun `getById() throws NotFoundException when the corresponding employee is a system user`() { + fun `getById() throws NotFoundException when the corresponding user is a system user`() { whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser)) assertThrows { @@ -107,7 +107,7 @@ class EmployeeServiceTest : // getByGroup() @Test - fun `getByGroup() returns all the employees with the given group from the repository`() { + fun `getByGroup() returns all the users with the given group from the repository`() { whenever(repository.findAllByGroup(group)).doReturn(entityList) val found = service.getByGroup(group) @@ -117,7 +117,7 @@ class EmployeeServiceTest : } @Test - fun `getByGroup() returns an empty list when there is no employee with the given group in the repository`() { + 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 = service.getByGroup(group) @@ -128,10 +128,10 @@ class EmployeeServiceTest : // getDefaultGroupUser() @Test - fun `getDefaultGroupUser() returns the default employee of the given group from the repository`() { + fun `getDefaultGroupUser() returns the default user of the given group from the repository`() { whenever(repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)).doReturn(entityDefaultGroupUser) - val found = service.getDefaultGroupEmployee(group) + val found = service.getDefaultGroupUser(group) assertEquals(entityDefaultGroupUser, found) } @@ -166,13 +166,13 @@ class EmployeeServiceTest : } @Test - fun `save(dto) calls and returns save() with the created employee`() { - doReturn(entitySaveDtoEmployee).whenever(service).save(any()) + fun `save(dto) calls and returns save() with the created user`() { + doReturn(entitySaveDtoUser).whenever(service).save(any()) val found = service.save(entitySaveDto) - verify(service).save(argThat { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName }) - assertEquals(entitySaveDtoEmployee, found) + verify(service).save(argThat { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName }) + assertEquals(entitySaveDtoUser, found) } // update() @@ -182,7 +182,7 @@ class EmployeeServiceTest : withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) @Test - fun `update() throws AlreadyExistsException when a different employee with the given first name and last name exists`() { + 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 ) @@ -198,47 +198,47 @@ class EmployeeServiceTest : } } -class EmployeeGroupServiceTest : - AbstractExternalNamedModelServiceTest() { - private val employeeService: EmployeeService = mock() - override val repository: EmployeeGroupRepository = mock() - override val service: EmployeeGroupServiceImpl = spy(EmployeeGroupServiceImpl(employeeService, repository)) +class GroupServiceTest : + AbstractExternalNamedModelServiceTest() { + private val userService: UserService = mock() + override val repository: GroupRepository = mock() + override val service: GroupServiceImpl = spy(GroupServiceImpl(userService, repository)) - override val entity: EmployeeGroup = employeeGroup(id = 0L, name = "group") - override val anotherEntity: EmployeeGroup = employeeGroup(id = 1L, name = "another group") - override val entitySaveDto: EmployeeGroupSaveDto = spy(employeeGroupSaveDto(name = "group")) - override val entityUpdateDto: EmployeeGroupUpdateDto = spy(employeeGroupUpdateDto(id = 0L, name = "group")) - override val entityWithEntityName: EmployeeGroup = employeeGroup(id = 2L, name = entity.name) + 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 groupEmployeeId = 1000000L + entity.id!! - private val groupEmployee = employee(BCryptPasswordEncoder(), id = groupEmployeeId, group = entity) + private val groupUserId = 1000000L + entity.id!! + private val groupUser = user(BCryptPasswordEncoder(), id = groupUserId, group = entity) @BeforeEach override fun afterEach() { - reset(employeeService) + reset(userService) super.afterEach() } - // getEmployeesForGroup() + // getUsersForGroup() @Test - fun `getEmployeesForGroup() returns all employees in the given group`() { - val group = employeeGroup(id = 1L) + fun `getUsersForGroup() returns all users in the given group`() { + val group = group(id = 1L) doReturn(group).whenever(service).getById(group.id!!) - whenever(employeeService.getByGroup(group)).doReturn(listOf(groupEmployee)) + whenever(userService.getByGroup(group)).doReturn(listOf(groupUser)) - val found = service.getEmployeesForGroup(group.id!!) + val found = service.getUsersForGroup(group.id!!) - assertTrue(found.contains(groupEmployee)) + assertTrue(found.contains(groupUser)) assertTrue(found.size == 1) } @Test - fun `getEmployeesForGroup() returns empty collection when the given group contains any employee`() { + fun `getUsersForGroup() returns empty collection when the given group contains any user`() { doReturn(entity).whenever(service).getById(entity.id!!) - val found = service.getEmployeesForGroup(entity.id!!) + val found = service.getUsersForGroup(entity.id!!) assertTrue(found.isEmpty()) } @@ -247,11 +247,11 @@ class EmployeeGroupServiceTest : @Test fun `getRequestDefaultGroup() returns the group contained in the cookie of the HTTP request`() { - val cookies: Array = arrayOf(Cookie(defaultGroupCookieName, groupEmployeeId.toString())) + val cookies: Array = arrayOf(Cookie(defaultGroupCookieName, groupUserId.toString())) val request: HttpServletRequest = mock() whenever(request.cookies).doReturn(cookies) - whenever(employeeService.getById(eq(groupEmployeeId), any(), any())).doReturn(groupEmployee) + whenever(userService.getById(eq(groupUserId), any(), any())).doReturn(groupUser) val found = service.getRequestDefaultGroup(request) @@ -273,7 +273,7 @@ class EmployeeGroupServiceTest : fun `setResponseDefaultGroup() the default group cookie has been added to the given HTTP response with the given group id`() { val response = MockHttpServletResponse() - whenever(employeeService.getDefaultGroupEmployee(entity)).doReturn(groupEmployee) + whenever(userService.getDefaultGroupUser(entity)).doReturn(groupUser) doReturn(entity).whenever(service).getById(entity.id!!) service.setResponseDefaultGroup(entity.id!!, response) @@ -281,7 +281,7 @@ class EmployeeGroupServiceTest : assertNotNull(found) assertEquals(defaultGroupCookieName, found.name) - assertEquals(groupEmployeeId.toString(), found.value) + assertEquals(groupUserId.toString(), found.value) assertEquals(defaultGroupCookieMaxAge, found.maxAge) assertTrue(found.isHttpOnly) assertTrue(found.secure) @@ -301,48 +301,48 @@ class EmployeeGroupServiceTest : withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) } -class EmployeeUserDetailsServiceTest { - private val employeeService: EmployeeService = mock() - private val service = spy(EmployeeUserDetailsServiceImpl(employeeService)) +class UserUserDetailsServiceTest { + private val userService: UserService = mock() + private val service = spy(CreUserDetailsServiceImpl(userService)) - private val employee = employee(id = 0L) + private val user = user(id = 0L) @BeforeEach fun beforeEach() { - reset(employeeService, service) + reset(userService, service) } // loadUserByUsername() @Test - fun `loadUserByUsername() calls loadUserByEmployeeId() with the given username as an id`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee) - doReturn(User(employee.id.toString(), employee.password, listOf())).whenever(service) - .loadUserByEmployeeId(employee.id) + fun `loadUserByUsername() calls loadUserByUserId() with the given username as an id`() { + whenever(userService.getById(eq(user.id), any(), any())).doReturn(user) + doReturn(SpringUser(user.id.toString(), user.password, listOf())).whenever(service) + .loadUserById(user.id) - service.loadUserByUsername(employee.id.toString()) + service.loadUserByUsername(user.id.toString()) - verify(service).loadUserByEmployeeId(eq(employee.id), any()) + verify(service).loadUserById(eq(user.id), any()) } @Test - fun `loadUserByUsername() throws UsernameNotFoundException when no employee with the given id exists`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doThrow( - employeeIdNotFoundException(employee.id) + fun `loadUserByUsername() throws UsernameNotFoundException when no user with the given id exists`() { + whenever(userService.getById(eq(user.id), any(), any())).doThrow( + userIdNotFoundException(user.id) ) - assertThrows { service.loadUserByUsername(employee.id.toString()) } + assertThrows { service.loadUserByUsername(user.id.toString()) } } - // loadUserByEmployeeId + // loadUserByUserId @Test - fun `loadUserByEmployeeId() returns an User corresponding to the employee with the given id`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee) + fun `loadUserByUserId() returns an User corresponding to the user with the given id`() { + whenever(userService.getById(eq(user.id), any(), any())).doReturn(user) - val found = service.loadUserByEmployeeId(employee.id) + val found = service.loadUserById(user.id) - assertEquals(employee.id, found.username.toLong()) - assertEquals(employee.password, found.password) + assertEquals(user.id, found.username.toLong()) + assertEquals(user.password, found.password) } } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 859f4d9..dd3793c 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -3,6 +3,7 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService import io.mockk.* @@ -21,7 +22,7 @@ class RecipeServiceTest : override val repository: RecipeRepository = mock() private val companyService: CompanyService = mock() private val mixService: MixService = mock() - private val groupService: EmployeeGroupService = mock() + private val groupService: GroupService = mock() private val recipeStepService: RecipeStepService = mock() override val service: RecipeService = spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock())) @@ -129,9 +130,9 @@ class RecipeServiceTest : fun `updatePublicData() updates the notes of a recipe groups information according to the RecipePublicDataDto`() { val recipe = recipe( id = 0L, groupsInformation = setOf( - recipeGroupInformation(id = 0L, group = employeeGroup(id = 1L), note = "Old note"), - recipeGroupInformation(id = 1L, group = employeeGroup(id = 2L), note = "Another note"), - recipeGroupInformation(id = 2L, group = employeeGroup(id = 3L), note = "Up to date note") + recipeGroupInformation(id = 0L, group = group(id = 1L), note = "Old note"), + recipeGroupInformation(id = 1L, group = group(id = 2L), note = "Another note"), + recipeGroupInformation(id = 2L, group = group(id = 3L), note = "Up to date note") ) ) val notes = setOf( diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index 48f476f..4fe2e5a 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -80,7 +81,7 @@ class RecipeStepServiceTest : private fun withGroupInformation(steps: MutableSet? = null, test: RecipeGroupInformation.() -> Unit) { recipeGroupInformation( - group = employeeGroup(id = 0L), + group = group(id = 0L), steps = steps ?: mutableSetOf( recipeStep(id = 0L, position = 1), recipeStep(id = 1L, position = 2), From 42adb0ce9b8edd9b34ac4e654fcfa33c8be38777 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Thu, 6 May 2021 21:36:25 -0400 Subject: [PATCH 3/6] =?UTF-8?q?Mise=20=C3=A0=20jour=20vers=20Kotlin=201.5.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 48 +++++++++++-------- .../model/touchupkit/TouchUpKit.kt | 12 +++++ 2 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5432c74..7538018 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,13 +2,20 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile group = "dev.fyloz.colorrecipesexplorer" +val kotlinVersion = "1.5.0" +val springBootVersion = "2.3.4.RELEASE" + plugins { + // Outer scope variables can't be accessed in the plugins section, so we have to redefine them here + val kotlinVersion = "1.5.0" + val springBootVersion = "2.3.4.RELEASE" + id("java") - id("org.jetbrains.kotlin.jvm") version "1.4.30" - id("org.jetbrains.dokka") version "1.4.20" - id("org.springframework.boot") version "2.3.4.RELEASE" - id("org.jetbrains.kotlin.plugin.spring") version "1.4.30" - id("org.jetbrains.kotlin.plugin.jpa") version "1.4.30" + id("org.jetbrains.kotlin.jvm") version kotlinVersion + id("org.jetbrains.dokka") version "1.4.32" + id("org.springframework.boot") version springBootVersion + id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion + id("org.jetbrains.kotlin.plugin.jpa") version kotlinVersion } repositories { @@ -21,9 +28,9 @@ repositories { } dependencies { - implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.4.10")) - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10") - implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.10") + implementation(platform("org.jetbrains.kotlin:kotlin-bom:${kotlinVersion}")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.3") implementation("javax.xml.bind:jaxb-api:2.3.0") implementation("io.jsonwebtoken:jjwt:0.9.1") @@ -31,23 +38,22 @@ dependencies { implementation("org.apache.pdfbox:pdfbox:2.0.4") implementation("dev.fyloz.colorrecipesexplorer:database-manager:1.2.0") - implementation("org.springframework.boot:spring-boot-starter-data-jpa:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-starter-web:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-starter-validation:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-starter-security:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-configuration-processor:2.3.4.RELEASE") - implementation("org.springframework.boot:spring-boot-devtools:2.3.4.RELEASE") + implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-starter-validation:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-starter-security:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}") + implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}") testImplementation("org.springframework:spring-test:5.1.6.RELEASE") testImplementation("org.mockito:mockito-inline:3.6.0") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.2") testImplementation("io.mockk:mockk:1.10.6") - testImplementation("org.springframework.boot:spring-boot-starter-test:2.3.4.RELEASE") - testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:2.3.4.RELEASE") - testImplementation("org.jetbrains.kotlin:kotlin-test:1.4.10") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.4.10") + testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") + testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}") + testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") runtimeOnly("com.h2database:h2:1.4.199") runtimeOnly("mysql:mysql-connector-java:8.0.22") @@ -90,8 +96,8 @@ tasks.withType().all { jvmTarget = JavaVersion.VERSION_11.toString() useIR = true freeCompilerArgs = listOf( - "-Xopt-in=kotlin.contracts.ExperimentalContracts", - "-Xinline-classes" + "-Xopt-in=kotlin.contracts.ExperimentalContracts", + "-Xinline-classes" ) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt new file mode 100644 index 0000000..7300624 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt @@ -0,0 +1,12 @@ +package dev.fyloz.colorrecipesexplorer.model.touchupkit + +data class TouchUpKit( + val id: Long, + val project: String, + val buggy: String +) + +sealed class TouchUpKitCompany { + inline class CompanyName(val name: String) + class Company(val company: Company) +} From cadf3dde8bef984cc87d8f4986a978679db1423d Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 19 May 2021 23:29:53 -0400 Subject: [PATCH 4/6] Ajout du support pour les kits de retouches (pas juste les PDFs) --- build.gradle.kts | 1 - .../annotations/PermissionAnnotations.kt | 12 - .../colorrecipesexplorer/model/Company.kt | 14 +- .../colorrecipesexplorer/model/Material.kt | 37 +-- .../model/MaterialType.kt | 16 +- .../fyloz/colorrecipesexplorer/model/Mix.kt | 30 +-- .../colorrecipesexplorer/model/MixMaterial.kt | 24 +- .../fyloz/colorrecipesexplorer/model/Model.kt | 5 + .../colorrecipesexplorer/model/Recipe.kt | 55 ++--- .../model/account/Group.kt | 20 +- .../model/account/Permission.kt | 30 +-- .../model/account/User.kt | 23 +- .../model/touchupkit/TouchUpKit.kt | 211 +++++++++++++++++- .../repository/TouchUpKitRepository.kt | 6 + .../rest/AccountControllers.kt | 5 +- .../rest/CompanyController.kt | 2 +- .../rest/MaterialController.kt | 2 +- .../rest/MaterialTypeController.kt | 2 +- .../rest/RecipeController.kt | 9 +- .../rest/TouchUpKitController.kt | 63 ++++++ .../rest/files/FileController.kt | 2 +- .../rest/files/TouchUpKitController.kt | 26 --- .../service/MaterialService.kt | 2 +- .../TouchUpKitService.kt | 55 ++++- src/main/resources/application.properties | 4 +- src/main/resources/junit-platform.properties | 1 - .../service/files/TouchUpKitServiceTest.kt | 7 +- 27 files changed, 427 insertions(+), 237 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/TouchUpKitRepository.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/TouchUpKitController.kt rename src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/{files => touchupkit}/TouchUpKitService.kt (52%) delete mode 100644 src/main/resources/junit-platform.properties diff --git a/build.gradle.kts b/build.gradle.kts index 7538018..02b0630 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,6 @@ plugins { } repositories { - jcenter() mavenCentral() maven { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt index 04ff417..8f78572 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/PermissionAnnotations.kt @@ -14,12 +14,6 @@ annotation class PreAuthorizeViewRecipes @PreAuthorize("hasAuthority('EDIT_RECIPES')") annotation class PreAuthorizeEditRecipes -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -@PreAuthorize("hasAuthority('REMOVE_RECIPES')") -annotation class PreAuthorizeRemoveRecipes - @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented @@ -37,9 +31,3 @@ annotation class PreAuthorizeViewUsers @MustBeDocumented @PreAuthorize("hasAuthority('EDIT_USERS')") annotation class PreAuthorizeEditUsers - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -@PreAuthorize("hasAuthority('REMOVE_USERS')") -annotation class PreAuthorizeRemoveUsers diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Company.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Company.kt index c6b1fdf..d2bcba7 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Company.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Company.kt @@ -8,9 +8,6 @@ import javax.persistence.* import javax.validation.constraints.NotBlank import javax.validation.constraints.NotNull -private const val COMPANY_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val COMPANY_NAME_NULL_MESSAGE = "Un nom est requis" - @Entity @Table(name = "company") data class Company( @@ -20,11 +17,15 @@ data class Company( @Column(unique = true) override val name: String -) : NamedModel +) : NamedModel { + override fun toString(): String { + return name + } +} open class CompanySaveDto( - @field:NotBlank(message = COMPANY_NAME_NULL_MESSAGE) + @field:NotBlank val name: String ) : EntityDto { override fun toEntity(): Company = Company(null, name) @@ -32,10 +33,9 @@ open class CompanySaveDto( open class CompanyUpdateDto( - @field:NotNull(message = COMPANY_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = COMPANY_NAME_NULL_MESSAGE) + @field:NotBlank val name: String? ) : EntityDto { override fun toEntity(): Company = Company(id, name ?: "") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt index 5dc53de..76f505a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Material.kt @@ -4,27 +4,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize -import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES -import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import org.springframework.web.multipart.MultipartFile -import java.net.URLEncoder -import java.nio.charset.StandardCharsets import javax.persistence.* import javax.validation.constraints.Min import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull - -private const val MATERIAL_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val MATERIAL_NAME_NULL_MESSAGE = "Un nom est requis" -private const val MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE = "Une quantité est requise" -private const val MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE = "La quantité doit être supérieure ou égale à 0" -private const val MATERIAL_TYPE_NULL_MESSAGE = "Un type de produit est requis" - -private const val MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE = "Un produit est requis" -private const val MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE = "Une quantité est requises" -private const val MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE = "La quantité doit être supérieure ou égale à 0" +import javax.validation.constraints.Size const val SIMDUT_FILES_PATH = "pdf/simdut" @@ -52,32 +36,27 @@ data class Material( @JsonIgnore @Transient get() = "$SIMDUT_FILES_PATH/$name.pdf" - - } open class MaterialSaveDto( - @field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE) + @field:NotBlank val name: String, - @field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE) - @field:Min(value = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val inventoryQuantity: Float, - @field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE) val materialTypeId: Long, val simdutFile: MultipartFile? = null ) : EntityDto open class MaterialUpdateDto( - @field:NotNull(message = MATERIAL_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = MATERIAL_NAME_NULL_MESSAGE) + @field:NotBlank val name: String?, - @field:NullOrSize(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val inventoryQuantity: Float?, val materialTypeId: Long?, @@ -95,11 +74,9 @@ data class MaterialOutputDto( ) : Model data class MaterialQuantityDto( - @field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE) val material: Long, - @field:NotNull(message = MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE) - @field:Min(value = 0, message = MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val quantity: Float ) @@ -147,7 +124,7 @@ fun materialQuantityDto( ) = MaterialQuantityDto(materialId, quantity).apply(op) // ==== Exceptions ==== - private const +private const val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found" private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists" private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MaterialType.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MaterialType.kt index c8f5e80..d19ac34 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MaterialType.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MaterialType.kt @@ -11,10 +11,7 @@ import javax.validation.constraints.NotBlank import javax.validation.constraints.NotNull import javax.validation.constraints.Size -private const val MATERIAL_TYPE_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val MATERIAL_TYPE_NAME_NULL_MESSAGE = "Un nom est requis" -private const val MATERIAL_TYPE_PREFIX_NULL_MESSAGE = "Un préfixe est requis" -private const val MATERIAL_TYPE_PREFIX_SIZE_MESSAGE = "Le préfixe doit faire exactement 3 caractères" +private const val VALIDATION_PREFIX_SIZE = "Must contains exactly 3 characters" @Entity @Table(name = "material_type") @@ -39,11 +36,11 @@ data class MaterialType( ) : NamedModel open class MaterialTypeSaveDto( - @field:NotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE) + @field:NotBlank val name: String, - @field:NotBlank(message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE) - @field:Size(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_SIZE_MESSAGE) + @field:NotBlank + @field:Size(min = 3, max = 3, message = VALIDATION_PREFIX_SIZE) val prefix: String, val usePercentages: Boolean = false @@ -53,13 +50,12 @@ open class MaterialTypeSaveDto( } open class MaterialTypeUpdateDto( - @field:NotNull(message = MATERIAL_TYPE_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE) + @field:NotBlank val name: String?, - @field:NullOrSize(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE) + @field:Size(min = 3, max = 3, message = VALIDATION_PREFIX_SIZE) val prefix: String? ) : EntityDto { override fun toEntity(): MaterialType = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt index e332b36..3622343 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Mix.kt @@ -4,20 +4,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import javax.persistence.* import javax.validation.constraints.Min import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -private const val MIX_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val MIX_NAME_NULL_MESSAGE = "Un nom est requis" -private const val MIX_RECIPE_NULL_MESSAGE = "Un recette est requise" -private const val MIX_MATERIAL_TYPE_NULL_MESSAGE = "Un type de produit est requis" - -private const val MIX_DEDUCT_MIX_ID_NULL_MESSAGE = "Un identifiant de mélange est requis" -private const val MIX_DEDUCT_RATIO_NULL_MESSAGE = "Un ratio est requis" -private const val MIX_DEDUCT_RATION_NEGATIVE_MESSAGE = "Le ratio doit être égal ou supérieur à 0" @Entity @Table(name = "mix") @@ -43,33 +33,26 @@ data class Mix( ) : Model open class MixSaveDto( - @field:NotBlank(message = MIX_NAME_NULL_MESSAGE) + @field:NotBlank val name: String, - @field:NotNull(message = MIX_RECIPE_NULL_MESSAGE) val recipeId: Long, - @field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE) val materialTypeId: Long, val mixMaterials: Set? -) : EntityDto { - override fun toEntity(): Mix = throw UnsupportedOperationException() -} +) : EntityDto open class MixUpdateDto( - @field:NotNull(message = MIX_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE) + @field:NotBlank val name: String?, val materialTypeId: Long?, var mixMaterials: Set? -) : EntityDto { - override fun toEntity(): Mix = throw UnsupportedOperationException() -} +) : EntityDto data class MixOutputDto( val id: Long, @@ -79,16 +62,13 @@ data class MixOutputDto( ) data class MixDeductDto( - @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) val id: Long, - @field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE) - @field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val ratio: Float ) data class MixLocationDto( - @field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE) val mixId: Long, val location: String? diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt index 97f751c..c48316b 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixMaterial.kt @@ -6,10 +6,6 @@ import javax.persistence.* import javax.validation.constraints.Min import javax.validation.constraints.NotNull -private const val MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE = "Un identifiant de produit est requis" -private const val MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE = "Une quantité est requise" -private const val MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE = "La quantité ne peut pas être négative" - @Entity @Table(name = "mix_material") data class MixMaterial( @@ -26,6 +22,15 @@ data class MixMaterial( var position: Int ) : Model +data class MixMaterialDto( + val materialId: Long, + + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) + val quantity: Float, + + val position: Int +) + data class MixMaterialOutputDto( val id: Long, val material: MaterialOutputDto, @@ -33,17 +38,6 @@ data class MixMaterialOutputDto( val position: Int ) -data class MixMaterialDto( - @field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE) - val materialId: Long, - - @field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE) - @field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE) - val quantity: Float, - - val position: Int -) - // ==== DSL ==== fun mixMaterial( id: Long? = null, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Model.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Model.kt index 2a50349..147285f 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Model.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Model.kt @@ -15,3 +15,8 @@ interface EntityDto { throw UnsupportedOperationException() } } + +// GENERAL VALIDATION MESSAGES +const val VALIDATION_SIZE_GE_ZERO = "Must be greater or equals to 0" +const val VALIDATION_SIZE_GE_ONE = "Must be greater or equals to 1" +const val VALIDATION_RANGE_PERCENTS = "Must be between 0 and 100" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index cf0aad3..4f6551a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -5,8 +5,6 @@ import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.account.group -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import java.net.URLEncoder @@ -15,19 +13,7 @@ import java.time.LocalDate import javax.persistence.* import javax.validation.constraints.* -private const val RECIPE_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val RECIPE_NAME_NULL_MESSAGE = "Un nom est requis" -private const val RECIPE_DESCRIPTION_NULL_MESSAGE = "Une description est requise" -private const val RECIPE_COLOR_NULL_MESSAGE = "Une couleur est requise" -private const val RECIPE_GLOSS_NULL_MESSAGE = "Le lustre de la couleur est requis" -private const val RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE = "Le lustre doit être entre 0 et 100" -private const val RECIPE_SAMPLE_TOO_SMALL_MESSAGE = "Le numéro d'échantillon doit être supérieur ou égal à 0" -private const val RECIPE_COMPANY_NULL_MESSAGE = "Une bannière est requise" - -private const val RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE = "Un identifiant de groupe est requis" -private const val RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE = "Des messages sont requis" - -private const val NOTE_GROUP_ID_NULL_MESSAGE = "Un identifiant de groupe est requis" +private const val VALIDATION_COLOR_PATTERN = "^#([0-9a-f]{6})$" const val RECIPE_IMAGES_DIRECTORY = "images/recipes" @@ -91,30 +77,28 @@ data class Recipe( } open class RecipeSaveDto( - @field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE) + @field:NotBlank val name: String, - @field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE) + @field:NotBlank val description: String, - @field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE) - @field:Pattern(regexp = "^#([0-9a-f]{6})$") + @field:NotBlank + @field:Pattern(regexp = VALIDATION_COLOR_PATTERN) val color: String, - @field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE) - @field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE) - @field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE) + @field:Min(0, message = VALIDATION_RANGE_PERCENTS) + @field:Max(100, message = VALIDATION_RANGE_PERCENTS) val gloss: Byte, - @field:Min(value = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val sample: Int?, val approbationDate: LocalDate?, val remark: String?, - @field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE) - val companyId: Long = -1L, + val companyId: Long ) : EntityDto { override fun toEntity(): Recipe = recipe( name = name, @@ -127,24 +111,23 @@ open class RecipeSaveDto( } open class RecipeUpdateDto( - @field:NotNull(message = RECIPE_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE) + @field:NotBlank val name: String?, - @field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE) + @field:NotBlank val description: String?, - @field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE) - @field:Pattern(regexp = "^#([0-9a-f]{6})$") + @field:NotBlank + @field:Pattern(regexp = VALIDATION_COLOR_PATTERN) val color: String?, - @field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE) - @field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE) + @field:Min(0, message = VALIDATION_RANGE_PERCENTS) + @field:Max(100, message = VALIDATION_RANGE_PERCENTS) val gloss: Byte?, - @field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE) + @field:Min(0, message = VALIDATION_SIZE_GE_ZERO) val sample: Int?, val approbationDate: LocalDate?, @@ -188,15 +171,12 @@ data class RecipeGroupInformation( ) data class RecipeStepsDto( - @field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE) val groupId: Long, - @field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE) val steps: Set ) data class RecipePublicDataDto( - @field:NotNull(message = RECIPE_ID_NULL_MESSAGE) val recipeId: Long, val notes: Set?, @@ -205,7 +185,6 @@ data class RecipePublicDataDto( ) data class NoteDto( - @field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE) val groupId: Long, val content: String? @@ -290,8 +269,6 @@ private const val RECIPE_NOT_FOUND_EXCEPTION_TITLE = "Recipe not found" private const val RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe already exists" private const val RECIPE_EXCEPTION_ERROR_CODE = "recipe" -sealed class RecipeException - fun recipeIdNotFoundException(id: Long) = NotFoundException( RECIPE_EXCEPTION_ERROR_CODE, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt index 4bc229d..1169c5e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt @@ -3,21 +3,16 @@ 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.EntityDto -import dev.fyloz.colorrecipesexplorer.model.Model -import dev.fyloz.colorrecipesexplorer.model.NamedModel +import dev.fyloz.colorrecipesexplorer.model.* 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 import javax.validation.constraints.NotNull import javax.validation.constraints.Size -private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis" -private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise" - @Entity @Table(name = "user_group") data class Group( @@ -43,11 +38,10 @@ data class Group( } open class GroupSaveDto( - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) + @field:NotBlank val name: String, - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + @field:NotEmpty val permissions: MutableSet ) : EntityDto { override fun toEntity(): Group = @@ -55,14 +49,12 @@ open class GroupSaveDto( } open class GroupUpdateDto( - @field:NotNull(message = GROUP_ID_NULL_MESSAGE) val id: Long, - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) + @field:NotBlank val name: String, - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + @field:NotEmpty val permissions: MutableSet ) : EntityDto { override fun toEntity(): Group = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt index 333f8fb..abd2fb0 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt @@ -9,14 +9,11 @@ enum class Permission( ) { READ_FILE, WRITE_FILE(listOf(READ_FILE)), - REMOVE_FILE(listOf(WRITE_FILE)), VIEW_RECIPES(listOf(READ_FILE)), VIEW_CATALOG(listOf(READ_FILE)), VIEW_USERS, - PRINT_MIXES(listOf(VIEW_RECIPES)), - EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)), EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA, WRITE_FILE)), EDIT_MATERIALS(listOf(VIEW_CATALOG, WRITE_FILE)), @@ -25,29 +22,24 @@ enum class Permission( EDIT_USERS(listOf(VIEW_USERS)), EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)), - REMOVE_RECIPES(listOf(EDIT_RECIPES, REMOVE_FILE)), - REMOVE_MATERIALS(listOf(EDIT_MATERIALS, REMOVE_FILE)), - REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)), - REMOVE_COMPANIES(listOf(EDIT_COMPANIES)), - REMOVE_USERS(listOf(EDIT_USERS)), - REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)), + VIEW_TOUCH_UP_KITS, + EDIT_TOUCH_UP_KITS(listOf(VIEW_TOUCH_UP_KITS)), + PRINT_MIXES(listOf(VIEW_RECIPES)), ADD_TO_INVENTORY(listOf(VIEW_CATALOG)), DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)), - GENERATE_TOUCH_UP_KIT, ADMIN( listOf( + EDIT_RECIPES, EDIT_CATALOG, + EDIT_USERS, - REMOVE_RECIPES, - REMOVE_USERS, - REMOVE_CATALOG, + EDIT_TOUCH_UP_KITS, PRINT_MIXES, ADD_TO_INVENTORY, DEDUCT_FROM_INVENTORY, - GENERATE_TOUCH_UP_KIT ) ), @@ -69,6 +61,16 @@ enum class Permission( EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true), EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true), + REMOVE_FILE(listOf(WRITE_FILE), true), + GENERATE_TOUCH_UP_KIT(listOf(VIEW_TOUCH_UP_KITS), true), + + REMOVE_RECIPES(listOf(EDIT_RECIPES, REMOVE_FILE), true), + REMOVE_MATERIALS(listOf(EDIT_MATERIALS, REMOVE_FILE), true), + REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES), true), + REMOVE_COMPANIES(listOf(EDIT_COMPANIES), true), + REMOVE_USERS(listOf(EDIT_USERS), true), + REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES), true), + REMOVE_RECIPE(listOf(REMOVE_RECIPES), true), REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true), REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true), diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt index c6e2b58..3f4a64a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt @@ -4,7 +4,6 @@ import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.EntityDto import dev.fyloz.colorrecipesexplorer.model.Model -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import org.hibernate.annotations.Fetch import org.hibernate.annotations.FetchMode import org.springframework.security.core.GrantedAuthority @@ -13,14 +12,9 @@ import org.springframework.security.crypto.password.PasswordEncoder import java.time.LocalDateTime import javax.persistence.* import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull import javax.validation.constraints.Size -private const val USER_ID_NULL_MESSAGE = "Un numéro d'utilisateur est requis" -private const val USER_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis" -private const val USER_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis" -private const val USER_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis" -private const val USER_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères" +private const val VALIDATION_PASSWORD_LENGTH = "Must contains at least 8 characters" @Entity @Table(name = "user") @@ -70,19 +64,17 @@ data class User( get() = flatPermissions.map { it.toAuthority() }.toMutableSet() } -/** DTO for creating users. Allows a [password] a [groupId]. */ open class UserSaveDto( - @field:NotNull(message = USER_ID_NULL_MESSAGE) val id: Long, - @field:NotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + @field:NotBlank val firstName: String, - @field:NotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + @field:NotBlank val lastName: String, - @field:NotBlank(message = USER_PASSWORD_EMPTY_MESSAGE) - @field:Size(min = 8, message = USER_PASSWORD_TOO_SHORT_MESSAGE) + @field:NotBlank + @field:Size(min = 8, message = VALIDATION_PASSWORD_LENGTH) val password: String, val groupId: Long?, @@ -92,13 +84,12 @@ open class UserSaveDto( ) : EntityDto open class UserUpdateDto( - @field:NotNull(message = USER_ID_NULL_MESSAGE) val id: Long, - @field:NullOrNotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + @field:NotBlank val firstName: String?, - @field:NullOrNotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + @field:NotBlank val lastName: String?, val groupId: Long?, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt index 7300624..e6a01ed 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/touchupkit/TouchUpKit.kt @@ -1,12 +1,211 @@ package dev.fyloz.colorrecipesexplorer.model.touchupkit +import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException +import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.model.EntityDto +import dev.fyloz.colorrecipesexplorer.model.Model +import dev.fyloz.colorrecipesexplorer.model.VALIDATION_SIZE_GE_ONE +import java.time.LocalDate +import javax.persistence.* +import javax.validation.constraints.Min +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotEmpty + +const val TOUCH_UP_KIT_DELIMITER = ';' + +@Entity +@Table(name = "touch_up_kit") data class TouchUpKit( - val id: Long, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override val id: Long?, + val project: String, - val buggy: String + + val buggy: String, + + val company: String, + + val quantity: Int, + + @Column(name = "shipping_date") + val shippingDate: LocalDate, + + @Column(name = "finish") + private val finishConcatenated: String, + + @Column(name = "material") + private val materialConcatenated: String, + + @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) + @JoinColumn(name = "touch_up_kit_id") + val content: Set +) : Model { + val finish + get() = finishConcatenated.split(TOUCH_UP_KIT_DELIMITER) + + val material + get() = materialConcatenated.split(TOUCH_UP_KIT_DELIMITER) +} + +@Entity +@Table(name = "touch_up_kit_product") +data class TouchUpKitProduct( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override val id: Long?, + + val name: String, + + val description: String?, + + val quantity: Float +) : Model + +data class TouchUpKitSaveDto( + @field:NotBlank + val project: String, + + @field:NotBlank + val buggy: String, + + @field:NotBlank + val company: String, + + @field:Min(1, message = VALIDATION_SIZE_GE_ONE) + val quantity: Int, + + val shippingDate: LocalDate, + + @field:NotEmpty + val finish: List, + + @field:NotEmpty + val material: List, + + @field:NotEmpty + val content: Set +) : EntityDto { + override fun toEntity() = touchUpKit(this) +} + +data class TouchUpKitUpdateDto( + val id: Long, + + @field:NotBlank + val project: String?, + + @field:NotBlank + val buggy: String?, + + @field:NotBlank + val company: String?, + + @field:Min(1, message = VALIDATION_SIZE_GE_ONE) + val quantity: Int?, + + val shippingDate: LocalDate?, + + @field:NotEmpty + val finish: List?, + + @field:NotEmpty + val material: List?, + + @field:NotEmpty + val content: Set? +) : EntityDto + +data class TouchUpKitOutputDto( + override val id: Long, + val project: String, + val buggy: String, + val company: String, + val quantity: Int, + val shippingDate: LocalDate, + val finish: List, + val material: List, + val content: Set, + val pdfUrl: String +) : Model + +data class TouchUpKitProductDto( + val name: String, + val description: String?, + val quantity: Float ) -sealed class TouchUpKitCompany { - inline class CompanyName(val name: String) - class Company(val company: Company) -} +// ==== DSL ==== +fun touchUpKit( + id: Long? = null, + project: String = "project", + buggy: String = "buggy", + company: String = "company", + quantity: Int = 1, + shippingDate: LocalDate = LocalDate.now(), + finish: List, + material: List, + content: Set, + op: TouchUpKit.() -> Unit = {} +) = TouchUpKit( + id, + project, + buggy, + company, + quantity, + shippingDate, + finish.reduce { acc, f -> "$acc$TOUCH_UP_KIT_DELIMITER$f" }, + material.reduce { acc, f -> "$acc$TOUCH_UP_KIT_DELIMITER$f" }, + content +).apply(op) + +fun touchUpKit(touchUpKitSaveDto: TouchUpKitSaveDto) = + with(touchUpKitSaveDto) { + touchUpKit( + project = project, + buggy = buggy, + company = company, + quantity = quantity, + shippingDate = shippingDate, + finish = finish, + material = material, + content = content.map { touchUpKitProduct(it) }.toSet() + ) + } + +fun touchUpKitProduct( + id: Long? = null, + name: String = "product", + description: String? = "description", + quantity: Float = 1f, + op: TouchUpKitProduct.() -> Unit = {} +) = TouchUpKitProduct(id, name, description, quantity) + .apply(op) + +fun touchUpKitProduct(touchUpKitProductDto: TouchUpKitProductDto) = + touchUpKitProduct( + name = touchUpKitProductDto.name, + description = touchUpKitProductDto.description, + quantity = touchUpKitProductDto.quantity + ) + +// ==== Exceptions ==== +private const val TOUCH_UP_KIT_NOT_FOUND_EXCEPTION_TITLE = "Touch up kit not found" +private const val TOUCH_UP_KIT_ALREADY_EXISTS_EXCEPTION_TITLE = "Touch up kit already exists" +private const val TOUCH_UP_KIT_EXCEPTION_ERROR_CODE = "touchupkit" + +fun touchUpKitIdNotFoundException(id: Long) = + NotFoundException( + TOUCH_UP_KIT_EXCEPTION_ERROR_CODE, + TOUCH_UP_KIT_NOT_FOUND_EXCEPTION_TITLE, + "A touch up kit with the id $id could not be found", + id + ) + +fun touchUpKitIdAlreadyExistsException(id: Long) = + AlreadyExistsException( + TOUCH_UP_KIT_EXCEPTION_ERROR_CODE, + TOUCH_UP_KIT_ALREADY_EXISTS_EXCEPTION_TITLE, + "A touch up kit with the id $id already exists", + id + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/TouchUpKitRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/TouchUpKitRepository.kt new file mode 100644 index 0000000..0819613 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/TouchUpKitRepository.kt @@ -0,0 +1,6 @@ +package dev.fyloz.colorrecipesexplorer.repository + +import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKit +import org.springframework.data.jpa.repository.JpaRepository + +interface TouchUpKitRepository : JpaRepository diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index 62f6d03..69165f4 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -1,7 +1,6 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers -import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers import dev.fyloz.colorrecipesexplorer.model.account.* import dev.fyloz.colorrecipesexplorer.service.UserService @@ -87,7 +86,7 @@ class UserController(private val userService: UserService) { } @DeleteMapping("{id}") - @PreAuthorizeRemoveUsers + @PreAuthorizeEditUsers fun deleteById(@PathVariable id: Long) = userService.deleteById(id) } @@ -147,7 +146,7 @@ class GroupsController( } @DeleteMapping("{id}") - @PreAuthorizeRemoveUsers + @PreAuthorizeEditUsers fun deleteById(@PathVariable id: Long) = noContent { groupService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt index e1acc00..f16f253 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/CompanyController.kt @@ -38,7 +38,7 @@ class CompanyController(private val companyService: CompanyService) { } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_COMPANIES')") + @PreAuthorize("hasAuthority('EDIT_COMPANIES')") fun deleteById(@PathVariable id: Long) = noContent { companyService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt index 8e99b03..0bec46a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialController.kt @@ -64,7 +64,7 @@ class MaterialController( } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_MATERIALS')") + @PreAuthorize("hasAuthority('EDIT_MATERIALS')") fun deleteById(@PathVariable id: Long) = noContent { materialService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt index 50e26f0..877f8d1 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/MaterialTypeController.kt @@ -38,7 +38,7 @@ class MaterialTypeController(private val materialTypeService: MaterialTypeServic } @DeleteMapping("{id}") - @PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPES')") + @PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')") fun deleteById(@PathVariable id: Long) = noContent { materialTypeService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index 8bf447e..efdd414 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -1,11 +1,8 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes -import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveRecipes import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewRecipes -import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH import dev.fyloz.colorrecipesexplorer.service.MixService import dev.fyloz.colorrecipesexplorer.service.RecipeImageService import dev.fyloz.colorrecipesexplorer.service.RecipeService @@ -14,8 +11,6 @@ import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -import java.net.URLEncoder -import java.nio.charset.StandardCharsets import javax.validation.Valid @@ -61,7 +56,7 @@ class RecipeController( } @DeleteMapping("{id}") - @PreAuthorizeRemoveRecipes + @PreAuthorizeEditRecipes fun deleteById(@PathVariable id: Long) = noContent { recipeService.deleteById(id) @@ -105,7 +100,7 @@ class MixController(private val mixService: MixService) { } @DeleteMapping("{id}") - @PreAuthorizeRemoveRecipes + @PreAuthorizeEditRecipes fun deleteById(@PathVariable id: Long) = noContent { mixService.deleteById(id) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt new file mode 100644 index 0000000..c39684d --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt @@ -0,0 +1,63 @@ +package dev.fyloz.colorrecipesexplorer.rest + +import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitOutputDto +import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitSaveDto +import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitUpdateDto +import dev.fyloz.colorrecipesexplorer.service.touchupkit.TouchUpKitService +import org.springframework.core.io.ByteArrayResource +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.* +import javax.validation.Valid + +const val TOUCH_UP_KIT_CONTROLLER_PATH = "/api/touchupkit" + +@RestController +@RequestMapping(TOUCH_UP_KIT_CONTROLLER_PATH) +@PreAuthorize("hasAuthority('VIEW_TOUCH_UP_KITS')") +class TouchUpKitController( + private val touchUpKitService: TouchUpKitService +) { + @GetMapping + fun getAll() = + ok(touchUpKitService.getAllForOutput()) + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + ok(touchUpKitService.getByIdForOutput(id)) + + @PostMapping + @PreAuthorize("hasAuthority('EDIT_TOUCH_UP_KITS')") + fun save(@Valid @RequestBody touchUpKit: TouchUpKitSaveDto) = + created(TOUCH_UP_KIT_CONTROLLER_PATH) { + with(touchUpKitService) { + save(touchUpKit).toOutput() + } + } + + @PutMapping + @PreAuthorize("hasAuthority('EDIT_TOUCH_UP_KITS')") + fun update(@Valid @RequestBody touchUpKit: TouchUpKitUpdateDto) = + noContent { + touchUpKitService.update(touchUpKit) + } + + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('EDIT_TOUCH_UP_KITS')") + fun deleteById(@PathVariable id: Long) = + noContent { + touchUpKitService.deleteById(id) + } + + @GetMapping("pdf") + fun getJobPdf(@RequestParam project: String): ResponseEntity { + with(touchUpKitService.generateJobPdfResource(project)) { + return ResponseEntity.ok() + .header("Content-Disposition", "filename=TouchUpKit_$project.pdf") + .contentLength(this.contentLength()) + .contentType(MediaType.APPLICATION_PDF) + .body(this) + } + } +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/FileController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/FileController.kt index 9e5125b..cad230a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/FileController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/FileController.kt @@ -46,7 +46,7 @@ class FileController( } @DeleteMapping - @PreAuthorize("hasAnyAuthority('REMOVE_FILE')") + @PreAuthorize("hasAnyAuthority('WRITE_FILE')") fun delete(@RequestParam path: String): ResponseEntity { return noContent { fileService.delete(path) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/TouchUpKitController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/TouchUpKitController.kt deleted file mode 100644 index 6993025..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/files/TouchUpKitController.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.rest.files - -import dev.fyloz.colorrecipesexplorer.service.files.TouchUpKitService -import org.springframework.core.io.ByteArrayResource -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.* - -@RestController -@RequestMapping("/api/touchup") -@PreAuthorize("hasAuthority('GENERATE_TOUCH_UP_KIT')") -class TouchUpKitController( - private val touchUpKitService: TouchUpKitService -) { - @GetMapping - fun getJobPdf(@RequestParam job: String): ResponseEntity { - with(touchUpKitService.generateJobPdfResource(job)) { - return ResponseEntity.ok() - .header("Content-Disposition", "filename=TouchUpKit_$job.pdf") - .contentLength(this.contentLength()) - .contentType(MediaType.APPLICATION_PDF) - .body(this) - } - } -} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt index 02b843d..cb5c3ab 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt @@ -136,7 +136,7 @@ class MaterialServiceImpl( override fun delete(entity: Material) { if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity) - fileService.delete(entity.simdutFilePath) + if (fileService.exists(entity.simdutFilePath)) fileService.delete(entity.simdutFilePath) super.delete(entity) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/touchupkit/TouchUpKitService.kt similarity index 52% rename from src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitService.kt rename to src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/touchupkit/TouchUpKitService.kt index 68b043e..79c9440 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/touchupkit/TouchUpKitService.kt @@ -1,6 +1,12 @@ -package dev.fyloz.colorrecipesexplorer.service.files +package dev.fyloz.colorrecipesexplorer.service.touchupkit import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties +import dev.fyloz.colorrecipesexplorer.model.touchupkit.* +import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository +import dev.fyloz.colorrecipesexplorer.rest.TOUCH_UP_KIT_CONTROLLER_PATH +import dev.fyloz.colorrecipesexplorer.service.AbstractExternalModelService +import dev.fyloz.colorrecipesexplorer.service.ExternalModelService +import dev.fyloz.colorrecipesexplorer.service.files.FileService import dev.fyloz.colorrecipesexplorer.utils.* import org.springframework.core.io.ByteArrayResource import org.springframework.stereotype.Service @@ -10,7 +16,8 @@ private const val TOUCH_UP_KIT_FILES_PATH = "pdf/touchupkits" const val TOUCH_UP_TEXT_FR = "KIT DE RETOUCHE" const val TOUCH_UP_TEXT_EN = "TOUCH UP KIT" -interface TouchUpKitService { +interface TouchUpKitService : + ExternalModelService { /** Generates and returns a [PdfDocument] for the given [job]. */ fun generateJobPdf(job: String): PdfDocument @@ -29,8 +36,45 @@ interface TouchUpKitService { @Service class TouchUpKitServiceImpl( private val fileService: FileService, - private val creProperties: CreProperties -) : TouchUpKitService { + touchUpKitRepository: TouchUpKitRepository, + private val creProperties: CreProperties, +) : AbstractExternalModelService( + touchUpKitRepository +), TouchUpKitService { + override fun idNotFoundException(id: Long) = touchUpKitIdNotFoundException(id) + override fun idAlreadyExistsException(id: Long) = touchUpKitIdAlreadyExistsException(id) + + override fun TouchUpKit.toOutput() = TouchUpKitOutputDto( + this.id!!, + this.project, + this.buggy, + this.company, + this.quantity, + this.shippingDate, + this.finish, + this.material, + this.content, + this.pdfUrl() + ) + + override fun update(entity: TouchUpKitUpdateDto): TouchUpKit { + val persistedKit by lazy { getById(entity.id) } + + return super.update(with(entity) { + touchUpKit( + id = id, + project = project ?: persistedKit.project, + buggy = buggy ?: persistedKit.buggy, + company = company ?: persistedKit.company, + quantity = quantity ?: persistedKit.quantity, + shippingDate = shippingDate ?: persistedKit.shippingDate, + finish = finish ?: persistedKit.finish, + material = material ?: persistedKit.material, + content = content?.map { touchUpKitProduct(it) }?.toSet() ?: persistedKit.content + ) + }) + } + override fun generateJobPdf(job: String) = pdf { container { centeredVertically = true @@ -75,4 +119,7 @@ class TouchUpKitServiceImpl( private fun String.pdfDocumentPath() = "$TOUCH_UP_KIT_FILES_PATH/$this.pdf" + + private fun TouchUpKit.pdfUrl() = + "${creProperties.deploymentUrl}$TOUCH_UP_KIT_CONTROLLER_PATH/pdf?job=$project" } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 74dd17a..d6fad50 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,7 +21,7 @@ entities.material-types.baseName=Base databaseupdater.username=root databaseupdater.password=pass # DEBUG -spring.jpa.show-sql=true +spring.jpa.show-sql=false # Do not modify spring.messages.fallback-to-system-locale=true spring.servlet.multipart.max-file-size=10MB @@ -30,4 +30,6 @@ spring.jpa.open-in-view=true server.http2.enabled=true server.error.whitelabel.enabled=false spring.h2.console.enabled=false +spring.jackson.deserialization.fail-on-null-for-primitives=true +spring.jackson.default-property-inclusion=non_null spring.profiles.active=@spring.profiles.active@ diff --git a/src/main/resources/junit-platform.properties b/src/main/resources/junit-platform.properties deleted file mode 100644 index 2af5bf8..0000000 --- a/src/main/resources/junit-platform.properties +++ /dev/null @@ -1 +0,0 @@ -junit.jupiter.testinstance.lifecycle.default=per_class diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitServiceTest.kt index 4affc53..3813fd5 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/TouchUpKitServiceTest.kt @@ -1,6 +1,10 @@ package dev.fyloz.colorrecipesexplorer.service.files import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties +import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository +import dev.fyloz.colorrecipesexplorer.service.touchupkit.TOUCH_UP_TEXT_EN +import dev.fyloz.colorrecipesexplorer.service.touchupkit.TOUCH_UP_TEXT_FR +import dev.fyloz.colorrecipesexplorer.service.touchupkit.TouchUpKitServiceImpl import dev.fyloz.colorrecipesexplorer.utils.PdfDocument import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource import io.mockk.* @@ -10,13 +14,14 @@ import org.springframework.core.io.ByteArrayResource import kotlin.test.assertEquals private class TouchUpKitServiceTestContext { + val touchUpKitRepository = mockk() val fileService = mockk { every { write(any(), any(), any()) } just Runs } val creProperties = mockk { every { cacheGeneratedFiles } returns false } - val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, creProperties)) + val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, touchUpKitRepository, creProperties)) val pdfDocumentData = mockk() val pdfDocument = mockk { mockkStatic(PdfDocument::toByteArrayResource) From 5b3524dae91c9b298a3775f86f70ab3a8430f9c1 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Thu, 20 May 2021 13:08:28 -0400 Subject: [PATCH 5/6] =?UTF-8?q?Ajout=20d'un=20endpoint=20pour=20r=C3=A9cup?= =?UTF-8?q?=C3=A9rer=20les=20couleurs=20selon=20leur=20nom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RecipeRepository.kt | 3 +++ .../colorrecipesexplorer/rest/RecipeController.kt | 9 +++++++-- .../colorrecipesexplorer/service/RecipeService.kt | 6 +++++- .../service/AccountsServiceTest.kt | 8 ++++---- .../service/CompanyServiceTest.kt | 2 ++ .../service/InventoryServiceTest.kt | 2 ++ .../service/MaterialServiceTest.kt | 2 ++ .../service/MaterialTypeServiceTest.kt | 2 ++ .../service/MixMaterialServiceTest.kt | 2 ++ .../service/MixServiceTest.kt | 2 ++ .../service/MixTypeServiceTest.kt | 2 ++ .../service/RecipeServiceTest.kt | 15 +++++++++++++++ .../service/RecipeStepServiceTest.kt | 2 ++ 13 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt index a7284f7..251374d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/RecipeRepository.kt @@ -11,6 +11,9 @@ interface RecipeRepository : JpaRepository { /** Checks if a recipe exists with the given [name] and [company]. */ fun existsByNameAndCompany(name: String, company: Company): Boolean + /** Gets all recipes with the given [name]. */ + fun findAllByName(name: String): Collection + /** Gets all recipes with the given [company]. */ fun findAllByCompany(company: Company): Collection } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt index efdd414..0b840a9 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RecipeController.kt @@ -25,8 +25,13 @@ class RecipeController( private val recipeImageService: RecipeImageService ) { @GetMapping - fun getAll() = - ok(recipeService.getAllForOutput()) + fun getAll(@RequestParam(required = false) name: String?) = + if (name == null) + ok(recipeService.getAllForOutput()) + else + ok(with(recipeService) { + getAllByName(name).map { it.toOutput() } + }) @GetMapping("{id}") fun getById(@PathVariable id: Long) = diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 7f0a1cc..c28ef6e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -20,6 +20,9 @@ interface RecipeService : /** Checks if a recipe exists with the given [name] and [company]. */ fun existsByNameAndCompany(name: String, company: Company): Boolean + /** Gets all recipes with the given [name]. */ + fun getAllByName(name: String): Collection + /** Gets all recipes with the given [company]. */ fun getAllByCompany(company: Company): Collection @@ -74,7 +77,8 @@ class RecipeServiceImpl( override fun existsByNameAndCompany(name: String, company: Company) = repository.existsByNameAndCompany(name, company) - override fun getAllByCompany(company: Company): Collection = repository.findAllByCompany(company) + override fun getAllByName(name: String) = repository.findAllByName(name) + override fun getAllByCompany(company: Company) = repository.findAllByCompany(company) override fun save(entity: RecipeSaveDto): Recipe { val company = companyService.getById(entity.companyId) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt index ba55ec5..4d15164 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt @@ -7,10 +7,7 @@ import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.account.* import dev.fyloz.colorrecipesexplorer.repository.UserRepository import dev.fyloz.colorrecipesexplorer.repository.GroupRepository -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows +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 @@ -23,6 +20,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import org.springframework.security.core.userdetails.User as SpringUser +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserServiceTest : AbstractExternalModelServiceTest() { private val passwordEncoder = BCryptPasswordEncoder() @@ -198,6 +196,7 @@ class UserServiceTest : } } +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class GroupServiceTest : AbstractExternalNamedModelServiceTest() { private val userService: UserService = mock() @@ -301,6 +300,7 @@ class GroupServiceTest : withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) } +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserUserDetailsServiceTest { private val userService: UserService = mock() private val service = spy(CreUserDetailsServiceImpl(userService)) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyServiceTest.kt index 75dfba1..7843cae 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/CompanyServiceTest.kt @@ -5,9 +5,11 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import kotlin.test.assertFalse import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class CompanyServiceTest : AbstractExternalNamedModelServiceTest() { private val recipeService: RecipeService = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/InventoryServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/InventoryServiceTest.kt index aae3b92..98eb32e 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/InventoryServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/InventoryServiceTest.kt @@ -4,10 +4,12 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class InventoryServiceTest { private val materialService: MaterialService = mock() private val mixService: MixService = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt index dc13ca3..9b3dacb 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt @@ -7,12 +7,14 @@ import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import org.springframework.mock.web.MockMultipartFile import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MaterialServiceTest : AbstractExternalNamedModelServiceTest() { override val repository: MaterialRepository = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeServiceTest.kt index 797c1d0..7d8a1dd 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeServiceTest.kt @@ -7,11 +7,13 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MaterialTypeServiceTest : AbstractExternalNamedModelServiceTest() { override val repository: MaterialTypeRepository = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt index bffc6a4..0ab18cf 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt @@ -4,12 +4,14 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MixMaterialServiceTest : AbstractModelServiceTest() { override val repository: MixMaterialRepository = mock() private val materialService: MaterialService = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixServiceTest.kt index 00d1c2c..a9debc1 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixServiceTest.kt @@ -5,9 +5,11 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import kotlin.test.assertEquals import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MixServiceTest : AbstractExternalModelServiceTest() { override val repository: MixRepository = mock() private val recipeService: RecipeService = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeServiceTest.kt index b57d4ce..4e6f246 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeServiceTest.kt @@ -7,10 +7,12 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class MixTypeServiceTest : AbstractNamedModelServiceTest() { override val repository: MixTypeRepository = mock() private val materialService: MaterialService = mock() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index dd3793c..a8ccd0b 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -9,6 +9,7 @@ import dev.fyloz.colorrecipesexplorer.service.files.FileService import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import org.springframework.mock.web.MockMultipartFile import org.springframework.web.multipart.MultipartFile @@ -17,6 +18,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class RecipeServiceTest : AbstractExternalModelServiceTest() { override val repository: RecipeRepository = mock() @@ -72,6 +74,19 @@ class RecipeServiceTest : } } + // getAllByName() + + @Test + fun `getAllByName() returns the recipes with the given name`() { + val recipes = listOf(entity, anotherEntity) + + whenever(repository.findAllByName(entity.name)).doReturn(recipes) + + val found = service.getAllByName(entity.name) + + assertEquals(recipes, found) + } + // getAllByCompany() @Test diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index 4fe2e5a..b0f9c73 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -5,9 +5,11 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import kotlin.test.assertTrue +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class RecipeStepServiceTest : AbstractModelServiceTest() { override val repository: RecipeStepRepository = mock() From b43f7ca357e7b8267a146fd605d9c209b97b1057 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Thu, 20 May 2021 13:37:31 -0400 Subject: [PATCH 6/6] =?UTF-8?q?Mise=20=C3=A0=20jour=20du=20gestionnaire=20?= =?UTF-8?q?de=20base=20de=20donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- .../DatabaseVersioning.kt | 2 +- .../model/Configuration.kt | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt diff --git a/build.gradle.kts b/build.gradle.kts index 02b0630..e437730 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { implementation("io.jsonwebtoken:jjwt:0.9.1") implementation("org.apache.poi:poi-ooxml:4.1.0") implementation("org.apache.pdfbox:pdfbox:2.0.4") - implementation("dev.fyloz.colorrecipesexplorer:database-manager:1.2.0") + implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.1") implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}") implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/DatabaseVersioning.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/DatabaseVersioning.kt index 81d6419..fae5ad8 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/DatabaseVersioning.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/DatabaseVersioning.kt @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Configuration import org.springframework.core.env.Environment import javax.sql.DataSource -const val SUPPORTED_DATABASE_VERSION = 4 +const val SUPPORTED_DATABASE_VERSION = 5 const val ENV_VAR_ENABLE_DATABASE_UPDATE_NAME = "CRE_ENABLE_DB_UPDATE" val DATABASE_NAME_REGEX = Regex("(\\w+)$") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt new file mode 100644 index 0000000..951db30 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt @@ -0,0 +1,20 @@ +package dev.fyloz.colorrecipesexplorer.model + +import java.time.LocalDateTime +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.Id +import javax.persistence.Table + +@Entity +@Table(name = "configuration") +data class Configuration( + @Id + @Column(name = "config_key") + val key: String, + + val content: String, + + @Column(name = "last_updated") + val lastUpdated: LocalDateTime +)