Skip to content

Commit

Permalink
#5 implemented exercise deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
wallysoncarvalho committed Nov 8, 2020
1 parent 651c2e6 commit 49708ac
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 44 deletions.
34 changes: 31 additions & 3 deletions api/src/main/java/info/wallyson/controller/ExerciseController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import info.wallyson.exception.ApiException;
import info.wallyson.service.ExerciseService;
import info.wallyson.validations.exerciseimage.ValidExerciseImage;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.apache.coyote.Response;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -24,6 +22,10 @@
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;

@Validated
@RestController
@RequestMapping(value = "/api/v1/exercises")
Expand All @@ -34,6 +36,20 @@ public ExerciseController(ExerciseService exerciseService) {
this.exerciseService = exerciseService;
}

@GetMapping("{id}")
public ResponseEntity<ExerciseDTO> getExerciseById(
@PathVariable(value = "id", required = true) String id) {

var exercise = this.exerciseService.getExercise(id);

if (exercise.isPresent()) {
var exerciseDto = ExerciseDTO.fromEntity(exercise.get());
return ResponseEntity.ok(exerciseDto);
}

throw ApiException.fromApiError(HttpStatus.NOT_FOUND, "No exercise found for the id " + id);
}

@GetMapping
public ResponseEntity<Page<Exercise>> getExercises(
@PageableDefault(page = 0, size = 10)
Expand All @@ -51,6 +67,18 @@ public ResponseEntity<ExerciseDTO> createExercise(@RequestBody @Valid ExerciseDT
return ResponseEntity.status(201).body(ExerciseDTO.fromEntity(createdExercise));
}

@DeleteMapping("{id}")
public ResponseEntity<ExerciseDTO> deleteExerciseById(
@PathVariable(value = "id", required = true) String id) {
var deletedExercise = this.exerciseService.deleteExercise(id);

if (deletedExercise.isPresent()) {
return ResponseEntity.status(200).body(ExerciseDTO.fromEntity(deletedExercise.get()));
}

throw ApiException.fromApiError(HttpStatus.NOT_FOUND, "No exercise found for the id " + id);
}

@PostMapping(value = "/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<List<ExerciseImageDTO>> uploadImages(
@RequestParam(value = "images", required = true) @ValidExerciseImage
Expand Down
34 changes: 24 additions & 10 deletions api/src/main/java/info/wallyson/dto/ExerciseDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import info.wallyson.entity.Exercise;
import info.wallyson.entity.ExerciseImage;
import info.wallyson.entity.ExerciseMuscle;
import lombok.Builder;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Data
Expand All @@ -15,29 +17,41 @@ public class ExerciseDTO {
private String id;
@NotBlank private String name;
private String description;
private List<String> images;
private Set<String> images;
private Set<String> muscles;
@NotBlank private String createdBy;

public Exercise toEntity() {
var imagesSet =
this.images != null
? this.images.stream()
.map(img -> new ExerciseImage(null, img))
.collect(Collectors.toSet())
: null;

return new Exercise(null, this.name, this.description, imagesSet, this.createdBy);
return new Exercise(
null, this.name, this.description, imagesToSet(), musclesToSet(), this.createdBy);
}

private Set<ExerciseImage> imagesToSet() {
return this.images != null
? this.images.stream().map(img -> new ExerciseImage(null, img)).collect(Collectors.toSet())
: null;
}

private Set<ExerciseMuscle> musclesToSet() {
return this.muscles != null
? this.muscles.stream()
.map(name -> new ExerciseMuscle(null, name))
.collect(Collectors.toSet())
: null;
}

public static ExerciseDTO fromEntity(Exercise ex) {
var images = ex.getImages().stream().map(ExerciseImage::getId_url).collect(Collectors.toList());
var images = ex.getImages().stream().map(ExerciseImage::getId_url).collect(Collectors.toSet());
var muscles =
ex.getMuscles().stream().map(ExerciseMuscle::getMuscleName).collect(Collectors.toSet());
var id = ex.getId() != null ? ex.getId().toString() : "";

return ExerciseDTO.builder()
.id(id)
.name(ex.getName())
.description(ex.getDescription())
.images(images)
.muscles(muscles)
.createdBy(ex.getCreatedBy())
.build();
}
Expand Down
18 changes: 14 additions & 4 deletions api/src/main/java/info/wallyson/entity/Exercise.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package info.wallyson.entity;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
Expand All @@ -9,6 +10,7 @@
import java.util.Set;
import java.util.UUID;

@Setter
@Getter
@Entity
@Table(
Expand All @@ -33,6 +35,10 @@ public class Exercise extends BaseEntity {
@JoinColumn(name = "exercise_id")
private Set<ExerciseImage> images;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "exercise_id")
private Set<ExerciseMuscle> muscles;

@NotBlank private String createdBy;

public Exercise() {}
Expand All @@ -42,14 +48,18 @@ public Exercise(
@NotBlank String name,
String description,
Set<ExerciseImage> images,
Set<ExerciseMuscle> muscles,
String createdBy) {
this.id = id;
this.name = name;
this.description = description;
this.images = images;
this.muscles = muscles;
this.createdBy = createdBy;
}



@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -58,8 +68,8 @@ public boolean equals(Object o) {
return Objects.equals(getId(), exercise.getId());
}

// @Override
// public int hashCode() {
// return Objects.hash(getId());
// }
@Override
public int hashCode() {
return Objects.hash(getId());
}
}
19 changes: 19 additions & 0 deletions api/src/main/java/info/wallyson/entity/ExerciseMuscle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package info.wallyson.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "exercise_muscle")
@AllArgsConstructor
@Getter
public class ExerciseMuscle {
@Id @GeneratedValue private Long id;

private String muscleName;
}
12 changes: 11 additions & 1 deletion api/src/main/java/info/wallyson/service/ExerciseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import info.wallyson.entity.Exercise;
import info.wallyson.exception.ApiException;
import info.wallyson.repository.ExerciseRepository;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -19,6 +18,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -51,6 +51,16 @@ public Exercise createExercise(Exercise exercise) {
List.of("An exercise with the name " + exercise.getName() + " already exists !"));
}

public Optional<Exercise> getExercise(String id) {
return this.exerciseRepository.findById(id);
}

public Optional<Exercise> deleteExercise(String id) {
var exercise = this.exerciseRepository.findById(id);
exercise.ifPresent(e -> this.exerciseRepository.deleteById(id));
return exercise;
}

public List<String> storeMultipartFiles(List<MultipartFile> images) {
return images.stream()
.filter(mp -> !mp.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import info.wallyson.dto.ExerciseDTO;
import info.wallyson.dto.ExerciseImageDTO;
import info.wallyson.entity.Exercise;
import info.wallyson.factory.ExerciseDTOFactory;
import info.wallyson.factory.ExerciseMother;
import info.wallyson.service.ExerciseService;
import info.wallyson.utils.JsonUtils;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -27,6 +27,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
Expand All @@ -46,9 +48,9 @@ class ExerciseControllerTest {
@DisplayName("Should get exercise page 0 and max size 10")
void should_get_exercise_page_with_default_params() throws Exception {
var pageable = PageRequest.of(0, 10);
var exerciseEntityList = ExerciseDTOFactory.dtoToEntity(ExerciseDTOFactory.exerciseList());
var exerciseEntityList = ExerciseMother.dtoToEntity(ExerciseMother.exerciseList());
var page =
new PageImpl<>(exerciseEntityList, pageable, ExerciseDTOFactory.exerciseList().size());
new PageImpl<>(exerciseEntityList, pageable, ExerciseMother.exerciseList().size());
when(exerciseService.getExercises(any(Pageable.class))).thenReturn(page);
var result =
this.mockMvc
Expand All @@ -66,9 +68,9 @@ void should_get_exercise_page_with_default_params() throws Exception {
@DisplayName("Should use default for invalid page and size params")
void should_return_bad_request_with_invalid_query_params() throws Exception {
var pageable = PageRequest.of(0, 10);
var exerciseEntityList = ExerciseDTOFactory.dtoToEntity(ExerciseDTOFactory.exerciseList());
var exerciseEntityList = ExerciseMother.dtoToEntity(ExerciseMother.exerciseList());
var page =
new PageImpl<>(exerciseEntityList, pageable, ExerciseDTOFactory.exerciseList().size());
new PageImpl<>(exerciseEntityList, pageable, ExerciseMother.exerciseList().size());
when(exerciseService.getExercises(any(Pageable.class))).thenReturn(page);
var result =
this.mockMvc
Expand All @@ -88,7 +90,7 @@ void should_return_bad_request_with_invalid_query_params() throws Exception {
@Test
@DisplayName("Should create a new exercise with all information")
void should_create_new_exercise_and_return() throws Exception {
var exercise = ExerciseDTOFactory.exercise();
var exercise = ExerciseMother.exercise();

when(exerciseService.createExercise(any(Exercise.class))).thenReturn(exercise.toEntity());

Expand All @@ -103,12 +105,83 @@ void should_create_new_exercise_and_return() throws Exception {
.andExpect(jsonPath("$.name").value(exercise.getName()))
.andExpect(jsonPath("$.createdBy").value(exercise.getCreatedBy()))
.andExpect(jsonPath("$.images").isArray())
.andDo(print())
.andExpect(jsonPath("$.muscles").isArray())
.andReturn();

verify(exerciseService, times(1)).createExercise(any(Exercise.class));
}

@Test
@DisplayName("Should get an exercise by its id")
void should_get_exercise_by_id() throws Exception {
var exercise = ExerciseMother.exercise();
when(exerciseService.getExercise(exercise.getId()))
.thenReturn(Optional.of(exercise.toEntity()));

this.mockMvc
.perform(
get("/api/v1/exercises/" + exercise.getId()).accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(exercise.getName()))
.andExpect(jsonPath("$.createdBy").value(exercise.getCreatedBy()))
.andExpect(jsonPath("$.images").isArray())
.andExpect(jsonPath("$.muscles").isArray())
.andReturn();

verify(exerciseService, times(1)).getExercise(exercise.getId());
}

@Test
@DisplayName("Should return not found when theres no exercise with provided id")
void should_not_find_exercise() throws Exception {
var id = "unknown id";
when(exerciseService.getExercise(id)).thenReturn(Optional.empty());

this.mockMvc
.perform(get("/api/v1/exercises/" + id).accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").exists())
.andReturn();

verify(exerciseService, times(1)).getExercise(id);
}

@Test
@DisplayName("Should delete an exercise by its id")
void should_delete_exercise() throws Exception {
var exercise = ExerciseMother.exercise();
when(exerciseService.deleteExercise(exercise.getId()))
.thenReturn(Optional.of(exercise.toEntity()));

this.mockMvc
.perform(
delete("/api/v1/exercises/" + exercise.getId())
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(exercise.getName()))
.andExpect(jsonPath("$.createdBy").value(exercise.getCreatedBy()))
.andExpect(jsonPath("$.images").isArray())
.andExpect(jsonPath("$.muscles").isArray())
.andReturn();

verify(exerciseService, times(1)).deleteExercise(exercise.getId());
}

@Test
@DisplayName("Should fail to delete by id a nonexistent exercise")
void should_fail_to_delete_nonexistent_exercise() throws Exception {
var id = UUID.randomUUID().toString();
when(exerciseService.deleteExercise(id)).thenReturn(Optional.empty());

this.mockMvc
.perform(delete("/api/v1/exercises/" + id).accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").exists())
.andReturn();

verify(exerciseService, times(1)).deleteExercise(id);
}

@Test
@DisplayName("Should fail to create an exercise without a name and id of creator")
void should_return_validation_error() throws Exception {
Expand Down
Loading

0 comments on commit 49708ac

Please sign in to comment.