Merge branch 'better-permissions' into 'master'

Resolve "Meilleure gestion des permissions"

Closes #57 and #56

See merge request color-recipes-explorer/backend!22
This commit is contained in:
William Nolin 2021-04-05 23:33:48 +00:00
commit 2753ffde1b
15 changed files with 534 additions and 487 deletions

View File

@ -26,21 +26,18 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
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 org.springframework.web.util.WebUtils
import java.util.*
@ -67,18 +64,16 @@ class WebSecurityConfig(
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
fun passwordEncoder() =
BCryptPasswordEncoder()
@Bean
override fun authenticationManagerBean(): AuthenticationManager {
return super.authenticationManagerBean()
}
override fun authenticationManagerBean(): AuthenticationManager =
super.authenticationManagerBean()
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
return UrlBasedCorsConfigurationSource().apply {
fun corsConfigurationSource() =
UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", CorsConfiguration().apply {
allowedOrigins = listOf("http://localhost:4200") // Angular development server
allowedMethods = listOf(
@ -92,7 +87,6 @@ class WebSecurityConfig(
allowCredentials = true
}.applyPermitDefaultValues())
}
}
@PostConstruct
fun initWebSecurity() {
@ -126,18 +120,6 @@ class WebSecurityConfig(
}
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
.headers().frameOptions().disable()
.and()
@ -160,11 +142,10 @@ class WebSecurityConfig(
if (!debugMode) {
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/").permitAll()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/employee/logout").permitAll()
.antMatchers(HttpMethod.GET, "/api/employee/current").authenticated()
.generateAuthorizations()
.antMatchers("/api/logout").authenticated()
.antMatchers("/api/employee/current").authenticated()
.anyRequest().authenticated()
} else {
http
.cors()
@ -304,83 +285,3 @@ class SecurityConfigurationProperties {
class SystemUserCredentials(var id: Long? = null, var password: String? = null)
}
private enum class ControllerAuthorizations(
val antMatcher: String,
val permissions: Map<HttpMethod, EmployeePermission>
) {
INVENTORY_ADD(
"/api/inventory/add/**", mapOf(
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL
)
),
INVENTORY_DEDUCT(
"/api/inventory/deduct/**", mapOf(
HttpMethod.PUT to EmployeePermission.VIEW_MATERIAL
)
),
MATERIALS(
"/api/material/**", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_MATERIAL,
HttpMethod.POST to EmployeePermission.EDIT_MATERIAL,
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL,
HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL
)
),
MATERIAL_TYPES(
"/api/materialtype/**", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_MATERIAL_TYPE,
HttpMethod.POST to EmployeePermission.EDIT_MATERIAL_TYPE,
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL_TYPE,
HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL_TYPE
)
),
COMPANY(
"/api/company/**", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_COMPANY,
HttpMethod.POST to EmployeePermission.EDIT_COMPANY,
HttpMethod.PUT to EmployeePermission.EDIT_COMPANY,
HttpMethod.DELETE to EmployeePermission.REMOVE_COMPANY
)
),
RECIPE_STEP(
"/api/recipe/**", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_RECIPE,
HttpMethod.POST to EmployeePermission.EDIT_RECIPE,
HttpMethod.PUT to EmployeePermission.EDIT_RECIPE,
HttpMethod.DELETE to EmployeePermission.REMOVE_RECIPE
)
),
SET_BROWSER_DEFAULT_GROUP(
"/api/employee/group/default/**", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE_GROUP,
HttpMethod.POST to EmployeePermission.SET_BROWSER_DEFAULT_GROUP
)
),
EMPLOYEES_FOR_GROUP(
"/api/employee/group/*/employees", mapOf(
HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE
)
),
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_PASSWORD(
"/api/employee/*/password", mapOf(
HttpMethod.PUT to EmployeePermission.EDIT_EMPLOYEE_PASSWORD
)
),
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
)
)
}

View File

@ -0,0 +1,45 @@
package dev.fyloz.colorrecipesexplorer.config.annotations
import org.springframework.security.access.prepost.PreAuthorize
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('VIEW_RECIPES')")
annotation class PreAuthorizeViewRecipes
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('EDIT_RECIPES')")
annotation class PreAuthorizeEditRecipes
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('REMOVE_RECIPES')")
annotation class PreAuthorizeRemoveRecipes
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('VIEW_CATALOG')")
annotation class PreAuthorizeViewCatalog
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('VIEW_USERS')")
annotation class PreAuthorizeViewUsers
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('EDIT_USERS')")
annotation class PreAuthorizeEditUsers
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@PreAuthorize("hasAuthority('REMOVE_USERS')")
annotation class PreAuthorizeRemoveUsers

