Merge branch 'features' into 'master'
Changement du nom interne "employé" version "utilisateur", ajout du support complet pour les kits de retouche See merge request color-recipes-explorer/backend!30
This commit is contained in:
commit
d7e335779b
|
@ -2,17 +2,23 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
group = "dev.fyloz.colorrecipesexplorer"
|
group = "dev.fyloz.colorrecipesexplorer"
|
||||||
|
|
||||||
|
val kotlinVersion = "1.5.0"
|
||||||
|
val springBootVersion = "2.3.4.RELEASE"
|
||||||
|
|
||||||
plugins {
|
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("java")
|
||||||
id("org.jetbrains.kotlin.jvm") version "1.4.30"
|
id("org.jetbrains.kotlin.jvm") version kotlinVersion
|
||||||
id("org.jetbrains.dokka") version "1.4.20"
|
id("org.jetbrains.dokka") version "1.4.32"
|
||||||
id("org.springframework.boot") version "2.3.4.RELEASE"
|
id("org.springframework.boot") version springBootVersion
|
||||||
id("org.jetbrains.kotlin.plugin.spring") version "1.4.30"
|
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
|
||||||
id("org.jetbrains.kotlin.plugin.jpa") version "1.4.30"
|
id("org.jetbrains.kotlin.plugin.jpa") version kotlinVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
||||||
maven {
|
maven {
|
||||||
|
@ -21,33 +27,32 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.4.10"))
|
implementation(platform("org.jetbrains.kotlin:kotlin-bom:${kotlinVersion}"))
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.10")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.3")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.3")
|
||||||
implementation("javax.xml.bind:jaxb-api:2.3.0")
|
implementation("javax.xml.bind:jaxb-api:2.3.0")
|
||||||
implementation("io.jsonwebtoken:jjwt:0.9.1")
|
implementation("io.jsonwebtoken:jjwt:0.9.1")
|
||||||
implementation("org.apache.poi:poi-ooxml:4.1.0")
|
implementation("org.apache.poi:poi-ooxml:4.1.0")
|
||||||
implementation("org.apache.pdfbox:pdfbox:2.0.4")
|
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:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-starter-validation:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-configuration-processor:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}")
|
||||||
implementation("org.springframework.boot:spring-boot-devtools:2.3.4.RELEASE")
|
implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
|
||||||
|
|
||||||
testImplementation("org.springframework:spring-test:5.1.6.RELEASE")
|
testImplementation("org.springframework:spring-test:5.1.6.RELEASE")
|
||||||
testImplementation("org.mockito:mockito-inline:3.6.0")
|
testImplementation("org.mockito:mockito-inline:3.6.0")
|
||||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.2")
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.2")
|
||||||
testImplementation("io.mockk:mockk:1.10.6")
|
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-starter-test:${springBootVersion}")
|
||||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:2.3.4.RELEASE")
|
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:1.4.10")
|
testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.4.10")
|
|
||||||
|
|
||||||
runtimeOnly("com.h2database:h2:1.4.199")
|
runtimeOnly("com.h2database:h2:1.4.199")
|
||||||
runtimeOnly("mysql:mysql-connector-java:8.0.22")
|
runtimeOnly("mysql:mysql-connector-java:8.0.22")
|
||||||
|
@ -90,8 +95,8 @@ tasks.withType<KotlinCompile>().all {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
useIR = true
|
useIR = true
|
||||||
freeCompilerArgs = listOf(
|
freeCompilerArgs = listOf(
|
||||||
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
|
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
|
||||||
"-Xinline-classes"
|
"-Xinline-classes"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.core.env.Environment
|
import org.springframework.core.env.Environment
|
||||||
import javax.sql.DataSource
|
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"
|
const val ENV_VAR_ENABLE_DATABASE_UPDATE_NAME = "CRE_ENABLE_DB_UPDATE"
|
||||||
val DATABASE_NAME_REGEX = Regex("(\\w+)$")
|
val DATABASE_NAME_REGEX = Regex("(\\w+)$")
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ package dev.fyloz.colorrecipesexplorer.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.Employee
|
import dev.fyloz.colorrecipesexplorer.model.account.UserLoginRequest
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EmployeeLoginRequest
|
import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EmployeePermission
|
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeService
|
import dev.fyloz.colorrecipesexplorer.service.UserService
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl
|
import dev.fyloz.colorrecipesexplorer.service.UserServiceImpl
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsService
|
import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsService
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsServiceImpl
|
import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsServiceImpl
|
||||||
import io.jsonwebtoken.ExpiredJwtException
|
import io.jsonwebtoken.ExpiredJwtException
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
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.Authentication
|
||||||
import org.springframework.security.core.AuthenticationException
|
import org.springframework.security.core.AuthenticationException
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
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.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint
|
import org.springframework.security.web.AuthenticationEntryPoint
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||||
|
@ -52,11 +52,11 @@ import javax.servlet.http.HttpServletResponse
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
@EnableConfigurationProperties(SecurityConfigurationProperties::class)
|
@EnableConfigurationProperties(SecurityConfigurationProperties::class)
|
||||||
class WebSecurityConfig(
|
class WebSecurityConfig(
|
||||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||||
@Lazy val userDetailsService: EmployeeUserDetailsServiceImpl,
|
@Lazy val userDetailsService: CreUserDetailsServiceImpl,
|
||||||
@Lazy val employeeService: EmployeeServiceImpl,
|
@Lazy val userService: UserServiceImpl,
|
||||||
val environment: Environment,
|
val environment: Environment,
|
||||||
val logger: Logger
|
val logger: Logger
|
||||||
) : WebSecurityConfigurerAdapter() {
|
) : WebSecurityConfigurerAdapter() {
|
||||||
var debugMode = false
|
var debugMode = false
|
||||||
|
|
||||||
|
@ -95,15 +95,15 @@ class WebSecurityConfig(
|
||||||
credentials: SecurityConfigurationProperties.SystemUserCredentials?,
|
credentials: SecurityConfigurationProperties.SystemUserCredentials?,
|
||||||
firstName: String,
|
firstName: String,
|
||||||
lastName: String,
|
lastName: String,
|
||||||
permissions: List<EmployeePermission>
|
permissions: List<Permission>
|
||||||
) {
|
) {
|
||||||
Assert.notNull(credentials, "No root user has been defined.")
|
Assert.notNull(credentials, "No root user has been defined.")
|
||||||
credentials!!
|
credentials!!
|
||||||
Assert.notNull(credentials.id, "The root user has no identifier defined.")
|
Assert.notNull(credentials.id, "The root user has no identifier defined.")
|
||||||
Assert.notNull(credentials.password, "The root user has no password defined.")
|
Assert.notNull(credentials.password, "The root user has no password defined.")
|
||||||
if (!employeeService.existsById(credentials.id!!)) {
|
if (!userService.existsById(credentials.id!!)) {
|
||||||
employeeService.save(
|
userService.save(
|
||||||
Employee(
|
User(
|
||||||
id = credentials.id!!,
|
id = credentials.id!!,
|
||||||
firstName = firstName,
|
firstName = firstName,
|
||||||
lastName = lastName,
|
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
|
debugMode = "debug" in environment.activeProfiles
|
||||||
if (debugMode) logger.warn("Debug mode is enabled, security will be disabled!")
|
if (debugMode) logger.warn("Debug mode is enabled, security will be disabled!")
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ class WebSecurityConfig(
|
||||||
.addFilter(
|
.addFilter(
|
||||||
JwtAuthenticationFilter(
|
JwtAuthenticationFilter(
|
||||||
authenticationManager(),
|
authenticationManager(),
|
||||||
employeeService,
|
userService,
|
||||||
securityConfigurationProperties
|
securityConfigurationProperties
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -145,7 +145,7 @@ class WebSecurityConfig(
|
||||||
http.authorizeRequests()
|
http.authorizeRequests()
|
||||||
.antMatchers("/api/login").permitAll()
|
.antMatchers("/api/login").permitAll()
|
||||||
.antMatchers("/api/logout").authenticated()
|
.antMatchers("/api/logout").authenticated()
|
||||||
.antMatchers("/api/employee/current").authenticated()
|
.antMatchers("/api/user/current").authenticated()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
} else {
|
} else {
|
||||||
http
|
http
|
||||||
|
@ -171,9 +171,9 @@ const val defaultGroupCookieName = "Default-Group"
|
||||||
val blacklistedJwtTokens = mutableListOf<String>()
|
val blacklistedJwtTokens = mutableListOf<String>()
|
||||||
|
|
||||||
class JwtAuthenticationFilter(
|
class JwtAuthenticationFilter(
|
||||||
private val authManager: AuthenticationManager,
|
private val authManager: AuthenticationManager,
|
||||||
private val employeeService: EmployeeService,
|
private val userService: UserService,
|
||||||
private val securityConfigurationProperties: SecurityConfigurationProperties
|
private val securityConfigurationProperties: SecurityConfigurationProperties
|
||||||
) : UsernamePasswordAuthenticationFilter() {
|
) : UsernamePasswordAuthenticationFilter() {
|
||||||
private var debugMode = false
|
private var debugMode = false
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ class JwtAuthenticationFilter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication {
|
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication {
|
||||||
val loginRequest = jacksonObjectMapper().readValue(request.inputStream, EmployeeLoginRequest::class.java)
|
val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequest::class.java)
|
||||||
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,12 +197,12 @@ class JwtAuthenticationFilter(
|
||||||
val jwtDuration = securityConfigurationProperties.jwtDuration
|
val jwtDuration = securityConfigurationProperties.jwtDuration
|
||||||
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
||||||
Assert.notNull(jwtDuration, "No JWT duration has been defined.")
|
Assert.notNull(jwtDuration, "No JWT duration has been defined.")
|
||||||
val employeeId = (authResult.principal as User).username
|
val userId = (authResult.principal as SpringUser).username
|
||||||
employeeService.updateLastLoginTime(employeeId.toLong())
|
userService.updateLastLoginTime(userId.toLong())
|
||||||
val expirationMs = System.currentTimeMillis() + jwtDuration!!
|
val expirationMs = System.currentTimeMillis() + jwtDuration!!
|
||||||
val expirationDate = Date(expirationMs)
|
val expirationDate = Date(expirationMs)
|
||||||
val token = Jwts.builder()
|
val token = Jwts.builder()
|
||||||
.setSubject(employeeId)
|
.setSubject(userId)
|
||||||
.setExpiration(expirationDate)
|
.setExpiration(expirationDate)
|
||||||
.signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray())
|
.signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray())
|
||||||
.compact()
|
.compact()
|
||||||
|
@ -220,9 +220,9 @@ class JwtAuthenticationFilter(
|
||||||
}
|
}
|
||||||
|
|
||||||
class JwtAuthorizationFilter(
|
class JwtAuthorizationFilter(
|
||||||
private val userDetailsService: EmployeeUserDetailsService,
|
private val userDetailsService: CreUserDetailsService,
|
||||||
private val securityConfigurationProperties: SecurityConfigurationProperties,
|
private val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||||
authenticationManager: AuthenticationManager
|
authenticationManager: AuthenticationManager
|
||||||
) : BasicAuthenticationFilter(authenticationManager) {
|
) : BasicAuthenticationFilter(authenticationManager) {
|
||||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||||
fun tryLoginFromBearer(): Boolean {
|
fun tryLoginFromBearer(): Boolean {
|
||||||
|
@ -259,20 +259,20 @@ class JwtAuthorizationFilter(
|
||||||
val jwtSecret = securityConfigurationProperties.jwtSecret
|
val jwtSecret = securityConfigurationProperties.jwtSecret
|
||||||
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
||||||
return try {
|
return try {
|
||||||
val employeeId = Jwts.parser()
|
val userId = Jwts.parser()
|
||||||
.setSigningKey(jwtSecret!!.toByteArray())
|
.setSigningKey(jwtSecret!!.toByteArray())
|
||||||
.parseClaimsJws(token.replace("Bearer", ""))
|
.parseClaimsJws(token.replace("Bearer", ""))
|
||||||
.body
|
.body
|
||||||
.subject
|
.subject
|
||||||
if (employeeId != null) getAuthenticationToken(employeeId) else null
|
if (userId != null) getAuthenticationToken(userId) else null
|
||||||
} catch (_: ExpiredJwtException) {
|
} catch (_: ExpiredJwtException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken? = try {
|
private fun getAuthenticationToken(userId: String): UsernamePasswordAuthenticationToken? = try {
|
||||||
val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), false)
|
val userDetails = userDetailsService.loadUserById(userId.toLong(), false)
|
||||||
UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities)
|
UsernamePasswordAuthenticationToken(userDetails.username, null, userDetails.authorities)
|
||||||
} catch (_: NotFoundException) {
|
} catch (_: NotFoundException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,6 @@ annotation class PreAuthorizeViewRecipes
|
||||||
@PreAuthorize("hasAuthority('EDIT_RECIPES')")
|
@PreAuthorize("hasAuthority('EDIT_RECIPES')")
|
||||||
annotation class PreAuthorizeEditRecipes
|
annotation class PreAuthorizeEditRecipes
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
@PreAuthorize("hasAuthority('REMOVE_RECIPES')")
|
|
||||||
annotation class PreAuthorizeRemoveRecipes
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
|
@ -37,9 +31,3 @@ annotation class PreAuthorizeViewUsers
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
@PreAuthorize("hasAuthority('EDIT_USERS')")
|
@PreAuthorize("hasAuthority('EDIT_USERS')")
|
||||||
annotation class PreAuthorizeEditUsers
|
annotation class PreAuthorizeEditUsers
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
@PreAuthorize("hasAuthority('REMOVE_USERS')")
|
|
||||||
annotation class PreAuthorizeRemoveUsers
|
|
||||||
|
|
|
@ -12,63 +12,62 @@ import org.springframework.web.context.request.WebRequest
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
||||||
|
|
||||||
abstract class RestException(
|
abstract class RestException(
|
||||||
val errorCode: String,
|
val errorCode: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val status: HttpStatus,
|
val status: HttpStatus,
|
||||||
val details: String,
|
val details: String,
|
||||||
val extensions: Map<String, Any> = mapOf()
|
val extensions: Map<String, Any> = mapOf()
|
||||||
) : RuntimeException(details) {
|
) : RuntimeException(details) {
|
||||||
fun buildExceptionBody() = mapOf(
|
fun buildExceptionBody() = mapOf(
|
||||||
"type" to errorCode,
|
"type" to errorCode,
|
||||||
"title" to title,
|
"title" to title,
|
||||||
"status" to status.value(),
|
"status" to status.value(),
|
||||||
"detail" to details,
|
"detail" to details,
|
||||||
|
|
||||||
*extensions.map { it.key to it.value }.toTypedArray()
|
*extensions.map { it.key to it.value }.toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotFoundException(
|
class NotFoundException(
|
||||||
errorCode: String,
|
errorCode: String,
|
||||||
title: String,
|
title: String,
|
||||||
details: String,
|
details: String,
|
||||||
identifierValue: Any,
|
identifierValue: Any,
|
||||||
identifierName: String = "id"
|
identifierName: String = "id"
|
||||||
) : RestException(
|
) : RestException(
|
||||||
errorCode = "notfound-$errorCode-$identifierName",
|
errorCode = "notfound-$errorCode-$identifierName",
|
||||||
title = title,
|
title = title,
|
||||||
status = HttpStatus.NOT_FOUND,
|
status = HttpStatus.NOT_FOUND,
|
||||||
details = details,
|
details = details,
|
||||||
extensions = mapOf(
|
extensions = mapOf(
|
||||||
identifierName to identifierValue
|
identifierName to identifierValue
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class AlreadyExistsException(
|
class AlreadyExistsException(
|
||||||
errorCode: String,
|
errorCode: String,
|
||||||
title: String,
|
title: String,
|
||||||
details: String,
|
details: String,
|
||||||
identifierValue: Any,
|
identifierValue: Any,
|
||||||
identifierName: String = "id"
|
identifierName: String = "id",
|
||||||
|
extensions: MutableMap<String, Any> = mutableMapOf()
|
||||||
) : RestException(
|
) : RestException(
|
||||||
errorCode = "exists-$errorCode-$identifierName",
|
errorCode = "exists-$errorCode-$identifierName",
|
||||||
title = title,
|
title = title,
|
||||||
status = HttpStatus.CONFLICT,
|
status = HttpStatus.CONFLICT,
|
||||||
details = details,
|
details = details,
|
||||||
extensions = mapOf(
|
extensions = extensions.apply { this[identifierName] = identifierValue }.toMap()
|
||||||
identifierName to identifierValue
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class CannotDeleteException(
|
class CannotDeleteException(
|
||||||
errorCode: String,
|
errorCode: String,
|
||||||
title: String,
|
title: String,
|
||||||
details: String
|
details: String
|
||||||
) : RestException(
|
) : RestException(
|
||||||
errorCode = "cannotdelete-$errorCode",
|
errorCode = "cannotdelete-$errorCode",
|
||||||
title = title,
|
title = title,
|
||||||
status = HttpStatus.CONFLICT,
|
status = HttpStatus.CONFLICT,
|
||||||
details = details
|
details = details
|
||||||
)
|
)
|
||||||
|
|
||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
|
@ -79,19 +78,19 @@ class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
|
||||||
finalBody["instance"] = (request as ServletWebRequest).request.requestURI
|
finalBody["instance"] = (request as ServletWebRequest).request.requestURI
|
||||||
|
|
||||||
return handleExceptionInternal(
|
return handleExceptionInternal(
|
||||||
exception,
|
exception,
|
||||||
finalBody,
|
finalBody,
|
||||||
HttpHeaders(),
|
HttpHeaders(),
|
||||||
exception.status,
|
exception.status,
|
||||||
request
|
request
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleMethodArgumentNotValid(
|
override fun handleMethodArgumentNotValid(
|
||||||
ex: MethodArgumentNotValidException,
|
ex: MethodArgumentNotValidException,
|
||||||
headers: HttpHeaders,
|
headers: HttpHeaders,
|
||||||
status: HttpStatus,
|
status: HttpStatus,
|
||||||
request: WebRequest
|
request: WebRequest
|
||||||
): ResponseEntity<Any> {
|
): ResponseEntity<Any> {
|
||||||
val errors = hashMapOf<String, String>()
|
val errors = hashMapOf<String, String>()
|
||||||
ex.bindingResult.allErrors.forEach {
|
ex.bindingResult.allErrors.forEach {
|
||||||
|
|
|
@ -8,9 +8,6 @@ import javax.persistence.*
|
||||||
import javax.validation.constraints.NotBlank
|
import javax.validation.constraints.NotBlank
|
||||||
import javax.validation.constraints.NotNull
|
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
|
@Entity
|
||||||
@Table(name = "company")
|
@Table(name = "company")
|
||||||
data class Company(
|
data class Company(
|
||||||
|
@ -20,11 +17,15 @@ data class Company(
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
override val name: String
|
override val name: String
|
||||||
) : NamedModel
|
) : NamedModel {
|
||||||
|
override fun toString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open class CompanySaveDto(
|
open class CompanySaveDto(
|
||||||
@field:NotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String
|
val name: String
|
||||||
) : EntityDto<Company> {
|
) : EntityDto<Company> {
|
||||||
override fun toEntity(): Company = Company(null, name)
|
override fun toEntity(): Company = Company(null, name)
|
||||||
|
@ -32,10 +33,9 @@ open class CompanySaveDto(
|
||||||
|
|
||||||
|
|
||||||
open class CompanyUpdateDto(
|
open class CompanyUpdateDto(
|
||||||
@field:NotNull(message = COMPANY_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String?
|
val name: String?
|
||||||
) : EntityDto<Company> {
|
) : EntityDto<Company> {
|
||||||
override fun toEntity(): Company = Company(id, name ?: "")
|
override fun toEntity(): Company = Company(id, name ?: "")
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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<EmployeePermission> = mutableSetOf(),
|
|
||||||
|
|
||||||
@Column(name = "last_login_time")
|
|
||||||
var lastLoginTime: LocalDateTime? = null
|
|
||||||
) : Model {
|
|
||||||
val flatPermissions: Set<EmployeePermission>
|
|
||||||
get() = permissions
|
|
||||||
.flatMap { it.flat() }
|
|
||||||
.filter { !it.deprecated }
|
|
||||||
.toMutableSet()
|
|
||||||
.apply {
|
|
||||||
if (group != null) this.addAll(group!!.flatPermissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
val authorities: Set<GrantedAuthority>
|
|
||||||
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** DTO for creating employees. Allows a [password] a [groupId]. */
|
|
||||||
open class EmployeeSaveDto(
|
|
||||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
|
||||||
val firstName: String,
|
|
||||||
|
|
||||||
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
|
||||||
val lastName: String,
|
|
||||||
|
|
||||||
@field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE)
|
|
||||||
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
|
|
||||||
val password: String,
|
|
||||||
|
|
||||||
val groupId: Long?,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
|
|
||||||
) : EntityDto<Employee>
|
|
||||||
|
|
||||||
open class EmployeeUpdateDto(
|
|
||||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
|
||||||
val firstName: String?,
|
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
|
||||||
val lastName: String?,
|
|
||||||
|
|
||||||
val groupId: Long?,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val permissions: Set<EmployeePermission>?
|
|
||||||
) : EntityDto<Employee>
|
|
||||||
|
|
||||||
data class EmployeeOutputDto(
|
|
||||||
override val id: Long,
|
|
||||||
val firstName: String,
|
|
||||||
val lastName: String,
|
|
||||||
val group: EmployeeGroup?,
|
|
||||||
val permissions: Set<EmployeePermission>,
|
|
||||||
val explicitPermissions: Set<EmployeePermission>,
|
|
||||||
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<EmployeePermission> = mutableSetOf(),
|
|
||||||
lastLoginTime: LocalDateTime? = null,
|
|
||||||
op: Employee.() -> Unit = {}
|
|
||||||
) = Employee(
|
|
||||||
id,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
password,
|
|
||||||
isDefaultGroupUser,
|
|
||||||
isSystemUser,
|
|
||||||
group,
|
|
||||||
permissions,
|
|
||||||
lastLoginTime
|
|
||||||
).apply(op)
|
|
||||||
|
|
||||||
fun employeeSaveDto(
|
|
||||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
password: String = passwordEncoder.encode("password"),
|
|
||||||
groupId: Long? = null,
|
|
||||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
|
||||||
op: EmployeeSaveDto.() -> Unit = {}
|
|
||||||
) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
|
|
||||||
|
|
||||||
fun employeeUpdateDto(
|
|
||||||
id: Long = 0L,
|
|
||||||
firstName: String = "firstName",
|
|
||||||
lastName: String = "lastName",
|
|
||||||
groupId: Long? = null,
|
|
||||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
|
||||||
op: EmployeeUpdateDto.() -> Unit = {}
|
|
||||||
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
|
|
||||||
|
|
||||||
// ==== Exceptions ====
|
|
||||||
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee not found"
|
|
||||||
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already exists"
|
|
||||||
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee"
|
|
||||||
|
|
||||||
fun employeeIdNotFoundException(id: Long) =
|
|
||||||
NotFoundException(
|
|
||||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
|
||||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"An employee with the id $id could not be found",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun employeeIdAlreadyExistsException(id: Long) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
|
||||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"An employee with the id $id already exists",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
|
||||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"An employee with the name '$firstName $lastName' already exists",
|
|
||||||
"$firstName $lastName",
|
|
||||||
"fullName"
|
|
||||||
)
|
|
|
@ -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<EmployeePermission> = mutableSetOf(),
|
|
||||||
) : NamedModel {
|
|
||||||
val flatPermissions: Set<EmployeePermission>
|
|
||||||
get() = this.permissions
|
|
||||||
.flatMap { it.flat() }
|
|
||||||
.filter { !it.deprecated }
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
open class EmployeeGroupSaveDto(
|
|
||||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
|
||||||
@field:Size(min = 3)
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
|
||||||
val permissions: MutableSet<EmployeePermission>
|
|
||||||
) : EntityDto<EmployeeGroup> {
|
|
||||||
override fun toEntity(): EmployeeGroup =
|
|
||||||
EmployeeGroup(null, name, permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
open class EmployeeGroupUpdateDto(
|
|
||||||
@field:NotNull(message = GROUP_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
|
||||||
@field:Size(min = 3)
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
|
||||||
val permissions: MutableSet<EmployeePermission>
|
|
||||||
) : EntityDto<EmployeeGroup> {
|
|
||||||
override fun toEntity(): EmployeeGroup =
|
|
||||||
EmployeeGroup(id, name, permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class EmployeeGroupOutputDto(
|
|
||||||
override val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val permissions: Set<EmployeePermission>,
|
|
||||||
val explicitPermissions: Set<EmployeePermission>
|
|
||||||
): Model
|
|
||||||
|
|
||||||
fun employeeGroup(
|
|
||||||
id: Long? = null,
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
|
||||||
op: EmployeeGroup.() -> Unit = {}
|
|
||||||
) = EmployeeGroup(id, name, permissions).apply(op)
|
|
||||||
|
|
||||||
fun employeeGroupSaveDto(
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
|
||||||
op: EmployeeGroupSaveDto.() -> Unit = {}
|
|
||||||
) = EmployeeGroupSaveDto(name, permissions).apply(op)
|
|
||||||
|
|
||||||
fun employeeGroupUpdateDto(
|
|
||||||
id: Long = 0L,
|
|
||||||
name: String = "name",
|
|
||||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
|
||||||
op: EmployeeGroupUpdateDto.() -> Unit = {}
|
|
||||||
) = EmployeeGroupUpdateDto(id, name, permissions).apply(op)
|
|
||||||
|
|
||||||
// ==== Exceptions ====
|
|
||||||
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee group not found"
|
|
||||||
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee group already exists"
|
|
||||||
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employeegroup"
|
|
||||||
|
|
||||||
class NoDefaultGroupException : RestException(
|
|
||||||
"nodefaultgroup",
|
|
||||||
"No default group",
|
|
||||||
HttpStatus.NOT_FOUND,
|
|
||||||
"No default group cookie is defined in the current request"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun employeeGroupIdNotFoundException(id: Long) =
|
|
||||||
NotFoundException(
|
|
||||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
|
||||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"An employee group with the id $id could not be found",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun employeeGroupNameNotFoundException(name: String) =
|
|
||||||
NotFoundException(
|
|
||||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
|
||||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"An employee group with the name $name could not be found",
|
|
||||||
name,
|
|
||||||
"name"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun 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"
|
|
||||||
)
|
|
|
@ -4,27 +4,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
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 org.springframework.web.multipart.MultipartFile
|
||||||
import java.net.URLEncoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
import javax.validation.constraints.Min
|
||||||
import javax.validation.constraints.NotBlank
|
import javax.validation.constraints.NotBlank
|
||||||
import javax.validation.constraints.NotNull
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
private const val MATERIAL_ID_NULL_MESSAGE = "Un identifiant est requis"
|
|
||||||
private const val MATERIAL_NAME_NULL_MESSAGE = "Un nom est requis"
|
|
||||||
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"
|
|
||||||
|
|
||||||
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
||||||
|
|
||||||
|
@ -52,32 +36,27 @@ data class Material(
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@Transient
|
@Transient
|
||||||
get() = "$SIMDUT_FILES_PATH/$name.pdf"
|
get() = "$SIMDUT_FILES_PATH/$name.pdf"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open class MaterialSaveDto(
|
open class MaterialSaveDto(
|
||||||
@field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE)
|
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||||
@field:Min(value = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
|
||||||
val inventoryQuantity: Float,
|
val inventoryQuantity: Float,
|
||||||
|
|
||||||
@field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE)
|
|
||||||
val materialTypeId: Long,
|
val materialTypeId: Long,
|
||||||
|
|
||||||
val simdutFile: MultipartFile? = null
|
val simdutFile: MultipartFile? = null
|
||||||
) : EntityDto<Material>
|
) : EntityDto<Material>
|
||||||
|
|
||||||
open class MaterialUpdateDto(
|
open class MaterialUpdateDto(
|
||||||
@field:NotNull(message = MATERIAL_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String?,
|
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 inventoryQuantity: Float?,
|
||||||
|
|
||||||
val materialTypeId: Long?,
|
val materialTypeId: Long?,
|
||||||
|
@ -95,11 +74,9 @@ data class MaterialOutputDto(
|
||||||
) : Model
|
) : Model
|
||||||
|
|
||||||
data class MaterialQuantityDto(
|
data class MaterialQuantityDto(
|
||||||
@field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE)
|
|
||||||
val material: Long,
|
val material: Long,
|
||||||
|
|
||||||
@field:NotNull(message = MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE)
|
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||||
@field:Min(value = 0, message = MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE)
|
|
||||||
val quantity: Float
|
val quantity: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -147,7 +124,7 @@ fun materialQuantityDto(
|
||||||
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
||||||
|
|
||||||
// ==== Exceptions ====
|
// ==== Exceptions ====
|
||||||
private const
|
private const
|
||||||
val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
|
val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
|
||||||
private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists"
|
private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists"
|
||||||
private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material"
|
private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material"
|
||||||
|
|
|
@ -11,10 +11,7 @@ import javax.validation.constraints.NotBlank
|
||||||
import javax.validation.constraints.NotNull
|
import javax.validation.constraints.NotNull
|
||||||
import javax.validation.constraints.Size
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
private const val MATERIAL_TYPE_ID_NULL_MESSAGE = "Un identifiant est requis"
|
private const val VALIDATION_PREFIX_SIZE = "Must contains exactly 3 characters"
|
||||||
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"
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "material_type")
|
@Table(name = "material_type")
|
||||||
|
@ -39,11 +36,11 @@ data class MaterialType(
|
||||||
) : NamedModel
|
) : NamedModel
|
||||||
|
|
||||||
open class MaterialTypeSaveDto(
|
open class MaterialTypeSaveDto(
|
||||||
@field:NotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@field:NotBlank(message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
@field:Size(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_SIZE_MESSAGE)
|
@field:Size(min = 3, max = 3, message = VALIDATION_PREFIX_SIZE)
|
||||||
val prefix: String,
|
val prefix: String,
|
||||||
|
|
||||||
val usePercentages: Boolean = false
|
val usePercentages: Boolean = false
|
||||||
|
@ -53,13 +50,12 @@ open class MaterialTypeSaveDto(
|
||||||
}
|
}
|
||||||
|
|
||||||
open class MaterialTypeUpdateDto(
|
open class MaterialTypeUpdateDto(
|
||||||
@field:NotNull(message = MATERIAL_TYPE_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String?,
|
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?
|
val prefix: String?
|
||||||
) : EntityDto<MaterialType> {
|
) : EntityDto<MaterialType> {
|
||||||
override fun toEntity(): MaterialType =
|
override fun toEntity(): MaterialType =
|
||||||
|
|
|
@ -4,20 +4,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
import javax.validation.constraints.Min
|
||||||
import javax.validation.constraints.NotBlank
|
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
|
@Entity
|
||||||
@Table(name = "mix")
|
@Table(name = "mix")
|
||||||
|
@ -43,33 +33,26 @@ data class Mix(
|
||||||
) : Model
|
) : Model
|
||||||
|
|
||||||
open class MixSaveDto(
|
open class MixSaveDto(
|
||||||
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@field:NotNull(message = MIX_RECIPE_NULL_MESSAGE)
|
|
||||||
val recipeId: Long,
|
val recipeId: Long,
|
||||||
|
|
||||||
@field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE)
|
|
||||||
val materialTypeId: Long,
|
val materialTypeId: Long,
|
||||||
|
|
||||||
val mixMaterials: Set<MixMaterialDto>?
|
val mixMaterials: Set<MixMaterialDto>?
|
||||||
) : EntityDto<Mix> {
|
) : EntityDto<Mix>
|
||||||
override fun toEntity(): Mix = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
open class MixUpdateDto(
|
open class MixUpdateDto(
|
||||||
@field:NotNull(message = MIX_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
|
||||||
val materialTypeId: Long?,
|
val materialTypeId: Long?,
|
||||||
|
|
||||||
var mixMaterials: Set<MixMaterialDto>?
|
var mixMaterials: Set<MixMaterialDto>?
|
||||||
) : EntityDto<Mix> {
|
) : EntityDto<Mix>
|
||||||
override fun toEntity(): Mix = throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class MixOutputDto(
|
data class MixOutputDto(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
@ -79,16 +62,13 @@ data class MixOutputDto(
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MixDeductDto(
|
data class MixDeductDto(
|
||||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
|
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||||
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
|
|
||||||
val ratio: Float
|
val ratio: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MixLocationDto(
|
data class MixLocationDto(
|
||||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
|
||||||
val mixId: Long,
|
val mixId: Long,
|
||||||
|
|
||||||
val location: String?
|
val location: String?
|
||||||
|
|
|
@ -6,10 +6,6 @@ import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
import javax.validation.constraints.Min
|
||||||
import javax.validation.constraints.NotNull
|
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
|
@Entity
|
||||||
@Table(name = "mix_material")
|
@Table(name = "mix_material")
|
||||||
data class MixMaterial(
|
data class MixMaterial(
|
||||||
|
@ -26,6 +22,15 @@ data class MixMaterial(
|
||||||
var position: Int
|
var position: Int
|
||||||
) : Model
|
) : Model
|
||||||
|
|
||||||
|
data class MixMaterialDto(
|
||||||
|
val materialId: Long,
|
||||||
|
|
||||||
|
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||||
|
val quantity: Float,
|
||||||
|
|
||||||
|
val position: Int
|
||||||
|
)
|
||||||
|
|
||||||
data class MixMaterialOutputDto(
|
data class MixMaterialOutputDto(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val material: MaterialOutputDto,
|
val material: MaterialOutputDto,
|
||||||
|
@ -33,17 +38,6 @@ data class MixMaterialOutputDto(
|
||||||
val position: Int
|
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 ====
|
// ==== DSL ====
|
||||||
fun mixMaterial(
|
fun mixMaterial(
|
||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
|
|
|
@ -15,3 +15,8 @@ interface EntityDto<out E> {
|
||||||
throw UnsupportedOperationException()
|
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"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package dev.fyloz.colorrecipesexplorer.model
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
|
import dev.fyloz.colorrecipesexplorer.model.account.group
|
||||||
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
|
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
|
||||||
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
|
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
@ -13,19 +13,7 @@ import java.time.LocalDate
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.*
|
import javax.validation.constraints.*
|
||||||
|
|
||||||
private const val RECIPE_ID_NULL_MESSAGE = "Un identifiant est requis"
|
private const val VALIDATION_COLOR_PATTERN = "^#([0-9a-f]{6})$"
|
||||||
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"
|
|
||||||
|
|
||||||
const val RECIPE_IMAGES_DIRECTORY = "images/recipes"
|
const val RECIPE_IMAGES_DIRECTORY = "images/recipes"
|
||||||
|
|
||||||
|
@ -89,30 +77,28 @@ data class Recipe(
|
||||||
}
|
}
|
||||||
|
|
||||||
open class RecipeSaveDto(
|
open class RecipeSaveDto(
|
||||||
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val description: String,
|
val description: String,
|
||||||
|
|
||||||
@field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
@field:Pattern(regexp = VALIDATION_COLOR_PATTERN)
|
||||||
val color: String,
|
val color: String,
|
||||||
|
|
||||||
@field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE)
|
@field:Min(0, message = VALIDATION_RANGE_PERCENTS)
|
||||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
@field:Max(100, message = VALIDATION_RANGE_PERCENTS)
|
||||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
|
||||||
val gloss: Byte,
|
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 sample: Int?,
|
||||||
|
|
||||||
val approbationDate: LocalDate?,
|
val approbationDate: LocalDate?,
|
||||||
|
|
||||||
val remark: String?,
|
val remark: String?,
|
||||||
|
|
||||||
@field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE)
|
val companyId: Long
|
||||||
val companyId: Long = -1L,
|
|
||||||
) : EntityDto<Recipe> {
|
) : EntityDto<Recipe> {
|
||||||
override fun toEntity(): Recipe = recipe(
|
override fun toEntity(): Recipe = recipe(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -125,24 +111,23 @@ open class RecipeSaveDto(
|
||||||
}
|
}
|
||||||
|
|
||||||
open class RecipeUpdateDto(
|
open class RecipeUpdateDto(
|
||||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
val description: String?,
|
val description: String?,
|
||||||
|
|
||||||
@field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
@field:NotBlank
|
||||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
@field:Pattern(regexp = VALIDATION_COLOR_PATTERN)
|
||||||
val color: String?,
|
val color: String?,
|
||||||
|
|
||||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
@field:Min(0, message = VALIDATION_RANGE_PERCENTS)
|
||||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
@field:Max(100, message = VALIDATION_RANGE_PERCENTS)
|
||||||
val gloss: Byte?,
|
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 sample: Int?,
|
||||||
|
|
||||||
val approbationDate: LocalDate?,
|
val approbationDate: LocalDate?,
|
||||||
|
@ -176,7 +161,7 @@ data class RecipeGroupInformation(
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "group_id")
|
@JoinColumn(name = "group_id")
|
||||||
val group: EmployeeGroup,
|
val group: Group,
|
||||||
|
|
||||||
var note: String?,
|
var note: String?,
|
||||||
|
|
||||||
|
@ -186,15 +171,12 @@ data class RecipeGroupInformation(
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RecipeStepsDto(
|
data class RecipeStepsDto(
|
||||||
@field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE)
|
|
||||||
val groupId: Long,
|
val groupId: Long,
|
||||||
|
|
||||||
@field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE)
|
|
||||||
val steps: Set<RecipeStep>
|
val steps: Set<RecipeStep>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RecipePublicDataDto(
|
data class RecipePublicDataDto(
|
||||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
|
||||||
val recipeId: Long,
|
val recipeId: Long,
|
||||||
|
|
||||||
val notes: Set<NoteDto>?,
|
val notes: Set<NoteDto>?,
|
||||||
|
@ -203,7 +185,6 @@ data class RecipePublicDataDto(
|
||||||
)
|
)
|
||||||
|
|
||||||
data class NoteDto(
|
data class NoteDto(
|
||||||
@field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE)
|
|
||||||
val groupId: Long,
|
val groupId: Long,
|
||||||
|
|
||||||
val content: String?
|
val content: String?
|
||||||
|
@ -264,7 +245,7 @@ fun recipeUpdateDto(
|
||||||
|
|
||||||
fun recipeGroupInformation(
|
fun recipeGroupInformation(
|
||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
group: EmployeeGroup = employeeGroup(),
|
group: Group = group(),
|
||||||
note: String? = null,
|
note: String? = null,
|
||||||
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
||||||
op: RecipeGroupInformation.() -> Unit = {}
|
op: RecipeGroupInformation.() -> Unit = {}
|
||||||
|
@ -303,3 +284,16 @@ fun recipeIdAlreadyExistsException(id: Long) =
|
||||||
"A recipe with the id $id already exists",
|
"A recipe with the id $id already exists",
|
||||||
id
|
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!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.model.account
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
|
import 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
|
||||||
|
|
||||||
|
@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<Permission> = mutableSetOf(),
|
||||||
|
) : NamedModel {
|
||||||
|
val flatPermissions: Set<Permission>
|
||||||
|
get() = this.permissions
|
||||||
|
.flatMap { it.flat() }
|
||||||
|
.filter { !it.deprecated }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class GroupSaveDto(
|
||||||
|
@field:NotBlank
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val permissions: MutableSet<Permission>
|
||||||
|
) : EntityDto<Group> {
|
||||||
|
override fun toEntity(): Group =
|
||||||
|
Group(null, name, permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class GroupUpdateDto(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val permissions: MutableSet<Permission>
|
||||||
|
) : EntityDto<Group> {
|
||||||
|
override fun toEntity(): Group =
|
||||||
|
Group(id, name, permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GroupOutputDto(
|
||||||
|
override val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val permissions: Set<Permission>,
|
||||||
|
val explicitPermissions: Set<Permission>
|
||||||
|
): Model
|
||||||
|
|
||||||
|
fun group(
|
||||||
|
id: Long? = null,
|
||||||
|
name: String = "name",
|
||||||
|
permissions: MutableSet<Permission> = mutableSetOf(),
|
||||||
|
op: Group.() -> Unit = {}
|
||||||
|
) = Group(id, name, permissions).apply(op)
|
||||||
|
|
||||||
|
fun groupSaveDto(
|
||||||
|
name: String = "name",
|
||||||
|
permissions: MutableSet<Permission> = mutableSetOf(),
|
||||||
|
op: GroupSaveDto.() -> Unit = {}
|
||||||
|
) = GroupSaveDto(name, permissions).apply(op)
|
||||||
|
|
||||||
|
fun groupUpdateDto(
|
||||||
|
id: Long = 0L,
|
||||||
|
name: String = "name",
|
||||||
|
permissions: MutableSet<Permission> = mutableSetOf(),
|
||||||
|
op: GroupUpdateDto.() -> Unit = {}
|
||||||
|
) = GroupUpdateDto(id, name, permissions).apply(op)
|
||||||
|
|
||||||
|
// ==== Exceptions ====
|
||||||
|
private const val GROUP_NOT_FOUND_EXCEPTION_TITLE = "Group not found"
|
||||||
|
private const val GROUP_ALREADY_EXISTS_EXCEPTION_TITLE = "Group already exists"
|
||||||
|
private const val GROUP_EXCEPTION_ERROR_CODE = "group"
|
||||||
|
|
||||||
|
class NoDefaultGroupException : RestException(
|
||||||
|
"nodefaultgroup",
|
||||||
|
"No default group",
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"No default group cookie is defined in the current request"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun groupIdNotFoundException(id: Long) =
|
||||||
|
NotFoundException(
|
||||||
|
GROUP_EXCEPTION_ERROR_CODE,
|
||||||
|
GROUP_NOT_FOUND_EXCEPTION_TITLE,
|
||||||
|
"A group with the id $id could not be found",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
|
||||||
|
fun groupNameNotFoundException(name: String) =
|
||||||
|
NotFoundException(
|
||||||
|
GROUP_EXCEPTION_ERROR_CODE,
|
||||||
|
GROUP_NOT_FOUND_EXCEPTION_TITLE,
|
||||||
|
"A group with the name $name could not be found",
|
||||||
|
name,
|
||||||
|
"name"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun groupIdAlreadyExistsException(id: Long) =
|
||||||
|
AlreadyExistsException(
|
||||||
|
GROUP_EXCEPTION_ERROR_CODE,
|
||||||
|
GROUP_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||||
|
"A group with the id $id already exists",
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun groupNameAlreadyExistsException(name: String) =
|
||||||
|
AlreadyExistsException(
|
||||||
|
GROUP_EXCEPTION_ERROR_CODE,
|
||||||
|
GROUP_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||||
|
"A group with the name $name already exists",
|
||||||
|
name,
|
||||||
|
"name"
|
||||||
|
)
|
|
@ -1,22 +1,19 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model
|
package dev.fyloz.colorrecipesexplorer.model.account
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority
|
import org.springframework.security.core.GrantedAuthority
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
|
||||||
enum class EmployeePermission(
|
enum class Permission(
|
||||||
val impliedPermissions: List<EmployeePermission> = listOf(),
|
val impliedPermissions: List<Permission> = listOf(),
|
||||||
val deprecated: Boolean = false
|
val deprecated: Boolean = false
|
||||||
) {
|
) {
|
||||||
READ_FILE,
|
READ_FILE,
|
||||||
WRITE_FILE(listOf(READ_FILE)),
|
WRITE_FILE(listOf(READ_FILE)),
|
||||||
REMOVE_FILE(listOf(WRITE_FILE)),
|
|
||||||
|
|
||||||
VIEW_RECIPES(listOf(READ_FILE)),
|
VIEW_RECIPES(listOf(READ_FILE)),
|
||||||
VIEW_CATALOG(listOf(READ_FILE)),
|
VIEW_CATALOG(listOf(READ_FILE)),
|
||||||
VIEW_USERS,
|
VIEW_USERS,
|
||||||
|
|
||||||
PRINT_MIXES(listOf(VIEW_RECIPES)),
|
|
||||||
|
|
||||||
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
|
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
|
||||||
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA, WRITE_FILE)),
|
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA, WRITE_FILE)),
|
||||||
EDIT_MATERIALS(listOf(VIEW_CATALOG, WRITE_FILE)),
|
EDIT_MATERIALS(listOf(VIEW_CATALOG, WRITE_FILE)),
|
||||||
|
@ -25,29 +22,24 @@ enum class EmployeePermission(
|
||||||
EDIT_USERS(listOf(VIEW_USERS)),
|
EDIT_USERS(listOf(VIEW_USERS)),
|
||||||
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
|
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
|
||||||
|
|
||||||
REMOVE_RECIPES(listOf(EDIT_RECIPES, REMOVE_FILE)),
|
VIEW_TOUCH_UP_KITS,
|
||||||
REMOVE_MATERIALS(listOf(EDIT_MATERIALS, REMOVE_FILE)),
|
EDIT_TOUCH_UP_KITS(listOf(VIEW_TOUCH_UP_KITS)),
|
||||||
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)),
|
|
||||||
|
|
||||||
|
PRINT_MIXES(listOf(VIEW_RECIPES)),
|
||||||
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
|
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
|
||||||
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
|
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
|
||||||
GENERATE_TOUCH_UP_KIT,
|
|
||||||
|
|
||||||
ADMIN(
|
ADMIN(
|
||||||
listOf(
|
listOf(
|
||||||
|
EDIT_RECIPES,
|
||||||
EDIT_CATALOG,
|
EDIT_CATALOG,
|
||||||
|
EDIT_USERS,
|
||||||
|
|
||||||
REMOVE_RECIPES,
|
EDIT_TOUCH_UP_KITS,
|
||||||
REMOVE_USERS,
|
|
||||||
REMOVE_CATALOG,
|
|
||||||
|
|
||||||
PRINT_MIXES,
|
PRINT_MIXES,
|
||||||
ADD_TO_INVENTORY,
|
ADD_TO_INVENTORY,
|
||||||
DEDUCT_FROM_INVENTORY,
|
DEDUCT_FROM_INVENTORY,
|
||||||
GENERATE_TOUCH_UP_KIT
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -69,6 +61,16 @@ enum class EmployeePermission(
|
||||||
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
|
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
|
||||||
EDIT_EMPLOYEE_GROUP(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_RECIPE(listOf(REMOVE_RECIPES), true),
|
||||||
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
|
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
|
||||||
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
|
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
|
||||||
|
@ -80,12 +82,12 @@ enum class EmployeePermission(
|
||||||
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
|
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 }
|
return permission == this || impliedPermissions.any { permission in it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
|
fun Permission.flat(): Iterable<Permission> {
|
||||||
return mutableSetOf(this).apply {
|
return mutableSetOf(this).apply {
|
||||||
impliedPermissions.forEach {
|
impliedPermissions.forEach {
|
||||||
addAll(it.flat())
|
addAll(it.flat())
|
||||||
|
@ -93,7 +95,7 @@ fun EmployeePermission.flat(): Iterable<EmployeePermission> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
|
/** Converts the given [Permission] to a [GrantedAuthority]. */
|
||||||
fun EmployeePermission.toAuthority(): GrantedAuthority {
|
fun Permission.toAuthority(): GrantedAuthority {
|
||||||
return SimpleGrantedAuthority(name)
|
return SimpleGrantedAuthority(name)
|
||||||
}
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
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 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.Size
|
||||||
|
|
||||||
|
private const val VALIDATION_PASSWORD_LENGTH = "Must contains at least 8 characters"
|
||||||
|
|
||||||
|
@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<Permission> = mutableSetOf(),
|
||||||
|
|
||||||
|
@Column(name = "last_login_time")
|
||||||
|
var lastLoginTime: LocalDateTime? = null
|
||||||
|
) : Model {
|
||||||
|
val flatPermissions: Set<Permission>
|
||||||
|
get() = permissions
|
||||||
|
.flatMap { it.flat() }
|
||||||
|
.filter { !it.deprecated }
|
||||||
|
.toMutableSet()
|
||||||
|
.apply {
|
||||||
|
if (group != null) this.addAll(group!!.flatPermissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
val authorities: Set<GrantedAuthority>
|
||||||
|
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class UserSaveDto(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val firstName: String,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val lastName: String,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
@field:Size(min = 8, message = VALIDATION_PASSWORD_LENGTH)
|
||||||
|
val password: String,
|
||||||
|
|
||||||
|
val groupId: Long?,
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val permissions: MutableSet<Permission> = mutableSetOf()
|
||||||
|
) : EntityDto<User>
|
||||||
|
|
||||||
|
open class UserUpdateDto(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val firstName: String?,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val lastName: String?,
|
||||||
|
|
||||||
|
val groupId: Long?,
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val permissions: Set<Permission>?
|
||||||
|
) : EntityDto<User>
|
||||||
|
|
||||||
|
data class UserOutputDto(
|
||||||
|
override val id: Long,
|
||||||
|
val firstName: String,
|
||||||
|
val lastName: String,
|
||||||
|
val group: Group?,
|
||||||
|
val permissions: Set<Permission>,
|
||||||
|
val explicitPermissions: Set<Permission>,
|
||||||
|
val lastLoginTime: LocalDateTime?
|
||||||
|
) : 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<Permission> = 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<Permission> = mutableSetOf(),
|
||||||
|
op: UserSaveDto.() -> Unit = {}
|
||||||
|
) = UserSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
|
||||||
|
|
||||||
|
fun userUpdateDto(
|
||||||
|
id: Long = 0L,
|
||||||
|
firstName: String = "firstName",
|
||||||
|
lastName: String = "lastName",
|
||||||
|
groupId: Long? = null,
|
||||||
|
permissions: MutableSet<Permission> = mutableSetOf(),
|
||||||
|
op: UserUpdateDto.() -> Unit = {}
|
||||||
|
) = UserUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
|
||||||
|
|
||||||
|
// ==== 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"
|
||||||
|
)
|
|
@ -0,0 +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(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
override val id: Long?,
|
||||||
|
|
||||||
|
val project: 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<TouchUpKitProduct>
|
||||||
|
) : 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<String>,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val material: List<String>,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val content: Set<TouchUpKitProductDto>
|
||||||
|
) : EntityDto<TouchUpKit> {
|
||||||
|
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<String>?,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val material: List<String>?,
|
||||||
|
|
||||||
|
@field:NotEmpty
|
||||||
|
val content: Set<TouchUpKitProductDto>?
|
||||||
|
) : EntityDto<TouchUpKit>
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
val material: List<String>,
|
||||||
|
val content: Set<TouchUpKitProduct>,
|
||||||
|
val pdfUrl: String
|
||||||
|
) : Model
|
||||||
|
|
||||||
|
data class TouchUpKitProductDto(
|
||||||
|
val name: String,
|
||||||
|
val description: String?,
|
||||||
|
val quantity: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==== DSL ====
|
||||||
|
fun touchUpKit(
|
||||||
|
id: Long? = null,
|
||||||
|
project: String = "project",
|
||||||
|
buggy: String = "buggy",
|
||||||
|
company: String = "company",
|
||||||
|
quantity: Int = 1,
|
||||||
|
shippingDate: LocalDate = LocalDate.now(),
|
||||||
|
finish: List<String>,
|
||||||
|
material: List<String>,
|
||||||
|
content: Set<TouchUpKitProduct>,
|
||||||
|
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
|
||||||
|
)
|
|
@ -1,20 +1,20 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.repository
|
package dev.fyloz.colorrecipesexplorer.repository
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.Employee
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
import dev.fyloz.colorrecipesexplorer.model.EmployeeGroup
|
import dev.fyloz.colorrecipesexplorer.model.account.User
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface EmployeeRepository : JpaRepository<Employee, Long> {
|
interface UserRepository : JpaRepository<User, Long> {
|
||||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
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<Employee>
|
fun findAllByGroup(group: Group): Collection<User>
|
||||||
|
|
||||||
fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: EmployeeGroup): Employee
|
fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: Group): User
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface EmployeeGroupRepository : NamedJpaRepository<EmployeeGroup>
|
interface GroupRepository : NamedJpaRepository<Group>
|
||||||
|
|
|
@ -8,6 +8,12 @@ interface RecipeRepository : JpaRepository<Recipe, Long> {
|
||||||
/** Checks if one or more recipes have the given [company]. */
|
/** Checks if one or more recipes have the given [company]. */
|
||||||
fun existsByCompany(company: Company): Boolean
|
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 [name]. */
|
||||||
|
fun findAllByName(name: String): Collection<Recipe>
|
||||||
|
|
||||||
/** Gets all recipes with the given [company]. */
|
/** Gets all recipes with the given [company]. */
|
||||||
fun findAllByCompany(company: Company): Collection<Recipe>
|
fun findAllByCompany(company: Company): Collection<Recipe>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<TouchUpKit, Long>
|
|
@ -1,11 +1,10 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
|
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.config.annotations.PreAuthorizeViewUsers
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.account.*
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupService
|
import dev.fyloz.colorrecipesexplorer.service.UserService
|
||||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeService
|
import dev.fyloz.colorrecipesexplorer.service.GroupService
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
@ -14,29 +13,29 @@ import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import javax.validation.Valid
|
import javax.validation.Valid
|
||||||
|
|
||||||
private const val EMPLOYEE_CONTROLLER_PATH = "api/employee"
|
private const val USER_CONTROLLER_PATH = "api/user"
|
||||||
private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group"
|
private const val GROUP_CONTROLLER_PATH = "api/user/group"
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(EMPLOYEE_CONTROLLER_PATH)
|
@RequestMapping(USER_CONTROLLER_PATH)
|
||||||
class EmployeeController(private val employeeService: EmployeeService) {
|
class UserController(private val userService: UserService) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getAll() =
|
fun getAll() =
|
||||||
ok(employeeService.getAllForOutput())
|
ok(userService.getAllForOutput())
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
ok(employeeService.getByIdForOutput(id))
|
ok(userService.getByIdForOutput(id))
|
||||||
|
|
||||||
@GetMapping("current")
|
@GetMapping("current")
|
||||||
fun getCurrent(loggedInEmployee: Principal?) =
|
fun getCurrent(loggedInUser: Principal?) =
|
||||||
if (loggedInEmployee != null)
|
if (loggedInUser != null)
|
||||||
ok(
|
ok(
|
||||||
with(employeeService) {
|
with(userService) {
|
||||||
getById(
|
getById(
|
||||||
loggedInEmployee.name.toLong(),
|
loggedInUser.name.toLong(),
|
||||||
ignoreDefaultGroupUsers = false,
|
ignoreDefaultGroupUsers = false,
|
||||||
ignoreSystemUsers = false
|
ignoreSystemUsers = false
|
||||||
).toOutput()
|
).toOutput()
|
||||||
|
@ -47,56 +46,56 @@ class EmployeeController(private val employeeService: EmployeeService) {
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun save(@Valid @RequestBody employee: EmployeeSaveDto) =
|
fun save(@Valid @RequestBody user: UserSaveDto) =
|
||||||
created<EmployeeOutputDto>(EMPLOYEE_CONTROLLER_PATH) {
|
created<UserOutputDto>(USER_CONTROLLER_PATH) {
|
||||||
with(employeeService) {
|
with(userService) {
|
||||||
save(employee).toOutput()
|
save(user).toOutput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun update(@Valid @RequestBody employee: EmployeeUpdateDto) =
|
fun update(@Valid @RequestBody user: UserUpdateDto) =
|
||||||
noContent {
|
noContent {
|
||||||
employeeService.update(employee)
|
userService.update(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE])
|
@PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE])
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun updatePassword(@PathVariable id: Long, @RequestBody password: String) =
|
fun updatePassword(@PathVariable id: Long, @RequestBody password: String) =
|
||||||
noContent {
|
noContent {
|
||||||
employeeService.updatePassword(id, password)
|
userService.updatePassword(id, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("{employeeId}/permissions/{permission}")
|
@PutMapping("{userId}/permissions/{permission}")
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun addPermission(
|
fun addPermission(
|
||||||
@PathVariable employeeId: Long,
|
@PathVariable userId: Long,
|
||||||
@PathVariable permission: EmployeePermission
|
@PathVariable permission: Permission
|
||||||
) = noContent {
|
) = noContent {
|
||||||
employeeService.addPermission(employeeId, permission)
|
userService.addPermission(userId, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{employeeId}/permissions/{permission}")
|
@DeleteMapping("{userId}/permissions/{permission}")
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun removePermission(
|
fun removePermission(
|
||||||
@PathVariable employeeId: Long,
|
@PathVariable userId: Long,
|
||||||
@PathVariable permission: EmployeePermission
|
@PathVariable permission: Permission
|
||||||
) = noContent {
|
) = noContent {
|
||||||
employeeService.removePermission(employeeId, permission)
|
userService.removePermission(userId, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorizeRemoveUsers
|
@PreAuthorizeEditUsers
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
employeeService.deleteById(id)
|
userService.deleteById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH)
|
@RequestMapping(GROUP_CONTROLLER_PATH)
|
||||||
class GroupsController(
|
class GroupsController(
|
||||||
private val groupService: EmployeeGroupService,
|
private val groupService: GroupService,
|
||||||
private val employeeService: EmployeeService
|
private val userService: UserService
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
||||||
|
@ -108,11 +107,11 @@ class GroupsController(
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
ok(groupService.getByIdForOutput(id))
|
ok(groupService.getByIdForOutput(id))
|
||||||
|
|
||||||
@GetMapping("{id}/employees")
|
@GetMapping("{id}/users")
|
||||||
@PreAuthorizeViewUsers
|
@PreAuthorizeViewUsers
|
||||||
fun getEmployeesForGroup(@PathVariable id: Long) =
|
fun getUsersForGroup(@PathVariable id: Long) =
|
||||||
ok(with(employeeService) {
|
ok(with(userService) {
|
||||||
groupService.getEmployeesForGroup(id)
|
groupService.getUsersForGroup(id)
|
||||||
.map { it.toOutput() }
|
.map { it.toOutput() }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -132,8 +131,8 @@ class GroupsController(
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) =
|
fun save(@Valid @RequestBody group: GroupSaveDto) =
|
||||||
created<EmployeeGroupOutputDto>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
|
created<GroupOutputDto>(GROUP_CONTROLLER_PATH) {
|
||||||
with(groupService) {
|
with(groupService) {
|
||||||
save(group).toOutput()
|
save(group).toOutput()
|
||||||
}
|
}
|
||||||
|
@ -141,13 +140,13 @@ class GroupsController(
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@PreAuthorizeEditUsers
|
@PreAuthorizeEditUsers
|
||||||
fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) =
|
fun update(@Valid @RequestBody group: GroupUpdateDto) =
|
||||||
noContent {
|
noContent {
|
||||||
groupService.update(group)
|
groupService.update(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorizeRemoveUsers
|
@PreAuthorizeEditUsers
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
groupService.deleteById(id)
|
groupService.deleteById(id)
|
||||||
|
@ -156,10 +155,10 @@ class GroupsController(
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api")
|
@RequestMapping("api")
|
||||||
class LogoutController(private val employeeService: EmployeeService) {
|
class LogoutController(private val userService: UserService) {
|
||||||
@GetMapping("logout")
|
@GetMapping("logout")
|
||||||
fun logout(request: HttpServletRequest) =
|
fun logout(request: HttpServletRequest) =
|
||||||
ok<Void> {
|
ok<Void> {
|
||||||
employeeService.logout(request)
|
userService.logout(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class CompanyController(private val companyService: CompanyService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorize("hasAuthority('REMOVE_COMPANIES')")
|
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
companyService.deleteById(id)
|
companyService.deleteById(id)
|
||||||
|
|
|
@ -64,7 +64,7 @@ class MaterialController(
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorize("hasAuthority('REMOVE_MATERIALS')")
|
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
materialService.deleteById(id)
|
materialService.deleteById(id)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class MaterialTypeController(private val materialTypeService: MaterialTypeServic
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPES')")
|
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
materialTypeService.deleteById(id)
|
materialTypeService.deleteById(id)
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes
|
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.annotations.PreAuthorizeViewRecipes
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
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.MixService
|
||||||
import dev.fyloz.colorrecipesexplorer.service.RecipeImageService
|
import dev.fyloz.colorrecipesexplorer.service.RecipeImageService
|
||||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService
|
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.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.net.URLEncoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import javax.validation.Valid
|
import javax.validation.Valid
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,8 +25,13 @@ class RecipeController(
|
||||||
private val recipeImageService: RecipeImageService
|
private val recipeImageService: RecipeImageService
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getAll() =
|
fun getAll(@RequestParam(required = false) name: String?) =
|
||||||
ok(recipeService.getAllForOutput())
|
if (name == null)
|
||||||
|
ok(recipeService.getAllForOutput())
|
||||||
|
else
|
||||||
|
ok(with(recipeService) {
|
||||||
|
getAllByName(name).map { it.toOutput() }
|
||||||
|
})
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
|
@ -61,7 +61,7 @@ class RecipeController(
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorizeRemoveRecipes
|
@PreAuthorizeEditRecipes
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
recipeService.deleteById(id)
|
recipeService.deleteById(id)
|
||||||
|
@ -105,7 +105,7 @@ class MixController(private val mixService: MixService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
@PreAuthorizeRemoveRecipes
|
@PreAuthorizeEditRecipes
|
||||||
fun deleteById(@PathVariable id: Long) =
|
fun deleteById(@PathVariable id: Long) =
|
||||||
noContent {
|
noContent {
|
||||||
mixService.deleteById(id)
|
mixService.deleteById(id)
|
||||||
|
|
|
@ -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<TouchUpKitOutputDto>(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<ByteArrayResource> {
|
||||||
|
with(touchUpKitService.generateJobPdfResource(project)) {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header("Content-Disposition", "filename=TouchUpKit_$project.pdf")
|
||||||
|
.contentLength(this.contentLength())
|
||||||
|
.contentType(MediaType.APPLICATION_PDF)
|
||||||
|
.body(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ class FileController(
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
@PreAuthorize("hasAnyAuthority('REMOVE_FILE')")
|
@PreAuthorize("hasAnyAuthority('WRITE_FILE')")
|
||||||
fun delete(@RequestParam path: String): ResponseEntity<Void> {
|
fun delete(@RequestParam path: String): ResponseEntity<Void> {
|
||||||
return noContent {
|
return noContent {
|
||||||
fileService.delete(path)
|
fileService.delete(path)
|
||||||
|
|
|
@ -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<ByteArrayResource> {
|
|
||||||
with(touchUpKitService.generateJobPdfResource(job)) {
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header("Content-Disposition", "filename=TouchUpKit_$job.pdf")
|
|
||||||
.contentLength(this.contentLength())
|
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
|
||||||
.body(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,12 +3,11 @@ package dev.fyloz.colorrecipesexplorer.service
|
||||||
import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens
|
import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens
|
||||||
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
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.model.validation.or
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
|
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
||||||
import org.springframework.context.annotation.Lazy
|
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.UserDetails
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
|
@ -19,73 +18,74 @@ import java.time.LocalDateTime
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import javax.transaction.Transactional
|
import javax.transaction.Transactional
|
||||||
|
import org.springframework.security.core.userdetails.User as SpringUser
|
||||||
|
|
||||||
interface EmployeeService :
|
interface UserService :
|
||||||
ExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeOutputDto, EmployeeRepository> {
|
ExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository> {
|
||||||
/** Check if an [Employee] with the given [firstName] and [lastName] exists. */
|
/** Check if an [User] with the given [firstName] and [lastName] exists. */
|
||||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
||||||
|
|
||||||
/** Gets the employee with the given [id]. */
|
/** Gets the user with the given [id]. */
|
||||||
fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee
|
fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User
|
||||||
|
|
||||||
/** Gets all employees which have the given [group]. */
|
/** Gets all users which have the given [group]. */
|
||||||
fun getByGroup(group: EmployeeGroup): Collection<Employee>
|
fun getByGroup(group: Group): Collection<User>
|
||||||
|
|
||||||
/** Gets the default user of the given [group]. */
|
/** 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]. */
|
/** Save a default group user for the given [group]. */
|
||||||
fun saveDefaultGroupEmployee(group: EmployeeGroup)
|
fun saveDefaultGroupUser(group: Group)
|
||||||
|
|
||||||
/** Updates de given [entity]. **/
|
/** 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]. */
|
/** Updates the last login time of the user with the given [userId]. */
|
||||||
fun updateLastLoginTime(employeeId: Long, time: LocalDateTime = LocalDateTime.now()): Employee
|
fun updateLastLoginTime(userId: Long, time: LocalDateTime = LocalDateTime.now()): User
|
||||||
|
|
||||||
/** Updates the password of the employee with the given [id]. */
|
/** Updates the password of the user with the given [id]. */
|
||||||
fun updatePassword(id: Long, password: String): Employee
|
fun updatePassword(id: Long, password: String): User
|
||||||
|
|
||||||
/** Adds the given [permission] to the employee with the given [employeeId]. */
|
/** Adds the given [permission] to the user with the given [userId]. */
|
||||||
fun addPermission(employeeId: Long, permission: EmployeePermission): Employee
|
fun addPermission(userId: Long, permission: Permission): User
|
||||||
|
|
||||||
/** Removes the given [permission] from the employee with the given [employeeId]. */
|
/** Removes the given [permission] from the user with the given [userId]. */
|
||||||
fun removePermission(employeeId: Long, permission: EmployeePermission): Employee
|
fun removePermission(userId: Long, permission: Permission): User
|
||||||
|
|
||||||
/** Logout an user. Add the authorization token of the given [request] to the blacklisted tokens. */
|
/** Logout an user. Add the authorization token of the given [request] to the blacklisted tokens. */
|
||||||
fun logout(request: HttpServletRequest)
|
fun logout(request: HttpServletRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmployeeGroupService :
|
interface GroupService :
|
||||||
ExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupOutputDto, EmployeeGroupRepository> {
|
ExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository> {
|
||||||
/** Gets all the employees of the group with the given [id]. */
|
/** Gets all the users of the group with the given [id]. */
|
||||||
fun getEmployeesForGroup(id: Long): Collection<Employee>
|
fun getUsersForGroup(id: Long): Collection<User>
|
||||||
|
|
||||||
/** Gets the default group from a cookie in the given HTTP [request]. */
|
/** Gets the default group from a cookie in the given HTTP [request]. */
|
||||||
fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup
|
fun getRequestDefaultGroup(request: HttpServletRequest): Group
|
||||||
|
|
||||||
/** Sets the default group cookie for the given HTTP [response]. */
|
/** Sets the default group cookie for the given HTTP [response]. */
|
||||||
fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse)
|
fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmployeeUserDetailsService : UserDetailsService {
|
interface CreUserDetailsService : UserDetailsService {
|
||||||
/** Loads an [User] for the given [employeeId]. */
|
/** Loads an [User] for the given [id]. */
|
||||||
fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails
|
fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class EmployeeServiceImpl(
|
class UserServiceImpl(
|
||||||
employeeRepository: EmployeeRepository,
|
userRepository: UserRepository,
|
||||||
@Lazy val groupService: EmployeeGroupService,
|
@Lazy val groupService: GroupService,
|
||||||
@Lazy val passwordEncoder: PasswordEncoder,
|
@Lazy val passwordEncoder: PasswordEncoder,
|
||||||
) : AbstractExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeOutputDto, EmployeeRepository>(
|
) : AbstractExternalModelService<User, UserSaveDto, UserUpdateDto, UserOutputDto, UserRepository>(
|
||||||
employeeRepository
|
userRepository
|
||||||
),
|
),
|
||||||
EmployeeService {
|
UserService {
|
||||||
override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id)
|
override fun idNotFoundException(id: Long) = userIdNotFoundException(id)
|
||||||
override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id)
|
override fun idAlreadyExistsException(id: Long) = userIdAlreadyExistsException(id)
|
||||||
|
|
||||||
override fun Employee.toOutput() = EmployeeOutputDto(
|
override fun User.toOutput() = UserOutputDto(
|
||||||
this.id,
|
this.id,
|
||||||
this.firstName,
|
this.firstName,
|
||||||
this.lastName,
|
this.lastName,
|
||||||
|
@ -98,29 +98,29 @@ class EmployeeServiceImpl(
|
||||||
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
|
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
|
||||||
repository.existsByFirstNameAndLastName(firstName, lastName)
|
repository.existsByFirstNameAndLastName(firstName, lastName)
|
||||||
|
|
||||||
override fun getAll(): Collection<Employee> =
|
override fun getAll(): Collection<User> =
|
||||||
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
|
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)
|
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 {
|
super.getById(id).apply {
|
||||||
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
|
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
|
||||||
throw idNotFoundException(id)
|
throw idNotFoundException(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getByGroup(group: EmployeeGroup): Collection<Employee> =
|
override fun getByGroup(group: Group): Collection<User> =
|
||||||
repository.findAllByGroup(group).filter {
|
repository.findAllByGroup(group).filter {
|
||||||
!it.isSystemUser && !it.isDefaultGroupUser
|
!it.isSystemUser && !it.isDefaultGroupUser
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee =
|
override fun getDefaultGroupUser(group: Group): User =
|
||||||
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
|
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
|
||||||
|
|
||||||
override fun save(entity: EmployeeSaveDto): Employee =
|
override fun save(entity: UserSaveDto): User =
|
||||||
save(with(entity) {
|
save(with(entity) {
|
||||||
Employee(
|
User(
|
||||||
id,
|
id,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
|
@ -132,20 +132,20 @@ class EmployeeServiceImpl(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
override fun save(entity: Employee): Employee {
|
override fun save(entity: User): User {
|
||||||
if (existsById(entity.id))
|
if (existsById(entity.id))
|
||||||
throw employeeIdAlreadyExistsException(entity.id)
|
throw userIdAlreadyExistsException(entity.id)
|
||||||
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
|
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
|
||||||
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
||||||
return super<AbstractExternalModelService>.save(entity)
|
return super<AbstractExternalModelService>.save(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveDefaultGroupEmployee(group: EmployeeGroup) {
|
override fun saveDefaultGroupUser(group: Group) {
|
||||||
save(
|
save(
|
||||||
employee(
|
user(
|
||||||
id = 1000000L + group.id!!,
|
id = 1000000L + group.id!!,
|
||||||
firstName = group.name,
|
firstName = group.name,
|
||||||
lastName = "EmployeeModel",
|
lastName = "User",
|
||||||
password = passwordEncoder.encode(group.name),
|
password = passwordEncoder.encode(group.name),
|
||||||
group = group,
|
group = group,
|
||||||
isDefaultGroupUser = true
|
isDefaultGroupUser = true
|
||||||
|
@ -153,49 +153,49 @@ class EmployeeServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateLastLoginTime(employeeId: Long, time: LocalDateTime): Employee {
|
override fun updateLastLoginTime(userId: Long, time: LocalDateTime): User {
|
||||||
val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
|
val user = getById(userId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
|
||||||
employee.lastLoginTime = time
|
user.lastLoginTime = time
|
||||||
return update(
|
return update(
|
||||||
employee,
|
user,
|
||||||
ignoreDefaultGroupUsers = true,
|
ignoreDefaultGroupUsers = true,
|
||||||
ignoreSystemUsers = false
|
ignoreSystemUsers = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(entity: EmployeeUpdateDto): Employee {
|
override fun update(entity: UserUpdateDto): User {
|
||||||
val persistedEmployee by lazy { getById(entity.id) }
|
val persistedUser by lazy { getById(entity.id) }
|
||||||
return update(with(entity) {
|
return update(with(entity) {
|
||||||
Employee(
|
User(
|
||||||
id = id,
|
id = id,
|
||||||
firstName = firstName or persistedEmployee.firstName,
|
firstName = firstName or persistedUser.firstName,
|
||||||
lastName = lastName or persistedEmployee.lastName,
|
lastName = lastName or persistedUser.lastName,
|
||||||
password = persistedEmployee.password,
|
password = persistedUser.password,
|
||||||
isDefaultGroupUser = false,
|
isDefaultGroupUser = false,
|
||||||
isSystemUser = false,
|
isSystemUser = false,
|
||||||
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
|
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedUser.group,
|
||||||
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
|
permissions = permissions?.toMutableSet() ?: persistedUser.permissions,
|
||||||
lastLoginTime = persistedEmployee.lastLoginTime
|
lastLoginTime = persistedUser.lastLoginTime
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(entity: Employee): Employee =
|
override fun update(entity: User): User =
|
||||||
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
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)) {
|
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
|
||||||
if (this != null && id != entity.id)
|
if (this != null && id != entity.id)
|
||||||
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super<AbstractExternalModelService>.update(entity)
|
return super.update(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePassword(id: Long, password: String): Employee {
|
override fun updatePassword(id: Long, password: String): User {
|
||||||
val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
val persistedUser = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||||
return super<AbstractExternalModelService>.update(with(persistedEmployee) {
|
return super.update(with(persistedUser) {
|
||||||
Employee(
|
User(
|
||||||
id,
|
id,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
|
@ -209,11 +209,11 @@ class EmployeeServiceImpl(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee =
|
override fun addPermission(userId: Long, permission: Permission): User =
|
||||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions += permission })
|
super.update(getById(userId).apply { permissions += permission })
|
||||||
|
|
||||||
override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee =
|
override fun removePermission(userId: Long, permission: Permission): User =
|
||||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions -= permission })
|
super.update(getById(userId).apply { permissions -= permission })
|
||||||
|
|
||||||
override fun logout(request: HttpServletRequest) {
|
override fun logout(request: HttpServletRequest) {
|
||||||
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
|
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
|
||||||
|
@ -229,19 +229,19 @@ class EmployeeServiceImpl(
|
||||||
const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class EmployeeGroupServiceImpl(
|
class GroupServiceImpl(
|
||||||
private val employeeService: EmployeeService,
|
private val userService: UserService,
|
||||||
employeeGroupRepository: EmployeeGroupRepository
|
groupRepository: GroupRepository
|
||||||
) : AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupOutputDto, EmployeeGroupRepository>(
|
) : AbstractExternalNamedModelService<Group, GroupSaveDto, GroupUpdateDto, GroupOutputDto, GroupRepository>(
|
||||||
employeeGroupRepository
|
groupRepository
|
||||||
),
|
),
|
||||||
EmployeeGroupService {
|
GroupService {
|
||||||
override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id)
|
override fun idNotFoundException(id: Long) = groupIdNotFoundException(id)
|
||||||
override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id)
|
override fun idAlreadyExistsException(id: Long) = groupIdAlreadyExistsException(id)
|
||||||
override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name)
|
override fun nameNotFoundException(name: String) = groupNameNotFoundException(name)
|
||||||
override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name)
|
override fun nameAlreadyExistsException(name: String) = groupNameAlreadyExistsException(name)
|
||||||
|
|
||||||
override fun EmployeeGroup.toOutput() = EmployeeGroupOutputDto(
|
override fun Group.toOutput() = GroupOutputDto(
|
||||||
this.id!!,
|
this.id!!,
|
||||||
this.name,
|
this.name,
|
||||||
this.permissions,
|
this.permissions,
|
||||||
|
@ -249,20 +249,20 @@ class EmployeeGroupServiceImpl(
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun existsByName(name: String): Boolean = repository.existsByName(name)
|
override fun existsByName(name: String): Boolean = repository.existsByName(name)
|
||||||
override fun getEmployeesForGroup(id: Long): Collection<Employee> =
|
override fun getUsersForGroup(id: Long): Collection<User> =
|
||||||
employeeService.getByGroup(getById(id))
|
userService.getByGroup(getById(id))
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun save(entity: EmployeeGroup): EmployeeGroup {
|
override fun save(entity: Group): Group {
|
||||||
return super<AbstractExternalNamedModelService>.save(entity).apply {
|
return super<AbstractExternalNamedModelService>.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) }
|
val persistedGroup by lazy { getById(entity.id) }
|
||||||
return update(with(entity) {
|
return update(with(entity) {
|
||||||
EmployeeGroup(
|
Group(
|
||||||
entity.id,
|
entity.id,
|
||||||
if (name.isNotBlank()) entity.name else persistedGroup.name,
|
if (name.isNotBlank()) entity.name else persistedGroup.name,
|
||||||
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
|
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
|
||||||
|
@ -271,15 +271,15 @@ class EmployeeGroupServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun delete(entity: EmployeeGroup) {
|
override fun delete(entity: Group) {
|
||||||
employeeService.delete(employeeService.getDefaultGroupEmployee(entity))
|
userService.delete(userService.getDefaultGroupUser(entity))
|
||||||
super.delete(entity)
|
super.delete(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup {
|
override fun getRequestDefaultGroup(request: HttpServletRequest): Group {
|
||||||
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
||||||
?: throw NoDefaultGroupException()
|
?: throw NoDefaultGroupException()
|
||||||
val defaultGroupUser = employeeService.getById(
|
val defaultGroupUser = userService.getById(
|
||||||
defaultGroupCookie.value.toLong(),
|
defaultGroupCookie.value.toLong(),
|
||||||
ignoreDefaultGroupUsers = false,
|
ignoreDefaultGroupUsers = false,
|
||||||
ignoreSystemUsers = true
|
ignoreSystemUsers = true
|
||||||
|
@ -289,7 +289,7 @@ class EmployeeGroupServiceImpl(
|
||||||
|
|
||||||
override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) {
|
override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) {
|
||||||
val group = getById(groupId)
|
val group = getById(groupId)
|
||||||
val defaultGroupUser = employeeService.getDefaultGroupEmployee(group)
|
val defaultGroupUser = userService.getDefaultGroupUser(group)
|
||||||
response.addHeader(
|
response.addHeader(
|
||||||
"Set-Cookie",
|
"Set-Cookie",
|
||||||
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
|
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
|
||||||
|
@ -298,13 +298,13 @@ class EmployeeGroupServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class EmployeeUserDetailsServiceImpl(
|
class CreUserDetailsServiceImpl(
|
||||||
private val employeeService: EmployeeService
|
private val userService: UserService
|
||||||
) :
|
) :
|
||||||
EmployeeUserDetailsService {
|
CreUserDetailsService {
|
||||||
override fun loadUserByUsername(username: String): UserDetails {
|
override fun loadUserByUsername(username: String): UserDetails {
|
||||||
try {
|
try {
|
||||||
return loadUserByEmployeeId(username.toLong(), true)
|
return loadUserById(username.toLong(), true)
|
||||||
} catch (ex: NotFoundException) {
|
} catch (ex: NotFoundException) {
|
||||||
throw UsernameNotFoundException(username)
|
throw UsernameNotFoundException(username)
|
||||||
} catch (ex: NotFoundException) {
|
} catch (ex: NotFoundException) {
|
||||||
|
@ -312,12 +312,12 @@ class EmployeeUserDetailsServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
||||||
val employee = employeeService.getById(
|
val user = userService.getById(
|
||||||
employeeId,
|
id,
|
||||||
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
||||||
ignoreSystemUsers = false
|
ignoreSystemUsers = false
|
||||||
)
|
)
|
||||||
return User(employee.id.toString(), employee.password, employee.authorities)
|
return SpringUser(user.id.toString(), user.password, user.authorities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ class MaterialServiceImpl(
|
||||||
|
|
||||||
override fun delete(entity: Material) {
|
override fun delete(entity: Material) {
|
||||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
|
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
|
||||||
fileService.delete(entity.simdutFilePath)
|
if (fileService.exists(entity.simdutFilePath)) fileService.delete(entity.simdutFilePath)
|
||||||
super.delete(entity)
|
super.delete(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.service
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
import dev.fyloz.colorrecipesexplorer.model.validation.or
|
import dev.fyloz.colorrecipesexplorer.model.validation.or
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
||||||
|
@ -16,6 +17,12 @@ interface RecipeService :
|
||||||
/** Checks if one or more recipes have the given [company]. */
|
/** Checks if one or more recipes have the given [company]. */
|
||||||
fun existsByCompany(company: Company): Boolean
|
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 [name]. */
|
||||||
|
fun getAllByName(name: String): Collection<Recipe>
|
||||||
|
|
||||||
/** Gets all recipes with the given [company]. */
|
/** Gets all recipes with the given [company]. */
|
||||||
fun getAllByCompany(company: Company): Collection<Recipe>
|
fun getAllByCompany(company: Company): Collection<Recipe>
|
||||||
|
|
||||||
|
@ -35,7 +42,7 @@ class RecipeServiceImpl(
|
||||||
val companyService: CompanyService,
|
val companyService: CompanyService,
|
||||||
val mixService: MixService,
|
val mixService: MixService,
|
||||||
val recipeStepService: RecipeStepService,
|
val recipeStepService: RecipeStepService,
|
||||||
@Lazy val groupService: EmployeeGroupService,
|
@Lazy val groupService: GroupService,
|
||||||
val recipeImageService: RecipeImageService
|
val recipeImageService: RecipeImageService
|
||||||
) :
|
) :
|
||||||
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeOutputDto, RecipeRepository>(
|
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeOutputDto, RecipeRepository>(
|
||||||
|
@ -67,10 +74,19 @@ class RecipeServiceImpl(
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
|
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
|
||||||
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
|
override fun existsByNameAndCompany(name: String, company: Company) =
|
||||||
|
repository.existsByNameAndCompany(name, company)
|
||||||
|
|
||||||
|
override fun getAllByName(name: String) = repository.findAllByName(name)
|
||||||
|
override fun getAllByCompany(company: Company) = repository.findAllByCompany(company)
|
||||||
|
|
||||||
override fun save(entity: RecipeSaveDto): Recipe {
|
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) {
|
return save(with(entity) {
|
||||||
recipe(
|
recipe(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -80,14 +96,23 @@ class RecipeServiceImpl(
|
||||||
sample = sample,
|
sample = sample,
|
||||||
approbationDate = approbationDate,
|
approbationDate = approbationDate,
|
||||||
remark = remark ?: "",
|
remark = remark ?: "",
|
||||||
company = companyService.getById(companyId)
|
company = company
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun update(entity: RecipeUpdateDto): Recipe {
|
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) {
|
return update(with(entity) {
|
||||||
recipe(
|
recipe(
|
||||||
|
@ -99,7 +124,7 @@ class RecipeServiceImpl(
|
||||||
sample = sample ?: persistedRecipe.sample,
|
sample = sample ?: persistedRecipe.sample,
|
||||||
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
|
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
|
||||||
remark = remark or persistedRecipe.remark,
|
remark = remark or persistedRecipe.remark,
|
||||||
company = persistedRecipe.company,
|
company = company,
|
||||||
mixes = persistedRecipe.mixes,
|
mixes = persistedRecipe.mixes,
|
||||||
groupsInformation = updateGroupsInformation(persistedRecipe, entity)
|
groupsInformation = updateGroupsInformation(persistedRecipe, entity)
|
||||||
)
|
)
|
||||||
|
@ -137,7 +162,7 @@ class RecipeServiceImpl(
|
||||||
if (publicDataDto.notes != null) {
|
if (publicDataDto.notes != null) {
|
||||||
val recipe = getById(publicDataDto.recipeId)
|
val recipe = getById(publicDataDto.recipeId)
|
||||||
|
|
||||||
fun noteForGroup(group: EmployeeGroup) =
|
fun noteForGroup(group: Group) =
|
||||||
publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content
|
publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content
|
||||||
|
|
||||||
// Notes
|
// Notes
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.findDuplicated
|
import dev.fyloz.colorrecipesexplorer.utils.findDuplicated
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.hasGaps
|
import dev.fyloz.colorrecipesexplorer.utils.hasGaps
|
||||||
|
@ -81,8 +82,8 @@ class InvalidStepsPositionsException(
|
||||||
)
|
)
|
||||||
|
|
||||||
class InvalidGroupStepsPositionsException(
|
class InvalidGroupStepsPositionsException(
|
||||||
val group: EmployeeGroup,
|
val group: Group,
|
||||||
val exception: InvalidStepsPositionsException
|
val exception: InvalidStepsPositionsException
|
||||||
) : RestException(
|
) : RestException(
|
||||||
"invalid-groupinformation-recipestep-position",
|
"invalid-groupinformation-recipestep-position",
|
||||||
"Invalid steps positions",
|
"Invalid steps positions",
|
||||||
|
|
|
@ -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.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 dev.fyloz.colorrecipesexplorer.utils.*
|
||||||
import org.springframework.core.io.ByteArrayResource
|
import org.springframework.core.io.ByteArrayResource
|
||||||
import org.springframework.stereotype.Service
|
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_FR = "KIT DE RETOUCHE"
|
||||||
const val TOUCH_UP_TEXT_EN = "TOUCH UP KIT"
|
const val TOUCH_UP_TEXT_EN = "TOUCH UP KIT"
|
||||||
|
|
||||||
interface TouchUpKitService {
|
interface TouchUpKitService :
|
||||||
|
ExternalModelService<TouchUpKit, TouchUpKitSaveDto, TouchUpKitUpdateDto, TouchUpKitOutputDto, TouchUpKitRepository> {
|
||||||
/** Generates and returns a [PdfDocument] for the given [job]. */
|
/** Generates and returns a [PdfDocument] for the given [job]. */
|
||||||
fun generateJobPdf(job: String): PdfDocument
|
fun generateJobPdf(job: String): PdfDocument
|
||||||
|
|
||||||
|
@ -29,8 +36,45 @@ interface TouchUpKitService {
|
||||||
@Service
|
@Service
|
||||||
class TouchUpKitServiceImpl(
|
class TouchUpKitServiceImpl(
|
||||||
private val fileService: FileService,
|
private val fileService: FileService,
|
||||||
private val creProperties: CreProperties
|
touchUpKitRepository: TouchUpKitRepository,
|
||||||
) : TouchUpKitService {
|
private val creProperties: CreProperties,
|
||||||
|
) : AbstractExternalModelService<TouchUpKit, TouchUpKitSaveDto, TouchUpKitUpdateDto, TouchUpKitOutputDto, TouchUpKitRepository>(
|
||||||
|
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 {
|
override fun generateJobPdf(job: String) = pdf {
|
||||||
container {
|
container {
|
||||||
centeredVertically = true
|
centeredVertically = true
|
||||||
|
@ -75,4 +119,7 @@ class TouchUpKitServiceImpl(
|
||||||
|
|
||||||
private fun String.pdfDocumentPath() =
|
private fun String.pdfDocumentPath() =
|
||||||
"$TOUCH_UP_KIT_FILES_PATH/$this.pdf"
|
"$TOUCH_UP_KIT_FILES_PATH/$this.pdf"
|
||||||
|
|
||||||
|
private fun TouchUpKit.pdfUrl() =
|
||||||
|
"${creProperties.deploymentUrl}$TOUCH_UP_KIT_CONTROLLER_PATH/pdf?job=$project"
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ entities.material-types.baseName=Base
|
||||||
databaseupdater.username=root
|
databaseupdater.username=root
|
||||||
databaseupdater.password=pass
|
databaseupdater.password=pass
|
||||||
# DEBUG
|
# DEBUG
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=false
|
||||||
# Do not modify
|
# Do not modify
|
||||||
spring.messages.fallback-to-system-locale=true
|
spring.messages.fallback-to-system-locale=true
|
||||||
spring.servlet.multipart.max-file-size=10MB
|
spring.servlet.multipart.max-file-size=10MB
|
||||||
|
@ -30,4 +30,6 @@ spring.jpa.open-in-view=true
|
||||||
server.http2.enabled=true
|
server.http2.enabled=true
|
||||||
server.error.whitelabel.enabled=false
|
server.error.whitelabel.enabled=false
|
||||||
spring.h2.console.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@
|
spring.profiles.active=@spring.profiles.active@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
junit.jupiter.testinstance.lifecycle.default=per_class
|
|
|
@ -4,15 +4,11 @@ import com.nhaarman.mockitokotlin2.*
|
||||||
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.account.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
|
import dev.fyloz.colorrecipesexplorer.repository.UserRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.GroupRepository
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.*
|
||||||
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.mock.web.MockHttpServletResponse
|
||||||
import org.springframework.security.core.userdetails.User
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -22,24 +18,26 @@ import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
import org.springframework.security.core.userdetails.User as SpringUser
|
||||||
|
|
||||||
class EmployeeServiceTest :
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
AbstractExternalModelServiceTest<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeService, EmployeeRepository>() {
|
class UserServiceTest :
|
||||||
|
AbstractExternalModelServiceTest<User, UserSaveDto, UserUpdateDto, UserService, UserRepository>() {
|
||||||
private val passwordEncoder = BCryptPasswordEncoder()
|
private val passwordEncoder = BCryptPasswordEncoder()
|
||||||
|
|
||||||
override val entity: Employee = employee(passwordEncoder, id = 0L)
|
override val entity: User = user(passwordEncoder, id = 0L)
|
||||||
override val anotherEntity: Employee = employee(passwordEncoder, id = 1L)
|
override val anotherEntity: User = user(passwordEncoder, id = 1L)
|
||||||
private val entityDefaultGroupUser = employee(passwordEncoder, id = 2L, isDefaultGroupUser = true)
|
private val entityDefaultGroupUser = user(passwordEncoder, id = 2L, isDefaultGroupUser = true)
|
||||||
private val entitySystemUser = employee(passwordEncoder, id = 3L, isSystemUser = true)
|
private val entitySystemUser = user(passwordEncoder, id = 3L, isSystemUser = true)
|
||||||
private val group = employeeGroup(id = 0L)
|
private val group = group(id = 0L)
|
||||||
override val entitySaveDto: EmployeeSaveDto = spy(employeeSaveDto(passwordEncoder, id = 0L))
|
override val entitySaveDto: UserSaveDto = spy(userSaveDto(passwordEncoder, id = 0L))
|
||||||
override val entityUpdateDto: EmployeeUpdateDto = spy(employeeUpdateDto(id = 0L))
|
override val entityUpdateDto: UserUpdateDto = spy(userUpdateDto(id = 0L))
|
||||||
|
|
||||||
override val repository: EmployeeRepository = mock()
|
override val repository: UserRepository = mock()
|
||||||
private val employeeGroupService: EmployeeGroupService = mock()
|
private val groupService: GroupService = mock()
|
||||||
override val service: EmployeeService = spy(EmployeeServiceImpl(repository, employeeGroupService, passwordEncoder))
|
override val service: UserService = spy(UserServiceImpl(repository, groupService, passwordEncoder))
|
||||||
|
|
||||||
private val entitySaveDtoEmployee = Employee(
|
private val entitySaveDtoUser = User(
|
||||||
entitySaveDto.id,
|
entitySaveDto.id,
|
||||||
entitySaveDto.firstName,
|
entitySaveDto.firstName,
|
||||||
entitySaveDto.lastName,
|
entitySaveDto.lastName,
|
||||||
|
@ -52,14 +50,14 @@ class EmployeeServiceTest :
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
override fun afterEach() {
|
override fun afterEach() {
|
||||||
reset(employeeGroupService)
|
reset(groupService)
|
||||||
super.afterEach()
|
super.afterEach()
|
||||||
}
|
}
|
||||||
|
|
||||||
// existsByFirstNameAndLastName()
|
// existsByFirstNameAndLastName()
|
||||||
|
|
||||||
@Test
|
@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)
|
whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(true)
|
||||||
|
|
||||||
val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
||||||
|
@ -68,7 +66,7 @@ class EmployeeServiceTest :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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)
|
whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(false)
|
||||||
|
|
||||||
val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
||||||
|
@ -79,7 +77,7 @@ class EmployeeServiceTest :
|
||||||
// getById()
|
// getById()
|
||||||
|
|
||||||
@Test
|
@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))
|
whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser))
|
||||||
|
|
||||||
assertThrows<NotFoundException> {
|
assertThrows<NotFoundException> {
|
||||||
|
@ -92,7 +90,7 @@ class EmployeeServiceTest :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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))
|
whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser))
|
||||||
|
|
||||||
assertThrows<NotFoundException> {
|
assertThrows<NotFoundException> {
|
||||||
|
@ -107,7 +105,7 @@ class EmployeeServiceTest :
|
||||||
// getByGroup()
|
// getByGroup()
|
||||||
|
|
||||||
@Test
|
@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)
|
whenever(repository.findAllByGroup(group)).doReturn(entityList)
|
||||||
|
|
||||||
val found = service.getByGroup(group)
|
val found = service.getByGroup(group)
|
||||||
|
@ -117,7 +115,7 @@ class EmployeeServiceTest :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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())
|
whenever(repository.findAllByGroup(group)).doReturn(listOf())
|
||||||
|
|
||||||
val found = service.getByGroup(group)
|
val found = service.getByGroup(group)
|
||||||
|
@ -128,10 +126,10 @@ class EmployeeServiceTest :
|
||||||
// getDefaultGroupUser()
|
// getDefaultGroupUser()
|
||||||
|
|
||||||
@Test
|
@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)
|
whenever(repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)).doReturn(entityDefaultGroupUser)
|
||||||
|
|
||||||
val found = service.getDefaultGroupEmployee(group)
|
val found = service.getDefaultGroupUser(group)
|
||||||
|
|
||||||
assertEquals(entityDefaultGroupUser, found)
|
assertEquals(entityDefaultGroupUser, found)
|
||||||
}
|
}
|
||||||
|
@ -166,13 +164,13 @@ class EmployeeServiceTest :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `save(dto) calls and returns save() with the created employee`() {
|
fun `save(dto) calls and returns save() with the created user`() {
|
||||||
doReturn(entitySaveDtoEmployee).whenever(service).save(any<Employee>())
|
doReturn(entitySaveDtoUser).whenever(service).save(any<User>())
|
||||||
|
|
||||||
val found = service.save(entitySaveDto)
|
val found = service.save(entitySaveDto)
|
||||||
|
|
||||||
verify(service).save(argThat<Employee> { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName })
|
verify(service).save(argThat<User> { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName })
|
||||||
assertEquals(entitySaveDtoEmployee, found)
|
assertEquals(entitySaveDtoUser, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update()
|
// update()
|
||||||
|
@ -182,7 +180,7 @@ class EmployeeServiceTest :
|
||||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||||
|
|
||||||
@Test
|
@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(
|
whenever(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(
|
||||||
entityDefaultGroupUser
|
entityDefaultGroupUser
|
||||||
)
|
)
|
||||||
|
@ -198,47 +196,48 @@ class EmployeeServiceTest :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmployeeGroupServiceTest :
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
AbstractExternalNamedModelServiceTest<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupService, EmployeeGroupRepository>() {
|
class GroupServiceTest :
|
||||||
private val employeeService: EmployeeService = mock()
|
AbstractExternalNamedModelServiceTest<Group, GroupSaveDto, GroupUpdateDto, GroupService, GroupRepository>() {
|
||||||
override val repository: EmployeeGroupRepository = mock()
|
private val userService: UserService = mock()
|
||||||
override val service: EmployeeGroupServiceImpl = spy(EmployeeGroupServiceImpl(employeeService, repository))
|
override val repository: GroupRepository = mock()
|
||||||
|
override val service: GroupServiceImpl = spy(GroupServiceImpl(userService, repository))
|
||||||
|
|
||||||
override val entity: EmployeeGroup = employeeGroup(id = 0L, name = "group")
|
override val entity: Group = group(id = 0L, name = "group")
|
||||||
override val anotherEntity: EmployeeGroup = employeeGroup(id = 1L, name = "another group")
|
override val anotherEntity: Group = group(id = 1L, name = "another group")
|
||||||
override val entitySaveDto: EmployeeGroupSaveDto = spy(employeeGroupSaveDto(name = "group"))
|
override val entitySaveDto: GroupSaveDto = spy(groupSaveDto(name = "group"))
|
||||||
override val entityUpdateDto: EmployeeGroupUpdateDto = spy(employeeGroupUpdateDto(id = 0L, name = "group"))
|
override val entityUpdateDto: GroupUpdateDto = spy(groupUpdateDto(id = 0L, name = "group"))
|
||||||
override val entityWithEntityName: EmployeeGroup = employeeGroup(id = 2L, name = entity.name)
|
override val entityWithEntityName: Group = group(id = 2L, name = entity.name)
|
||||||
|
|
||||||
private val groupEmployeeId = 1000000L + entity.id!!
|
private val groupUserId = 1000000L + entity.id!!
|
||||||
private val groupEmployee = employee(BCryptPasswordEncoder(), id = groupEmployeeId, group = entity)
|
private val groupUser = user(BCryptPasswordEncoder(), id = groupUserId, group = entity)
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
override fun afterEach() {
|
override fun afterEach() {
|
||||||
reset(employeeService)
|
reset(userService)
|
||||||
super.afterEach()
|
super.afterEach()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEmployeesForGroup()
|
// getUsersForGroup()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getEmployeesForGroup() returns all employees in the given group`() {
|
fun `getUsersForGroup() returns all users in the given group`() {
|
||||||
val group = employeeGroup(id = 1L)
|
val group = group(id = 1L)
|
||||||
|
|
||||||
doReturn(group).whenever(service).getById(group.id!!)
|
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)
|
assertTrue(found.size == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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!!)
|
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||||
|
|
||||||
val found = service.getEmployeesForGroup(entity.id!!)
|
val found = service.getUsersForGroup(entity.id!!)
|
||||||
|
|
||||||
assertTrue(found.isEmpty())
|
assertTrue(found.isEmpty())
|
||||||
}
|
}
|
||||||
|
@ -247,11 +246,11 @@ class EmployeeGroupServiceTest :
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getRequestDefaultGroup() returns the group contained in the cookie of the HTTP request`() {
|
fun `getRequestDefaultGroup() returns the group contained in the cookie of the HTTP request`() {
|
||||||
val cookies: Array<Cookie> = arrayOf(Cookie(defaultGroupCookieName, groupEmployeeId.toString()))
|
val cookies: Array<Cookie> = arrayOf(Cookie(defaultGroupCookieName, groupUserId.toString()))
|
||||||
val request: HttpServletRequest = mock()
|
val request: HttpServletRequest = mock()
|
||||||
|
|
||||||
whenever(request.cookies).doReturn(cookies)
|
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)
|
val found = service.getRequestDefaultGroup(request)
|
||||||
|
|
||||||
|
@ -273,7 +272,7 @@ class EmployeeGroupServiceTest :
|
||||||
fun `setResponseDefaultGroup() the default group cookie has been added to the given HTTP response with the given group id`() {
|
fun `setResponseDefaultGroup() the default group cookie has been added to the given HTTP response with the given group id`() {
|
||||||
val response = MockHttpServletResponse()
|
val response = MockHttpServletResponse()
|
||||||
|
|
||||||
whenever(employeeService.getDefaultGroupEmployee(entity)).doReturn(groupEmployee)
|
whenever(userService.getDefaultGroupUser(entity)).doReturn(groupUser)
|
||||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||||
|
|
||||||
service.setResponseDefaultGroup(entity.id!!, response)
|
service.setResponseDefaultGroup(entity.id!!, response)
|
||||||
|
@ -281,7 +280,7 @@ class EmployeeGroupServiceTest :
|
||||||
|
|
||||||
assertNotNull(found)
|
assertNotNull(found)
|
||||||
assertEquals(defaultGroupCookieName, found.name)
|
assertEquals(defaultGroupCookieName, found.name)
|
||||||
assertEquals(groupEmployeeId.toString(), found.value)
|
assertEquals(groupUserId.toString(), found.value)
|
||||||
assertEquals(defaultGroupCookieMaxAge, found.maxAge)
|
assertEquals(defaultGroupCookieMaxAge, found.maxAge)
|
||||||
assertTrue(found.isHttpOnly)
|
assertTrue(found.isHttpOnly)
|
||||||
assertTrue(found.secure)
|
assertTrue(found.secure)
|
||||||
|
@ -301,48 +300,49 @@ class EmployeeGroupServiceTest :
|
||||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmployeeUserDetailsServiceTest {
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
private val employeeService: EmployeeService = mock()
|
class UserUserDetailsServiceTest {
|
||||||
private val service = spy(EmployeeUserDetailsServiceImpl(employeeService))
|
private val userService: UserService = mock()
|
||||||
|
private val service = spy(CreUserDetailsServiceImpl(userService))
|
||||||
|
|
||||||
private val employee = employee(id = 0L)
|
private val user = user(id = 0L)
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun beforeEach() {
|
fun beforeEach() {
|
||||||
reset(employeeService, service)
|
reset(userService, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadUserByUsername()
|
// loadUserByUsername()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `loadUserByUsername() calls loadUserByEmployeeId() with the given username as an id`() {
|
fun `loadUserByUsername() calls loadUserByUserId() with the given username as an id`() {
|
||||||
whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee)
|
whenever(userService.getById(eq(user.id), any(), any())).doReturn(user)
|
||||||
doReturn(User(employee.id.toString(), employee.password, listOf())).whenever(service)
|
doReturn(SpringUser(user.id.toString(), user.password, listOf())).whenever(service)
|
||||||
.loadUserByEmployeeId(employee.id)
|
.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
|
@Test
|
||||||
fun `loadUserByUsername() throws UsernameNotFoundException when no employee with the given id exists`() {
|
fun `loadUserByUsername() throws UsernameNotFoundException when no user with the given id exists`() {
|
||||||
whenever(employeeService.getById(eq(employee.id), any(), any())).doThrow(
|
whenever(userService.getById(eq(user.id), any(), any())).doThrow(
|
||||||
employeeIdNotFoundException(employee.id)
|
userIdNotFoundException(user.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThrows<UsernameNotFoundException> { service.loadUserByUsername(employee.id.toString()) }
|
assertThrows<UsernameNotFoundException> { service.loadUserByUsername(user.id.toString()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadUserByEmployeeId
|
// loadUserByUserId
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `loadUserByEmployeeId() returns an User corresponding to the employee with the given id`() {
|
fun `loadUserByUserId() returns an User corresponding to the user with the given id`() {
|
||||||
whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee)
|
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(user.id, found.username.toLong())
|
||||||
assertEquals(employee.password, found.password)
|
assertEquals(user.password, found.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository
|
import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class CompanyServiceTest :
|
class CompanyServiceTest :
|
||||||
AbstractExternalNamedModelServiceTest<Company, CompanySaveDto, CompanyUpdateDto, CompanyService, CompanyRepository>() {
|
AbstractExternalNamedModelServiceTest<Company, CompanySaveDto, CompanyUpdateDto, CompanyService, CompanyRepository>() {
|
||||||
private val recipeService: RecipeService = mock()
|
private val recipeService: RecipeService = mock()
|
||||||
|
|
|
@ -4,10 +4,12 @@ import com.nhaarman.mockitokotlin2.*
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class InventoryServiceTest {
|
class InventoryServiceTest {
|
||||||
private val materialService: MaterialService = mock()
|
private val materialService: MaterialService = mock()
|
||||||
private val mixService: MixService = mock()
|
private val mixService: MixService = mock()
|
||||||
|
|
|
@ -7,12 +7,14 @@ import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.springframework.mock.web.MockMultipartFile
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class MaterialServiceTest :
|
class MaterialServiceTest :
|
||||||
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
|
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
|
||||||
override val repository: MaterialRepository = mock()
|
override val repository: MaterialRepository = mock()
|
||||||
|
|
|
@ -7,11 +7,13 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class MaterialTypeServiceTest :
|
class MaterialTypeServiceTest :
|
||||||
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeService, MaterialTypeRepository>() {
|
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeService, MaterialTypeRepository>() {
|
||||||
override val repository: MaterialTypeRepository = mock()
|
override val repository: MaterialTypeRepository = mock()
|
||||||
|
|
|
@ -4,12 +4,14 @@ import com.nhaarman.mockitokotlin2.*
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
|
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertNotEquals
|
import kotlin.test.assertNotEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterialService, MixMaterialRepository>() {
|
class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterialService, MixMaterialRepository>() {
|
||||||
override val repository: MixMaterialRepository = mock()
|
override val repository: MixMaterialRepository = mock()
|
||||||
private val materialService: MaterialService = mock()
|
private val materialService: MaterialService = mock()
|
||||||
|
|
|
@ -5,9 +5,11 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixService, MixRepository>() {
|
class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixService, MixRepository>() {
|
||||||
override val repository: MixRepository = mock()
|
override val repository: MixRepository = mock()
|
||||||
private val recipeService: RecipeService = mock()
|
private val recipeService: RecipeService = mock()
|
||||||
|
|
|
@ -7,10 +7,12 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService, MixTypeRepository>() {
|
class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService, MixTypeRepository>() {
|
||||||
override val repository: MixTypeRepository = mock()
|
override val repository: MixTypeRepository = mock()
|
||||||
private val materialService: MaterialService = mock()
|
private val materialService: MaterialService = mock()
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.service
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.group
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
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.mock.web.MockMultipartFile
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -14,12 +18,13 @@ import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class RecipeServiceTest :
|
class RecipeServiceTest :
|
||||||
AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
|
AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
|
||||||
override val repository: RecipeRepository = mock()
|
override val repository: RecipeRepository = mock()
|
||||||
private val companyService: CompanyService = mock()
|
private val companyService: CompanyService = mock()
|
||||||
private val mixService: MixService = mock()
|
private val mixService: MixService = mock()
|
||||||
private val groupService: EmployeeGroupService = mock()
|
private val groupService: GroupService = mock()
|
||||||
private val recipeStepService: RecipeStepService = mock()
|
private val recipeStepService: RecipeStepService = mock()
|
||||||
override val service: RecipeService =
|
override val service: RecipeService =
|
||||||
spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock()))
|
spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock()))
|
||||||
|
@ -56,6 +61,32 @@ class RecipeServiceTest :
|
||||||
assertFalse(found)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
// getAllByCompany()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -73,14 +104,40 @@ class RecipeServiceTest :
|
||||||
@Test
|
@Test
|
||||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
override fun `save(dto) calls and returns save() with the created entity`() {
|
||||||
whenever(companyService.getById(company.id!!)).doReturn(company)
|
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 } })
|
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<AlreadyExistsException> { service.save(entitySaveDto) }) {
|
||||||
|
this.assertErrorCode("company-name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update()
|
// update()
|
||||||
|
|
||||||
@Test
|
@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() })
|
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<AlreadyExistsException> { service.update(entityUpdateDto) }) {
|
||||||
|
this.assertErrorCode("company-name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// updatePublicData()
|
// updatePublicData()
|
||||||
|
|
||||||
|
@ -88,9 +145,9 @@ class RecipeServiceTest :
|
||||||
fun `updatePublicData() updates the notes of a recipe groups information according to the RecipePublicDataDto`() {
|
fun `updatePublicData() updates the notes of a recipe groups information according to the RecipePublicDataDto`() {
|
||||||
val recipe = recipe(
|
val recipe = recipe(
|
||||||
id = 0L, groupsInformation = setOf(
|
id = 0L, groupsInformation = setOf(
|
||||||
recipeGroupInformation(id = 0L, group = employeeGroup(id = 1L), note = "Old note"),
|
recipeGroupInformation(id = 0L, group = group(id = 1L), note = "Old note"),
|
||||||
recipeGroupInformation(id = 1L, group = employeeGroup(id = 2L), note = "Another note"),
|
recipeGroupInformation(id = 1L, group = group(id = 2L), note = "Another note"),
|
||||||
recipeGroupInformation(id = 2L, group = employeeGroup(id = 3L), note = "Up to date note")
|
recipeGroupInformation(id = 2L, group = group(id = 3L), note = "Up to date note")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val notes = setOf(
|
val notes = setOf(
|
||||||
|
|
|
@ -2,11 +2,14 @@ package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.*
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.account.group
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class RecipeStepServiceTest :
|
class RecipeStepServiceTest :
|
||||||
AbstractModelServiceTest<RecipeStep, RecipeStepService, RecipeStepRepository>() {
|
AbstractModelServiceTest<RecipeStep, RecipeStepService, RecipeStepRepository>() {
|
||||||
override val repository: RecipeStepRepository = mock()
|
override val repository: RecipeStepRepository = mock()
|
||||||
|
@ -80,7 +83,7 @@ class RecipeStepServiceTest :
|
||||||
|
|
||||||
private fun withGroupInformation(steps: MutableSet<RecipeStep>? = null, test: RecipeGroupInformation.() -> Unit) {
|
private fun withGroupInformation(steps: MutableSet<RecipeStep>? = null, test: RecipeGroupInformation.() -> Unit) {
|
||||||
recipeGroupInformation(
|
recipeGroupInformation(
|
||||||
group = employeeGroup(id = 0L),
|
group = group(id = 0L),
|
||||||
steps = steps ?: mutableSetOf(
|
steps = steps ?: mutableSetOf(
|
||||||
recipeStep(id = 0L, position = 1),
|
recipeStep(id = 0L, position = 1),
|
||||||
recipeStep(id = 1L, position = 2),
|
recipeStep(id = 1L, position = 2),
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.service.files
|
package dev.fyloz.colorrecipesexplorer.service.files
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
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.PdfDocument
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource
|
import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
|
@ -10,13 +14,14 @@ import org.springframework.core.io.ByteArrayResource
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
private class TouchUpKitServiceTestContext {
|
private class TouchUpKitServiceTestContext {
|
||||||
|
val touchUpKitRepository = mockk<TouchUpKitRepository>()
|
||||||
val fileService = mockk<FileService> {
|
val fileService = mockk<FileService> {
|
||||||
every { write(any<ByteArrayResource>(), any(), any()) } just Runs
|
every { write(any<ByteArrayResource>(), any(), any()) } just Runs
|
||||||
}
|
}
|
||||||
val creProperties = mockk<CreProperties> {
|
val creProperties = mockk<CreProperties> {
|
||||||
every { cacheGeneratedFiles } returns false
|
every { cacheGeneratedFiles } returns false
|
||||||
}
|
}
|
||||||
val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, creProperties))
|
val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, touchUpKitRepository, creProperties))
|
||||||
val pdfDocumentData = mockk<ByteArrayResource>()
|
val pdfDocumentData = mockk<ByteArrayResource>()
|
||||||
val pdfDocument = mockk<PdfDocument> {
|
val pdfDocument = mockk<PdfDocument> {
|
||||||
mockkStatic(PdfDocument::toByteArrayResource)
|
mockkStatic(PdfDocument::toByteArrayResource)
|
||||||
|
|
Loading…
Reference in New Issue