diff --git a/build.gradle.kts b/build.gradle.kts index 9added0..5d9fc87 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") } diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/ColorRecipesExplorerApplication.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/ColorRecipesExplorerApplication.kt index 6454151..1ab5b17 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/ColorRecipesExplorerApplication.kt +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/ColorRecipesExplorerApplication.kt @@ -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) { runApplication(*args) diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/SpringConfiguration.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/SpringConfiguration.java index c6a6022..cc0f830 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/SpringConfiguration.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/SpringConfiguration.java @@ -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(); } diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt new file mode 100644 index 0000000..bae22ca --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt @@ -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.ExpressionInterceptUrlRegistry.generateAuthorizations(): + ExpressionUrlAuthorizationConfigurer.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 +) { + 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 + )) +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/RestException.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/RestException.kt new file mode 100644 index 0000000..2d53f98 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/RestException.kt @@ -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 { + return handleRestExceptions(exception.toRestException(), request) + } + + @ExceptionHandler(RestException::class) + fun handleRestExceptions(exception: RestException, request: WebRequest): ResponseEntity { + return handleExceptionInternal(exception, exception.buildBody(), HttpHeaders(), exception.httpStatus, request) + } + + override fun handleMethodArgumentNotValid(ex: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity { + val errors = hashMapOf() + 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") + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.java index 90a830e..5810205 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.java @@ -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 type, IdentifierType identifierType, Object requestedId) { - super(type); - this.identifierType = identifierType; - this.requestedId = requestedId; - } - - public EntityAlreadyExistsException(Class 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 type, IdentifierType identifierType, Object requestedId) { +// super(type); +// this.identifierType = identifierType; +// this.requestedId = requestedId; +// } +// +// public EntityAlreadyExistsException(Class type, IdentifierType identifierType, String identifierName, Object requestedId) { +// super(type); +// this.identifierType = identifierType; +// this.identifierName = identifierName != null ? identifierName : identifierType.getName(); +// this.requestedId = requestedId; +// } +//} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.kt new file mode 100644 index 0000000..0ddac37 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityAlreadyExistsException.kt @@ -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, val identifierType: IdentifierType, val identifierName: String?, val requestedId: Any) : ModelException(modelType) { + constructor(modelType: Class, 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 + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.java index 0a83d13..6308de8 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.java @@ -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 type, IdentifierType identifierType, Object requestedId) { - super(type); - this.identifierType = identifierType; - this.requestedId = requestedId; - } - - public EntityNotFoundException(Class 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 type, IdentifierType identifierType, Object requestedId) { +// super(type); +// this.identifierType = identifierType; +// this.requestedId = requestedId; +// } +// +// public EntityNotFoundException(Class type, IdentifierType identifierType, String identifierName, Object requestedId) { +// super(type); +// this.identifierType = identifierType; +// this.identifierName = identifierName != null ? identifierName : identifierType.getName(); +// this.requestedId = requestedId; +// } +//} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.kt new file mode 100644 index 0000000..3276adc --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/EntityNotFoundException.kt @@ -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, val identifierType: IdentifierType, val identifierName: String, val requestedId: Any) : ModelException(modelType) { + constructor(modelType: Class, 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 + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/ModelException.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/ModelException.java index 1938320..0e6f431 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/ModelException.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/model/ModelException.java @@ -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 type; diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt new file mode 100644 index 0000000..2833f8e --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt @@ -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 = mutableListOf(), + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @Fetch(FetchMode.SUBSELECT) + val excludedPermissions: MutableList = 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 = mutableListOf(), + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @Fetch(FetchMode.SUBSELECT) + val excludedPermissions: MutableList = 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 = mutableListOf(), + + @OneToMany + @JsonIgnore + val employees: MutableList = mutableListOf() +) : IModel + + +data class EmployeeLoginRequest(val id: Long, val password: String) + + +enum class EmployeePermission(val impliedPermissions: List = 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 { + return getPermissions().map { it.toAuthority() }.toMutableSet() +} + +/** Gets [EmployeePermission]s of the given [Employee]. */ +fun Employee.getPermissions(): Iterable { + val grantedPermissions: MutableSet = 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 { + return mutableSetOf(this).apply { + impliedPermissions.forEach { + addAll(it.flat()) + } + } +} + +/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */ +private fun EmployeePermission.toAuthority(): GrantedAuthority { + return SimpleGrantedAuthority(name) +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractJavaService.java similarity index 91% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractService.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractJavaService.java index beab218..d848dcf 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AbstractJavaService.java @@ -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> implements IGenericService { +public abstract class AbstractJavaService> implements IGenericJavaService { protected Logger logger = Preferences.logger; protected R dao; protected Class type; - public AbstractService(Class type) { + public AbstractJavaService(Class type) { this.type = type; } @@ -70,7 +69,7 @@ public abstract class AbstractService(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 { + 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(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()) + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericJavaService.java similarity index 96% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericService.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericJavaService.java index 40deabc..72f9789 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/IGenericJavaService.java @@ -5,7 +5,7 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel; import java.util.Collection; import java.util.List; -public interface IGenericService { +public interface IGenericJavaService { @Deprecated(since = "1.3.0", forRemoval = true) boolean exists(T entity); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/Service.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/Service.kt new file mode 100644 index 0000000..54b096d --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/Service.kt @@ -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 { + /** Gets all entities. */ + fun getAll(): Collection + + /** Saves a given [entity]. */ + fun save(entity: E): E + + /** Saves all given [entities]. */ + fun saveAll(entities: Iterable): Collection + + /** 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) +} + +/** A service for entities implementing the [IModel] interface. This service implements CRUD operations for [Long] identifiers. */ +interface IGenericModelService : IGenericService { + /** 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>(val repository: R, val type: Class) : IGenericService { + override fun getAll(): Collection = repository.findAll() + + override fun save(entity: E): E = repository.save(entity) + + override fun saveAll(entities: Iterable): Collection = 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) = entities.forEach(this::delete) +} + +abstract class AbstractModelService>(repository: R, type: Class) : AbstractService(repository, type), IGenericModelService { + 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) +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/CompanyService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/CompanyService.java index d0b9ea1..3891b75 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/CompanyService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/CompanyService.java @@ -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 { +public class CompanyService extends AbstractJavaService { private RecipeService recipeService; @@ -22,8 +22,8 @@ public class CompanyService extends AbstractService { } @Autowired - public void setCompanyDao(CompanyDao companyDao) { - this.dao = companyDao; + public void setCompanyDao(CompanyRepository companyRepository) { + this.dao = companyRepository; } // Pour éviter les dépendances circulaires diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialService.java index af02186..24681f4 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialService.java @@ -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 { +public class MaterialService extends AbstractJavaService { private MixQuantityService mixQuantityService; private SimdutService simdutService; @@ -28,8 +28,8 @@ public class MaterialService extends AbstractService { } @Autowired - public void setMaterialDao(MaterialDao materialDao) { - this.dao = materialDao; + public void setMaterialDao(MaterialRepository materialRepository) { + this.dao = materialRepository; } @Autowired diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialTypeService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialTypeService.java index 75b6d4b..7d8e18c 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialTypeService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MaterialTypeService.java @@ -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 { +public class MaterialTypeService extends AbstractJavaService { private MaterialService materialService; @@ -30,8 +30,8 @@ public class MaterialTypeService extends AbstractService { +public class MixQuantityService extends AbstractJavaService { public MixQuantityService() { super(MixQuantity.class); } @Autowired - public void setMixQuantityDao(MixQuantityDao mixQuantityDao) { - this.dao = mixQuantityDao; + public void setMixQuantityDao(MixQuantityRepository mixQuantityRepository) { + this.dao = mixQuantityRepository; } /** diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixService.java index ee9b668..91b4971 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixService.java @@ -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 { +public class MixService extends AbstractJavaService { private MaterialService materialService; private MixQuantityService mixQuantityService; @@ -30,8 +30,8 @@ public class MixService extends AbstractService { } @Autowired - public void setMixDao(MixDao mixDao) { - this.dao = mixDao; + public void setMixDao(MixRepository mixRepository) { + this.dao = mixRepository; } @Autowired diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixTypeService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixTypeService.java index 0e167f5..eae92d0 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixTypeService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/MixTypeService.java @@ -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 { +public class MixTypeService extends AbstractJavaService { private MaterialService materialService; @@ -24,8 +24,8 @@ public class MixTypeService extends AbstractService { } @Autowired - public void setMixTypeDao(MixTypeDao mixTypeDao) { - this.dao = mixTypeDao; + public void setMixTypeDao(MixTypeRepository mixTypeRepository) { + this.dao = mixTypeRepository; } @Autowired diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/RecipeService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/RecipeService.java index 63b8e45..5f659b5 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/RecipeService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/RecipeService.java @@ -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 { +public class RecipeService extends AbstractJavaService { private CompanyService companyService; private MixService mixService; @@ -29,8 +29,8 @@ public class RecipeService extends AbstractService { } @Autowired - public void setRecipeDao(RecipeDao recipeDao) { - this.dao = recipeDao; + public void setRecipeDao(RecipeRepository recipeRepository) { + this.dao = recipeRepository; } @Autowired diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/StepService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/StepService.java index a87eaf5..98675fc 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/StepService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/model/StepService.java @@ -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 { +public class StepService extends AbstractJavaService { public StepService() { super(RecipeStep.class); } @Autowired - public void setStepDao(StepDao stepDao) { - this.dao = stepDao; + public void setStepDao(StepRepository stepRepository) { + this.dao = stepRepository; } /** diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyRepository.java similarity index 79% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyRepository.java index c1e2e85..0c9c076 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/CompanyRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface CompanyDao extends JpaRepository { +public interface CompanyRepository extends JpaRepository { boolean existsByName(String name); } diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/EmployeeRepository.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/EmployeeRepository.kt new file mode 100644 index 0000000..9a6c240 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/EmployeeRepository.kt @@ -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 { + fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean + + fun findByFirstNameAndLastName(firstName: String, lastName: String): Employee? +} + +@Repository +interface EmployeeGroupRepository : JpaRepository diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialRepository.java similarity index 88% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialRepository.java index b9d48f3..5756412 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialRepository.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Optional; @Repository -public interface MaterialDao extends JpaRepository { +public interface MaterialRepository extends JpaRepository { boolean existsByName(String name); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeRepository.java similarity index 82% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeRepository.java index 479d2a0..2463e08 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MaterialTypeRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -public interface MaterialTypeDao extends JpaRepository { +public interface MaterialTypeRepository extends JpaRepository { boolean existsByName(String name); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityRepository.java similarity index 84% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityRepository.java index 4bcaf07..e4fc5bb 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixQuantityRepository.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface MixQuantityDao extends JpaRepository { +public interface MixQuantityRepository extends JpaRepository { List findAllByMaterial(Material material); boolean existsByMaterial(Material material); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixRepository.java similarity index 84% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixRepository.java index 1e82afa..2fe8942 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixRepository.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface MixDao extends JpaRepository { +public interface MixRepository extends JpaRepository { List findAllByRecipe(Recipe recipe); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeRepository.java similarity index 86% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeRepository.java index 73319b8..52f8c45 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/MixTypeRepository.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface MixTypeDao extends JpaRepository { +public interface MixTypeRepository extends JpaRepository { boolean existsByName(String name); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeRepository.java similarity index 87% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeRepository.java index eb7ddfb..f0ad937 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/RecipeRepository.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface RecipeDao extends JpaRepository { +public interface RecipeRepository extends JpaRepository { List findAllByCompany(Company company); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepDao.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepRepository.java similarity index 81% rename from src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepDao.java rename to src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepRepository.java index 0f32f01..ccf70d1 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepDao.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/dao/StepRepository.java @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -public interface StepDao extends JpaRepository { +public interface StepRepository extends JpaRepository { List findAllByRecipe(Recipe recipe); diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt new file mode 100644 index 0000000..d39bc26 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt @@ -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(employeeService, EMPLOYEE_CONTROLLER_PATH) { + @GetMapping("current") + @ResponseStatus(HttpStatus.OK) + fun getCurrent(loggedInEmployee: Principal): ResponseEntity = getById(loggedInEmployee.name.toLong()) + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + fun save(@Valid @RequestBody employee: EmployeeDto): ResponseEntity { + 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 = ResponseEntity.notFound().build() + + @PutMapping("{employeeId}/permissions/{permission}") + @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasAnyAuthority('EDIT_EMPLOYEE')") + fun addPermission(@PathVariable employeeId: Long, @PathVariable permission: EmployeePermission): ResponseEntity { + 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 { + 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 { + 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 { + service.removeExcludedPermission(employeeId, excludedPermission) + return ResponseEntity + .noContent() + .build() + } +} + +@RestController +@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) +@Profile("rest") +class GroupsController(groupService: EmployeeGroupService) : + AbstractRestModelController(groupService, EMPLOYEE_GROUP_CONTROLLER_PATH) { + @PutMapping("{groupId}/{employeeId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + fun addEmployeeToGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity { + service.addEmployeeToGroup(groupId, employeeId) + return ResponseEntity + .noContent() + .build() + } + + @DeleteMapping("{groupId}/{employeeId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + fun removeEmployeeFromGroup(@PathVariable groupId: Long, @PathVariable employeeId: Long): ResponseEntity { + service.removeEmployeeFromGroup(groupId, employeeId) + return ResponseEntity + .noContent() + .build() + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/RestController.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/RestController.kt new file mode 100644 index 0000000..dc69700 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/RestController.kt @@ -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 { + @ResponseStatus(HttpStatus.OK) + fun getAll(): ResponseEntity> + + @ResponseStatus(HttpStatus.CREATED) + fun save(entity: E): ResponseEntity + + @ResponseStatus(HttpStatus.NO_CONTENT) + fun update(entity: E): ResponseEntity + + @ResponseStatus(HttpStatus.NO_CONTENT) + fun delete(entity: E): ResponseEntity +} + +interface IRestModelController : IRestController { + @ResponseStatus(HttpStatus.OK) + fun getById(id: Long): ResponseEntity + + @ResponseStatus(HttpStatus.NO_CONTENT) + fun deleteById(id: Long): ResponseEntity +} + +abstract class AbstractRestController>(val service: S, protected val controllerPath: String) : + IRestController { + protected abstract fun getEntityId(entity: E): Any? + + @GetMapping + override fun getAll(): ResponseEntity> = ResponseEntity.ok(service.getAll()) + + @PostMapping + override fun save(@Valid @RequestBody entity: E): ResponseEntity { + val saved = service.save(entity) + return ResponseEntity + .created(URI("$controllerPath/${getEntityId(saved)}")) + .body(saved) + } + + @PutMapping + override fun update(@Valid @RequestBody entity: E): ResponseEntity { + service.update(entity) + return ResponseEntity + .noContent() + .build() + } + + @DeleteMapping + override fun delete(@Valid @RequestBody entity: E): ResponseEntity { + service.delete(entity) + return ResponseEntity + .noContent() + .build() + } +} + +abstract class AbstractRestModelController>(service: S, controllerPath: String) : + AbstractRestController(service, controllerPath), IRestModelController { + override fun getEntityId(entity: E) = entity.id + + @GetMapping("{id}") + override fun getById(@PathVariable id: Long): ResponseEntity = ResponseEntity.ok(service.getById(id)) + + @DeleteMapping("{id}") + override fun deleteById(@PathVariable id: Long): ResponseEntity { + service.deleteById(id) + return ResponseEntity + .noContent() + .build() + } +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/thymeleaf/creators/MixCreatorController.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/thymeleaf/creators/MixCreatorController.java index a35d5b7..9a5948b 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/thymeleaf/creators/MixCreatorController.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/thymeleaf/creators/MixCreatorController.java @@ -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); diff --git a/src/main/resources/application-angular.properties b/src/main/resources/application-angular.properties index 2299578..bec1d94 100644 --- a/src/main/resources/application-angular.properties +++ b/src/main/resources/application-angular.properties @@ -1,2 +1 @@ spring.resources.static-locations=classpath:/angular/static/ -spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration diff --git a/src/main/resources/application-rest.properties b/src/main/resources/application-rest.properties new file mode 100644 index 0000000..fe895e2 --- /dev/null +++ b/src/main/resources/application-rest.properties @@ -0,0 +1 @@ +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6f6d293..0befda7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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@ diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index b9b70ff..4ddd81b 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -30,4 +30,4 @@ - \ No newline at end of file +