Ajout des comptes pour l'API REST.
This commit is contained in:
parent
a16844d747
commit
3bafc3d9ef
|
@ -9,6 +9,8 @@ plugins {
|
|||
id("com.leobia.gradle.sassjavacompiler") version "0.2.1"
|
||||
id("io.freefair.lombok") version "5.2.1"
|
||||
id("org.springframework.boot") version "2.3.4.RELEASE"
|
||||
id("org.jetbrains.kotlin.plugin.spring") version "1.4.10"
|
||||
id("org.jetbrains.kotlin.plugin.jpa") version "1.4.10"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -24,23 +26,29 @@ dependencies {
|
|||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa:2.1.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-jdbc:2.1.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf:2.1.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:2.1.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-configuration-processor:2.3.4.RELEASE")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test:2.3.4.RELEASE")
|
||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:2.3.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-devtools:2.3.4.RELEASE")
|
||||
|
||||
implementation("javax.xml.bind:jaxb-api:2.3.0")
|
||||
implementation("io.jsonwebtoken:jjwt:0.9.1")
|
||||
implementation("org.apache.poi:poi-ooxml:4.1.0")
|
||||
implementation("org.apache.pdfbox:pdfbox:2.0.4")
|
||||
implementation("org.springframework.boot:spring-boot-configuration-processor:2.1.4.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-devtools:2.1.4.RELEASE")
|
||||
implementation("com.atlassian.commonmark:commonmark:0.13.1")
|
||||
implementation("commons-io:commons-io:2.6")
|
||||
implementation("org.springframework:spring-test:5.1.6.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-test-autoconfigure:2.1.4.RELEASE")
|
||||
implementation("org.mockito:mockito-core:2.23.4")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api:5.3.2")
|
||||
|
||||
runtimeOnly("com.h2database:h2:1.4.199")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE")
|
||||
compileOnly("org.projectlombok:lombok:1.18.10")
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.config.MaterialTypeProper
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(MaterialTypeProperties::class, CREProperties::class)
|
||||
open class ColorRecipesExplorerApplication
|
||||
@EnableCaching
|
||||
class ColorRecipesExplorerApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<ColorRecipesExplorerApplication>(*args)
|
||||
|
|
|
@ -46,13 +46,18 @@ public class SpringConfiguration {
|
|||
this.materialTypeProperties = materialTypeProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Logger getLogger() {
|
||||
return LoggerFactory.getLogger(ColorRecipesExplorerApplication.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void setPreferences() {
|
||||
Preferences.urlUsePort = creProperties.isUrlUsePort();
|
||||
Preferences.urlUseHttps = creProperties.isUrlUseHttps();
|
||||
Preferences.uploadDirectory = creProperties.getUploadDirectory();
|
||||
Preferences.passwordsFileName = creProperties.getPasswordFile();
|
||||
Preferences.logger = LoggerFactory.getLogger(ColorRecipesExplorerApplication.class);
|
||||
Preferences.logger = getLogger();
|
||||
Preferences.messageSource = messageSource;
|
||||
Preferences.baseMaterialTypeName = materialTypeProperties.getBaseName();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.configuration
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Employee
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeeLoginRequest
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeePermission
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.EmployeeService
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.EmployeeUserDetailsService
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.web.AuthenticationEntryPoint
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.util.Assert
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.CorsConfigurationSource
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||
import java.util.*
|
||||
import javax.annotation.PostConstruct
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@EnableConfigurationProperties(SecurityConfigurationProperties::class)
|
||||
class WebSecurityConfig(
|
||||
val restAuthenticationEntryPoint: RestAuthenticationEntryPoint,
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
val logger: Logger) : WebSecurityConfigurerAdapter() {
|
||||
@Autowired
|
||||
private lateinit var userDetailsService: EmployeeUserDetailsService
|
||||
|
||||
@Autowired
|
||||
private lateinit var employeeService: EmployeeService
|
||||
|
||||
override fun configure(authBuilder: AuthenticationManagerBuilder) {
|
||||
authBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder())
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder {
|
||||
return BCryptPasswordEncoder()
|
||||
}
|
||||
|
||||
@Bean
|
||||
override fun authenticationManagerBean(): AuthenticationManager {
|
||||
return super.authenticationManagerBean()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
return UrlBasedCorsConfigurationSource().apply {
|
||||
registerCorsConfiguration("/**", CorsConfiguration().applyPermitDefaultValues())
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
fun createRootUser() {
|
||||
val rootUserCredentials = securityConfigurationProperties.root
|
||||
Assert.notNull(rootUserCredentials, "No root user has been defined.")
|
||||
Assert.notNull(rootUserCredentials!!.id, "The root user has no identifier defined.")
|
||||
Assert.notNull(rootUserCredentials.password, "The root user has no password defined.")
|
||||
if (!employeeService.existsById(rootUserCredentials.id!!)) {
|
||||
employeeService.save(Employee(rootUserCredentials.id!!, "Root", "Employee", passwordEncoder().encode(rootUserCredentials.password!!), true, permissions = mutableListOf(EmployeePermission.ADMIN)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
fun ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry.generateAuthorizations():
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry {
|
||||
ControllerAuthorizations.values().forEach { controller ->
|
||||
val antMatcher = controller.antMatcher
|
||||
controller.permissions.forEach {
|
||||
antMatchers(it.key, antMatcher).hasAuthority(it.value.name)
|
||||
logger.debug("Added authorization for path '$antMatcher': ${it.key.name} -> ${it.value.name}")
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
http
|
||||
.cors()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/").permitAll()
|
||||
.antMatchers("/api/login").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/api/employee/current").authenticated()
|
||||
.generateAuthorizations()
|
||||
.and()
|
||||
.addFilter(JwtAuthenticationFilter(authenticationManager(), employeeService, securityConfigurationProperties))
|
||||
.addFilter(JwtAuthorizationFilter(userDetailsService, securityConfigurationProperties, authenticationManager()))
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class RestAuthenticationEntryPoint : AuthenticationEntryPoint {
|
||||
override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) = response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized")
|
||||
}
|
||||
|
||||
class JwtAuthenticationFilter(
|
||||
val authManager: AuthenticationManager,
|
||||
val employeeService: EmployeeService,
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties
|
||||
) : UsernamePasswordAuthenticationFilter() {
|
||||
init {
|
||||
setFilterProcessesUrl("/api/login")
|
||||
}
|
||||
|
||||
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication {
|
||||
val loginRequest = jacksonObjectMapper().readValue(request.inputStream, EmployeeLoginRequest::class.java)
|
||||
return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password))
|
||||
}
|
||||
|
||||
override fun successfulAuthentication(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, authResult: Authentication) {
|
||||
val jwtSecret = securityConfigurationProperties.jwtSecret
|
||||
val jwtDuration = securityConfigurationProperties.jwtDuration
|
||||
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
||||
Assert.notNull(jwtDuration, "No JWT duration has been defined.")
|
||||
val employeeId = (authResult.principal as User).username
|
||||
employeeService.updateLastLoginTime(employeeId.toLong())
|
||||
val token = Jwts.builder()
|
||||
.setSubject(employeeId)
|
||||
.setExpiration(Date(System.currentTimeMillis() + jwtDuration!!))
|
||||
.signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray())
|
||||
.compact()
|
||||
response.addHeader("Authorization", "Bearer $token")
|
||||
}
|
||||
}
|
||||
|
||||
class JwtAuthorizationFilter(
|
||||
val userDetailsService: EmployeeUserDetailsService,
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
authenticationManager: AuthenticationManager
|
||||
) : BasicAuthenticationFilter(authenticationManager) {
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||
val header = request.getHeader("Authorization")
|
||||
if (header != null && header.startsWith("Bearer")) {
|
||||
val authenticationToken = getAuthentication(request)
|
||||
SecurityContextHolder.getContext().authentication = authenticationToken
|
||||
}
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
|
||||
private fun getAuthentication(request: HttpServletRequest): UsernamePasswordAuthenticationToken? {
|
||||
val jwtSecret = securityConfigurationProperties.jwtSecret
|
||||
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
||||
val token = request.getHeader("Authorization")
|
||||
if (token != null) {
|
||||
val employeeId = Jwts.parser()
|
||||
.setSigningKey(jwtSecret!!.toByteArray())
|
||||
.parseClaimsJws(token.replace("Bearer", ""))
|
||||
.body
|
||||
.subject
|
||||
return if (employeeId != null) {
|
||||
val employeeDetails = userDetailsService.loadUserByUsername(employeeId)
|
||||
UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities)
|
||||
} else null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigurationProperties("cre.security")
|
||||
class SecurityConfigurationProperties {
|
||||
var jwtSecret: String? = null
|
||||
var jwtDuration: Long? = null
|
||||
var root: RootUserCredentials? = null
|
||||
|
||||
class RootUserCredentials(var id: Long? = null, var password: String? = null)
|
||||
}
|
||||
|
||||
private enum class ControllerAuthorizations(
|
||||
val antMatcher: String,
|
||||
val permissions: Map<HttpMethod, EmployeePermission>
|
||||
) {
|
||||
EMPLOYEE_GROUP("/api/employee/group/**", mapOf(
|
||||
HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE_GROUP,
|
||||
HttpMethod.POST to EmployeePermission.EDIT_EMPLOYEE_GROUP,
|
||||
HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE_GROUP,
|
||||
HttpMethod.DELETE to EmployeePermission.REMOVE_EMPLOYEE_GROUP
|
||||
)),
|
||||
EMPLOYEE("/api/employee/**", mapOf(
|
||||
HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE,
|
||||
HttpMethod.POST to EmployeePermission.EDIT_EMPLOYEE,
|
||||
HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE,
|
||||
HttpMethod.DELETE to EmployeePermission.REMOVE_EMPLOYEE
|
||||
))
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.exception
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.*
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.validation.FieldError
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.context.request.WebRequest
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
||||
|
||||
abstract class RestException(val httpStatus: HttpStatus) : RuntimeException() {
|
||||
abstract val exceptionMessage: String
|
||||
abstract fun buildBody(): RestExceptionBody
|
||||
|
||||
override val message: String by lazy { exceptionMessage }
|
||||
|
||||
open inner class RestExceptionBody(val status: Int = httpStatus.value(), @JsonProperty("message") val message: String = exceptionMessage)
|
||||
}
|
||||
|
||||
@ControllerAdvice
|
||||
@Profile("rest")
|
||||
class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
|
||||
@ExceptionHandler(ModelException::class)
|
||||
fun handleModelExceptions(exception: ModelException, request: WebRequest): ResponseEntity<Any> {
|
||||
return handleRestExceptions(exception.toRestException(), request)
|
||||
}
|
||||
|
||||
@ExceptionHandler(RestException::class)
|
||||
fun handleRestExceptions(exception: RestException, request: WebRequest): ResponseEntity<Any> {
|
||||
return handleExceptionInternal(exception, exception.buildBody(), HttpHeaders(), exception.httpStatus, request)
|
||||
}
|
||||
|
||||
override fun handleMethodArgumentNotValid(ex: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
|
||||
val errors = hashMapOf<String, String>()
|
||||
ex.bindingResult.allErrors.forEach {
|
||||
val fieldName = (it as FieldError).field
|
||||
val errorMessage = it.defaultMessage
|
||||
errors[fieldName] = errorMessage
|
||||
}
|
||||
return ResponseEntity(errors, headers, status)
|
||||
}
|
||||
}
|
||||
|
||||
fun ModelException.toRestException(): RestException {
|
||||
return when (this) {
|
||||
is EntityAlreadyExistsException -> EntityAlreadyExistsRestException(requestedId)
|
||||
is EntityNotFoundException -> EntityNotFoundRestException(requestedId)
|
||||
else -> throw UnsupportedOperationException("Cannot convert ${this::class.simpleName} to REST exception")
|
||||
}
|
||||
}
|
|
@ -1,34 +1,37 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Getter
|
||||
public class EntityAlreadyExistsException extends ModelException {
|
||||
|
||||
@NonNull
|
||||
private IdentifierType identifierType;
|
||||
|
||||
private String identifierName;
|
||||
|
||||
@NonNull
|
||||
private Object requestedId;
|
||||
|
||||
public EntityAlreadyExistsException(EntityAlreadyExistsException ex) {
|
||||
this(ex.type, ex.identifierType, ex.identifierName, ex.requestedId);
|
||||
}
|
||||
|
||||
public EntityAlreadyExistsException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
|
||||
super(type);
|
||||
this.identifierType = identifierType;
|
||||
this.requestedId = requestedId;
|
||||
}
|
||||
|
||||
public EntityAlreadyExistsException(Class<? extends IModel> type, IdentifierType identifierType, String identifierName, Object requestedId) {
|
||||
super(type);
|
||||
this.identifierType = identifierType;
|
||||
this.identifierName = identifierName != null ? identifierName : identifierType.getName();
|
||||
this.requestedId = requestedId;
|
||||
}
|
||||
}
|
||||
//package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
|
||||
//
|
||||
//import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
||||
//import lombok.Getter;
|
||||
//import lombok.NonNull;
|
||||
//import org.springframework.http.HttpStatus;
|
||||
//import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
//
|
||||
//@Getter
|
||||
//@ResponseStatus(HttpStatus.CONFLICT)
|
||||
//public class EntityAlreadyExistsException extends ModelException {
|
||||
//
|
||||
// @NonNull
|
||||
// private IdentifierType identifierType;
|
||||
//
|
||||
// private String identifierName;
|
||||
//
|
||||
// @NonNull
|
||||
// private Object requestedId;
|
||||
//
|
||||
// public EntityAlreadyExistsException(EntityAlreadyExistsException ex) {
|
||||
// this(ex.type, ex.identifierType, ex.identifierName, ex.requestedId);
|
||||
// }
|
||||
//
|
||||
// public EntityAlreadyExistsException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
|
||||
// super(type);
|
||||
// this.identifierType = identifierType;
|
||||
// this.requestedId = requestedId;
|
||||
// }
|
||||
//
|
||||
// public EntityAlreadyExistsException(Class<? extends IModel> type, IdentifierType identifierType, String identifierName, Object requestedId) {
|
||||
// super(type);
|
||||
// this.identifierType = identifierType;
|
||||
// this.identifierName = identifierName != null ? identifierName : identifierType.getName();
|
||||
// this.requestedId = requestedId;
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.RestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
|
||||
class EntityAlreadyExistsException(modelType: Class<out IModel>, val identifierType: IdentifierType, val identifierName: String?, val requestedId: Any) : ModelException(modelType) {
|
||||
constructor(modelType: Class<out IModel>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
|
||||
constructor(exception: EntityAlreadyExistsException) : this(exception.type, exception.identifierType, exception.identifierName, exception.requestedId)
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
class EntityAlreadyExistsRestException(val value: Any) : RestException(HttpStatus.CONFLICT) {
|
||||
override val exceptionMessage: String = "An entity with the given identifier already exists"
|
||||
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
}
|
|
@ -1,39 +1,39 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Représente une exception qui sera lancée lorsqu'un objet du modèle n'est pas trouvé.
|
||||
*/
|
||||
@Getter
|
||||
public class EntityNotFoundException extends ModelException {
|
||||
|
||||
/**
|
||||
* Le type d'identifiant utilisé
|
||||
*/
|
||||
private IdentifierType identifierType;
|
||||
|
||||
/**
|
||||
* Le nom de l'identifiant utilisé (optionnel)
|
||||
*/
|
||||
private String identifierName;
|
||||
|
||||
/**
|
||||
* La valeur de l'identifiant
|
||||
*/
|
||||
private Object requestedId;
|
||||
|
||||
public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
|
||||
super(type);
|
||||
this.identifierType = identifierType;
|
||||
this.requestedId = requestedId;
|
||||
}
|
||||
|
||||
public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, String identifierName, Object requestedId) {
|
||||
super(type);
|
||||
this.identifierType = identifierType;
|
||||
this.identifierName = identifierName != null ? identifierName : identifierType.getName();
|
||||
this.requestedId = requestedId;
|
||||
}
|
||||
}
|
||||
//package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
|
||||
//
|
||||
//import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
||||
//import lombok.Getter;
|
||||
//
|
||||
///**
|
||||
// * Représente une exception qui sera lancée lorsqu'un objet du modèle n'est pas trouvé.
|
||||
// */
|
||||
//@Getter
|
||||
//public class EntityNotFoundException extends ModelException {
|
||||
//
|
||||
// /**
|
||||
// * Le type d'identifiant utilisé
|
||||
// */
|
||||
// private IdentifierType identifierType;
|
||||
//
|
||||
// /**
|
||||
// * Le nom de l'identifiant utilisé (optionnel)
|
||||
// */
|
||||
// private String identifierName;
|
||||
//
|
||||
// /**
|
||||
// * La valeur de l'identifiant
|
||||
// */
|
||||
// private Object requestedId;
|
||||
//
|
||||
// public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
|
||||
// super(type);
|
||||
// this.identifierType = identifierType;
|
||||
// this.requestedId = requestedId;
|
||||
// }
|
||||
//
|
||||
// public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, String identifierName, Object requestedId) {
|
||||
// super(type);
|
||||
// this.identifierType = identifierType;
|
||||
// this.identifierName = identifierName != null ? identifierName : identifierType.getName();
|
||||
// this.requestedId = requestedId;
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.RestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
|
||||
class EntityNotFoundException(modelType: Class<out IModel>, val identifierType: IdentifierType, val identifierName: String, val requestedId: Any) : ModelException(modelType) {
|
||||
constructor(modelType: Class<out IModel>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
class EntityNotFoundRestException(val value: Any) : RestException(HttpStatus.NOT_FOUND) {
|
||||
override val exceptionMessage: String = "An entity could not be found with the given identifier"
|
||||
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
|||
public class ModelException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Le type de modèle qui est sujette à l'exception
|
||||
* Le type de modèle qui est sujet à l'exception
|
||||
*/
|
||||
protected Class<? extends IModel> type;
|
||||
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
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
|
||||
class Employee(
|
||||
@Id
|
||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
override val id: Long,
|
||||
|
||||
val firstName: String = "",
|
||||
|
||||
val lastName: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
val password: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
val isRoot: Boolean = false,
|
||||
|
||||
@field:ManyToOne
|
||||
@Fetch(FetchMode.SELECT)
|
||||
var group: EmployeeGroup? = null,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
val permissions: MutableList<EmployeePermission> = mutableListOf(),
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
val excludedPermissions: MutableList<EmployeePermission> = mutableListOf(),
|
||||
|
||||
val lastLoginTime: LocalDateTime? = null
|
||||
) : IModel
|
||||
|
||||
/** DTO for creating employees. The [Employee] entity doesn't allow to modify passwords. */
|
||||
data class EmployeeDto(
|
||||
@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,
|
||||
|
||||
@field:ManyToOne
|
||||
@Fetch(FetchMode.SELECT)
|
||||
var group: EmployeeGroup? = null,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
val permissions: MutableList<EmployeePermission> = mutableListOf(),
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
val excludedPermissions: MutableList<EmployeePermission> = mutableListOf()
|
||||
)
|
||||
|
||||
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
|
||||
data class EmployeeGroup(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
override val id: Long? = null,
|
||||
|
||||
@Column(unique = true)
|
||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
||||
@field:Size(min = 3)
|
||||
val name: String = "",
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
||||
val permissions: MutableList<EmployeePermission> = mutableListOf(),
|
||||
|
||||
@OneToMany
|
||||
@JsonIgnore
|
||||
val employees: MutableList<Employee> = mutableListOf()
|
||||
) : IModel
|
||||
|
||||
|
||||
data class EmployeeLoginRequest(val id: Long, val password: String)
|
||||
|
||||
|
||||
enum class EmployeePermission(val impliedPermissions: List<EmployeePermission> = listOf()) {
|
||||
// View
|
||||
VIEW_EMPLOYEE,
|
||||
VIEW_EMPLOYEE_GROUP,
|
||||
VIEW(listOf(
|
||||
)),
|
||||
|
||||
// Edit
|
||||
EDIT_EMPLOYEE,
|
||||
EDIT_EMPLOYEE_GROUP,
|
||||
EDIT(listOf(
|
||||
)),
|
||||
|
||||
// Remove
|
||||
REMOVE_EMPLOYEE,
|
||||
REMOVE_EMPLOYEE_GROUP,
|
||||
REMOVE(listOf(
|
||||
)),
|
||||
|
||||
ADMIN(listOf(
|
||||
VIEW,
|
||||
EDIT,
|
||||
REMOVE,
|
||||
|
||||
// Admin only permissions
|
||||
VIEW_EMPLOYEE,
|
||||
VIEW_EMPLOYEE_GROUP,
|
||||
EDIT_EMPLOYEE,
|
||||
EDIT_EMPLOYEE_GROUP,
|
||||
REMOVE_EMPLOYEE,
|
||||
REMOVE_EMPLOYEE_GROUP
|
||||
));
|
||||
|
||||
operator fun contains(permission: EmployeePermission): Boolean {
|
||||
return permission == this || impliedPermissions.any { permission in it }
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets [GrantedAuthority]s of the given [Employee]. */
|
||||
fun Employee.getAuthorities(): MutableCollection<GrantedAuthority> {
|
||||
return getPermissions().map { it.toAuthority() }.toMutableSet()
|
||||
}
|
||||
|
||||
/** Gets [EmployeePermission]s of the given [Employee]. */
|
||||
fun Employee.getPermissions(): Iterable<EmployeePermission> {
|
||||
val grantedPermissions: MutableSet<EmployeePermission> = mutableSetOf()
|
||||
if (group != null) grantedPermissions.addAll(group!!.permissions.flatMap { it.flat() }.filter { excludedPermissions.isEmpty() || excludedPermissions.any { excludedPermission -> it !in excludedPermission } })
|
||||
grantedPermissions.addAll(permissions.flatMap { it.flat() }.filter { excludedPermissions.isEmpty() || excludedPermissions.any { excludedPermission -> it !in excludedPermission } })
|
||||
return grantedPermissions
|
||||
}
|
||||
|
||||
private fun EmployeePermission.flat(): Iterable<EmployeePermission> {
|
||||
return mutableSetOf(this).apply {
|
||||
impliedPermissions.forEach {
|
||||
addAll(it.flat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
|
||||
private fun EmployeePermission.toAuthority(): GrantedAuthority {
|
||||
return SimpleGrantedAuthority(name)
|
||||
}
|
|
@ -10,7 +10,6 @@ import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.NullIdentifierE
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -19,13 +18,13 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractService<E extends IModel, R extends JpaRepository<E, Long>> implements IGenericService<E> {
|
||||
public abstract class AbstractJavaService<E extends IModel, R extends JpaRepository<E, Long>> implements IGenericJavaService<E> {
|
||||
|
||||
protected Logger logger = Preferences.logger;
|
||||
protected R dao;
|
||||
protected Class<E> type;
|
||||
|
||||
public AbstractService(Class<E> type) {
|
||||
public AbstractJavaService(Class<E> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
@ -70,7 +69,7 @@ public abstract class AbstractService<E extends IModel, R extends JpaRepository<
|
|||
}
|
||||
|
||||
@Override
|
||||
public E update(@NonNull E entity) {
|
||||
public E update(@NotNull E entity) {
|
||||
if (entity.getId() == null) throw new NullIdentifierException(type);
|
||||
if (!existsById(entity.getId()))
|
||||
throw new EntityNotFoundException(type, ModelException.IdentifierType.ID, entity.getId());
|
||||
|
@ -79,7 +78,7 @@ public abstract class AbstractService<E extends IModel, R extends JpaRepository<
|
|||
}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull E entity) {
|
||||
public void delete(@NotNull E entity) {
|
||||
dao.delete(entity);
|
||||
}
|
||||
|
||||
|
@ -111,7 +110,7 @@ public abstract class AbstractService<E extends IModel, R extends JpaRepository<
|
|||
* @return Si l'entité est valide pour l'édition.
|
||||
*/
|
||||
@Deprecated(since = "1.3.0", forRemoval = true)
|
||||
public boolean isValidForUpdate(@NonNull E entity) {
|
||||
public boolean isValidForUpdate(@NotNull E entity) {
|
||||
return entity.getId() != null && existsById(entity.getId());
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.services
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.EmployeeGroupRepository
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.EmployeeRepository
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDateTime
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@Service
|
||||
class EmployeeService(val employeeRepository: EmployeeRepository, val passwordEncoder: PasswordEncoder) :
|
||||
AbstractModelService<Employee, EmployeeRepository>(employeeRepository, Employee::class.java) {
|
||||
/** Check if an [Employee] with the given [firstName] and [lastName] exists. */
|
||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean {
|
||||
return repository.existsByFirstNameAndLastName(firstName, lastName)
|
||||
}
|
||||
|
||||
override fun getAll(): Collection<Employee> {
|
||||
return super.getAll().filter { !it.isRoot }
|
||||
}
|
||||
|
||||
override fun getById(id: Long): Employee {
|
||||
return getById(id, true)
|
||||
}
|
||||
|
||||
/** Gets the employee with the given [id]. */
|
||||
fun getById(id: Long, ignoreRoot: Boolean): Employee {
|
||||
return super.getById(id).apply {
|
||||
if (ignoreRoot && isRoot) throw EntityNotFoundRestException(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** Saves the given [employee]. The password contained in the DTO will be hashed in the created [Employee]. */
|
||||
fun save(employee: EmployeeDto): Employee {
|
||||
return save(with(employee) {
|
||||
Employee(id, firstName, lastName, passwordEncoder.encode(password), false, group, permissions, excludedPermissions)
|
||||
})
|
||||
}
|
||||
|
||||
override fun save(entity: Employee): Employee {
|
||||
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
|
||||
throw EntityAlreadyExistsException(type, ModelException.IdentifierType.NAME, "${entity.firstName} ${entity.lastName}")
|
||||
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
/** Updates the last login time of the employee with the given [employeeId]. */
|
||||
fun updateLastLoginTime(employeeId: Long) {
|
||||
update(Employee(id = employeeId, lastLoginTime = LocalDateTime.now()), false)
|
||||
}
|
||||
|
||||
override fun update(entity: Employee): Employee {
|
||||
return update(entity, true)
|
||||
}
|
||||
|
||||
/** Updates de given [entity]. **/
|
||||
fun update(entity: Employee, ignoreRoot: Boolean): Employee {
|
||||
val persistedEmployee = getById(entity.id, ignoreRoot)
|
||||
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
|
||||
if (this != null && id != entity.id)
|
||||
throw EntityAlreadyExistsRestException("${entity.firstName} ${entity.lastName}")
|
||||
}
|
||||
|
||||
return super.update(with(entity) {
|
||||
Employee(
|
||||
id,
|
||||
if (firstName.isNotBlank()) firstName else persistedEmployee.firstName,
|
||||
if (lastName.isNotBlank()) lastName else persistedEmployee.lastName,
|
||||
persistedEmployee.password,
|
||||
if (ignoreRoot) false else persistedEmployee.isRoot,
|
||||
persistedEmployee.group,
|
||||
if (permissions.isNotEmpty()) permissions else persistedEmployee.permissions,
|
||||
if (excludedPermissions.isNotEmpty()) excludedPermissions else persistedEmployee.excludedPermissions,
|
||||
lastLoginTime ?: persistedEmployee.lastLoginTime
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** Adds the given [permission] to the employee with the given [employeeId]. */
|
||||
fun addPermission(employeeId: Long, permission: EmployeePermission) = super.update(getById(employeeId).apply { permissions += permission })
|
||||
|
||||
/** Removes the given [permission] from the employee with the given [employeeId]. */
|
||||
fun removePermission(employeeId: Long, permission: EmployeePermission) = super.update(getById(employeeId).apply { permissions -= permission })
|
||||
|
||||
/** Adds the given [excludedPermission] to the employee with the given [employeeId]. */
|
||||
fun addExcludedPermission(employeeId: Long, excludedPermission: EmployeePermission) = super.update(getById(employeeId).apply { excludedPermissions += excludedPermission })
|
||||
|
||||
/** Removes the given [excludedPermission] to the employee with the given [employeeId]. */
|
||||
fun removeExcludedPermission(employeeId: Long, excludedPermission: EmployeePermission) = super.update(getById(employeeId).apply { excludedPermissions -= excludedPermission })
|
||||
}
|
||||
|
||||
@Service
|
||||
class EmployeeGroupService(val employeeGroupRepository: EmployeeGroupRepository, val employeeService: EmployeeService) : AbstractModelService<EmployeeGroup, EmployeeGroupRepository>(employeeGroupRepository, EmployeeGroup::class.java) {
|
||||
/** Adds the employee with the given [employeeId] to the group with the given [groupId]. */
|
||||
fun addEmployeeToGroup(groupId: Long, employeeId: Long) {
|
||||
addEmployeeToGroup(getById(groupId), employeeService.getById(employeeId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given [employee] to a given [group].
|
||||
*
|
||||
* If the [employee] is already in the [group], nothing will be done.
|
||||
* If the [employee] is already in a group, it will be removed from it.
|
||||
*/
|
||||
@Transactional
|
||||
fun addEmployeeToGroup(group: EmployeeGroup, employee: Employee) {
|
||||
if (employee.group != null) removeEmployeeFromGroup(employee.group!!, employee)
|
||||
if (employee.group == group) return
|
||||
|
||||
group.employees.add(employee)
|
||||
employee.group = group
|
||||
update(group)
|
||||
employeeService.update(employee)
|
||||
}
|
||||
|
||||
/** Removes the employee with the given [employeeId] from the group with the given [groupId]. */
|
||||
fun removeEmployeeFromGroup(groupId: Long, employeeId: Long) =
|
||||
removeEmployeeFromGroup(getById(groupId), employeeService.getById(employeeId))
|
||||
|
||||
/** Removes a given [employee] from the given [group]. */
|
||||
@Transactional
|
||||
fun removeEmployeeFromGroup(group: EmployeeGroup, employee: Employee) {
|
||||
if (employee.group == null) return
|
||||
|
||||
group.employees.remove(employee)
|
||||
employee.group = null
|
||||
update(group)
|
||||
employeeService.update(employee)
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
class EmployeeUserDetailsService(val employeeService: EmployeeService) : UserDetailsService {
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
try {
|
||||
return loadUserByEmployeeId(username.toLong())
|
||||
} catch (ex: EntityNotFoundException) {
|
||||
throw UsernameNotFoundException(username)
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads an [User] for the given [employeeId]. */
|
||||
fun loadUserByEmployeeId(employeeId: Long): UserDetails {
|
||||
val employee = employeeService.getById(employeeId, false)
|
||||
return User(employee.id.toString(), employee.password, employee.getAuthorities())
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface IGenericService<T extends IModel> {
|
||||
public interface IGenericJavaService<T extends IModel> {
|
||||
|
||||
@Deprecated(since = "1.3.0", forRemoval = true)
|
||||
boolean exists(T entity);
|
|
@ -0,0 +1,79 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.core.services
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
|
||||
/** A service implementing the basics CRUD operations. */
|
||||
interface IGenericService<E> {
|
||||
/** Gets all entities. */
|
||||
fun getAll(): Collection<E>
|
||||
|
||||
/** Saves a given [entity]. */
|
||||
fun save(entity: E): E
|
||||
|
||||
/** Saves all given [entities]. */
|
||||
fun saveAll(entities: Iterable<E>): Collection<E>
|
||||
|
||||
/** Updates a given [entity]. */
|
||||
fun update(entity: E): E
|
||||
|
||||
/** Deletes a given [entity]. */
|
||||
fun delete(entity: E)
|
||||
|
||||
/** Deletes all give [entities]. */
|
||||
fun deleteAll(entities: Iterable<E>)
|
||||
}
|
||||
|
||||
/** A service for entities implementing the [IModel] interface. This service implements CRUD operations for [Long] identifiers. */
|
||||
interface IGenericModelService<E : IModel> : IGenericService<E> {
|
||||
/** Checks if an entity with the given [id] exists. */
|
||||
fun existsById(id: Long): Boolean
|
||||
|
||||
/** Gets the entity with the given [id]. */
|
||||
fun getById(id: Long): E
|
||||
|
||||
/** Deletes the entity with the given [id]. */
|
||||
fun deleteById(id: Long)
|
||||
}
|
||||
|
||||
abstract class AbstractService<E, R : JpaRepository<E, *>>(val repository: R, val type: Class<out IModel>) : IGenericService<E> {
|
||||
override fun getAll(): Collection<E> = repository.findAll()
|
||||
|
||||
override fun save(entity: E): E = repository.save(entity)
|
||||
|
||||
override fun saveAll(entities: Iterable<E>): Collection<E> = entities.map(this::save)
|
||||
|
||||
override fun update(entity: E): E = repository.save(entity)
|
||||
|
||||
override fun delete(entity: E) = repository.delete(entity)
|
||||
|
||||
override fun deleteAll(entities: Iterable<E>) = entities.forEach(this::delete)
|
||||
}
|
||||
|
||||
abstract class AbstractModelService<E : IModel, R : JpaRepository<E, Long>>(repository: R, type: Class<out IModel>) : AbstractService<E, R>(repository, type), IGenericModelService<E> {
|
||||
override fun existsById(id: Long): Boolean = repository.existsById(id)
|
||||
|
||||
override fun getById(id: Long): E = repository.findByIdOrNull(id)
|
||||
?: throw EntityNotFoundException(type, ModelException.IdentifierType.ID, id)
|
||||
|
||||
override fun save(entity: E): E {
|
||||
with(entity.id) {
|
||||
if (this != null && existsById(this))
|
||||
throw EntityNotFoundRestException(this)
|
||||
}
|
||||
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) = delete(getById(id))
|
||||
}
|
||||
|
||||
/** Transforms the given object to JSON. **/
|
||||
fun Any.asJson(): String {
|
||||
return jacksonObjectMapper().writeValueAsString(this)
|
||||
}
|
|
@ -4,8 +4,8 @@ import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityAlreadyEx
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityLinkedException;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Company;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.CompanyDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.CompanyRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -13,7 +13,7 @@ import org.springframework.stereotype.Service;
|
|||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Service
|
||||
public class CompanyService extends AbstractService<Company, CompanyDao> {
|
||||
public class CompanyService extends AbstractJavaService<Company, CompanyRepository> {
|
||||
|
||||
private RecipeService recipeService;
|
||||
|
||||
|
@ -22,8 +22,8 @@ public class CompanyService extends AbstractService<Company, CompanyDao> {
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setCompanyDao(CompanyDao companyDao) {
|
||||
this.dao = companyDao;
|
||||
public void setCompanyDao(CompanyRepository companyRepository) {
|
||||
this.dao = companyRepository;
|
||||
}
|
||||
|
||||
// Pour éviter les dépendances circulaires
|
||||
|
|
|
@ -5,9 +5,9 @@ import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundE
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.SimdutService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MaterialDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MaterialRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
@ -18,7 +18,7 @@ import java.util.Optional;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MaterialService extends AbstractService<Material, MaterialDao> {
|
||||
public class MaterialService extends AbstractJavaService<Material, MaterialRepository> {
|
||||
|
||||
private MixQuantityService mixQuantityService;
|
||||
private SimdutService simdutService;
|
||||
|
@ -28,8 +28,8 @@ public class MaterialService extends AbstractService<Material, MaterialDao> {
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setMaterialDao(MaterialDao materialDao) {
|
||||
this.dao = materialDao;
|
||||
public void setMaterialDao(MaterialRepository materialRepository) {
|
||||
this.dao = materialRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -7,8 +7,8 @@ import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundE
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MaterialTypeEditorDto;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MaterialTypeDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MaterialTypeRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -19,7 +19,7 @@ import java.util.Optional;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MaterialTypeService extends AbstractService<MaterialType, MaterialTypeDao> {
|
||||
public class MaterialTypeService extends AbstractJavaService<MaterialType, MaterialTypeRepository> {
|
||||
|
||||
private MaterialService materialService;
|
||||
|
||||
|
@ -30,8 +30,8 @@ public class MaterialTypeService extends AbstractService<MaterialType, MaterialT
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setMaterialTypeDao(MaterialTypeDao materialTypeDao) {
|
||||
this.dao = materialTypeDao;
|
||||
public void setMaterialTypeDao(MaterialTypeRepository materialTypeRepository) {
|
||||
this.dao = materialTypeRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -2,21 +2,21 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
|
|||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixQuantity;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixQuantityDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixQuantityRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MixQuantityService extends AbstractService<MixQuantity, MixQuantityDao> {
|
||||
public class MixQuantityService extends AbstractJavaService<MixQuantity, MixQuantityRepository> {
|
||||
|
||||
public MixQuantityService() {
|
||||
super(MixQuantity.class);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setMixQuantityDao(MixQuantityDao mixQuantityDao) {
|
||||
this.dao = mixQuantityDao;
|
||||
public void setMixQuantityDao(MixQuantityRepository mixQuantityRepository) {
|
||||
this.dao = mixQuantityRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,9 +7,9 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MixFormDto;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.utils.MixBuilder;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -19,7 +19,7 @@ import java.util.Comparator;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MixService extends AbstractService<Mix, MixDao> {
|
||||
public class MixService extends AbstractJavaService<Mix, MixRepository> {
|
||||
|
||||
private MaterialService materialService;
|
||||
private MixQuantityService mixQuantityService;
|
||||
|
@ -30,8 +30,8 @@ public class MixService extends AbstractService<Mix, MixDao> {
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setMixDao(MixDao mixDao) {
|
||||
this.dao = mixDao;
|
||||
public void setMixDao(MixRepository mixRepository) {
|
||||
this.dao = mixRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -6,8 +6,8 @@ import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixTypeDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.MixTypeRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import javax.validation.constraints.NotNull;
|
|||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class MixTypeService extends AbstractService<MixType, MixTypeDao> {
|
||||
public class MixTypeService extends AbstractJavaService<MixType, MixTypeRepository> {
|
||||
|
||||
private MaterialService materialService;
|
||||
|
||||
|
@ -24,8 +24,8 @@ public class MixTypeService extends AbstractService<MixType, MixTypeDao> {
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setMixTypeDao(MixTypeDao mixTypeDao) {
|
||||
this.dao = mixTypeDao;
|
||||
public void setMixTypeDao(MixTypeRepository mixTypeRepository) {
|
||||
this.dao = mixTypeRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -5,9 +5,9 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeEditorFormDto;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeExplorerFormDto;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.ImagesService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.RecipeDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.RecipeRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -17,7 +17,7 @@ import java.util.*;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class RecipeService extends AbstractService<Recipe, RecipeDao> {
|
||||
public class RecipeService extends AbstractJavaService<Recipe, RecipeRepository> {
|
||||
|
||||
private CompanyService companyService;
|
||||
private MixService mixService;
|
||||
|
@ -29,8 +29,8 @@ public class RecipeService extends AbstractService<Recipe, RecipeDao> {
|
|||
}
|
||||
|
||||
@Autowired
|
||||
public void setRecipeDao(RecipeDao recipeDao) {
|
||||
this.dao = recipeDao;
|
||||
public void setRecipeDao(RecipeRepository recipeRepository) {
|
||||
this.dao = recipeRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -2,8 +2,8 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
|
|||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.RecipeStep;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.StepDao;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.AbstractJavaService;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.dao.StepRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -12,15 +12,15 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class StepService extends AbstractService<RecipeStep, StepDao> {
|
||||
public class StepService extends AbstractJavaService<RecipeStep, StepRepository> {
|
||||
|
||||
public StepService() {
|
||||
super(RecipeStep.class);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setStepDao(StepDao stepDao) {
|
||||
this.dao = stepDao;
|
||||
public void setStepDao(StepRepository stepRepository) {
|
||||
this.dao = stepRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CompanyDao extends JpaRepository<Company, Long> {
|
||||
public interface CompanyRepository extends JpaRepository<Company, Long> {
|
||||
|
||||
boolean existsByName(String name);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.dao
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Employee
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeeGroup
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface EmployeeRepository : JpaRepository<Employee, Long> {
|
||||
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
|
||||
|
||||
fun findByFirstNameAndLastName(firstName: String, lastName: String): Employee?
|
||||
}
|
||||
|
||||
@Repository
|
||||
interface EmployeeGroupRepository : JpaRepository<EmployeeGroup, Long>
|
|
@ -9,7 +9,7 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MaterialDao extends JpaRepository<Material, Long> {
|
||||
public interface MaterialRepository extends JpaRepository<Material, Long> {
|
||||
|
||||
boolean existsByName(String name);
|
||||
|
|
@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface MaterialTypeDao extends JpaRepository<MaterialType, Long> {
|
||||
public interface MaterialTypeRepository extends JpaRepository<MaterialType, Long> {
|
||||
|
||||
boolean existsByName(String name);
|
||||
|
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
|
|||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface MixQuantityDao extends JpaRepository<MixQuantity, Long> {
|
||||
public interface MixQuantityRepository extends JpaRepository<MixQuantity, Long> {
|
||||
List<MixQuantity> findAllByMaterial(Material material);
|
||||
|
||||
boolean existsByMaterial(Material material);
|
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
|
|||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface MixDao extends JpaRepository<Mix, Long> {
|
||||
public interface MixRepository extends JpaRepository<Mix, Long> {
|
||||
|
||||
List<Mix> findAllByRecipe(Recipe recipe);
|
||||
|
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
|
|||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MixTypeDao extends JpaRepository<MixType, Long> {
|
||||
public interface MixTypeRepository extends JpaRepository<MixType, Long> {
|
||||
|
||||
boolean existsByName(String name);
|
||||
|
|
@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository;
|
|||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface RecipeDao extends JpaRepository<Recipe, Long> {
|
||||
public interface RecipeRepository extends JpaRepository<Recipe, Long> {
|
||||
|
||||
List<Recipe> findAllByCompany(Company company);
|
||||
|
|
@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public interface StepDao extends JpaRepository<RecipeStep, Long> {
|
||||
public interface StepRepository extends JpaRepository<RecipeStep, Long> {
|
||||
|
||||
List<RecipeStep> findAllByRecipe(Recipe recipe);
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.rest
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Employee
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeeDto
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeeGroup
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.EmployeePermission
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.EmployeeGroupService
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.EmployeeService
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.net.URI
|
||||
import java.security.Principal
|
||||
import javax.validation.Valid
|
||||
|
||||
private const val EMPLOYEE_CONTROLLER_PATH = "api/employee"
|
||||
private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group"
|
||||
|
||||
@RestController
|
||||
@RequestMapping(EMPLOYEE_CONTROLLER_PATH)
|
||||
@Profile("rest")
|
||||
class EmployeeController(employeeService: EmployeeService) :
|
||||
AbstractRestModelController<Employee, EmployeeService>(employeeService, EMPLOYEE_CONTROLLER_PATH) {
|
||||
@GetMapping("current")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
fun getCurrent(loggedInEmployee: Principal): ResponseEntity<Employee> = getById(loggedInEmployee.name.toLong())
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun save(@Valid @RequestBody employee: EmployeeDto): ResponseEntity<Employee> {
|
||||
val saved = service.save(employee)
|
||||
return ResponseEntity
|
||||
.created(URI("$controllerPath/${getEntityId(saved)}"))
|
||||
.body(saved)
|
||||
}
|
||||
|
||||
@PostMapping("create")
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
override fun save(entity: Employee): ResponseEntity<Employee> = ResponseEntity.notFound().build()
|
||||
|
||||
@PutMapping("{employeeId}/permissions/{permission}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasAnyAuthority('EDIT_EMPLOYEE')")
|
||||
fun addPermission(@PathVariable employeeId: Long, @PathVariable permission: EmployeePermission): ResponseEntity<Void> {
|
||||
service.addPermission(employeeId, permission)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
|
||||
@DeleteMapping("{employeeId}/permissions/{permission}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasAnyAuthority('EDIT_EMPLOYEE')")
|
||||
fun removePermission(@PathVariable employeeId: Long, @PathVariable permission: EmployeePermission): ResponseEntity<Void> {
|
||||
service.removePermission(employeeId, permission)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
|
||||
@PutMapping("{employeeId}/excludedPermissions/{excludedPermission}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasAnyAuthority('EDIT_EMPLOYEE')")
|
||||
fun addExcludedPermission(@PathVariable employeeId: Long, @PathVariable excludedPermission: EmployeePermission): ResponseEntity<Void> {
|
||||
service.addExcludedPermission(employeeId, excludedPermission)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
|
||||
@DeleteMapping("{employeeId}/excludedPermissions/{excludedPermission}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasAnyAuthority('EDIT_EMPLOYEE')")
|
||||
fun removeExcludedPermission(@PathVariable employeeId: Long, @PathVariable excludedPermission: EmployeePermission): ResponseEntity<Void> {
|
||||
service.removeExcludedPermission(employeeId, excludedPermission)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH)
|
||||
@Profile("rest")
|
||||
class GroupsController(groupService: EmployeeGroupService) :
|
||||
AbstractRestModelController<EmployeeGroup, EmployeeGroupService>(groupService, EMPLOYEE_GROUP_CONTROLLER_PATH) {
|
||||
@PutMapping("{groupId}/{employeeId}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun addEmployeeToGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity<Void> {
|
||||
service.addEmployeeToGroup(groupId, employeeId)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
|
||||
@DeleteMapping("{groupId}/{employeeId}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun removeEmployeeFromGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity<Void> {
|
||||
service.removeEmployeeFromGroup(groupId, employeeId)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.rest
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.IGenericModelService
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.IGenericService
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.net.URI
|
||||
import javax.validation.Valid
|
||||
|
||||
interface IRestController<E> {
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
fun getAll(): ResponseEntity<Iterable<E>>
|
||||
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun save(entity: E): ResponseEntity<E>
|
||||
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun update(entity: E): ResponseEntity<Void>
|
||||
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun delete(entity: E): ResponseEntity<Void>
|
||||
}
|
||||
|
||||
interface IRestModelController<E : IModel> : IRestController<E> {
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
fun getById(id: Long): ResponseEntity<E>
|
||||
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun deleteById(id: Long): ResponseEntity<Void>
|
||||
}
|
||||
|
||||
abstract class AbstractRestController<E, S : IGenericService<E>>(val service: S, protected val controllerPath: String) :
|
||||
IRestController<E> {
|
||||
protected abstract fun getEntityId(entity: E): Any?
|
||||
|
||||
@GetMapping
|
||||
override fun getAll(): ResponseEntity<Iterable<E>> = ResponseEntity.ok(service.getAll())
|
||||
|
||||
@PostMapping
|
||||
override fun save(@Valid @RequestBody entity: E): ResponseEntity<E> {
|
||||
val saved = service.save(entity)
|
||||
return ResponseEntity
|
||||
.created(URI("$controllerPath/${getEntityId(saved)}"))
|
||||
.body(saved)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
override fun update(@Valid @RequestBody entity: E): ResponseEntity<Void> {
|
||||
service.update(entity)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
override fun delete(@Valid @RequestBody entity: E): ResponseEntity<Void> {
|
||||
service.delete(entity)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractRestModelController<E : IModel, S : IGenericModelService<E>>(service: S, controllerPath: String) :
|
||||
AbstractRestController<E, S>(service, controllerPath), IRestModelController<E> {
|
||||
override fun getEntityId(entity: E) = entity.id
|
||||
|
||||
@GetMapping("{id}")
|
||||
override fun getById(@PathVariable id: Long): ResponseEntity<E> = ResponseEntity.ok(service.getById(id))
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
override fun deleteById(@PathVariable id: Long): ResponseEntity<Void> {
|
||||
service.deleteById(id)
|
||||
return ResponseEntity
|
||||
.noContent()
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
|
|||
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MixFormDto;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.ServiceKt;
|
||||
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
@ -52,7 +53,7 @@ public class MixCreatorController {
|
|||
modelResponseBuilder
|
||||
.addResponseData(ResponseDataType.RECIPE, recipe)
|
||||
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll())
|
||||
.addResponseData(ResponseDataType.MATERIALS_JSON, materialService.asJson(mixService.getAvailableMaterialsForNewMix(recipe)));
|
||||
.addResponseData(ResponseDataType.MATERIALS_JSON, ServiceKt.asJson(mixService.getAvailableMaterialsForNewMix(recipe)));
|
||||
|
||||
if (materialService.getAll().isEmpty())
|
||||
modelResponseBuilder.addResponseData(ResponseDataType.BLOCK_BUTTON, true);
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
spring.resources.static-locations=classpath:/angular/static/
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
|
|
@ -2,50 +2,43 @@
|
|||
spring.datasource.url=jdbc:h2:file:./workdir/recipes
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=LWK4Y7TvEbNyhu1yCoG3
|
||||
|
||||
# CONSOLE DE LA BDD
|
||||
spring.h2.console.path=/dbconsole
|
||||
|
||||
# PORT
|
||||
server.port=9090
|
||||
|
||||
# CRE
|
||||
cre.server.upload-directory=./workdir
|
||||
cre.server.password-file=passwords.txt
|
||||
cre.server.url-use-port=true
|
||||
cre.server.url-use-https=false
|
||||
|
||||
cre.security.jwt-secret=CtnvGQjgZ44A1fh295gE
|
||||
cre.security.jwt-duration=18000000
|
||||
cre.security.root.id=9999
|
||||
cre.security.root.password=password
|
||||
# TYPES DE PRODUIT PAR DÉFAUT
|
||||
entities.material-types.defaults[0].name=Aucun
|
||||
entities.material-types.defaults[0].prefix=
|
||||
entities.material-types.defaults[0].use-percentages=false
|
||||
|
||||
entities.material-types.defaults[1].name=Base
|
||||
entities.material-types.defaults[1].prefix=BAS
|
||||
entities.material-types.defaults[1].use-percentages=false
|
||||
|
||||
entities.material-types.base-name=Base
|
||||
|
||||
# DEBUG
|
||||
spring.jpa.show-sql=true
|
||||
spring.h2.console.enabled=true
|
||||
# Permet d'accéder à la console de la BDD à distance
|
||||
spring.h2.console.settings.trace=false
|
||||
spring.h2.console.settings.web-allow-others=false
|
||||
|
||||
# NE PAS MODIFIER
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
|
||||
spring.messages.fallback-to-system-locale=true
|
||||
|
||||
spring.servlet.multipart.max-file-size=10MB
|
||||
spring.servlet.multipart.max-request-size=15MB
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
spring.jpa.open-in-view=true
|
||||
|
||||
server.http2.enabled=true
|
||||
server.error.whitelabel.enabled=false
|
||||
|
||||
#spring.redis.host=localhost
|
||||
#spring.redis.port=6379
|
||||
spring.profiles.active=@spring.profiles.active@
|
||||
|
|
|
@ -30,4 +30,4 @@
|
|||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="LOG_FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
|
Loading…
Reference in New Issue