diff --git a/pom.xml b/pom.xml
index 611e651b..40764dd4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -181,6 +181,19 @@
spring-test
+
+ org.mockito
+ mockito-inline
+ 5.2.0
+ test
+
+
+
+ org.mockito
+ mockito-core
+ 5.12.0
+ test
+
diff --git a/src/main/java/org/petmarket/users/controller/ComplaintsAdminController.java b/src/main/java/org/petmarket/users/controller/ComplaintsAdminController.java
new file mode 100644
index 00000000..e0a1cb4b
--- /dev/null
+++ b/src/main/java/org/petmarket/users/controller/ComplaintsAdminController.java
@@ -0,0 +1,98 @@
+package org.petmarket.users.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.petmarket.users.dto.ComplaintResponseDto;
+import org.petmarket.users.entity.ComplaintStatus;
+import org.petmarket.users.service.ComplaintService;
+import org.petmarket.utils.annotations.parametrs.ParameterPageNumber;
+import org.petmarket.utils.annotations.parametrs.ParameterPageSize;
+import org.petmarket.utils.annotations.responses.ApiResponseForbidden;
+import org.petmarket.utils.annotations.responses.ApiResponseNotFound;
+import org.petmarket.utils.annotations.responses.ApiResponseSuccessful;
+import org.petmarket.utils.annotations.responses.ApiResponseUnauthorized;
+import org.springframework.data.domain.Sort;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "Complaints", description = "the user complaints API")
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@Validated
+@RequestMapping(value = "/v1/admin/complaints")
+public class ComplaintsAdminController {
+ private final ComplaintService complaintService;
+
+ @Operation(summary = "Delete complaint")
+ @ApiResponseSuccessful
+ @ApiResponseUnauthorized
+ @ApiResponseForbidden
+ @PreAuthorize("isAuthenticated()")
+ @DeleteMapping("/{id}")
+ public void deleteComplaint(@PathVariable Long id) {
+ log.info("Deleting complaint");
+ complaintService.deleteComplaint(id);
+ }
+
+ @Operation(summary = "Get complaint")
+ @ApiResponseSuccessful
+ @ApiResponseNotFound
+ @ApiResponseUnauthorized
+ @ApiResponseForbidden
+ @PreAuthorize("isAuthenticated()")
+ @GetMapping("/{id}")
+ public ComplaintResponseDto getComplaint(@PathVariable Long id) {
+ log.info("Getting complaint");
+ return complaintService.getComplaint(id);
+ }
+
+ @Operation(summary = "Get complaints")
+ @ApiResponseSuccessful
+ @ApiResponseUnauthorized
+ @ApiResponseForbidden
+ @PreAuthorize("isAuthenticated()")
+ @GetMapping
+ public List getComplaints(
+ @RequestParam(required = false, defaultValue = "PENDING") ComplaintStatus complaintStatus,
+ @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page,
+ @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size,
+ @RequestParam(defaultValue = "DESC") Sort.Direction direction) {
+ log.info("Getting complaints");
+ return complaintService.getComplaints(complaintStatus, size, page, direction);
+ }
+
+ @Operation(summary = "Get complaints by user id")
+ @ApiResponseSuccessful
+ @ApiResponseUnauthorized
+ @ApiResponseForbidden
+ @PreAuthorize("isAuthenticated()")
+ @GetMapping("/user/{userId}")
+ public List getComplaintsByUserId(
+ @PathVariable Long userId,
+ @RequestParam(required = false, defaultValue = "PENDING") ComplaintStatus status,
+ @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page,
+ @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size,
+ @RequestParam(defaultValue = "DESC") Sort.Direction direction) {
+ log.info("Getting complaints by user id");
+ return complaintService.getComplaintsByUserId(userId, status, size, page, direction);
+ }
+
+ @Operation(summary = "Update complaint status")
+ @ApiResponseSuccessful
+ @ApiResponseUnauthorized
+ @ApiResponseForbidden
+ @PreAuthorize("isAuthenticated()")
+ @PutMapping("/{id}")
+ public void updateComplaintStatus(@PathVariable Long id,
+ @RequestParam(defaultValue = "RESOLVED") ComplaintStatus status) {
+ log.info("Updating complaint status");
+ complaintService.updateStatusById(id, status);
+ }
+}
diff --git a/src/main/java/org/petmarket/users/controller/ComplaintsController.java b/src/main/java/org/petmarket/users/controller/ComplaintsController.java
index 287906b2..87027086 100644
--- a/src/main/java/org/petmarket/users/controller/ComplaintsController.java
+++ b/src/main/java/org/petmarket/users/controller/ComplaintsController.java
@@ -1,17 +1,36 @@
package org.petmarket.users.controller;
+import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.petmarket.users.dto.ComplaintRequestDto;
+import org.petmarket.users.service.ComplaintService;
+import org.petmarket.utils.annotations.responses.ApiResponseBadRequest;
+import org.petmarket.utils.annotations.responses.ApiResponseSuccessful;
+import org.petmarket.utils.annotations.responses.ApiResponseUnauthorized;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
-@Tag(name = "Users", description = "the users API")
+@Tag(name = "Complaints", description = "the user complaints API")
@Slf4j
@RequiredArgsConstructor
@RestController
@Validated
@RequestMapping(value = "/v1/complaints")
public class ComplaintsController {
+ private final ComplaintService complaintService;
+
+ @Operation(summary = "Add complaint")
+ @ApiResponseSuccessful
+ @ApiResponseUnauthorized
+ @ApiResponseBadRequest
+ @PreAuthorize("isAuthenticated()")
+ @PostMapping
+ public void addComplaint(@RequestBody @Valid ComplaintRequestDto complaintRequestDto) {
+ log.info("Adding complaint");
+ complaintService.addComplaint(complaintRequestDto);
+ }
}
diff --git a/src/main/java/org/petmarket/users/dto/ComplaintRequestDto.java b/src/main/java/org/petmarket/users/dto/ComplaintRequestDto.java
index 20544837..98e3948e 100644
--- a/src/main/java/org/petmarket/users/dto/ComplaintRequestDto.java
+++ b/src/main/java/org/petmarket/users/dto/ComplaintRequestDto.java
@@ -1,13 +1,25 @@
package org.petmarket.users.dto;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
import lombok.*;
+import org.springframework.validation.annotation.Validated;
@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
+@Validated
public class ComplaintRequestDto {
+ @NotBlank(message = "The 'text' cannot be empty")
+ @Schema(example = "Hello, how are you?")
+ @Size(max = 10000, message = "The 'text' length must be less than or equal to 10000")
private String complaint;
+
+ @Schema(example = "1")
+ @JsonProperty("complained_user_id")
private Long complainedUserId;
}
diff --git a/src/main/java/org/petmarket/users/dto/ComplaintResponseDto.java b/src/main/java/org/petmarket/users/dto/ComplaintResponseDto.java
new file mode 100644
index 00000000..4a26e42e
--- /dev/null
+++ b/src/main/java/org/petmarket/users/dto/ComplaintResponseDto.java
@@ -0,0 +1,20 @@
+package org.petmarket.users.dto;
+
+import lombok.*;
+
+import java.util.Date;
+
+@Setter
+@Getter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ComplaintResponseDto {
+ private Long id;
+ private String complaint;
+ private Long userId;
+ private Long complainedUserId;
+ private String status;
+ private Date created;
+ private Date updated;
+}
diff --git a/src/main/java/org/petmarket/users/mapper/ComplaintMapper.java b/src/main/java/org/petmarket/users/mapper/ComplaintMapper.java
index 4eac2d78..556b64e5 100644
--- a/src/main/java/org/petmarket/users/mapper/ComplaintMapper.java
+++ b/src/main/java/org/petmarket/users/mapper/ComplaintMapper.java
@@ -1,11 +1,21 @@
package org.petmarket.users.mapper;
import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
import org.petmarket.config.MapperConfig;
import org.petmarket.users.dto.ComplaintRequestDto;
+import org.petmarket.users.dto.ComplaintResponseDto;
import org.petmarket.users.entity.Complaint;
+import java.util.List;
+
@Mapper(config = MapperConfig.class)
public interface ComplaintMapper {
Complaint mapDtoToComplaint(ComplaintRequestDto complaintRequestDto);
+
+ @Mapping(target = "userId", source = "user.id")
+ @Mapping(target = "complainedUserId", source = "complainedUser.id")
+ ComplaintResponseDto mapComplaintToDto(Complaint complaint);
+
+ List mapComplaintToDto(List complaints);
}
diff --git a/src/main/java/org/petmarket/users/repository/ComplaintRepository.java b/src/main/java/org/petmarket/users/repository/ComplaintRepository.java
index 6c5da6b6..f3c6f312 100644
--- a/src/main/java/org/petmarket/users/repository/ComplaintRepository.java
+++ b/src/main/java/org/petmarket/users/repository/ComplaintRepository.java
@@ -5,6 +5,9 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
@@ -12,4 +15,8 @@ public interface ComplaintRepository extends JpaRepository {
Page findAllByComplainedUserIdAndStatus(Long userId, ComplaintStatus complaintStatus, Pageable pageable);
Page findAllByStatus(ComplaintStatus complaintStatus, Pageable pageable);
+
+ @Modifying
+ @Query("UPDATE Complaint c SET c.status = :status WHERE c.id = :id")
+ void updateStatusById(@Param("id") Long id, @Param("status") ComplaintStatus status);
}
diff --git a/src/main/java/org/petmarket/users/service/ComplaintService.java b/src/main/java/org/petmarket/users/service/ComplaintService.java
index 71dbef5a..0b7e22a3 100644
--- a/src/main/java/org/petmarket/users/service/ComplaintService.java
+++ b/src/main/java/org/petmarket/users/service/ComplaintService.java
@@ -1,17 +1,20 @@
package org.petmarket.users.service;
+import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.petmarket.users.dto.ComplaintRequestDto;
+import org.petmarket.users.dto.ComplaintResponseDto;
import org.petmarket.users.entity.Complaint;
import org.petmarket.users.entity.ComplaintStatus;
import org.petmarket.users.mapper.ComplaintMapper;
import org.petmarket.users.repository.ComplaintRepository;
-import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
+import java.util.List;
+
@Slf4j
@RequiredArgsConstructor
@Service
@@ -20,39 +23,43 @@ public class ComplaintService {
private final ComplaintMapper complaintMapper;
private final UserService userService;
+ @Transactional
public void addComplaint(ComplaintRequestDto complaintRequestDto) {
if (complaintRequestDto.getComplainedUserId().equals(UserService.getCurrentUserId())) {
log.error("User cannot complain about himself");
throw new IllegalArgumentException("User cannot complain about himself");
}
- log.info("Adding complaint: {}", complaintRequestDto);
Complaint complaint = complaintMapper.mapDtoToComplaint(complaintRequestDto);
complaint.setUser(userService.getCurrentUser());
+ complaint.setComplainedUser(userService.findById(complaintRequestDto.getComplainedUserId()));
+ complaint.setStatus(ComplaintStatus.PENDING);
complaintRepository.save(complaint);
}
public void deleteComplaint(Long id) {
- log.info("Deleting complaint with id: {}", id);
complaintRepository.deleteById(id);
}
- public Complaint getComplaint(Long id) {
- log.info("Getting complaint with id: {}", id);
- return complaintRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Complaint not found"));
+ public ComplaintResponseDto getComplaint(Long id) {
+ return complaintMapper.mapComplaintToDto(complaintRepository.findById(id)
+ .orElseThrow(() -> new IllegalArgumentException("Complaint not found")));
}
- public Page getComplaints(ComplaintStatus complaintStatus, int size,
- int page, Sort.Direction direction) {
- log.info("Getting all complaints");
- return complaintRepository.findAllByStatus(complaintStatus,
- PageRequest.of(page - 1, size, Sort.by(direction, "created")));
+ public List getComplaints(ComplaintStatus complaintStatus, int size,
+ int page, Sort.Direction direction) {
+ return complaintMapper.mapComplaintToDto(complaintRepository.findAllByStatus(complaintStatus,
+ PageRequest.of(page - 1, size, Sort.by(direction, "created"))).toList());
}
- public Page getComplaintsByUserId(Long userId, ComplaintStatus status,
+ public List getComplaintsByUserId(Long userId, ComplaintStatus status,
int size, int page, Sort.Direction direction) {
- log.info("Getting all complaints by user id: {}", userId);
- return complaintRepository.findAllByComplainedUserIdAndStatus(userId, status, PageRequest
- .of(page - 1, size, Sort.by(direction, "created")));
+ return complaintMapper.mapComplaintToDto(complaintRepository.findAllByComplainedUserIdAndStatus(userId,
+ status, PageRequest.of(page - 1, size, Sort.by(direction, "created"))).toList());
+ }
+
+ @Transactional
+ public void updateStatusById(Long id, ComplaintStatus status) {
+ complaintRepository.updateStatusById(id, status);
}
}
diff --git a/src/test/java/org/petmarket/users/service/ComplaintServiceTest.java b/src/test/java/org/petmarket/users/service/ComplaintServiceTest.java
new file mode 100644
index 00000000..db05b320
--- /dev/null
+++ b/src/test/java/org/petmarket/users/service/ComplaintServiceTest.java
@@ -0,0 +1,182 @@
+package org.petmarket.users.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.*;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.petmarket.users.dto.ComplaintRequestDto;
+import org.petmarket.users.dto.ComplaintResponseDto;
+import org.petmarket.users.entity.Complaint;
+import org.petmarket.users.entity.ComplaintStatus;
+import org.petmarket.users.entity.User;
+import org.petmarket.users.mapper.ComplaintMapper;
+import org.petmarket.users.repository.ComplaintRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@ExtendWith(MockitoExtension.class)
+class ComplaintServiceTest {
+ @Mock
+ private ComplaintRepository complaintRepository;
+
+ @Mock
+ private ComplaintMapper complaintMapper;
+
+ @Mock
+ private UserService userService;
+
+ @InjectMocks
+ private ComplaintService complaintService;
+
+ @Test
+ public void testAddComplaintSelfComplaintThrowsException() {
+ try (MockedStatic mockedUserService = mockStatic(UserService.class)) {
+ // Arrange
+ ComplaintRequestDto dto = new ComplaintRequestDto();
+ dto.setComplainedUserId(1L);
+
+ mockedUserService.when(UserService::getCurrentUserId).thenReturn(1L);
+
+ // Act
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> complaintService.addComplaint(dto));
+
+ // Assert
+ assertEquals("User cannot complain about himself", exception.getMessage());
+ }
+ }
+
+ @Test
+ public void testAddComplaintSuccess() {
+ try (MockedStatic mockedUserService = mockStatic(UserService.class)) {
+ // Arrange
+ ComplaintRequestDto dto = new ComplaintRequestDto();
+ dto.setComplainedUserId(2L);
+
+ Complaint complaint = new Complaint();
+ mockedUserService.when(UserService::getCurrentUserId).thenReturn(1L);
+ when(userService.getCurrentUser()).thenReturn(new User());
+ when(complaintMapper.mapDtoToComplaint(dto)).thenReturn(complaint);
+
+ // Act
+ complaintService.addComplaint(dto);
+
+ // Assert
+ verify(complaintRepository, times(1)).save(complaint);
+ }
+ }
+
+ @Test
+ public void testDeleteComplaintSuccess() {
+ // Arrange
+ Long complaintId = 1L;
+
+ // Act
+ complaintService.deleteComplaint(complaintId);
+
+ // Assert
+ verify(complaintRepository, times(1)).deleteById(complaintId);
+ }
+
+ @Test
+ public void testGetComplaintNotFound() {
+ // Arrange
+ Long complaintId = 1L;
+ when(complaintRepository.findById(complaintId)).thenReturn(Optional.empty());
+
+ // Act
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> complaintService.getComplaint(complaintId));
+
+ // Assert
+ assertEquals("Complaint not found", exception.getMessage());
+ }
+
+ @Test
+ public void testGetComplaintSuccess() {
+ // Arrange
+ Long complaintId = 1L;
+ Complaint complaint = new Complaint();
+ ComplaintResponseDto complaintResponseDto = new ComplaintResponseDto();
+
+ when(complaintRepository.findById(complaintId)).thenReturn(Optional.of(complaint));
+ when(complaintMapper.mapComplaintToDto(complaint)).thenReturn(complaintResponseDto);
+
+ // Act
+ ComplaintResponseDto result = complaintService.getComplaint(complaintId);
+
+ // Assert
+ assertEquals(complaintResponseDto, result);
+ }
+
+ @Test
+ public void testGetComplaintsSuccess() {
+ // Arrange
+ ComplaintStatus status = ComplaintStatus.PENDING;
+ int page = 1;
+ int size = 10;
+ Sort.Direction direction = Sort.Direction.ASC;
+
+ Page complaintPage = new PageImpl<>(Collections.emptyList());
+ List complaintList = complaintPage.getContent();
+ List complaintResponseDtoList = Collections.emptyList();
+
+ when(complaintRepository.findAllByStatus(status,
+ PageRequest.of(0, size, Sort.by(direction, "created")))).thenReturn(complaintPage);
+ when(complaintMapper.mapComplaintToDto(complaintList)).thenReturn(complaintResponseDtoList);
+
+ // Act
+ List result = complaintService.getComplaints(status, size, page, direction);
+
+ // Assert
+ assertEquals(complaintResponseDtoList, result);
+ }
+
+ @Test
+ public void testGetComplaintsByUserIdSuccess() {
+ // Arrange
+ Long userId = 1L;
+ ComplaintStatus status = ComplaintStatus.PENDING;
+ int page = 1;
+ int size = 10;
+ Sort.Direction direction = Sort.Direction.ASC;
+
+ Page complaintPage = new PageImpl<>(Collections.emptyList());
+ List complaintList = complaintPage.getContent();
+ List complaintResponseDtoList = Collections.emptyList();
+
+ when(complaintRepository.findAllByComplainedUserIdAndStatus(userId, status,
+ PageRequest.of(0, size, Sort.by(direction, "created"))))
+ .thenReturn(complaintPage);
+ when(complaintMapper.mapComplaintToDto(complaintList)).thenReturn(complaintResponseDtoList);
+
+ // Act
+ List result = complaintService
+ .getComplaintsByUserId(userId, status, size, page, direction);
+
+ // Assert
+ assertEquals(complaintResponseDtoList, result);
+ }
+
+ @Test
+ public void testUpdateStatusById_Success() {
+ // Arrange
+ Long complaintId = 1L;
+ ComplaintStatus status = ComplaintStatus.RESOLVED;
+
+ // Act
+ complaintService.updateStatusById(complaintId, status);
+
+ // Assert
+ verify(complaintRepository, times(1)).updateStatusById(complaintId, status);
+ }
+}