-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from Veselovnd88/feature/43-rest-controller
Feature/43 rest controller
- Loading branch information
Showing
29 changed files
with
1,006 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
src/main/java/ru/veselov/companybot/config/OpenApiConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package ru.veselov.companybot.config; | ||
|
||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.info.Info; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class OpenApiConfig { | ||
|
||
@Bean | ||
public OpenAPI springShopOpenAPI() { | ||
return new OpenAPI() | ||
.info(new Info() | ||
.title("Интерфейс управления ботом-ассистентом") | ||
.description("Управления отделами, информацией и другими функциями телеграм бота") | ||
.version("v2.0.0")); | ||
} | ||
|
||
} |
110 changes: 110 additions & 0 deletions
110
src/main/java/ru/veselov/companybot/controller/DivisionController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package ru.veselov.companybot.controller; | ||
|
||
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; | ||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.validation.annotation.Validated; | ||
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.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import ru.veselov.companybot.dto.DivisionDTO; | ||
import ru.veselov.companybot.model.DivisionModel; | ||
import ru.veselov.companybot.service.DivisionService; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/division") | ||
@RequiredArgsConstructor | ||
@Validated | ||
@Tag(name = "Division, Отдел, Департамент", description = "Управление отделами") | ||
public class DivisionController { | ||
|
||
private final DivisionService divisionService; | ||
|
||
@Operation(summary = "Добавить новый отдел", | ||
description = "Принимает название и описание отдела") | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "201", description = "Отдел создан", | ||
content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, | ||
schema = @Schema(implementation = DivisionModel.class))}) | ||
}) | ||
@PostMapping | ||
@ResponseStatus(HttpStatus.CREATED) | ||
public DivisionModel addDivision( | ||
@io.swagger.v3.oas.annotations.parameters.RequestBody( | ||
content = @Content(schema = @Schema(implementation = DivisionDTO.class), | ||
mediaType = MediaType.APPLICATION_JSON_VALUE | ||
)) | ||
@RequestBody @Valid DivisionDTO divisionDTO) { | ||
return divisionService.save(divisionDTO); | ||
} | ||
|
||
@Operation(summary = "Получить все отделы", | ||
description = "Выгрузка из базы данных всех отделов") | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "200", description = "Отдел успешно выгружены", | ||
content = {@Content(array = @ArraySchema(schema = @Schema(implementation = DivisionModel.class)), | ||
mediaType = MediaType.APPLICATION_JSON_VALUE)}) | ||
}) | ||
@GetMapping("/all") | ||
public List<DivisionModel> getDivisions() { | ||
return divisionService.findAll(); | ||
} | ||
|
||
@Operation(summary = "Получить отдел по его id", | ||
description = "Получение информации об отделе по его id") | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "200", description = "Отдел успешно выгружены", | ||
content = {@Content(schema = @Schema(implementation = DivisionModel.class), | ||
mediaType = MediaType.APPLICATION_JSON_VALUE)}) | ||
}) | ||
@GetMapping("/{divisionId}") | ||
public DivisionModel getDivisionById(@PathVariable UUID divisionId) { | ||
return divisionService.findById(divisionId); | ||
} | ||
|
||
@Operation(summary = "Обновить отдел", | ||
description = "Обновление наименования и описания отдела") | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "200", description = "Отдел обновлен", | ||
content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, | ||
schema = @Schema(implementation = DivisionModel.class))}) | ||
}) | ||
@PutMapping("/{divisionId}") | ||
public DivisionModel updateDivision(@PathVariable UUID divisionId, | ||
@io.swagger.v3.oas.annotations.parameters.RequestBody( | ||
content = @Content(schema = @Schema(implementation = DivisionDTO.class), | ||
mediaType = MediaType.APPLICATION_JSON_VALUE | ||
)) | ||
@RequestBody @Valid DivisionDTO divisionDTO) { | ||
return divisionService.update(divisionId, divisionDTO); | ||
} | ||
|
||
@Operation(summary = "Удалить отдел", | ||
description = "Удаление отдела") | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "204", description = "Отдел удален") | ||
}) | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
@DeleteMapping("/{divisionId}") | ||
public void deleteDivision(@PathVariable UUID divisionId) { | ||
divisionService.delete(divisionId); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package ru.veselov.companybot.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotEmpty; | ||
import jakarta.validation.constraints.Size; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class DivisionDTO { | ||
|
||
@Schema(description = "Short name of division", example = "Common") | ||
@NotEmpty | ||
@Size(max = 10) | ||
private String name; | ||
|
||
@Schema(description = "Description of division", example = "Common questions here") | ||
@NotEmpty | ||
@Size(max = 45) | ||
private String description; | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/ru/veselov/companybot/exception/DivisionAlreadyExistsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package ru.veselov.companybot.exception; | ||
|
||
public class DivisionAlreadyExistsException extends ObjectAlreadyExistsException { | ||
|
||
public DivisionAlreadyExistsException(String message) { | ||
super(message); | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/ru/veselov/companybot/exception/ObjectAlreadyExistsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package ru.veselov.companybot.exception; | ||
|
||
public class ObjectAlreadyExistsException extends RuntimeException { | ||
|
||
public ObjectAlreadyExistsException(String message) { | ||
super(message); | ||
} | ||
|
||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/ru/veselov/companybot/exception/handler/ErrorCode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package ru.veselov.companybot.exception.handler; | ||
|
||
public enum ErrorCode { | ||
|
||
NOT_FOUND, | ||
|
||
VALIDATION, | ||
|
||
INTERNAL_SERVER_ERROR, | ||
|
||
CONFLICT, | ||
|
||
BAD_REQUEST, | ||
|
||
FORBIDDEN, | ||
|
||
UNAUTHORIZED | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/ru/veselov/companybot/exception/handler/ErrorMessage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package ru.veselov.companybot.exception.handler; | ||
|
||
import lombok.experimental.UtilityClass; | ||
|
||
@UtilityClass | ||
public class ErrorMessage { | ||
|
||
public static final String ERROR_CODE = "errorCode"; | ||
|
||
public static final String TIMESTAMP = "timestamp"; | ||
|
||
public static final String VALIDATION_ERROR = "Validation error"; | ||
|
||
public static final String VIOLATIONS = "violations"; | ||
|
||
public static final String OBJECT_NOT_FOUND = "Object not found"; | ||
|
||
public static final String OBJECT_ALREADY_EXISTS = "Object already exists"; | ||
|
||
public static final String WRONG_ARGUMENT_PASSED = "Wrong type of argument passed"; | ||
|
||
} |
151 changes: 151 additions & 0 deletions
151
src/main/java/ru/veselov/companybot/exception/handler/RestExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package ru.veselov.companybot.exception.handler; | ||
|
||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.media.ExampleObject; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import jakarta.persistence.EntityNotFoundException; | ||
import jakarta.validation.ConstraintViolationException; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.ProblemDetail; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
import org.springframework.web.bind.annotation.ControllerAdvice; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; | ||
import ru.veselov.companybot.exception.ObjectAlreadyExistsException; | ||
|
||
import java.time.Instant; | ||
import java.util.List; | ||
|
||
@ControllerAdvice | ||
@Slf4j | ||
@ApiResponse(responseCode = "400", description = "Валидация полей объекта не прошла", | ||
content = @Content( | ||
schema = @Schema(implementation = ProblemDetail.class), | ||
examples = @ExampleObject(value = """ | ||
{ | ||
"type": "about:blank", | ||
"title": "Validation error", | ||
"status": 400, | ||
"detail": "Validation failed", | ||
"instance": "/api/v1/division", | ||
"timestamp": "2024-01-20T13:55:59.666535500Z", | ||
"errorCode": "VALIDATION", | ||
"violations": [ | ||
{ | ||
"name": "name", | ||
"message": "размер должен находиться в диапазоне от 0 до 10", | ||
"currentValue": "Commonfasdfasdfasdfasdfasdfdf" | ||
} | ||
] | ||
} | ||
"""), | ||
mediaType = MediaType.APPLICATION_JSON_VALUE | ||
)) | ||
@ApiResponse(responseCode = "409", description = "Отдел с таким наименованием уже существует", | ||
content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, | ||
examples = @ExampleObject(value = """ | ||
{ | ||
"type": "about:blank", | ||
"title": "Object already exists", | ||
"status": 409, | ||
"detail": "Object with name COMMON already exists", | ||
"instance": "/api/v1/????", | ||
"timestamp": "2024-01-20T13:57:29.576908600Z", | ||
"errorCode": "CONFLICT" | ||
} | ||
"""), | ||
schema = @Schema(implementation = ProblemDetail.class)) | ||
}) | ||
public class RestExceptionHandler { | ||
private static final String LOG_MSG_DETAILS = "[Exception {} with message {}] handled"; | ||
|
||
@ExceptionHandler(EntityNotFoundException.class) | ||
public ProblemDetail handleEntityNotFoundException(EntityNotFoundException e) { | ||
ProblemDetail problemDetail = createProblemDetail(HttpStatus.NOT_FOUND, e); | ||
problemDetail.setTitle(ErrorMessage.OBJECT_NOT_FOUND); | ||
problemDetail.setProperty(ErrorMessage.ERROR_CODE, ErrorCode.NOT_FOUND.toString()); | ||
log.debug(LOG_MSG_DETAILS, e.getClass(), e.getMessage()); | ||
return problemDetail; | ||
} | ||
|
||
@ExceptionHandler(ObjectAlreadyExistsException.class) | ||
public ProblemDetail handleAlreadyExistsException(ObjectAlreadyExistsException e) { | ||
ProblemDetail problemDetail = createProblemDetail(HttpStatus.CONFLICT, e); | ||
problemDetail.setTitle(ErrorMessage.OBJECT_ALREADY_EXISTS); | ||
problemDetail.setProperty(ErrorMessage.ERROR_CODE, ErrorCode.CONFLICT.toString()); | ||
log.debug(LOG_MSG_DETAILS, e.getClass(), e.getMessage()); | ||
return problemDetail; | ||
} | ||
|
||
@ExceptionHandler(ConstraintViolationException.class) | ||
public ProblemDetail handleConstraintViolationException(ConstraintViolationException e) { | ||
List<ViolationError> violationErrors = e.getConstraintViolations().stream() | ||
.map(v -> new ViolationError( | ||
fieldNameFromPath(v.getPropertyPath().toString()), | ||
v.getMessage(), | ||
formatValidationCurrentValue(v.getInvalidValue()))) | ||
.toList(); | ||
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed"); | ||
ProblemDetail configuredProblemDetails = setUpValidationDetails(problemDetail, violationErrors); | ||
log.debug(LOG_MSG_DETAILS, e.getClass(), e.getMessage()); | ||
return configuredProblemDetails; | ||
} | ||
|
||
@ExceptionHandler(MethodArgumentTypeMismatchException.class) | ||
public ProblemDetail handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { | ||
ProblemDetail problemDetail = createProblemDetail(HttpStatus.BAD_REQUEST, e); | ||
problemDetail.setTitle(ErrorMessage.WRONG_ARGUMENT_PASSED); | ||
problemDetail.setProperty(ErrorMessage.ERROR_CODE, ErrorCode.BAD_REQUEST.toString()); | ||
log.debug(LOG_MSG_DETAILS, e.getClass(), e.getMessage()); | ||
return problemDetail; | ||
} | ||
|
||
@ExceptionHandler(MethodArgumentNotValidException.class) | ||
public ProblemDetail handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { | ||
final List<ViolationError> violationErrors = e.getBindingResult().getFieldErrors().stream() | ||
.map(error -> new ViolationError( | ||
error.getField(), error.getDefaultMessage(), | ||
formatValidationCurrentValue(error.getRejectedValue()))) | ||
.toList(); | ||
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed"); | ||
problemDetail.setProperty(ErrorMessage.TIMESTAMP, Instant.now()); | ||
ProblemDetail configuredProblemDetails = setUpValidationDetails(problemDetail, violationErrors); | ||
log.debug(LOG_MSG_DETAILS, e.getClass(), e.getMessage()); | ||
return configuredProblemDetails; | ||
} | ||
|
||
private ProblemDetail createProblemDetail(HttpStatus status, Exception e) { | ||
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, e.getMessage()); | ||
problemDetail.setProperty(ErrorMessage.TIMESTAMP, Instant.now()); | ||
return problemDetail; | ||
} | ||
|
||
private ProblemDetail setUpValidationDetails(ProblemDetail problemDetail, List<ViolationError> violationErrors) { | ||
problemDetail.setTitle(ErrorMessage.VALIDATION_ERROR); | ||
problemDetail.setProperty(ErrorMessage.ERROR_CODE, ErrorCode.VALIDATION.toString()); | ||
problemDetail.setProperty(ErrorMessage.VIOLATIONS, violationErrors); | ||
return problemDetail; | ||
} | ||
|
||
private String fieldNameFromPath(String path) { | ||
String[] split = path.split("\\."); | ||
if (split.length > 1) { | ||
return split[split.length - 1]; | ||
} | ||
return path; | ||
} | ||
|
||
private String formatValidationCurrentValue(Object object) { | ||
if (object == null) { | ||
return "null"; | ||
} | ||
if (object.toString().contains(object.getClass().getName())) { | ||
return object.getClass().getSimpleName(); | ||
} | ||
return object.toString(); | ||
} | ||
|
||
} |
Oops, something went wrong.