Skip to content
Snippets Groups Projects
Commit c73d04dd authored by Karsch Lukas's avatar Karsch Lukas
Browse files

#42 enable method security. delete and update plant endpoint + service.

parent 43d995d1
No related branches found
No related tags found
1 merge request!27Resolve "plants controller: require admin role & add delete endpoint"
Showing
with 165 additions and 2 deletions
package hdm.mi.growbros.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration
@EnableMethodSecurity(
securedEnabled = true
)
public class MethodSecurityConfig {
}
...@@ -2,6 +2,8 @@ package hdm.mi.growbros.controllers; ...@@ -2,6 +2,8 @@ package hdm.mi.growbros.controllers;
import hdm.mi.growbros.exceptions.PlantNotFoundException; import hdm.mi.growbros.exceptions.PlantNotFoundException;
import hdm.mi.growbros.models.dto.CustomPageDto; import hdm.mi.growbros.models.dto.CustomPageDto;
import hdm.mi.growbros.models.dto.DeletedPlantDto;
import hdm.mi.growbros.models.dto.PlantUpdateRequest;
import hdm.mi.growbros.models.plant.Plant; import hdm.mi.growbros.models.plant.Plant;
import hdm.mi.growbros.models.search.SearchRequest; import hdm.mi.growbros.models.search.SearchRequest;
import hdm.mi.growbros.service.PlantsService; import hdm.mi.growbros.service.PlantsService;
...@@ -45,4 +47,14 @@ public class PlantsController { ...@@ -45,4 +47,14 @@ public class PlantsController {
public void createPlant(@RequestBody @Valid Plant plant) { public void createPlant(@RequestBody @Valid Plant plant) {
plantsService.createPlant(plant); plantsService.createPlant(plant);
} }
@DeleteMapping("/{id}")
public DeletedPlantDto deletePlant(@PathVariable long id) {
return plantsService.deletePlant(id);
}
@PatchMapping("/{id}")
public Plant updatePlant(@PathVariable long id, @RequestBody PlantUpdateRequest updateRequest) {
return plantsService.updatePlant(id, updateRequest);
}
} }
...@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; ...@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
...@@ -22,6 +23,11 @@ public class RestErrorHandler extends ResponseEntityExceptionHandler { ...@@ -22,6 +23,11 @@ public class RestErrorHandler extends ResponseEntityExceptionHandler {
return ResponseEntity.status(e.getStatusCode()).body(e.getMessage()); return ResponseEntity.status(e.getStatusCode()).body(e.getMessage());
} }
@ExceptionHandler(AccessDeniedException.class)
protected ResponseEntity<String> handleAccessDeniedException() {
return ResponseEntity.status(401).body("Access denied: role not sufficient");
}
@ExceptionHandler @ExceptionHandler
protected ResponseEntity<Object> handleInternalServerError(RuntimeException e, WebRequest req) { protected ResponseEntity<Object> handleInternalServerError(RuntimeException e, WebRequest req) {
log.error("Handling internal server error: {}", e.getClass()); log.error("Handling internal server error: {}", e.getClass());
......
package hdm.mi.growbros.models.dto;
import hdm.mi.growbros.models.plant.Plant;
public record DeletedPlantDto(Plant plant, int deletedGardenEntries, int deletedWishlistEntries) {
}
package hdm.mi.growbros.models.dto;
import hdm.mi.growbros.models.plant.GroundType;
import hdm.mi.growbros.models.plant.LightingDemand;
import hdm.mi.growbros.models.plant.NutrientDemand;
import hdm.mi.growbros.models.plant.WaterDemand;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class PlantUpdateRequest {
private String name;
private String latinName;
private String description;
private String origin;
private String growingTips;
private Integer plantWeekStart;
private Integer plantWeekEnd;
private Integer growthDuration;
private Integer harvestWeekStart;
private Integer harvestWeekEnd;
private GroundType groundType;
private NutrientDemand nutrientDemand;
private WaterDemand waterDemand;
private LightingDemand lightingDemand;
private String imageUrl;
}
...@@ -30,7 +30,6 @@ public class Plant { ...@@ -30,7 +30,6 @@ public class Plant {
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String description; private String description;
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String origin; private String origin;
......
...@@ -22,6 +22,8 @@ public interface GardenRepository extends JpaRepository<GardenEntry, Long> { ...@@ -22,6 +22,8 @@ public interface GardenRepository extends JpaRepository<GardenEntry, Long> {
void deleteByIdAndUser(Long entryId, User user); void deleteByIdAndUser(Long entryId, User user);
int deleteAllByPlant_Id(Long plantId);
@Query(""" @Query("""
SELECT ge FROM GardenEntry ge SELECT ge FROM GardenEntry ge
WHERE ge.user = :user WHERE ge.user = :user
......
package hdm.mi.growbros.repositories; package hdm.mi.growbros.repositories;
import hdm.mi.growbros.models.plant.Plant;
import hdm.mi.growbros.models.WishListEntry; import hdm.mi.growbros.models.WishListEntry;
import hdm.mi.growbros.models.plant.Plant;
import hdm.mi.growbros.models.user.User; import hdm.mi.growbros.models.user.User;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
...@@ -48,4 +48,6 @@ public interface WishListRepository extends JpaRepository<WishListEntry, Long> { ...@@ -48,4 +48,6 @@ public interface WishListRepository extends JpaRepository<WishListEntry, Long> {
List<WishListEntry> findByCurrentPlantingWeek(@Param("user") User user, @Param("week") int currentWeek); List<WishListEntry> findByCurrentPlantingWeek(@Param("user") User user, @Param("week") int currentWeek);
boolean existsByUserAndPlant(User user, Plant plant); boolean existsByUserAndPlant(User user, Plant plant);
int deleteAllByPlant_Id(long id);
} }
\ No newline at end of file
package hdm.mi.growbros.service; package hdm.mi.growbros.service;
import hdm.mi.growbros.exceptions.PlantNotFoundException;
import hdm.mi.growbros.models.dto.CustomPageDto; import hdm.mi.growbros.models.dto.CustomPageDto;
import hdm.mi.growbros.models.dto.DeletedPlantDto;
import hdm.mi.growbros.models.dto.PlantUpdateRequest;
import hdm.mi.growbros.models.plant.Plant; import hdm.mi.growbros.models.plant.Plant;
import hdm.mi.growbros.models.search.PlantSpecification; import hdm.mi.growbros.models.search.PlantSpecification;
import hdm.mi.growbros.models.search.SearchRequest; import hdm.mi.growbros.models.search.SearchRequest;
import hdm.mi.growbros.repositories.GardenRepository;
import hdm.mi.growbros.repositories.PlantRepository; import hdm.mi.growbros.repositories.PlantRepository;
import hdm.mi.growbros.repositories.WishListRepository;
import hdm.mi.growbros.util.EntityMapper;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.*;
...@@ -22,7 +30,10 @@ public class PlantsService { ...@@ -22,7 +30,10 @@ public class PlantsService {
private final static int MAX_PAGE_SIZE = 100; private final static int MAX_PAGE_SIZE = 100;
private final PlantRepository plantRepository; private final PlantRepository plantRepository;
private final WishListRepository wishListRepository;
private final GardenRepository gardenRepository;
private final PlantSpecification plantSpecification; private final PlantSpecification plantSpecification;
private final EntityMapper entityMapper;
public CustomPageDto<Plant> getPlants(int pageNumber, int pageSize) { public CustomPageDto<Plant> getPlants(int pageNumber, int pageSize) {
pageNumber = Math.max(0, pageNumber); pageNumber = Math.max(0, pageNumber);
...@@ -51,6 +62,7 @@ public class PlantsService { ...@@ -51,6 +62,7 @@ public class PlantsService {
return plantRepository.findAll(spec); return plantRepository.findAll(spec);
} }
@Secured({"ADMIN"})
public void createPlant(Plant plant) { public void createPlant(Plant plant) {
log.info("Saving plant '{}' to repository.", plant.getName()); log.info("Saving plant '{}' to repository.", plant.getName());
plantRepository.save(plant); plantRepository.save(plant);
...@@ -59,4 +71,27 @@ public class PlantsService { ...@@ -59,4 +71,27 @@ public class PlantsService {
public Optional<Plant> findById(long id) { public Optional<Plant> findById(long id) {
return plantRepository.findById(id); return plantRepository.findById(id);
} }
@Transactional
@Secured({"ADMIN"})
public DeletedPlantDto deletePlant(long id) {
Optional<Plant> plant = plantRepository.findById(id);
if (plant.isEmpty()) {
return new DeletedPlantDto(null, 0, 0);
}
log.info("Deleting plant with id {}.", id);
int deletedGardenEntries = gardenRepository.deleteAllByPlant_Id(id);
int deletedWishlistEntries = wishListRepository.deleteAllByPlant_Id(id);
plantRepository.deleteById(id);
log.info("Plant '{}' was deleted. {} garden entries and {} wishlist entries were affected", plant.get().getName(), deletedGardenEntries, deletedWishlistEntries);
return new DeletedPlantDto(plant.get(), deletedGardenEntries, deletedWishlistEntries);
}
@Transactional
@Secured({"ADMIN"})
public Plant updatePlant(long id, PlantUpdateRequest updateRequest) {
Plant plant = plantRepository.findById(id).orElseThrow(() -> new PlantNotFoundException(id));
entityMapper.map(updateRequest, plant);
return plantRepository.save(plant);
}
} }
...@@ -88,6 +88,7 @@ public class EntityMapper { ...@@ -88,6 +88,7 @@ public class EntityMapper {
entityField.setAccessible(true); entityField.setAccessible(true);
final Object newValue = updateField.get(updateRequest); final Object newValue = updateField.get(updateRequest);
entityField.set(existingEntity, newValue); entityField.set(existingEntity, newValue);
log.debug("set new value for {} : {}", updateField.getName(), newValue);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
log.error("Illegal access exception while mapping object", e); log.error("Illegal access exception while mapping object", e);
} }
......
package hdm.mi.growbros.service;
import hdm.mi.growbros.models.dto.PlantUpdateRequest;
import hdm.mi.growbros.models.plant.Plant;
import hdm.mi.growbros.repositories.PlantRepository;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
@ActiveProfiles("test")
class PlantsServiceIntegrationTest {
@Autowired
private PlantsService plantsService;
@Autowired
private PlantRepository plantRepository;
private static Plant plant;
@BeforeAll
static void setup(@Autowired PlantRepository plantRepository) {
plant = plantRepository.save(Plant.builder().name("My plant!").build());
}
@Test
void canUpdatePlant() {
String newName = "new name!";
plantsService.updatePlant(plant.getId(), PlantUpdateRequest.builder().name(newName).build());
assertEquals(newName, plantRepository.findById(plant.getId()).get().getName());
}
}
package hdm.mi.growbros.util; package hdm.mi.growbros.util;
import hdm.mi.growbros.models.dto.PlantUpdateRequest;
import hdm.mi.growbros.models.plant.Plant;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -46,6 +48,25 @@ class EntityMapperTest { ...@@ -46,6 +48,25 @@ class EntityMapperTest {
); );
} }
@Test
void shouldWork_onActualModel() {
//arrange
String description = "My description";
final Plant plant = Plant.builder().name("Plant initial name").description(description).build();
String newName = "new name!";
final PlantUpdateRequest updateRequest = PlantUpdateRequest.builder().name(newName).build();
//act
entityMapper.map(updateRequest, plant);
//assert
assertAll(
() -> assertEquals(newName, plant.getName()),
() -> assertEquals(description, plant.getDescription())
);
}
private class UpdaterObject { private class UpdaterObject {
private String value1; private String value1;
private String value2; private String value2;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment