Skip to content

Commit

Permalink
feat(SILVA-514): adding favorite function on the backend (#423)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulushcgcj authored and jazzgrewal committed Nov 7, 2024
1 parent 2391107 commit c903285
Show file tree
Hide file tree
Showing 23 changed files with 1,056 additions and 318 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ca.bc.gov.restapi.results.common.exception;

public class UserFavoriteNotFoundException extends NotFoundGenericException {

public UserFavoriteNotFoundException() {
super("UserFavoriteEntity");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ca.bc.gov.restapi.results.postgres.endpoint;

import ca.bc.gov.restapi.results.postgres.service.UserOpeningService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/api/openings/favorites", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class OpeningFavoriteEndpoint {

private final UserOpeningService userOpeningService;

@GetMapping
public List<Long> getFavorites() {
return userOpeningService.listUserFavoriteOpenings();
}

@PutMapping("/{id}")
@ResponseStatus(HttpStatus.ACCEPTED)
public void addToFavorites(@PathVariable Long id) {
userOpeningService.addUserFavoriteOpening(id);
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void removeFromFavorites(@PathVariable Long id) {
userOpeningService.removeUserFavoriteOpening(id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public ResponseEntity<List<MyRecentActionsRequestsDto>> getUserTrackedOpenings()
* @return HTTP status code 201 if success, no response body.
*/
@PostMapping("/{id}")
public ResponseEntity<Void> saveUserOpening(Long id) {
userOpeningService.saveOpeningToUser(id);
public ResponseEntity<Void> saveUserOpening(@PathVariable Long id) {
userOpeningService.addUserFavoriteOpening(id);
return ResponseEntity.status(HttpStatus.CREATED).build();
}

Expand All @@ -60,7 +60,7 @@ public ResponseEntity<Void> saveUserOpening(Long id) {
public ResponseEntity<Void> deleteUserOpening(
@PathVariable
Long id) {
userOpeningService.deleteOpeningFromUserFavourite(id);
userOpeningService.removeUserFavoriteOpening(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package ca.bc.gov.restapi.results.postgres.service;

import ca.bc.gov.restapi.results.common.exception.OpeningNotFoundException;
import ca.bc.gov.restapi.results.common.exception.UserFavoriteNotFoundException;
import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException;
import ca.bc.gov.restapi.results.common.security.LoggedUserService;
import ca.bc.gov.restapi.results.oracle.dto.OpeningSearchResponseDto;
import ca.bc.gov.restapi.results.oracle.entity.OpeningEntity;
import ca.bc.gov.restapi.results.oracle.enums.OpeningCategoryEnum;
import ca.bc.gov.restapi.results.oracle.enums.OpeningStatusEnum;
import ca.bc.gov.restapi.results.oracle.repository.OpeningRepository;
import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto;
import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity;
import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity;
Expand All @@ -11,13 +18,14 @@
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ocpsoft.prettytime.PrettyTime;
import org.springframework.stereotype.Service;

/** This class contains methods for handling User favourite Openings. */
/**
* This class contains methods for handling User favourite Openings.
*/
@Slf4j
@Service
@RequiredArgsConstructor
Expand All @@ -29,6 +37,8 @@ public class UserOpeningService {

private final OpeningsActivityRepository openingsActivityRepository;

private final OpeningRepository openingRepository;

/**
* Gets user's tracked Openings.
*
Expand Down Expand Up @@ -78,46 +88,63 @@ public List<MyRecentActionsRequestsDto> getUserTrackedOpenings() {
return resultList;
}

/**
* Saves one or more Openings IDs to an user.
*
* @param openingId The opening ID.
*/
@Transactional
public void saveOpeningToUser(Long openingId) {
log.info("Opening ID to save in the user favourites: {}", openingId);
public List<Long> listUserFavoriteOpenings() {
log.info("Loading user favorite openings for {}", loggedUserService.getLoggedUserId());

final String userId = loggedUserService.getLoggedUserId();
List<UserOpeningEntity> userList = userOpeningRepository
.findAllByUserId(loggedUserService.getLoggedUserId());

UserOpeningEntity entity = new UserOpeningEntity();
entity.setUserId(userId);
entity.setOpeningId(openingId);
if (userList.isEmpty()) {
log.info("No saved openings for {}", loggedUserService.getLoggedUserId());
return List.of();
}

userOpeningRepository.saveAndFlush(entity);
log.info("Opening ID saved in the user's favourites!");
return
userList
.stream()
.map(UserOpeningEntity::getOpeningId)
.toList();
}

/**
* Deletes one or more user opening from favourite.
*
* @param openingId The opening ID.
*/
@Transactional
public void deleteOpeningFromUserFavourite(Long openingId) {
log.info("Opening ID to delete from the user's favourites: {}", openingId);
String userId = loggedUserService.getLoggedUserId();

UserOpeningEntityId openingPk = new UserOpeningEntityId(userId, openingId);
public void addUserFavoriteOpening(Long openingId) {
log.info("Adding opening ID {} as favorite for user {}", openingId,
loggedUserService.getLoggedUserId());

Optional<UserOpeningEntity> userOpeningsOp = userOpeningRepository.findById(openingPk);

if (userOpeningsOp.isEmpty()) {
log.info("Opening id {} not found in the user's favourite list!", openingId);
throw new UserOpeningNotFoundException();
if (openingRepository.findById(openingId).isEmpty()) {
log.info("Opening ID not found: {}", openingId);
throw new OpeningNotFoundException();
}

userOpeningRepository.delete(userOpeningsOp.get());
userOpeningRepository.flush();
log.info("Opening ID deleted from the favourites!");
log.info("Opening ID {} added as favorite for user {}", openingId,
loggedUserService.getLoggedUserId());
userOpeningRepository.saveAndFlush(
new UserOpeningEntity(
loggedUserService.getLoggedUserId(),
openingId
)
);
}

@Transactional
public void removeUserFavoriteOpening(Long openingId) {
log.info("Removing opening ID {} from the favorites for user {}", openingId,
loggedUserService.getLoggedUserId());
userOpeningRepository.findById(
new UserOpeningEntityId(
loggedUserService.getLoggedUserId(),
openingId
)
).ifPresentOrElse(
userOpening -> {
userOpeningRepository.delete(userOpening);
userOpeningRepository.flush();
log.info("Opening ID deleted from the favourites!");
},
() -> {
log.info("Opening id {} not found in the user's favourite list!", openingId);
throw new UserFavoriteNotFoundException();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package ca.bc.gov.restapi.results.postgres.endpoint;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import ca.bc.gov.restapi.results.extensions.AbstractTestContainerIntegrationTest;
import ca.bc.gov.restapi.results.extensions.WithMockJwt;
import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@DisplayName("Integration Test | Favorite Openings Endpoint")
@TestMethodOrder(OrderAnnotation.class)
@WithMockJwt
@AutoConfigureMockMvc
class OpeningFavoriteEndpointIntegrationTest extends AbstractTestContainerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private UserOpeningRepository userOpeningRepository;

@Test
@Order(1)
@DisplayName("No favorites to begin with")
void shouldBeEmpty() throws Exception {

mockMvc
.perform(
MockMvcRequestBuilders.get("/api/openings/favorites")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isEmpty());
}

@Test
@Order(2)
@DisplayName("Should add to favorite")
void shouldAddToFavorite() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.put("/api/openings/favorites/{openingId}", 101)
.with(csrf().asHeader())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isAccepted())
.andExpect(content().string(StringUtils.EMPTY));

assertThat(userOpeningRepository.findAll())
.isNotNull()
.isNotEmpty()
.hasSize(1);
}

@Test
@Order(3)
@DisplayName("Should not add to favorite if doesn't exist")
void shouldNotAddIfDoesNotExist() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.put("/api/openings/favorites/{openingId}", 987)
.with(csrf().asHeader())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(content().string(StringUtils.EMPTY));
//.andExpect(content().string("UserOpening record(s) not found!"));
}

@Test
@Order(4)
@DisplayName("Multiple requests to add to favorite should not fail, nor duplicate")
void shouldAddToFavoriteAgain() throws Exception {
shouldAddToFavorite();
}

@Test
@Order(5)
@DisplayName("Should see list of favorites")
void shouldBeAbleToSeeOpening() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.get("/api/openings/favorites")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.[0]").value(101));
}

@Test
@Order(6)
@DisplayName("Should remove from favorite")
void shouldRemoveFromFavorites() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.delete("/api/openings/favorites/{openingId}", 101)
.with(csrf().asHeader())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andExpect(content().string(StringUtils.EMPTY));

assertThat(userOpeningRepository.findAll())
.isNotNull()
.isEmpty();
}

@Test
@Order(7)
@DisplayName("Should thrown an error if trying to remove entry that doesn't exist")
void shouldThrownErrorIfNoFavoriteFound() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.delete("/api/openings/favorites/{openingId}", 101)
.with(csrf().asHeader())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(content().string(StringUtils.EMPTY));

}


}
Loading

0 comments on commit c903285

Please sign in to comment.