RoleController.java

package api.controllers;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import api.dtos.ErrorDto;
import api.dtos.RoleDto;
import api.entities.Role;
import api.exceptions.DuplicateEntityException;
import api.mapper.RoleMapper;
import api.services.RoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * {@link RoleController}.
 */
@RestController
@RequestMapping("/roles")
@Tag(
    name = "Roles",
    description = "Roles contain a set of authorities which athorize users access different functionality."
)
public class RoleController {
    @Autowired
    private RoleService roleService;
    @Autowired
    private RoleMapper roleMapper;

    /**
     * Get roles.
     *
     * @return {@link List} of {@link RoleDto}
     */
    // region
    @Operation(
        summary = "Get Roles",
        description = "Get list of all role's information."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                array = @ArraySchema(schema = @Schema(implementation = RoleDto.class)),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "403",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @GetMapping("")
    @PreAuthorize("hasAuthority(@DbSetup.ROLE_READ)")
    public List<RoleDto> getRoles() {
        return roleService.getAll().stream()
            .map(roleMapper::toDto)
            .collect(Collectors.toList());
    }

    /**
     * Get role.
     *
     * @param id Role id
     * @return {@link RoleDto}
     */
    // region
    @Operation(
        summary = "Get Role",
        description = "Get specific role's information."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = RoleDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "403",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority(@DbSetup.ROLE_READ)")
    public RoleDto getRole(@PathVariable int id) {
        return roleMapper.toDto(roleService.get(id));
    }

    /**
     * Create role.
     *
     * @param roleDto Role
     * @return {@link RoleDto}
     */
    // region
    @Operation(
        summary = "Create Role",
        description = "Create a new role."
            + "<ul>"
                + "<li>The name of the role must not be empty.</li>"
                + "<li>The name of the role must not conflict with an existing role.</li>"
            + "</ul>"
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = RoleDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "400",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "403",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "409",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @PostMapping("")
    @PreAuthorize("hasAuthority('ROLE_WRITE')")
    public RoleDto createRole(@RequestBody RoleDto roleDto) {
        if (!StringUtils.hasText(roleDto.getName())) {
            throw new IllegalArgumentException("Role name must not be empty.");
        }
        if (roleService.exists(roleDto.getName())) {
            throw DuplicateEntityException.fromRole(roleDto.getName());
        }
        return roleMapper.toDto(roleService.save(roleMapper.toEntity(roleDto)));
    }

    /**
     * Update role.
     *
     * @param id Id of role to update
     * @param roleDto Updated information
     * @return {@link RoleDto}
     */
    // region
    @Operation(
        summary = "Update Role",
        description = "Update a specific role."
            + "<ul>"
                + "<li>The name of the role must not be empty.</li>"
                + "<li>The name of the role must not conflict with an existing role.</li>"
                + "<li>Ignores <em>id</em> from request body.</li>"
                + "<li>Any field missing or null from request body will be left unchanged in user information.</li>"
            + "</ul>"
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = RoleDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "400",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "403",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "409",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority(@DbSetup.ROLE_READ) and hasAuthority('ROLE_WRITE')")
    public RoleDto updateRole(@PathVariable int id, @RequestBody RoleDto roleDto) {
        Role role = roleService.get(id);
        if (RoleService.ADMIN_ROLE_NAME.equals(role.getName())) {
            throw new IllegalArgumentException("Role '" + RoleService.ADMIN_ROLE_NAME + "' cannot be editted.");
        }
        if (roleDto.getName() != null && !StringUtils.hasText(roleDto.getName())) {
            throw new IllegalArgumentException("Role name must not be empty.");
        } else if (!Objects.equals(roleDto.getName(), role.getName()) && roleService.exists(roleDto.getName())) {
            throw DuplicateEntityException.fromRole(roleDto.getName());
        }

        roleMapper.update(role, roleDto);
        return roleMapper.toDto(roleService.save(role));
    }

    /**
     * Delete role.
     *
     * @param id Role id
     */
    // region
    @Operation(
        summary = "Delete Role",
        description = "Delete a specific role."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200"
        ),
        @ApiResponse(
            responseCode = "400",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "403",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('ROLE_WRITE')")
    public void deleteRole(@PathVariable int id) {
        roleService.delete(id);
    }
}