View File

@ -61,8 +61,19 @@ data class Employee(
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
) : Model {
@JsonProperty("permissions")
fun getFlattenedPermissions(): Iterable<EmployeePermission> = getPermissions()
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
@get:JsonIgnore
val authorities: Set<GrantedAuthority>
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
}
/** DTO for creating employees. Allows a [password] a [groupId]. */
@ -80,25 +91,11 @@ open class EmployeeSaveDto(
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
val password: String,
@field:ManyToOne
@Fetch(FetchMode.SELECT)
var groupId: Long? = null,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
) : EntityDto<Employee> {
override fun toEntity(): Employee =
Employee(
id,
firstName,
lastName,
"",
isDefaultGroupUser = false,
isSystemUser = false,
group = null,
permissions = permissions
)
}
) : EntityDto<Employee>
open class EmployeeUpdateDto(
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
@ -110,6 +107,8 @@ open class EmployeeUpdateDto(
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission>?
) : EntityDto<Employee>
@ -134,8 +133,16 @@ data class EmployeeGroup(
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
@get:JsonProperty("explicitPermissions")
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
) : NamedModel
) : NamedModel {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = this.permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toSet()
}
open class EmployeeGroupSaveDto(
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
@ -168,96 +175,83 @@ open class EmployeeGroupUpdateDto(
data class EmployeeLoginRequest(val id: Long, val password: String)
enum class EmployeePermission(val impliedPermissions: List<EmployeePermission> = listOf()) {
// View
VIEW_MATERIAL,
VIEW_MATERIAL_TYPE,
VIEW_COMPANY,
VIEW_RECIPE,
VIEW(
listOf(
VIEW_MATERIAL,
VIEW_MATERIAL_TYPE,
VIEW_COMPANY,
VIEW_RECIPE
)
),
VIEW_EMPLOYEE,
VIEW_EMPLOYEE_GROUP,
enum class EmployeePermission(
val impliedPermissions: List<EmployeePermission> = listOf(),
val deprecated: Boolean = false
) {
VIEW_RECIPES,
VIEW_CATALOG,
VIEW_USERS,
// Edit
EDIT_MATERIAL(listOf(VIEW_MATERIAL)),
EDIT_MATERIAL_TYPE(listOf(VIEW_MATERIAL_TYPE)),
EDIT_COMPANY(listOf(VIEW_COMPANY)),
EDIT_RECIPE(listOf(VIEW_RECIPE)),
EDIT(
listOf(
EDIT_MATERIAL,
EDIT_MATERIAL_TYPE,
EDIT_COMPANY,
EDIT_RECIPE,
VIEW
)
),
EDIT_EMPLOYEE(listOf(VIEW_EMPLOYEE)),
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_EMPLOYEE)),
EDIT_EMPLOYEE_GROUP(listOf(VIEW_EMPLOYEE_GROUP)),
PRINT_MIXES(listOf(VIEW_RECIPES)),
// Remove
REMOVE_MATERIAL(listOf(EDIT_MATERIAL)),
REMOVE_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPE)),
REMOVE_COMPANY(listOf(EDIT_COMPANY)),
REMOVE_RECIPE(listOf(EDIT_RECIPE)),
REMOVE(
listOf(
REMOVE_MATERIAL,
REMOVE_MATERIAL_TYPE,
REMOVE_COMPANY,
REMOVE_RECIPE,
EDIT
)
),
REMOVE_EMPLOYEE(listOf(EDIT_EMPLOYEE)),
REMOVE_EMPLOYEE_GROUP(listOf(EDIT_EMPLOYEE_GROUP)),
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)),
EDIT_MATERIALS(listOf(VIEW_CATALOG)),
EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)),
EDIT_COMPANIES(listOf(VIEW_CATALOG)),
EDIT_USERS(listOf(VIEW_USERS)),
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
// Others
SET_BROWSER_DEFAULT_GROUP(
listOf(
VIEW_EMPLOYEE_GROUP
)
),
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
REMOVE_RECIPES(listOf(EDIT_RECIPES)),
REMOVE_MATERIALS(listOf(EDIT_MATERIALS)),
REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)),
REMOVE_COMPANIES(listOf(EDIT_COMPANIES)),
REMOVE_USERS(listOf(EDIT_USERS)),
REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)),
ADMIN(
listOf(
REMOVE,
SET_BROWSER_DEFAULT_GROUP,
EDIT_CATALOG,
// Admin only permissions
REMOVE_EMPLOYEE,
EDIT_EMPLOYEE_PASSWORD,
REMOVE_EMPLOYEE_GROUP,
REMOVE_RECIPES,
REMOVE_USERS,
REMOVE_CATALOG,
PRINT_MIXES,
ADD_TO_INVENTORY,
DEDUCT_FROM_INVENTORY
)
);
),
// deprecated permissions
VIEW_RECIPE(listOf(VIEW_RECIPES), true),
VIEW_MATERIAL(listOf(VIEW_CATALOG), true),
VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true),
VIEW_COMPANY(listOf(VIEW_CATALOG), true),
VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true),
VIEW_EMPLOYEE(listOf(VIEW_USERS), true),
VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true),
EDIT_RECIPE(listOf(EDIT_RECIPES), true),
EDIT_MATERIAL(listOf(EDIT_MATERIALS), true),
EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true),
EDIT_COMPANY(listOf(EDIT_COMPANIES), true),
EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true),
EDIT_EMPLOYEE(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true),
REMOVE_RECIPE(listOf(REMOVE_RECIPES), true),
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true),
REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true),
REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true),
REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true),
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
;
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() })
grantedPermissions.addAll(permissions.flatMap { it.flat() })
return grantedPermissions
}
private fun EmployeePermission.flat(): Iterable<EmployeePermission> {
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
return mutableSetOf(this).apply {
impliedPermissions.forEach {
addAll(it.flat())
@ -311,9 +305,10 @@ fun employeeUpdateDto(
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
groupId: Long? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeUpdateDto.() -> Unit = {}
) = EmployeeUpdateDto(id, firstName, lastName, permissions).apply(op)
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
fun employeeGroup(
id: Long? = null,

View File

@ -1,123 +1,151 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupServiceImpl
import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl
import org.springframework.http.HttpStatus
import dev.fyloz.colorrecipesexplorer.service.EmployeeService
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import java.security.Principal
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.validation.Valid
private const val EMPLOYEE_CONTROLLER_PATH = "api/employee"
private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group"
@RestController
@RequestMapping(EMPLOYEE_CONTROLLER_PATH)
class EmployeeController(employeeService: EmployeeServiceImpl) :
AbstractModelRestApiController<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeServiceImpl>(
employeeService,
EMPLOYEE_CONTROLLER_PATH
) {
class EmployeeController(private val employeeService: EmployeeService) {
@GetMapping
@PreAuthorizeViewUsers
fun getAll() =
ok(employeeService.getAll())
@GetMapping("{id}")
@PreAuthorizeViewUsers
fun getById(@PathVariable id: Long) =
ok(employeeService.getById(id))
@GetMapping("current")
@ResponseStatus(HttpStatus.OK)
fun getCurrent(loggedInEmployee: Principal?): ResponseEntity<Employee> = if (loggedInEmployee != null)
ResponseEntity.ok(
service.getById(
loggedInEmployee.name.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = false
fun getCurrent(loggedInEmployee: Principal?) =
if (loggedInEmployee != null)
ok(
employeeService.getById(
loggedInEmployee.name.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = false
)
)
)
else
ResponseEntity.status(HttpStatus.FORBIDDEN).build()
else
forbidden()
@PostMapping
@PreAuthorizeEditUsers
fun save(@Valid @RequestBody employee: EmployeeSaveDto) =
created<Employee>(EMPLOYEE_CONTROLLER_PATH) {
employeeService.save(employee)
}
@PutMapping
@PreAuthorizeEditUsers
fun update(@Valid @RequestBody employee: EmployeeUpdateDto) =
noContent {
employeeService.update(employee)
}
@PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE])
@ResponseStatus(HttpStatus.NO_CONTENT)
fun updatePassword(@PathVariable id: Long, @RequestBody password: String): ResponseEntity<Void> {
service.updatePassword(id, password)
return ResponseEntity
.noContent()
.build()
}
@PreAuthorizeEditUsers
fun updatePassword(@PathVariable id: Long, @RequestBody password: String) =
noContent {
employeeService.updatePassword(id, password)
}
@PutMapping("{employeeId}/permissions/{permission}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorizeEditUsers
fun addPermission(
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
): ResponseEntity<Void> {
service.addPermission(employeeId, permission)
return ResponseEntity
.noContent()
.build()
) = noContent {
employeeService.addPermission(employeeId, permission)
}
@DeleteMapping("{employeeId}/permissions/{permission}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorizeEditUsers
fun removePermission(
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
): ResponseEntity<Void> {
service.removePermission(employeeId, permission)
return ResponseEntity
.noContent()
.build()
) = noContent {
employeeService.removePermission(employeeId, permission)
}
@GetMapping("logout")
@ResponseStatus(HttpStatus.OK)
fun logout(request: HttpServletRequest): ResponseEntity<Void> {
service.logout(request)
return ResponseEntity.ok().build()
}
@DeleteMapping("{id}")
@PreAuthorizeRemoveUsers
fun deleteById(@PathVariable id: Long) =
employeeService.deleteById(id)
}
@RestController
@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH)
class GroupsController(groupService: EmployeeGroupServiceImpl) :
AbstractModelRestApiController<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupServiceImpl>(
groupService,
EMPLOYEE_GROUP_CONTROLLER_PATH
) {
class GroupsController(private val groupService: EmployeeGroupServiceImpl) {
@GetMapping
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
fun getAll() =
ok(groupService.getAll())
@GetMapping("{id}")
@PreAuthorizeViewUsers
fun getById(@PathVariable id: Long) =
ok(groupService.getById(id))
@GetMapping("{id}/employees")
@ResponseStatus(HttpStatus.OK)
fun getEmployeesForGroup(@PathVariable id: Long): ResponseEntity<Collection<Employee>> =
ResponseEntity.ok(service.getEmployeesForGroup(id))
@PreAuthorizeViewUsers
fun getEmployeesForGroup(@PathVariable id: Long) =
ok(groupService.getEmployeesForGroup(id))
@PostMapping("default/{groupId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse): ResponseEntity<Void> {
service.setResponseDefaultGroup(groupId, response)
return ResponseEntity
.noContent()
.build()
}
@PreAuthorizeViewUsers
fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) =
noContent {
groupService.setResponseDefaultGroup(groupId, response)
}
@GetMapping("default")
@ResponseStatus(HttpStatus.OK)
fun getRequestDefaultGroup(request: HttpServletRequest): ResponseEntity<EmployeeGroup> =
ResponseEntity.ok(service.getRequestDefaultGroup(request))
@PreAuthorizeViewUsers
fun getRequestDefaultGroup(request: HttpServletRequest) =
ok(groupService.getRequestDefaultGroup(request))
@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()
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build() // TODO Go to employee controller
}
@PostMapping
@PreAuthorizeEditUsers
fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) =
created<EmployeeGroup>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
groupService.save(group)
}
@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()
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build() // TODO Go to employee controller
}
@PutMapping
@PreAuthorizeEditUsers
fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) =
noContent {
groupService.update(group)
}
@DeleteMapping("{id}")
@PreAuthorizeRemoveUsers
fun deleteById(@PathVariable id: Long) =
noContent {
groupService.deleteById(id)
}
}
@RestController
@RequestMapping("api")
class LogoutController(private val employeeService: EmployeeService) {
@GetMapping("logout")
fun logout(request: HttpServletRequest) =
ok<Void> {
employeeService.logout(request)
}
}

