fix audit report mismatches across backend and android
This commit is contained in:
@@ -153,6 +153,16 @@ public class DropdownController {
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/customers/{customerId}/pets")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getCustomerPets(@PathVariable Long customerId) {
|
||||
return ResponseEntity.ok(
|
||||
petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream()
|
||||
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/suppliers")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getSuppliers() {
|
||||
|
||||
@@ -48,12 +48,8 @@ public class PetImageController {
|
||||
|
||||
@GetMapping("/{id}/image")
|
||||
public ResponseEntity<Resource> getPetImage(@PathVariable Long id) {
|
||||
try {
|
||||
PetService.ImagePayload payload = petService.loadPetImage(id, currentUserId(), currentUserRole());
|
||||
return ResponseEntity.ok().contentType(payload.mediaType()).body(payload.resource());
|
||||
} catch (PetService.ForbiddenImageAccessException ex) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||
}
|
||||
PetService.ImagePayload payload = petService.loadPetImage(id, currentUserId(), currentUserRole());
|
||||
return ResponseEntity.ok().contentType(payload.mediaType()).body(payload.resource());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/image")
|
||||
|
||||
@@ -15,9 +15,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/refunds")
|
||||
@@ -33,27 +31,20 @@ public class RefundController {
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> createRefund(@Valid @RequestBody RefundRequest request) {
|
||||
try {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
||||
.orElse(null);
|
||||
public ResponseEntity<RefundResponse> createRefund(@Valid @RequestBody RefundRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
||||
.orElse(null);
|
||||
|
||||
Long customerId = null;
|
||||
if (role != null && role.equals("CUSTOMER")) {
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
RefundResponse refund = refundService.createRefund(request, customerId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.badRequest().body(error);
|
||||
Long customerId = null;
|
||||
if (role != null && role.equals("CUSTOMER")) {
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(refundService.createRefund(request, customerId));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -77,54 +68,32 @@ public class RefundController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> getRefundById(@PathVariable Long id) {
|
||||
try {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
||||
.orElse(null);
|
||||
public ResponseEntity<RefundResponse> getRefundById(@PathVariable Long id) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
||||
.orElse(null);
|
||||
|
||||
Long customerId = null;
|
||||
if (role != null && role.equals("CUSTOMER")) {
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
RefundResponse refund = refundService.getRefundById(id, customerId);
|
||||
return ResponseEntity.ok(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
Long customerId = null;
|
||||
if (role != null && role.equals("CUSTOMER")) {
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(refundService.getRefundById(id, customerId));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) {
|
||||
try {
|
||||
RefundResponse refund = refundService.updateRefundStatus(id, request.getStatus());
|
||||
return ResponseEntity.ok(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.badRequest().body(error);
|
||||
}
|
||||
public ResponseEntity<RefundResponse> updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) {
|
||||
return ResponseEntity.ok(refundService.updateRefundStatus(id, request.getStatus()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<?> deleteRefund(@PathVariable Long id) {
|
||||
try {
|
||||
refundService.deleteRefund(id);
|
||||
Map<String, String> response = new HashMap<>();
|
||||
response.put("message", "Refund deleted successfully");
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
public ResponseEntity<Void> deleteRefund(@PathVariable Long id) {
|
||||
refundService.deleteRefund(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.petshop.backend.exception;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -13,7 +14,10 @@ import java.time.LocalDateTime;
|
||||
@Component
|
||||
public class ApiErrorResponder {
|
||||
|
||||
private final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||
private final ObjectMapper objectMapper = JsonMapper.builder()
|
||||
.findAndAddModules()
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.build();
|
||||
|
||||
public void write(HttpServletResponse response, HttpStatus status, String message, String details, String path) throws IOException {
|
||||
response.setStatus(status.value());
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.petshop.backend.exception;
|
||||
|
||||
import com.petshop.backend.service.PetService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.core.PropertyReferenceException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -78,6 +82,26 @@ public class GlobalExceptionHandler {
|
||||
return buildErrorResponse(HttpStatus.valueOf(ex.getStatusCode().value()), message, ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleNoResourceFound(NoResourceFoundException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.NOT_FOUND, "Route not found", ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(PropertyReferenceException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleBadSortProperty(PropertyReferenceException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName(), ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(PetService.ForbiddenImageAccessException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleForbiddenImageAccess(PetService.ForbiddenImageAccessException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.FORBIDDEN, "Access to this pet image is not allowed", ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleGenericException(Exception ex, HttpServletRequest request) {
|
||||
String message = ex.getMessage() == null || ex.getMessage().isBlank()
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.petshop.backend.entity.Product;
|
||||
import com.petshop.backend.entity.Refund;
|
||||
import com.petshop.backend.entity.RefundItem;
|
||||
import com.petshop.backend.entity.Sale;
|
||||
import com.petshop.backend.exception.BusinessException;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.ProductRepository;
|
||||
import com.petshop.backend.repository.RefundRepository;
|
||||
import com.petshop.backend.repository.SaleRepository;
|
||||
@@ -40,14 +42,14 @@ public class RefundService {
|
||||
@Transactional
|
||||
public RefundResponse createRefund(RefundRequest request, Long customerId) {
|
||||
Sale sale = saleRepository.findById(request.getSaleId())
|
||||
.orElseThrow(() -> new RuntimeException("Sale not found"));
|
||||
.orElseThrow(() -> new BusinessException("Sale not found"));
|
||||
|
||||
if (sale.getCustomer() == null) {
|
||||
throw new RuntimeException("Sale has no associated customer");
|
||||
throw new BusinessException("Sale has no associated customer");
|
||||
}
|
||||
|
||||
if (customerId != null && !sale.getCustomer().getId().equals(customerId)) {
|
||||
throw new RuntimeException("You can only create refunds for your own purchases");
|
||||
throw new BusinessException("You can only create refunds for your own purchases");
|
||||
}
|
||||
|
||||
Refund refund = new Refund();
|
||||
@@ -59,13 +61,13 @@ public class RefundService {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
for (var itemRequest : request.getItems()) {
|
||||
Product product = productRepository.findById(itemRequest.getProdId())
|
||||
.orElseThrow(() -> new RuntimeException("Product not found: " + itemRequest.getProdId()));
|
||||
|
||||
.orElseThrow(() -> new BusinessException("Product not found: " + itemRequest.getProdId()));
|
||||
|
||||
BigDecimal unitPrice = sale.getItems().stream()
|
||||
.filter(item -> item.getProduct().getProdId().equals(itemRequest.getProdId()))
|
||||
.findFirst()
|
||||
.map(item -> item.getUnitPrice())
|
||||
.orElseThrow(() -> new RuntimeException("Product " + itemRequest.getProdId() + " was not in original sale"));
|
||||
.orElseThrow(() -> new BusinessException("Product " + itemRequest.getProdId() + " was not in original sale"));
|
||||
|
||||
RefundItem refundItem = new RefundItem();
|
||||
refundItem.setProduct(product);
|
||||
@@ -84,10 +86,10 @@ public class RefundService {
|
||||
@Transactional(readOnly = true)
|
||||
public RefundResponse getRefundById(Long id, Long customerId) {
|
||||
Refund refund = refundRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Refund not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Refund not found"));
|
||||
|
||||
if (customerId != null && !refund.getCustomerId().equals(customerId)) {
|
||||
throw new RuntimeException("You can only view your own refunds");
|
||||
throw new ResourceNotFoundException("You can only view your own refunds");
|
||||
}
|
||||
|
||||
return toResponse(refund);
|
||||
@@ -111,18 +113,18 @@ public class RefundService {
|
||||
@Transactional
|
||||
public RefundResponse updateRefundStatus(Long id, String status) {
|
||||
Refund refund = refundRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Refund not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Refund not found"));
|
||||
|
||||
Refund.RefundStatus newStatus;
|
||||
try {
|
||||
newStatus = Refund.RefundStatus.valueOf(status.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Invalid status: " + status);
|
||||
throw new BusinessException("Invalid status: " + status);
|
||||
}
|
||||
|
||||
if (refund.getStatus() == Refund.RefundStatus.PENDING && newStatus == Refund.RefundStatus.APPROVED) {
|
||||
Sale originalSale = saleRepository.findById(refund.getSaleId())
|
||||
.orElseThrow(() -> new RuntimeException("Original sale not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found"));
|
||||
|
||||
SaleRequest saleRequest = new SaleRequest();
|
||||
saleRequest.setStoreId(originalSale.getStore().getStoreId());
|
||||
@@ -150,7 +152,7 @@ public class RefundService {
|
||||
@Transactional
|
||||
public void deleteRefund(Long id) {
|
||||
if (!refundRepository.existsById(id)) {
|
||||
throw new RuntimeException("Refund not found");
|
||||
throw new ResourceNotFoundException("Refund not found");
|
||||
}
|
||||
refundRepository.deleteById(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user