Skip to content

Commit

Permalink
#5 Implemented relation of exercise and images
Browse files Browse the repository at this point in the history
  • Loading branch information
wallysoncarvalho committed Nov 5, 2020
1 parent 104fafa commit 5750166
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import info.wallyson.service.ExerciseService;
import info.wallyson.validations.exerciseimage.ValidExerciseImage;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -46,12 +45,11 @@ public ResponseEntity<Page<Exercise>> getExercises(
}

@PostMapping
public ResponseEntity<Exercise> createExercise(@RequestBody @Valid ExerciseDTO exercise) {
// get the id of the user from security context
public ResponseEntity<ExerciseDTO> createExercise(@RequestBody @Valid ExerciseDTO exercise) {
exercise.setCreatedBy("1fa67471-fc71-4a13-9d87-15d51f932bb2");

var createdExercise = this.exerciseService.createExercise(exercise);
return ResponseEntity.status(201).body(createdExercise);
var exerciseEntity = exercise.toEntity();
var createdExercise = this.exerciseService.createExercise(exerciseEntity);
return ResponseEntity.status(201).body(ExerciseDTO.fromEntity(createdExercise));
}

@PostMapping(value = "/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Expand Down
32 changes: 28 additions & 4 deletions api/src/main/java/info/wallyson/dto/ExerciseDTO.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
package info.wallyson.dto;

import info.wallyson.entity.Exercise;
import javax.validation.constraints.NotBlank;
import info.wallyson.entity.ExerciseImage;
import lombok.Builder;
import lombok.Data;

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

@Data
@Builder
public class ExerciseDTO {
private String id;
@NotBlank private String name;
private String description;
private String imageUrl;
private String createdBy;
private List<String> images;
@NotBlank private String createdBy;

public Exercise toEntity() {
return new Exercise(null, this.name, this.description, this.imageUrl, this.createdBy);
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);
}

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

return ExerciseDTO.builder()
.id(id)
.name(ex.getName())
.description(ex.getDescription())
.images(images)
.createdBy(ex.getCreatedBy())
.build();
}
}
12 changes: 5 additions & 7 deletions api/src/main/java/info/wallyson/dto/ExerciseImageDTO.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package info.wallyson.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExerciseImageDTO {
private String name;
private String url;
private String name;
private String url;
}
42 changes: 23 additions & 19 deletions api/src/main/java/info/wallyson/entity/Exercise.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package info.wallyson.entity;

import java.util.Objects;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

@Getter
@Setter
@Entity
@Table(
name = "EXERCISE",
Expand All @@ -31,18 +26,27 @@ public class Exercise extends BaseEntity {
private UUID id;

@NotBlank private String name;

private String description;
private String imageUrl;
private String createdBy;

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

@NotBlank private String createdBy;

public Exercise() {}

public Exercise(
UUID id, @NotBlank String name, String description, String imageUrl, String createdBy) {
UUID id,
@NotBlank String name,
String description,
Set<ExerciseImage> images,
String createdBy) {
this.id = id;
this.name = name;
this.description = description;
this.imageUrl = imageUrl;
this.images = images;
this.createdBy = createdBy;
}

Expand All @@ -54,8 +58,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/ExerciseImage.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_images")
@AllArgsConstructor
@Getter
public class ExerciseImage {
@Id @GeneratedValue private Long id;

private String id_url;
}
4 changes: 2 additions & 2 deletions api/src/main/java/info/wallyson/exception/ApiException.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public static ApiException fromApiError(HttpStatus status, String message) {
return new ApiException(api);
}

public static ApiException fromApiError(HttpStatus status, String message, String errors) {
var api = new ApiError(status, message);
public static ApiException fromApiError(HttpStatus status, String message, List<String> errors) {
var api = new ApiError(status, message, errors);
return new ApiException(api);
}
}
25 changes: 11 additions & 14 deletions api/src/main/java/info/wallyson/service/ExerciseService.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
package info.wallyson.service;

import info.wallyson.dto.ExerciseDTO;
import info.wallyson.entity.Exercise;
import info.wallyson.exception.ApiException;
import info.wallyson.repository.ExerciseRepository;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -24,6 +13,14 @@
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ExerciseService {
Expand All @@ -40,17 +37,17 @@ public Page<Exercise> getExercises(Pageable pageable) {
return this.exerciseRepository.findAll(pageable);
}

public Exercise createExercise(ExerciseDTO exercise) {
public Exercise createExercise(Exercise exercise) {
var exerciseExists = this.exerciseRepository.existsByName(exercise.getName());

if (!exerciseExists) {
return this.exerciseRepository.save(exercise.toEntity());
return this.exerciseRepository.save(exercise);
}

throw ApiException.fromApiError(
HttpStatus.BAD_REQUEST,
"Constraint error !",
"An exercise with the name " + exercise.getName() + " already exists !");
List.of("An exercise with the name " + exercise.getName() + " already exists !"));
}

public List<String> storeMultipartFiles(List<MultipartFile> images) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package info.wallyson.controller;

import ch.qos.logback.core.util.FileUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import info.wallyson.dto.ExerciseDTO;
import info.wallyson.dto.ExerciseImageDTO;
import info.wallyson.exception.ApiException;
import info.wallyson.entity.Exercise;
import info.wallyson.factory.ExerciseDTOFactory;
import info.wallyson.service.ExerciseService;
import info.wallyson.utils.JsonUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -20,7 +18,6 @@
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit.jupiter.SpringExtension;
Expand All @@ -46,19 +43,8 @@ class ExerciseControllerTest {
@MockBean private ExerciseService exerciseService;

@Test
@DisplayName("Make a GET request to exercises endpoint and expect HTTP status 200")
void should_get_status_isOk() throws Exception {
var result =
this.mockMvc
.perform(get("/api/v1/exercises").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
assertEquals(200, result.getResponse().getStatus());
}

@Test
@DisplayName("Should make request without paging params and return page = 0 of size = 10")
void should_get_page_with_default_params() throws Exception {
@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 page =
Expand All @@ -77,8 +63,7 @@ void should_get_page_with_default_params() throws Exception {
}

@Test
@DisplayName(
"Should fall to default params when invalid values for 'page' and 'size' are provided")
@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());
Expand All @@ -101,45 +86,43 @@ void should_return_bad_request_with_invalid_query_params() throws Exception {
}

@Test
@DisplayName(
"Make POST request to create a new exercise. Returns created exercise with ID and HTTP"
+ " status 201")
@DisplayName("Should create a new exercise with all information")
void should_create_new_exercise_and_return() throws Exception {
when(exerciseService.createExercise(any(ExerciseDTO.class)))
.thenReturn(ExerciseDTOFactory.exercise().toEntity());
var result =
this.mockMvc
.perform(
post("/api/v1/exercises")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.characterEncoding("utf-8")
.content(JsonUtils.toJson(ExerciseDTOFactory.exercise()))
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value(ExerciseDTOFactory.exercise().getName()))
.andExpect(jsonPath("$.createdBy").exists())
.andReturn();
var exercise = ExerciseDTOFactory.exercise();

verify(exerciseService, times(1)).createExercise(any(ExerciseDTO.class));
assertNotNull(result.getResponse());
when(exerciseService.createExercise(any(Exercise.class))).thenReturn(exercise.toEntity());

this.mockMvc
.perform(
post("/api/v1/exercises")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.characterEncoding("utf-8")
.content(JsonUtils.toJson(exercise))
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value(exercise.getName()))
.andExpect(jsonPath("$.createdBy").value(exercise.getCreatedBy()))
.andExpect(jsonPath("$.images").isArray())
.andDo(print())
.andReturn();

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

@Test
@DisplayName(
"Returns validation error when trying to create a new exercise with blank name and get HTTP"
+ " status 400")
@DisplayName("Should fail to create an exercise without a name and id of creator")
void should_return_validation_error() throws Exception {
var result =
this.mockMvc
.perform(
post("/api/v1/exercises")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJson(ExerciseDTO.builder().build()))
.characterEncoding("utf-8")
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().is(400))
.andReturn();
assertEquals("{\"name\":\"must not be blank\"}", result.getResponse().getContentAsString());
this.mockMvc
.perform(
post("/api/v1/exercises")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJson(ExerciseDTO.builder().build()))
.characterEncoding("utf-8")
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().is(400))
.andExpect(jsonPath("$.name").exists())
.andExpect(jsonPath("$.createdBy").exists())
.andReturn();
}

@Test
Expand Down
Loading

0 comments on commit 5750166

Please sign in to comment.