View File

@ -1,18 +1,46 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
import dev.fyloz.colorrecipesexplorer.model.Company
import dev.fyloz.colorrecipesexplorer.model.CompanySaveDto
import dev.fyloz.colorrecipesexplorer.model.CompanyUpdateDto
import dev.fyloz.colorrecipesexplorer.service.CompanyService
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import javax.validation.Valid
private const val COMPANY_CONTROLLER_PATH = "api/company"
@RestController
@RequestMapping(COMPANY_CONTROLLER_PATH)
class CompanyController(companyService: CompanyService) :
AbstractModelRestApiController<Company, CompanySaveDto, CompanyUpdateDto, CompanyService>(
companyService,
COMPANY_CONTROLLER_PATH
)
@PreAuthorizeViewCatalog
class CompanyController(private val companyService: CompanyService) {
@GetMapping
fun getAll() =
ok(companyService.getAll())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(companyService.getById(id))
@PostMapping
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")
fun save(@Valid @RequestBody company: CompanySaveDto) =
created<Company>(COMPANY_CONTROLLER_PATH) {
companyService.save(company)
}
@PutMapping
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")
fun update(@Valid @RequestBody company: CompanyUpdateDto) =
noContent {
companyService.update(company)
}
@DeleteMapping("{id}")
@PreAuthorize("hasAuthority('REMOVE_COMPANIES')")
fun deleteById(@PathVariable id: Long) =
noContent {
companyService.deleteById(id)
}
}

View File

@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.colorrecipesexplorer.model.MixDeductDto
import dev.fyloz.colorrecipesexplorer.service.InventoryService
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
@ -17,17 +18,18 @@ class InventoryController(
private val inventoryService: InventoryService
) {
@PutMapping("add")
@PreAuthorize("hasAuthority('ADD_TO_INVENTORY')")
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.add(quantities))
}
@PutMapping("deduct")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.deduct(quantities))
}
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>) =
ok(inventoryService.deduct(quantities))
@PutMapping("deduct/mix")
fun deduct(@RequestBody mixRatio: MixDeductDto): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.deductMix(mixRatio))
}
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
fun deduct(@RequestBody mixRatio: MixDeductDto) =
ok(inventoryService.deductMix(mixRatio))
}

View File

@ -1,11 +1,11 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.MaterialService
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import javax.validation.Valid
@ -14,75 +14,79 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material"
@RestController
@RequestMapping(MATERIAL_CONTROLLER_PATH)
class MaterialController(materialService: MaterialService) :
AbstractModelRestApiController<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService>(
materialService,
MATERIAL_CONTROLLER_PATH
) {
@PreAuthorizeViewCatalog
class MaterialController(private val materialService: MaterialService) {
@GetMapping
fun getAll() =
ok(materialService.getAll())
@GetMapping("notmixtype")
fun getAllNotMixType(): ResponseEntity<Collection<Material>> =
ResponseEntity.ok(service.getAllNotMixType())
fun getAllNotMixType() =
ok(materialService.getAllNotMixType())
@GetMapping("mix/create/{recipeId}")
@ResponseStatus(HttpStatus.OK)
fun getAllForMixCreation(@PathVariable recipeId: Long): ResponseEntity<Collection<Material>> =
ResponseEntity.ok(service.getAllForMixCreation(recipeId))
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(materialService.getById(id))
@GetMapping("mix/update/{mixId}")
@ResponseStatus(HttpStatus.OK)
fun getAllForMixUpdate(@PathVariable mixId: Long): ResponseEntity<Collection<Material>> =
ResponseEntity.ok(service.getAllForMixUpdate(mixId))
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
fun save(@Valid @RequestBody material: MaterialSaveDto, simdutFile: MultipartFile?) =
created<Material>(MATERIAL_CONTROLLER_PATH) {
materialService.save(
materialSaveDto(
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
)
}
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
fun update(@Valid @RequestBody material: MaterialUpdateDto, simdutFile: MultipartFile?) =
noContent {
materialService.update(
materialUpdateDto(
id = material.id,
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
)
}
@DeleteMapping("{id}")
@PreAuthorize("hasAuthority('REMOVE_MATERIALS')")
fun deleteById(@PathVariable id: Long) =
noContent {
materialService.deleteById(id)
}
@GetMapping("{id}/simdut/exists")
@ResponseStatus(HttpStatus.OK)
fun hasSimdut(@PathVariable id: Long): ResponseEntity<Boolean> =
ResponseEntity.ok(service.hasSimdut(id))
fun hasSimdut(@PathVariable id: Long) =
ok(materialService.hasSimdut(id))
@GetMapping("{id}/simdut", produces = [MediaType.APPLICATION_PDF_VALUE])
@ResponseStatus(HttpStatus.OK)
fun getSimdut(@PathVariable id: Long): ResponseEntity<ByteArray> {
val simdutFile = service.getSimdut(id)
return if (simdutFile.isEmpty()) {
ResponseEntity.notFound().build()
fun getSimdut(@PathVariable id: Long): ResponseEntity<ByteArray> = with(materialService.getSimdut(id)) {
if (this.isEmpty()) {
notFound()
} else {
val headers = HttpHeaders().apply { contentType = MediaType.APPLICATION_PDF }
ResponseEntity(simdutFile, headers, HttpStatus.OK)
ok(this, httpHeaders(contentType = MediaType.APPLICATION_PDF))
}
}
@GetMapping("/simdut")
fun getAllIdsWithSimdut(): ResponseEntity<Collection<Long>> =
ResponseEntity.ok(service.getAllIdsWithSimdut())
fun getAllIdsWithSimdut() =
ok(materialService.getAllIdsWithSimdut())
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun save(@Valid entity: MaterialSaveDto, simdutFile: MultipartFile?): ResponseEntity<Material> =
super.save(
materialSaveDto(
name = entity.name,
inventoryQuantity = entity.inventoryQuantity,
materialTypeId = entity.materialTypeId,
simdutFile = simdutFile
)
)
@GetMapping("mix/create/{recipeId}")
fun getAllForMixCreation(@PathVariable recipeId: Long) =
ok(materialService.getAllForMixCreation(recipeId))
@PostMapping("unused_save")
override fun save(entity: MaterialSaveDto): ResponseEntity<Material> =
ResponseEntity.notFound().build()
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun update(@Valid entity: MaterialUpdateDto, simdutFile: MultipartFile?): ResponseEntity<Void> =
super.update(
materialUpdateDto(
id = entity.id,
name = entity.name,
inventoryQuantity = entity.inventoryQuantity,
materialTypeId = entity.materialTypeId,
simdutFile = simdutFile
)
)
@PutMapping("unused_update")
override fun update(entity: MaterialUpdateDto): ResponseEntity<Void> =
ResponseEntity.notFound().build()
@GetMapping("mix/update/{mixId}")
fun getAllForMixUpdate(@PathVariable mixId: Long) =
ok(materialService.getAllForMixUpdate(mixId))
}

View File

@ -1,19 +1,47 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
import dev.fyloz.colorrecipesexplorer.model.MaterialType
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeSaveDto
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeUpdateDto
import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import javax.validation.Valid
private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype"
@RestController
@RequestMapping(MATERIAL_TYPE_CONTROLLER_PATH)
class MaterialTypeController(materialTypeService: MaterialTypeService) :
AbstractModelRestApiController<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeService>(
materialTypeService,
MATERIAL_TYPE_CONTROLLER_PATH
)
@PreAuthorizeViewCatalog
class MaterialTypeController(private val materialTypeService: MaterialTypeService) {
@GetMapping
fun getAll() =
ok(materialTypeService.getAll())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(materialTypeService.getById(id))
@PostMapping
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")
fun save(@Valid @RequestBody materialType: MaterialTypeSaveDto) =
created<MaterialType>(MATERIAL_TYPE_CONTROLLER_PATH) {
materialTypeService.save(materialType)
}
@PutMapping
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")
fun update(@Valid @RequestBody materialType: MaterialTypeUpdateDto) =
noContent {
materialTypeService.update(materialType)
}
@DeleteMapping("{id}")
@PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPEs')")
fun deleteById(@PathVariable id: Long) =
noContent {
materialTypeService.deleteById(id)
}
}

View File

@ -1,12 +1,15 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveRecipes
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewRecipes
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.MixService
import dev.fyloz.colorrecipesexplorer.service.RecipeImageService
import dev.fyloz.colorrecipesexplorer.service.RecipeService
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import java.net.URI
@ -18,49 +21,98 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix"
@RestController
@RequestMapping(RECIPE_CONTROLLER_PATH)
class RecipeController(recipeService: RecipeService) :
AbstractModelRestApiController<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService>(
recipeService,
RECIPE_CONTROLLER_PATH
) {
@PreAuthorizeViewRecipes
class RecipeController(private val recipeService: RecipeService) {
@GetMapping
fun getAll() =
ok(recipeService.getAll())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(recipeService.getById(id))
@PostMapping
@PreAuthorizeEditRecipes
fun save(@Valid @RequestBody recipe: RecipeSaveDto) =
created<Recipe>(RECIPE_CONTROLLER_PATH) {
recipeService.save(recipe)
}
@PutMapping
@PreAuthorizeEditRecipes
fun update(@Valid @RequestBody recipe: RecipeUpdateDto) =
noContent {
recipeService.update(recipe)
}
@PutMapping("public")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto): ResponseEntity<Void> {
service.updatePublicData(publicDataDto)
return ResponseEntity.noContent().build()
}
@PreAuthorize("hasAuthority('EDIT_RECIPES_PUBLIC_DATA')")
fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto) =
noContent {
recipeService.updatePublicData(publicDataDto)
}
@DeleteMapping("{id}")
@PreAuthorizeRemoveRecipes
fun deleteById(@PathVariable id: Long) =
noContent {
recipeService.deleteById(id)
}
}
@RestController
@RequestMapping(RECIPE_CONTROLLER_PATH)
@PreAuthorizeViewRecipes
class RecipeImageController(val recipeImageService: RecipeImageService) {
@GetMapping("{recipeId}/image")
@ResponseStatus(HttpStatus.OK)
fun getAllIdsForRecipe(@PathVariable recipeId: Long): ResponseEntity<Collection<Long>> =
ResponseEntity.ok(recipeImageService.getAllIdsForRecipe(recipeId))
fun getAllIdsForRecipe(@PathVariable recipeId: Long) =
ok(recipeImageService.getAllIdsForRecipe(recipeId))
@GetMapping("{recipeId}/image/{id}", produces = [MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE])
@ResponseStatus(HttpStatus.OK)
fun getById(@PathVariable recipeId: Long, @PathVariable id: Long): ResponseEntity<ByteArray> =
ResponseEntity.ok(recipeImageService.getByIdForRecipe(id, recipeId))
fun getById(@PathVariable recipeId: Long, @PathVariable id: Long) =
ok(recipeImageService.getByIdForRecipe(id, recipeId))
@PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorizeEditRecipes
fun save(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity<Void> {
val id = recipeImageService.save(image, recipeId)
return ResponseEntity.created(URI.create("/$RECIPE_CONTROLLER_PATH/$recipeId/image/$id")).build()
}
@DeleteMapping("{recipeId}/image/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun delete(@PathVariable recipeId: Long, @PathVariable id: Long): ResponseEntity<Void> {
recipeImageService.delete(id, recipeId)
return ResponseEntity.noContent().build()
}
@PreAuthorizeRemoveRecipes
fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) =
noContent {
recipeImageService.delete(id, recipeId)
}
}
@RestController
@RequestMapping(MIX_CONTROLLER_PATH)
class MixController(val mixService: MixService) :
AbstractModelRestApiController<Mix, MixSaveDto, MixUpdateDto, MixService>(mixService, MIX_CONTROLLER_PATH)
@PreAuthorizeViewRecipes
class MixController(private val mixService: MixService) {
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(mixService.getById(id))
@PostMapping
@PreAuthorizeEditRecipes
fun save(@Valid @RequestBody mix: MixSaveDto) =
created<Mix>(MIX_CONTROLLER_PATH) {
mixService.save(mix)
}
@PutMapping
@PreAuthorizeEditRecipes
fun update(@Valid @RequestBody mix: MixUpdateDto) =
noContent {
mixService.update(mix)
}
@DeleteMapping("{id}")
@PreAuthorizeRemoveRecipes
fun deleteById(@PathVariable id: Long) =
noContent {
mixService.deleteById(id)
}
}

View File

@ -1,87 +0,0 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.model.EntityDto
import dev.fyloz.colorrecipesexplorer.model.Model
import dev.fyloz.colorrecipesexplorer.service.ExternalModelService
import dev.fyloz.colorrecipesexplorer.service.ExternalService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.net.URI
import javax.validation.Valid
interface RestApiController<E, S : EntityDto<E>, U : EntityDto<E>> {
@ResponseStatus(HttpStatus.OK)
fun getAll(): ResponseEntity<Iterable<E>>
@ResponseStatus(HttpStatus.CREATED)
fun save(entity: S): ResponseEntity<E>
@ResponseStatus(HttpStatus.NO_CONTENT)
fun update(entity: U): ResponseEntity<Void>
@ResponseStatus(HttpStatus.NO_CONTENT)
fun delete(entity: E): ResponseEntity<Void>
}
interface RestModelApiController<E : Model, S : EntityDto<E>, U : EntityDto<E>> : RestApiController<E, S, U> {
@ResponseStatus(HttpStatus.OK)
fun getById(id: Long): ResponseEntity<E>
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteById(id: Long): ResponseEntity<Void>
}
abstract class AbstractRestApiController<E, N : EntityDto<E>, U : EntityDto<E>, S : ExternalService<E, N, U, *>>(
val service: S,
private val controllerPath: String
) :
RestApiController<E, N, U> {
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: N): ResponseEntity<E> {
val saved = service.save(entity)
return ResponseEntity
.created(URI("$controllerPath/${getEntityId(saved)}"))
.body(saved)
}
@PutMapping
override fun update(@Valid @RequestBody entity: U): 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 AbstractModelRestApiController<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *>>(
service: S,
controllerPath: String
) :
AbstractRestApiController<E, N, U, S>(service, controllerPath), RestModelApiController<E, N, U> {
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()
}
}

View File

@ -0,0 +1,58 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.model.Model
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import java.net.URI
/** Creates a HTTP OK [ResponseEntity] from the given [body]. */
fun <T> ok(body: T): ResponseEntity<T> =
ResponseEntity.ok(body)
/** Creates a HTTP OK [ResponseEntity] from the given [body] and [headers]. */
fun <T> ok(body: T, headers: HttpHeaders): ResponseEntity<T> =
ResponseEntity(body, headers, HttpStatus.OK)
/** Executes the given [action] then returns an HTTP OK [ResponseEntity] form the given [body]. */
fun <T> ok(action: () -> Unit): ResponseEntity<T> {
action()
return ResponseEntity.ok().build()
}
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
fun <T : Model> created(controllerPath: String, body: T): ResponseEntity<T> =
ResponseEntity.created(URI.create("$controllerPath/${body.id}")).body(body)
/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */
fun <T : Model> created(controllerPath: String, producer: () -> T): ResponseEntity<T> =
created(controllerPath, producer())
/** Creates a HTTP NOT FOUND [ResponseEntity]. */
fun <T> notFound(): ResponseEntity<T> =
ResponseEntity.notFound().build()
/** Creates a HTTP NO CONTENT [ResponseEntity]. */
fun noContent(): ResponseEntity<Void> =
ResponseEntity.noContent().build()
/** Executes the given [action] then returns an HTTP NO CONTENT [ResponseEntity]. */
fun noContent(action: () -> Unit): ResponseEntity<Void> {
action()
return noContent()
}
/** Creates a HTTP FORBIDDEN [ResponseEntity]. */
fun <T> forbidden(): ResponseEntity<T> =
ResponseEntity.status(HttpStatus.FORBIDDEN).build()
/** Creates an [HttpHeaders] instance from the given options. */
fun httpHeaders(
contentType: MediaType = MediaType.APPLICATION_JSON,
op: HttpHeaders.() -> Unit = {}
) = HttpHeaders().apply {
this.contentType = contentType
op()
}

View File

@ -116,7 +116,7 @@ class EmployeeServiceImpl(
passwordEncoder.encode(password),
isDefaultGroupUser = false,
isSystemUser = false,
group = if (groupId != null) groupService.getById(groupId!!) else null,
group = if (groupId != null) groupService.getById(groupId) else null,
permissions = permissions
)
})
@ -160,7 +160,7 @@ class EmployeeServiceImpl(
password = persistedEmployee.password,
isDefaultGroupUser = false,
isSystemUser = false,
group = persistedEmployee.group,
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
lastLoginTime = persistedEmployee.lastLoginTime
)
@ -294,6 +294,6 @@ class EmployeeUserDetailsServiceImpl(
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
ignoreSystemUsers = false
)
return User(employee.id.toString(), employee.password, employee.getAuthorities())
return User(employee.id.toString(), employee.password, employee.authorities)
}
}

