diff --git a/games.yaml b/games.yaml index 7adaadb..5ecad5c 100644 --- a/games.yaml +++ b/games.yaml @@ -4,9 +4,9 @@ info: description: API sobre videojuegos version: 1.0.0 servers: - - url: 'https://www.games.com' + - url: 'https://www.games.com/api' paths: - /games: + /v1/games: get: summary: Devuelve la lista de juegos description: | @@ -46,6 +46,7 @@ paths: post: summary: Registrar un juego description: Registra un nuevo juego + deprecated: true requestBody: description: Los datos del nuevo juego content: @@ -77,6 +78,41 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerError' + /v2/games: + post: + summary: Registrar un juego + description: Registra un nuevo juego + requestBody: + description: Los datos del nuevo juego + content: + application/json: + schema: + $ref: '#/components/schemas/GameInDtoV2' + responses: + '201': + description: Juego registrado + content: + application/json: + schema: + $ref: '#/components/schemas/GameV2' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequest' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Conflict' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' components: schemas: Game: @@ -96,6 +132,24 @@ components: releaseDate: 2020-01-12 price: 45 category: Survival + GameV2: + allOf: + - $ref: '#/components/schemas/GameInDtoV2' + type: object + properties: + id: + type: number + format: int64 + description: Identificador unico del juego + example: + id: 1 + name: 7 Days to die + description: Surival game + type: Shooter + releaseDate: 2020-01-12 + price: 45 + category: Survival + rate: 4 GameInDto: type: object properties: @@ -126,6 +180,42 @@ components: releaseDate: 2020-01-12 price: 45 category: Survival + GameInDtoV2: + type: object + properties: + name: + type: string + description: Nombre del juego + description: + type: string + description: Descripción del juego + type: + type: string + description: Tipo de juego + category: + type: string + description: Categoría del juego + releaseDate: + type: string + format: date + description: Fecha de lanzamiento del juego + price: + type: number + format: float + description: Precio aproximado del juego + rate: + type: number + format: int32 + description: Valoración del juego + required: true + example: + name: 7 Days to die + description: Surival game + type: Shooter + releaseDate: 2020-01-12 + price: 45 + category: Survival + rate: 4 GameOutDto: type: object properties: diff --git a/src/main/java/com/svalero/games/controller/GameController.java b/src/main/java/com/svalero/games/controller/GameController.java index eaaca25..984aa12 100644 --- a/src/main/java/com/svalero/games/controller/GameController.java +++ b/src/main/java/com/svalero/games/controller/GameController.java @@ -1,26 +1,27 @@ package com.svalero.games.controller; import com.svalero.games.domain.Game; +import com.svalero.games.domain.GameV2; import com.svalero.games.dto.GameDto; import com.svalero.games.dto.GameOutDto; import com.svalero.games.exception.ErrorResponse; import com.svalero.games.exception.GameNotFoundException; import com.svalero.games.service.GameService; import jakarta.validation.Valid; -import org.modelmapper.TypeToken; +import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; -import org.modelmapper.ModelMapper; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController +@RequestMapping("/api") public class GameController { @Autowired @@ -28,33 +29,39 @@ public class GameController { @Autowired private ModelMapper modelMapper; - @GetMapping("/games") + @GetMapping("/v1/games") public ResponseEntity> getAll(@RequestParam(value = "category", defaultValue = "") String category) { List gamesOutDto = gameService.findAll(category); return ResponseEntity.ok(gamesOutDto); } - @GetMapping("/games/{id}") + @GetMapping("/v1/games/{id}") public ResponseEntity get(@PathVariable long id) throws GameNotFoundException { GameDto gameDto = gameService.findById(id); return ResponseEntity.ok(gameDto); } - @PostMapping("/games") - public ResponseEntity addGame(@Valid @RequestBody Game game) { - // TODO Añadir validación + @PostMapping("/v1/games") + public ResponseEntity addGameV1(@Valid @RequestBody Game game) { // TODO Comprobar que no exista ya un juego con el mismo nombre Game newGame = gameService.add(game); return new ResponseEntity<>(newGame, HttpStatus.CREATED); } - @PutMapping("/games/{id}") + @PostMapping("/v2/games") + public ResponseEntity addGamev2(@Valid @RequestBody GameV2 game) { + // TODO Comprobar que no exista ya un juego con el mismo nombre + GameV2 newGame = gameService.addV2(game); + return new ResponseEntity<>(newGame, HttpStatus.CREATED); + } + + @PutMapping("/v1/games/{id}") public ResponseEntity modifyGame(@PathVariable long id, @RequestBody Game game) throws GameNotFoundException { Game newGame = gameService.modify(id, game); return ResponseEntity.ok(newGame); } - @DeleteMapping("/games/{id}") + @DeleteMapping("/v1/games/{id}") public ResponseEntity deleteGame(@PathVariable long id) throws GameNotFoundException { gameService.delete(id); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/svalero/games/controller/ReviewController.java b/src/main/java/com/svalero/games/controller/ReviewController.java index a3b74fd..83ea6a5 100644 --- a/src/main/java/com/svalero/games/controller/ReviewController.java +++ b/src/main/java/com/svalero/games/controller/ReviewController.java @@ -26,6 +26,7 @@ import java.util.Map; @RestController +@RequestMapping("/api/v1") public class ReviewController { @Autowired diff --git a/src/main/java/com/svalero/games/controller/UserController.java b/src/main/java/com/svalero/games/controller/UserController.java index 1b35a46..d131474 100644 --- a/src/main/java/com/svalero/games/controller/UserController.java +++ b/src/main/java/com/svalero/games/controller/UserController.java @@ -12,6 +12,7 @@ import java.util.List; @RestController +@RequestMapping("/api/v1") public class UserController { @Autowired diff --git a/src/main/java/com/svalero/games/domain/GameV2.java b/src/main/java/com/svalero/games/domain/GameV2.java new file mode 100644 index 0000000..ca0aa15 --- /dev/null +++ b/src/main/java/com/svalero/games/domain/GameV2.java @@ -0,0 +1,50 @@ +package com.svalero.games.domain; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.persistence.*; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Entity(name = "GameV2") +@Table(name = "games") +public class GameV2 { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + @Column + @NotNull(message="name is mandatory") + private String name; + @Column + private String description; + @Column + private String type; + @Column(name = "release_date") + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate releaseDate; + @Column + @Min(value=0, message="the price must be a positive number") + private float price; + @Column + private String category; + @Column + private String comments; + @Column + @NotNull(message="rate is mandatory") + private int rate; + + @OneToMany(mappedBy = "game") + @JsonBackReference + private List reviews; +} diff --git a/src/main/java/com/svalero/games/dto/GameDto.java b/src/main/java/com/svalero/games/dto/GameDto.java index 52b7512..e042937 100644 --- a/src/main/java/com/svalero/games/dto/GameDto.java +++ b/src/main/java/com/svalero/games/dto/GameDto.java @@ -1,6 +1,7 @@ package com.svalero.games.dto; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,4 +19,5 @@ public class GameDto { private String category; private float price; private long daysToRelease; + private int rate; } diff --git a/src/main/java/com/svalero/games/dto/GameOutDto.java b/src/main/java/com/svalero/games/dto/GameOutDto.java index cb21bb3..d7e5b1c 100644 --- a/src/main/java/com/svalero/games/dto/GameOutDto.java +++ b/src/main/java/com/svalero/games/dto/GameOutDto.java @@ -13,4 +13,5 @@ public class GameOutDto { private String description; private String type; private String category; + private int rate; } diff --git a/src/main/java/com/svalero/games/repository/GameRepositoryV2.java b/src/main/java/com/svalero/games/repository/GameRepositoryV2.java new file mode 100644 index 0000000..6ac1497 --- /dev/null +++ b/src/main/java/com/svalero/games/repository/GameRepositoryV2.java @@ -0,0 +1,9 @@ +package com.svalero.games.repository; + +import com.svalero.games.domain.GameV2; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface GameRepositoryV2 extends CrudRepository { +} diff --git a/src/main/java/com/svalero/games/service/GameService.java b/src/main/java/com/svalero/games/service/GameService.java index 109ebb1..6c8c9dd 100644 --- a/src/main/java/com/svalero/games/service/GameService.java +++ b/src/main/java/com/svalero/games/service/GameService.java @@ -1,10 +1,12 @@ package com.svalero.games.service; import com.svalero.games.domain.Game; +import com.svalero.games.domain.GameV2; import com.svalero.games.dto.GameDto; import com.svalero.games.dto.GameOutDto; import com.svalero.games.exception.GameNotFoundException; import com.svalero.games.repository.GameRepository; +import com.svalero.games.repository.GameRepositoryV2; import com.svalero.games.util.DateUtil; import org.modelmapper.ModelMapper; import org.modelmapper.TypeToken; @@ -13,6 +15,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.stream.Stream; @Service public class GameService { @@ -20,12 +23,30 @@ public class GameService { @Autowired private GameRepository gameRepository; @Autowired + private GameRepositoryV2 gameRepositoryV2; + @Autowired private ModelMapper modelMapper; public Game add(Game game) { return gameRepository.save(game); } + public GameV2 addV2(GameV2 game) { + return gameRepositoryV2.save(game); + } + + public List getGames(String name, String description, String address) { + List allGames = gameRepository.findAll(); + + Stream gameStream = allGames.stream(); + if (name != null) + gameStream = gameStream.filter(i -> i.getName().equalsIgnoreCase(name)); + if (description != null) + gameStream = gameStream.filter(i -> i.getDescription().equalsIgnoreCase(description)); + + return gameStream.toList(); + } + public void delete(long id) throws GameNotFoundException { Game game = gameRepository.findById(id) .orElseThrow(GameNotFoundException::new); diff --git a/src/test/java/com/svalero/games/GameControllerIntegrationTest.java b/src/test/java/com/svalero/games/GameControllerIntegrationTest.java new file mode 100644 index 0000000..944a8e8 --- /dev/null +++ b/src/test/java/com/svalero/games/GameControllerIntegrationTest.java @@ -0,0 +1,78 @@ +package com.svalero.games; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.svalero.games.domain.Game; +import com.svalero.games.repository.GameRepository; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class GameControllerIntegrationTest { + + @Autowired + private GameRepository gameRepository; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + public void setUp() { + gameRepository.save(Game.builder().name("testGame1").build()); + gameRepository.save(Game.builder().name("testGame2").build()); + } + + @AfterEach + public void clean() { + gameRepository.deleteAll(); + } + + @Test + @Order(1) + public void shouldDeleteGame() throws Exception { + Long id = 1L; + + mockMvc.perform(delete("/games/{id}", id)) + .andExpect(status().isNoContent()); + } + + @Test + @Order(2) + public void shouldGetAllGames() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/games")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(2)); + } + + @Test + @Order(3) + public void shouldAddGame() throws Exception { + Game game = new Game(); + game.setName("testGame"); + + String json = objectMapper.writeValueAsString(game); + + mockMvc.perform(post("/games") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.name").value("testGame")); + } +} diff --git a/src/test/java/com/svalero/games/GameControllerTests.java b/src/test/java/com/svalero/games/GameControllerTests.java index a5e4005..931c8f5 100644 --- a/src/test/java/com/svalero/games/GameControllerTests.java +++ b/src/test/java/com/svalero/games/GameControllerTests.java @@ -40,8 +40,8 @@ public class GameControllerTests { @Test public void testGetAll() throws Exception { List gamesOutDto = List.of( - new GameOutDto(1, "7 Days to die", "Survival game", "shooter", "survival"), - new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports") + new GameOutDto(1, "7 Days to die", "Survival game", "shooter", "survival", 0), + new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports", 0) ); when(gameService.findAll("")).thenReturn(gamesOutDto); @@ -62,8 +62,8 @@ public void testGetAll() throws Exception { @Test public void testGetAllByCategory() throws Exception { List gamesOutDto = List.of( - new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports"), - new GameOutDto(3, "FIFA 2026", "Football game", "sport", "sports") + new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports", 0), + new GameOutDto(3, "FIFA 2026", "Football game", "sport", "sports", 0) ); when(gameService.findAll("sports")).thenReturn(gamesOutDto); diff --git a/src/test/java/com/svalero/games/GameServiceTests.java b/src/test/java/com/svalero/games/GameServiceTests.java index 1755fef..dc723d3 100644 --- a/src/test/java/com/svalero/games/GameServiceTests.java +++ b/src/test/java/com/svalero/games/GameServiceTests.java @@ -37,8 +37,8 @@ public void testFindAll() { new Game(2, "FIFA 2025", "Football game", "sport", LocalDate.now(), 60, "sports", null, null) ); List modelMapperOut = List.of( - new GameOutDto(1, "7 Days to die", "Survival game", "shooter", "survival"), - new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports") + new GameOutDto(1, "7 Days to die", "Survival game", "shooter", "survival", 0), + new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports", 0) ); when(gameRepository.findAll()).thenReturn(mockGameList); @@ -61,8 +61,8 @@ public void testFindAllByCategory() { new Game(3, "FIFA 2026", "Football game", "sport", LocalDate.now(), 60, "sports", null, null) ); List mockModelMapperOut = List.of( - new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports"), - new GameOutDto(3, "FIFA 2026", "Football game", "sport", "sports") + new GameOutDto(2, "FIFA 2025", "Football game", "sport", "sports", 0), + new GameOutDto(3, "FIFA 2026", "Football game", "sport", "sports", 0) ); when(gameRepository.findByCategoryOrderByNameDesc("sports")).thenReturn(mockGameList);