-
Notifications
You must be signed in to change notification settings - Fork 1
feat(task 2): add Statistics feature #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mashroommole
Are you sure you want to change the base?
Changes from all commits
4451016
fb3a6b5
fa47ed8
92f06a0
452d522
56eb210
5494ce0
45532c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package lv.ctco.springboottemplate.errorhandling; | ||
|
|
||
| public final class ErrorMessages { | ||
| public static final String TODO_STATS_DATE_RANGE_INVALID = | ||
| "Either from, to, or both can be provided. 'from' date must be before or equal to 'to' date"; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package lv.ctco.springboottemplate.errorhandling; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.springframework.context.support.DefaultMessageSourceResolvable; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.BindException; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
|
||
| @RestControllerAdvice | ||
| public class ExceptionHandler { | ||
| @org.springframework.web.bind.annotation.ExceptionHandler({ | ||
| MethodArgumentNotValidException.class, | ||
| BindException.class | ||
| }) | ||
| public ResponseEntity<Map<String, Object>> handleValidationExceptions(Exception ex) { | ||
| List<String> errors = new ArrayList<>(); | ||
|
|
||
| if (ex instanceof MethodArgumentNotValidException invalidArgumentException) { | ||
| errors = | ||
| invalidArgumentException.getBindingResult().getAllErrors().stream() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like error extraction is the same, why then this IF is required? |
||
| .map(DefaultMessageSourceResolvable::getDefaultMessage) | ||
| .toList(); | ||
| } else if (ex instanceof BindException be) { | ||
| errors = | ||
| be.getBindingResult().getAllErrors().stream() | ||
| .map(DefaultMessageSourceResolvable::getDefaultMessage) | ||
| .toList(); | ||
| } | ||
|
|
||
| return ResponseEntity.badRequest() | ||
| .body(Map.of("status", 400, "error", "Bad Request", "messages", errors)); | ||
| } | ||
|
|
||
| @org.springframework.web.bind.annotation.ExceptionHandler(RuntimeException.class) | ||
| public ResponseEntity<Map<String, String>> handleServerError(RuntimeException ex) { | ||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
| .body(Map.of("error", "Internal server error", "details", ex.getMessage())); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,17 @@ public List<Todo> searchTodos(String title) { | |
|
|
||
| public Todo createTodo(String title, String description, boolean completed, String createdBy) { | ||
| var now = Instant.now(); | ||
| var todo = new Todo(null, title, description, completed, createdBy, createdBy, now, now); | ||
| var todo = | ||
| new Todo( | ||
| null, | ||
| title, | ||
| description, | ||
| completed, | ||
| createdBy, | ||
| createdBy, | ||
| now, | ||
| now, | ||
| completed ? Instant.now() : null); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? now : null |
||
| return todoRepository.save(todo); | ||
| } | ||
|
|
||
|
|
@@ -69,7 +79,8 @@ public Optional<Todo> updateTodo( | |
| existingTodo.createdBy(), | ||
| updatedBy, | ||
| existingTodo.createdAt(), | ||
| Instant.now()); | ||
| Instant.now(), | ||
| completed ? Instant.now() : existingTodo.completedAt()); | ||
| return todoRepository.save(updatedTodo); | ||
| }); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package lv.ctco.springboottemplate.features.todo_statistics; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lv.ctco.springboottemplate.features.todo.Todo; | ||
| import lv.ctco.springboottemplate.features.todo_statistics.dto.TodoStatistics; | ||
| import lv.ctco.springboottemplate.features.todo_statistics.dto.TodoStatisticsRequest; | ||
| import org.springdoc.core.annotations.ParameterObject; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| /** REST controller for getting statistics for {@link Todo} items. */ | ||
| @RestController | ||
| @RequestMapping("/api/statistics") | ||
| @Tag(name = "Statistics Controller", description = "Provides statistics for Todo items") | ||
| public class TodoStatisticsController { | ||
| private final TodoStatisticsService statisticsService; | ||
|
|
||
| public TodoStatisticsController(TodoStatisticsService statisticsService) { | ||
| this.statisticsService = statisticsService; | ||
| } | ||
|
|
||
| @GetMapping | ||
| @Operation(summary = "Get todo items statistics") | ||
| public ResponseEntity<TodoStatistics> getStatistics( | ||
| @Valid @ParameterObject @ModelAttribute TodoStatisticsRequest request) { | ||
|
|
||
| TodoStatistics stats = statisticsService.makeStatistics(request); | ||
|
|
||
| return ResponseEntity.ok(stats); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package lv.ctco.springboottemplate.features.todo_statistics; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import com.fasterxml.jackson.annotation.JsonValue; | ||
|
|
||
| public enum TodoStatisticsFormatEnum { | ||
| SUMMARY("summary"), | ||
| DETAILED("detailed"); | ||
|
|
||
| private final String value; | ||
|
|
||
| TodoStatisticsFormatEnum(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| @JsonValue | ||
| public String getValue() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (y) great |
||
| return value; | ||
| } | ||
|
|
||
| @JsonCreator | ||
| public static TodoStatisticsFormatEnum fromString(String value) { | ||
| for (TodoStatisticsFormatEnum format : TodoStatisticsFormatEnum.values()) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest to convert to Java Stream API |
||
| if (format.value.equalsIgnoreCase(value)) { | ||
| return format; | ||
| } | ||
| } | ||
| throw new IllegalArgumentException( | ||
| "Unknown format: " + value + "must be summary, detailed or empty"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lv.ctco.springboottemplate.features.todo_statistics; | ||
|
|
||
| import com.mongodb.lang.Nullable; | ||
| import org.springframework.core.convert.converter.Converter; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class TodoStatisticsFormatEnumConverter | ||
| implements Converter<String, TodoStatisticsFormatEnum> { | ||
| @Override | ||
| public TodoStatisticsFormatEnum convert(@Nullable String source) { | ||
| if (source == null || source.isBlank()) { | ||
| return TodoStatisticsFormatEnum.SUMMARY; | ||
| } | ||
| return TodoStatisticsFormatEnum.fromString(source); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this can be import shortened