View File

@ -102,6 +102,7 @@ class RecipeServiceImpl(
return updatedGroupsInformation
}
@Transactional
override fun updatePublicData(publicDataDto: RecipePublicDataDto) {
if (publicDataDto.notes != null) {
val recipe = getById(publicDataDto.recipeId)

View File

@ -1,13 +1,11 @@
package dev.fyloz.colorrecipesexplorer.service
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
import dev.fyloz.colorrecipesexplorer.model.EntityDto
import dev.fyloz.colorrecipesexplorer.model.Model
import dev.fyloz.colorrecipesexplorer.model.NamedModel
import dev.fyloz.colorrecipesexplorer.repository.NamedJpaRepository
import dev.fyloz.colorrecipesexplorer.rest.RestApiController
import io.jsonwebtoken.lang.Assert
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.findByIdOrNull
@ -114,13 +112,13 @@ abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<
return super.update(entity)
}
protected fun assertName(name: String) {
private fun assertName(name: String) {
Assert.notNull(name, "${javaClass.simpleName}.update() was called with a null name")
}
}
/**
* A service that will receive *external* interactions, from a [RestApiController] for example.
* A service that will receive *external* interactions, from the REST API, for example.
*
* @param E The entity type
* @param S The entity save DTO type
@ -156,8 +154,3 @@ abstract class AbstractExternalModelService<E : Model, S : EntityDto<E>, U : Ent
abstract class AbstractExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : NamedJpaRepository<E>>(
repository: R
) : AbstractNamedModelService<E, R>(repository), ExternalNamedModelService<E, S, U, R>
/** Transforms the given object to JSON. **/
fun Any.asJson(): String {
return jacksonObjectMapper().writeValueAsString(this)
}

View File

@ -163,7 +163,6 @@ class EmployeeServiceTest :
@Test
fun `save(dto) calls and returns save() with the created employee`() {
whenever(entitySaveDto.toEntity()).doReturn(entitySaveDtoEmployee)
doReturn(entitySaveDtoEmployee).whenever(service).save(any<Employee>())
val found = service.save(entitySaveDto)