-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Introduce Problem Management module with CRUD, API, and tests #6
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
f5e8060
Introduce Problem Management module: includes CRUD operations, REST A…
MathCunha16 88085a0
Add unit tests for Problem Management module's use cases and enhance …
MathCunha16 e44e227
Fix incorrect parameter in ResourceNotFoundException for project ID i…
MathCunha16 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
255 changes: 255 additions & 0 deletions
255
backend/src/main/java/com/devaulty/backend/adapter/in/web/problem/ProblemApi.java
This file contains hidden or 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,255 @@ | ||
| package com.devaulty.backend.adapter.in.web.problem; | ||
|
|
||
| import com.devaulty.backend.adapter.in.web.exception.ApiErrorResponse; | ||
| import com.devaulty.backend.adapter.in.web.problem.dto.*; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| 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 org.springframework.data.domain.Page; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Tag(name = "Problems", description = "Endpoints for managing problems/errors encountered within a project") | ||
| public interface ProblemApi { | ||
|
|
||
| @Operation( | ||
| summary = "Create a new problem log", | ||
| description = "Logs a new problem associated with the specified project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "201", | ||
| description = "Problem logged successfully", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ProblemViewResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "400", | ||
| description = "Invalid request body or validation constraints failed", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @PostMapping | ||
| ResponseEntity<ProblemViewResponse> create( | ||
| @PathVariable UUID projectId, | ||
| @RequestBody @Valid CreateProblemRequest request | ||
| ); | ||
|
|
||
| @Operation( | ||
| summary = "Get all problems by project", | ||
| description = "Retrieves a paginated list of all problem summaries belonging to the specified project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "Successfully retrieved list of problem summaries" | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @GetMapping | ||
| ResponseEntity<Page<ProblemSummaryResponse>> getAllByProject( | ||
| @PathVariable UUID projectId, | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "10") int size | ||
| ); | ||
|
|
||
| @Operation( | ||
| summary = "Get problem details by ID", | ||
| description = "Retrieves details of a single problem by its unique identifier and its project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "Successfully retrieved the problem details", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ProblemViewResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project or Problem not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @GetMapping("/{problemId}") | ||
| ResponseEntity<ProblemViewResponse> getById( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId | ||
| ); | ||
|
|
||
| @Operation( | ||
| summary = "Update a problem", | ||
| description = "Updates details of an existing problem by its unique identifier and its project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "Problem updated successfully", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ProblemViewResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "400", | ||
| description = "Invalid request body or validation constraints failed", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project or Problem not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @PatchMapping("/{problemId}") | ||
| ResponseEntity<ProblemViewResponse> update( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId, | ||
| @RequestBody @Valid UpdateProblemRequest request | ||
| ); | ||
|
|
||
| @Operation( | ||
| summary = "Update problem status", | ||
| description = "Updates status of an existing problem by its unique identifier and its project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "Problem status updated successfully", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ProblemViewResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "400", | ||
| description = "Invalid request body or validation constraints failed", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project or Problem not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @PatchMapping("/{problemId}/status") | ||
| ResponseEntity<ProblemViewResponse> updateStatus( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId, | ||
| @RequestBody @Valid UpdateProblemStatusRequest request | ||
| ); | ||
|
|
||
| @Operation( | ||
| summary = "Delete a problem log", | ||
| description = "Deletes an existing problem log by its unique identifier and its project ID." | ||
| ) | ||
| @ApiResponses(value = { | ||
| @ApiResponse( | ||
| responseCode = "204", | ||
| description = "Problem deleted successfully" | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "Project or Problem not found", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "500", | ||
| description = "Internal server error occurred", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiErrorResponse.class) | ||
| ) | ||
| ) | ||
| }) | ||
| @DeleteMapping("/{problemId}") | ||
| ResponseEntity<Void> delete( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId | ||
| ); | ||
| } | ||
105 changes: 105 additions & 0 deletions
105
backend/src/main/java/com/devaulty/backend/adapter/in/web/problem/ProblemController.java
This file contains hidden or 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,105 @@ | ||
| package com.devaulty.backend.adapter.in.web.problem; | ||
|
|
||
| import com.devaulty.backend.adapter.in.web.problem.dto.*; | ||
| import com.devaulty.backend.adapter.in.web.util.UriLocationBuilderHelper; | ||
| import com.devaulty.backend.application.port.in.problem.*; | ||
| import com.devaulty.backend.domain.model.Problem; | ||
| import jakarta.validation.Valid; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.net.URI; | ||
| import java.util.UUID; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/v1/projects/{projectId}/problems") | ||
| public class ProblemController implements ProblemApi{ | ||
|
|
||
| private final CreateProblemUseCase createProblemUseCase; | ||
| private final GetAllProblemsByProjectUseCase getAllProblemsByProjectUseCase; | ||
| private final GetProblemByIdUseCase getProblemByIdUseCase; | ||
| private final UpdateProblemUseCase updateProblemUseCase; | ||
| private final UpdateProblemStatusUseCase updateProblemStatusUseCase; | ||
| private final DeleteProblemUseCase deleteProblemUseCase; | ||
| private final ProblemWebMapper mapper; | ||
| private final UriLocationBuilderHelper uriLocationBuilderHelper; | ||
|
|
||
| public ProblemController(CreateProblemUseCase createProblemUseCase, GetAllProblemsByProjectUseCase getAllProblemsByProjectUseCase, GetProblemByIdUseCase getProblemByIdUseCase, UpdateProblemUseCase updateProblemUseCase, UpdateProblemStatusUseCase updateProblemStatusUseCase, DeleteProblemUseCase deleteProblemUseCase, ProblemWebMapper mapper, UriLocationBuilderHelper uriLocationBuilderHelper) { | ||
| this.createProblemUseCase = createProblemUseCase; | ||
| this.getAllProblemsByProjectUseCase = getAllProblemsByProjectUseCase; | ||
| this.getProblemByIdUseCase = getProblemByIdUseCase; | ||
| this.updateProblemUseCase = updateProblemUseCase; | ||
| this.updateProblemStatusUseCase = updateProblemStatusUseCase; | ||
| this.deleteProblemUseCase = deleteProblemUseCase; | ||
| this.mapper = mapper; | ||
| this.uriLocationBuilderHelper = uriLocationBuilderHelper; | ||
| } | ||
|
|
||
| @Override | ||
| @PostMapping | ||
| public ResponseEntity<ProblemViewResponse> create( | ||
| @PathVariable UUID projectId, | ||
| @RequestBody @Valid CreateProblemRequest request) | ||
| { | ||
| CreateProblemCommand command = mapper.toCreateProblemCommand(request, projectId); | ||
| Problem problem = createProblemUseCase.execute(command); | ||
| URI location = uriLocationBuilderHelper.buildLocationUri(problem.getId()); | ||
| return ResponseEntity.created(location).body(mapper.toViewResponse(problem)); | ||
| } | ||
|
|
||
| @Override | ||
| @GetMapping | ||
| public ResponseEntity<Page<ProblemSummaryResponse>> getAllByProject( | ||
| @PathVariable UUID projectId, | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "10") int size | ||
| ){ | ||
| Page<Problem> problems = getAllProblemsByProjectUseCase.execute(projectId, page, size); | ||
| return ResponseEntity.ok(problems.map(mapper::toSummaryResponse)); | ||
| } | ||
|
|
||
| @Override | ||
| @GetMapping("/{problemId}") | ||
| public ResponseEntity<ProblemViewResponse> getById( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId | ||
| ){ | ||
| Problem problem = getProblemByIdUseCase.execute(projectId, problemId); | ||
| return ResponseEntity.ok(mapper.toViewResponse(problem)); | ||
| } | ||
|
|
||
| @Override | ||
| @PatchMapping("/{problemId}") | ||
| public ResponseEntity<ProblemViewResponse> update( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId, | ||
| @RequestBody @Valid UpdateProblemRequest request | ||
| ){ | ||
| UpdateProblemCommand command = mapper.toUpdateProblemCommand(request, projectId, problemId); | ||
| Problem problem = updateProblemUseCase.execute(command); | ||
| return ResponseEntity.ok(mapper.toViewResponse(problem)); | ||
| } | ||
|
|
||
| @Override | ||
| @PatchMapping("/{problemId}/status") | ||
| public ResponseEntity<ProblemViewResponse> updateStatus( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId, | ||
| @RequestBody @Valid UpdateProblemStatusRequest request | ||
| ){ | ||
| UpdateProblemStatusCommand command = mapper.toUpdateProblemStatusCommand(request, projectId, problemId); | ||
| Problem problem = updateProblemStatusUseCase.execute(command); | ||
| return ResponseEntity.ok(mapper.toViewResponse(problem)); | ||
| } | ||
|
|
||
| @Override | ||
| @DeleteMapping("/{problemId}") | ||
| public ResponseEntity<Void> delete( | ||
| @PathVariable UUID projectId, | ||
| @PathVariable UUID problemId | ||
| ){ | ||
| deleteProblemUseCase.execute(projectId, problemId); | ||
| return ResponseEntity.noContent().build(); | ||
| } | ||
| } |
30 changes: 30 additions & 0 deletions
30
backend/src/main/java/com/devaulty/backend/adapter/in/web/problem/ProblemWebMapper.java
This file contains hidden or 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,30 @@ | ||
| package com.devaulty.backend.adapter.in.web.problem; | ||
|
|
||
| import com.devaulty.backend.adapter.in.web.problem.dto.*; | ||
| import com.devaulty.backend.application.port.in.problem.CreateProblemCommand; | ||
| import com.devaulty.backend.application.port.in.problem.UpdateProblemCommand; | ||
| import com.devaulty.backend.application.port.in.problem.UpdateProblemStatusCommand; | ||
| import com.devaulty.backend.domain.model.Problem; | ||
| import org.mapstruct.Mapper; | ||
| import org.mapstruct.Mapping; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Mapper(componentModel = "spring") | ||
| public interface ProblemWebMapper { | ||
|
|
||
| ProblemViewResponse toViewResponse(Problem problem); | ||
|
|
||
| ProblemSummaryResponse toSummaryResponse(Problem problem); | ||
|
|
||
| @Mapping(target = "projectId", source = "projectId") | ||
| CreateProblemCommand toCreateProblemCommand(CreateProblemRequest request, UUID projectId); | ||
|
|
||
| @Mapping(target = "projectId", source = "projectId") | ||
| @Mapping(target = "id", source = "problemId") | ||
| UpdateProblemCommand toUpdateProblemCommand(UpdateProblemRequest request, UUID projectId, UUID problemId); | ||
|
|
||
| @Mapping(target = "projectId", source = "projectId") | ||
| @Mapping(target = "id", source = "problemId") | ||
| UpdateProblemStatusCommand toUpdateProblemStatusCommand(UpdateProblemStatusRequest request, UUID projectId, UUID problemId); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.