diff --git a/core/src/main/java/greencity/controller/PlaceController.java b/core/src/main/java/greencity/controller/PlaceController.java index 2faf0c790c..f350df4411 100644 --- a/core/src/main/java/greencity/controller/PlaceController.java +++ b/core/src/main/java/greencity/controller/PlaceController.java @@ -6,18 +6,19 @@ import greencity.dto.PageableDto; import greencity.dto.favoriteplace.FavoritePlaceDto; import greencity.dto.filter.FilterPlaceDto; -import greencity.dto.place.AddPlaceDto; -import greencity.dto.place.AdminPlaceDto; -import greencity.dto.place.BulkUpdatePlaceStatusDto; -import greencity.dto.place.FilterPlaceCategory; import greencity.dto.place.PlaceAddDto; -import greencity.dto.place.PlaceByBoundsDto; import greencity.dto.place.PlaceInfoDto; -import greencity.dto.place.PlaceResponse; import greencity.dto.place.PlaceUpdateDto; -import greencity.dto.place.PlaceVO; import greencity.dto.place.PlaceWithUserDto; +import greencity.dto.place.PlaceByBoundsDto; +import greencity.dto.place.AdminPlaceDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.place.UpdatePlaceStatusDto; +import greencity.dto.place.PlaceVO; +import greencity.dto.place.BulkUpdatePlaceStatusDto; +import greencity.dto.place.FilterPlaceCategory; +import greencity.dto.place.PlaceResponse; +import greencity.dto.place.AddPlaceDto; import greencity.dto.user.UserVO; import greencity.enums.PlaceStatus; import greencity.service.FavoritePlaceService; @@ -303,9 +304,9 @@ public ResponseEntity> getFilteredPlaces( content = @Content(examples = @ExampleObject(HttpStatuses.FORBIDDEN))) }) @PatchMapping("/status") - public ResponseEntity updateStatus(@Valid @RequestBody UpdatePlaceStatusDto dto) { - return ResponseEntity.status(HttpStatus.OK) - .body(placeService.updateStatus(dto.getId(), dto.getStatus())); + public ResponseEntity updateStatus( + @Valid @RequestBody UpdatePlaceStatusWithUserEmailDto dto) { + return ResponseEntity.ok(placeService.updatePlaceStatus(dto)); } /** diff --git a/core/src/test/java/greencity/controller/PlaceControllerTest.java b/core/src/test/java/greencity/controller/PlaceControllerTest.java index 46248ac697..3dbe243d85 100644 --- a/core/src/test/java/greencity/controller/PlaceControllerTest.java +++ b/core/src/test/java/greencity/controller/PlaceControllerTest.java @@ -1,6 +1,14 @@ package greencity.controller; import greencity.converters.UserArgumentResolver; +import greencity.dto.place.PlaceAddDto; +import greencity.dto.place.PlaceUpdateDto; +import greencity.dto.place.PlaceVO; +import greencity.dto.place.AddPlaceDto; +import greencity.dto.place.BulkUpdatePlaceStatusDto; +import greencity.dto.place.PlaceWithUserDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; +import greencity.enums.PlaceStatus; import greencity.service.UserService; import java.security.Principal; import java.time.DayOfWeek; @@ -38,13 +46,6 @@ import greencity.dto.location.LocationAddressAndGeoDto; import greencity.dto.location.LocationAddressAndGeoForUpdateDto; import greencity.dto.openhours.OpeningHoursDto; -import greencity.dto.place.BulkUpdatePlaceStatusDto; -import greencity.dto.place.PlaceAddDto; -import greencity.dto.place.PlaceUpdateDto; -import greencity.dto.place.PlaceVO; -import greencity.dto.place.PlaceWithUserDto; -import greencity.dto.place.UpdatePlaceStatusDto; -import greencity.dto.place.AddPlaceDto; import greencity.dto.photo.PhotoAddDto; import greencity.dto.specification.SpecificationNameDto; import greencity.dto.user.UserVO; @@ -56,17 +57,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static greencity.ModelUtils.getPrincipal; import static greencity.enums.PlaceStatus.APPROVED; import static greencity.enums.PlaceStatus.PROPOSED; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -428,28 +429,6 @@ void getFilteredPlaces() throws Exception { verify(placeService).getPlacesByFilter(filterPlaceDto, userVO); } - @Test - void updateStatus() throws Exception { - UpdatePlaceStatusDto updatePlaceStatusDto = new UpdatePlaceStatusDto(); - updatePlaceStatusDto.setId(1L); - updatePlaceStatusDto.setStatus(PROPOSED); - String json = """ - { - "id": 1, - "status": "PROPOSED" - } - """; - - this.mockMvc.perform(patch(placeLink + "/status") - .content(json) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - - verify(placeService).updateStatus(updatePlaceStatusDto.getId(), updatePlaceStatusDto.getStatus()); - - } - @Test void filterPlaceBySearchPredicate() throws Exception { int pageNumber = 5; @@ -606,4 +585,43 @@ void getAllPlaces() throws Exception { verify(placeService, times(1)) .findAll(pageable, principal); } + + @Test + void updateStatusSuccessfulTest() throws Exception { + String json = """ + { + "placeName": "Test Place", + "newStatus": "APPROVED", + "userName": "Test User", + "email": "test@example.com" + } + """; + UpdatePlaceStatusWithUserEmailDto mockDto = new UpdatePlaceStatusWithUserEmailDto(); + mockDto.setPlaceName("Test Place"); + mockDto.setNewStatus(PlaceStatus.APPROVED); + mockDto.setUserName("Test User"); + mockDto.setEmail("test@example.com"); + when(placeService.updatePlaceStatus(any(UpdatePlaceStatusWithUserEmailDto.class))).thenReturn(mockDto); + mockMvc.perform(patch(placeLink + "/status") + .contentType(MediaType.APPLICATION_JSON) + .content(json)); + verify(placeService, times(1)).updatePlaceStatus(any(UpdatePlaceStatusWithUserEmailDto.class)); + } + + @Test + void updateStatusInvalidInputTest() throws Exception { + String json = """ + { + "placeName": "", + "newStatus": "APPROVED", + "userName": "Test User", + "email": "invalid-email" + } + """; + mockMvc.perform(patch(placeLink + "/status") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()); + verify(placeService, times(0)).updatePlaceStatus(any(UpdatePlaceStatusWithUserEmailDto.class)); + } } diff --git a/dao/src/main/java/greencity/repository/PlaceRepo.java b/dao/src/main/java/greencity/repository/PlaceRepo.java index bf129058b5..c705c6cbef 100644 --- a/dao/src/main/java/greencity/repository/PlaceRepo.java +++ b/dao/src/main/java/greencity/repository/PlaceRepo.java @@ -4,6 +4,7 @@ import greencity.enums.PlaceStatus; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -79,4 +80,13 @@ List findAllByModifiedDateBetweenAndStatus(ZonedDateTime startDate, Zoned + "WHERE c.name IN (:category) " + "or c.name_ua IN (:category)") List findPlaceByCategory(String[] category); + + /** + * Finds a place by its name. + * + * @param name the name of the place. + * @return an optional containing the place if found, or empty otherwise. + */ + @Query("SELECT p FROM Place p WHERE LOWER(p.name) = LOWER(:name)") + Optional findByNameIgnoreCase(@Param("name") String name); } \ No newline at end of file diff --git a/service-api/src/main/java/greencity/client/RestClient.java b/service-api/src/main/java/greencity/client/RestClient.java index ff4cac8af6..c4b1571dfa 100644 --- a/service-api/src/main/java/greencity/client/RestClient.java +++ b/service-api/src/main/java/greencity/client/RestClient.java @@ -3,6 +3,7 @@ import greencity.annotations.CheckEmailPreference; import greencity.constant.AppConstant; import greencity.dto.econews.InterestingEcoNewsDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.user.UserManagementDto; import greencity.dto.user.UserManagementUpdateDto; import greencity.dto.user.UserManagementVO; @@ -427,6 +428,14 @@ public void sendInterestingEcoNews(InterestingEcoNewsDto message) { + RestTemplateLinks.SEND_INTERESTING_ECO_NEWS, HttpMethod.POST, entity, Object.class); } + public void sendEmailNotificationChangesPlaceStatus(UpdatePlaceStatusWithUserEmailDto message) { + HttpHeaders headers = setHeader(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(message, headers); + restTemplate.exchange(greenCityUserServerAddress + + RestTemplateLinks.SEND_NOTIFICATION_STATUS_PLACE, HttpMethod.POST, entity, Object.class); + } + /** * send SendReportEmailMessage to GreenCityUser. * diff --git a/service-api/src/main/java/greencity/constant/ErrorMessage.java b/service-api/src/main/java/greencity/constant/ErrorMessage.java index 0c6729c035..5bcf4fce23 100644 --- a/service-api/src/main/java/greencity/constant/ErrorMessage.java +++ b/service-api/src/main/java/greencity/constant/ErrorMessage.java @@ -120,6 +120,7 @@ public class ErrorMessage { public static final String BAD_DEFAULT_SOCIAL_NETWORK_IMAGE_PATH = "Bad default social network image host path (Row in database doesn't exists)"; public static final String PLACE_NOT_FOUND_BY_ID = "The place does not exist by this id: "; + public static final String PLACE_NOT_FOUND_BY_NAME = "The place does not exist by this name: "; public static final String PLACE_STATUS_NOT_DIFFERENT = "Place with id: %d already has this status: %s"; public static final String COMMENT_NOT_FOUND_EXCEPTION = "The comment with entered id or other params doesn't exist"; diff --git a/service-api/src/main/java/greencity/constant/RestTemplateLinks.java b/service-api/src/main/java/greencity/constant/RestTemplateLinks.java index 72502aff4f..11eabbb7fd 100644 --- a/service-api/src/main/java/greencity/constant/RestTemplateLinks.java +++ b/service-api/src/main/java/greencity/constant/RestTemplateLinks.java @@ -37,4 +37,5 @@ public class RestTemplateLinks { public static final String FIND_ALL_REGISTRATION_MONTHS_MAP = "/user/findAllRegistrationMonthsMap"; public static final String FIND_ALL_USERS_CITIES = "/user/findAllUsersCities"; public static final String SEND_SCHEDULED_NOTIFICATION = "/email/scheduled/notification"; + public static final String SEND_NOTIFICATION_STATUS_PLACE = "/email/sendPlaceStatusChange"; } diff --git a/service-api/src/main/java/greencity/dto/place/UpdatePlaceStatusWithUserEmailDto.java b/service-api/src/main/java/greencity/dto/place/UpdatePlaceStatusWithUserEmailDto.java new file mode 100644 index 0000000000..c9b10c0371 --- /dev/null +++ b/service-api/src/main/java/greencity/dto/place/UpdatePlaceStatusWithUserEmailDto.java @@ -0,0 +1,26 @@ +package greencity.dto.place; + +import greencity.enums.PlaceStatus; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class UpdatePlaceStatusWithUserEmailDto { + @NotBlank + private String placeName; + + @NotNull + private PlaceStatus newStatus; + + @NotBlank + private String userName; + + @Email + private String email; +} diff --git a/service-api/src/main/java/greencity/message/ScheduledEmailMessage.java b/service-api/src/main/java/greencity/message/ScheduledEmailMessage.java index 08852fce77..aabb967f7e 100644 --- a/service-api/src/main/java/greencity/message/ScheduledEmailMessage.java +++ b/service-api/src/main/java/greencity/message/ScheduledEmailMessage.java @@ -17,9 +17,4 @@ public class ScheduledEmailMessage implements EmailMessage { private String body; private String language; private boolean isUbs; - - @Override - public String getEmail() { - return email; - } } diff --git a/service-api/src/main/java/greencity/service/PlaceService.java b/service-api/src/main/java/greencity/service/PlaceService.java index f459112606..7bd31635ed 100644 --- a/service-api/src/main/java/greencity/service/PlaceService.java +++ b/service-api/src/main/java/greencity/service/PlaceService.java @@ -14,9 +14,11 @@ import greencity.dto.place.PlaceUpdateDto; import greencity.dto.place.PlaceVO; import greencity.dto.place.UpdatePlaceStatusDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.search.SearchPlacesDto; import greencity.dto.user.UserVO; import greencity.enums.PlaceStatus; +import greencity.exception.exceptions.NotFoundException; import java.security.Principal; import java.util.List; import java.util.Optional; @@ -251,4 +253,14 @@ public interface PlaceService { * @return PageableDto of {@link SearchPlacesDto} instances */ PageableDto search(Pageable pageable, String searchQuery, Boolean isFavorite, Long userId); -} + + /** + * Updates the status of a place and ensures the user with the given email + * exists. + * + * @param dto Contains the place name, user email, and the new status. + * @return The same UpdatePlaceStatusWithUserEmailDto. + * @throws NotFoundException If the place or user is not found. + */ + UpdatePlaceStatusWithUserEmailDto updatePlaceStatus(UpdatePlaceStatusWithUserEmailDto dto); +} \ No newline at end of file diff --git a/service-api/src/test/java/greencity/client/RestClientTest.java b/service-api/src/test/java/greencity/client/RestClientTest.java index 9271470039..af76d43b06 100644 --- a/service-api/src/test/java/greencity/client/RestClientTest.java +++ b/service-api/src/test/java/greencity/client/RestClientTest.java @@ -9,6 +9,7 @@ import static greencity.TestConst.USER_ID; import static greencity.constant.AppConstant.AUTHORIZATION; import greencity.dto.econews.InterestingEcoNewsDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.user.UserStatusDto; import greencity.dto.user.UserVO; import greencity.dto.user.UserManagementDto; @@ -22,6 +23,7 @@ import greencity.dto.PageableAdvancedDto; import greencity.dto.achievement.UserVOAchievement; import greencity.enums.EmailNotification; +import greencity.enums.PlaceStatus; import greencity.enums.Role; import greencity.enums.UserStatus; import greencity.message.ScheduledEmailMessage; @@ -55,11 +57,12 @@ import org.springframework.web.context.request.RequestContextHolder; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) class RestClientTest { @@ -679,4 +682,21 @@ void sendEmailNotificationPlacesTest() { verify(restTemplate).exchange(GREEN_CITY_USER_ADDRESS + RestTemplateLinks.SEND_SCHEDULED_NOTIFICATION, HttpMethod.POST, entity, Object.class); } + + @Test + void sendEmailNotificationChangesPlaceStatusTest() { + UpdatePlaceStatusWithUserEmailDto message = new UpdatePlaceStatusWithUserEmailDto(); + message.setPlaceName("TestPlace"); + message.setNewStatus(PlaceStatus.APPROVED); + message.setUserName("TestUser"); + message.setEmail("test@example.com"); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + String expectedUrl = GREEN_CITY_USER_ADDRESS + RestTemplateLinks.SEND_NOTIFICATION_STATUS_PLACE; + when(restTemplate.exchange(eq(expectedUrl), eq(HttpMethod.POST), any(HttpEntity.class), eq(Object.class))) + .thenReturn(ResponseEntity.ok().build()); + restClient.sendEmailNotificationChangesPlaceStatus(message); + verify(restTemplate, times(1)).exchange(eq(expectedUrl), eq(HttpMethod.POST), any(HttpEntity.class), + eq(Object.class)); + } } diff --git a/service/src/main/java/greencity/service/PlaceServiceImpl.java b/service/src/main/java/greencity/service/PlaceServiceImpl.java index d860705c65..b37add7141 100644 --- a/service/src/main/java/greencity/service/PlaceServiceImpl.java +++ b/service/src/main/java/greencity/service/PlaceServiceImpl.java @@ -1,6 +1,7 @@ package greencity.service; import com.google.maps.model.GeocodingResult; +import greencity.client.RestClient; import greencity.constant.ErrorMessage; import greencity.constant.LogMessage; import greencity.dto.PageableDto; @@ -13,18 +14,19 @@ import greencity.dto.openhours.OpenHoursDto; import greencity.dto.openhours.OpeningHoursDto; import greencity.dto.openhours.OpeningHoursVO; +import greencity.dto.place.PlaceByBoundsDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.place.AddPlaceDto; -import greencity.dto.place.AdminPlaceDto; -import greencity.dto.place.BulkUpdatePlaceStatusDto; -import greencity.dto.place.FilterAdminPlaceDto; +import greencity.dto.place.PlaceResponse; import greencity.dto.place.FilterPlaceCategory; -import greencity.dto.place.PlaceAddDto; -import greencity.dto.place.PlaceByBoundsDto; +import greencity.dto.place.FilterAdminPlaceDto; import greencity.dto.place.PlaceInfoDto; -import greencity.dto.place.PlaceResponse; +import greencity.dto.place.BulkUpdatePlaceStatusDto; +import greencity.dto.place.UpdatePlaceStatusDto; +import greencity.dto.place.AdminPlaceDto; +import greencity.dto.place.PlaceAddDto; import greencity.dto.place.PlaceUpdateDto; import greencity.dto.place.PlaceVO; -import greencity.dto.place.UpdatePlaceStatusDto; import greencity.dto.search.SearchPlacesDto; import greencity.dto.user.UserVO; import greencity.entity.Category; @@ -99,6 +101,7 @@ public class PlaceServiceImpl implements PlaceService { private final FavoritePlaceRepo favoritePlaceRepo; private final FileService fileService; private final UserNotificationService userNotificationService; + private final RestClient restClient; /** * {@inheritDoc} @@ -635,4 +638,31 @@ private PageableDto getSearchPlacesDtoPageableDto(Page p page.getPageable().getPageNumber(), page.getTotalPages()); } -} + + /** + * Updates the status of a place, validates the user's existence, and sends a + * notification if the status changes to APPROVED or DECLINED. + * + * @param dto The data transfer object containing place name, user email, and + * the new status. + * @return The updated UpdatePlaceStatusWithUserEmailDto. + * @throws NotFoundException If the place or user is not found. + */ + @Override + public UpdatePlaceStatusWithUserEmailDto updatePlaceStatus(UpdatePlaceStatusWithUserEmailDto dto) { + Place place = placeRepo.findByNameIgnoreCase(dto.getPlaceName()) + .orElseThrow(() -> new NotFoundException(ErrorMessage.PLACE_NOT_FOUND_BY_NAME + dto.getPlaceName())); + + if (userRepo.findByEmail(dto.getEmail()).isEmpty()) { + throw new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + dto.getEmail()); + } + + place.setStatus(dto.getNewStatus()); + placeRepo.save(place); + + if (dto.getNewStatus() == PlaceStatus.APPROVED || dto.getNewStatus() == PlaceStatus.DECLINED) { + restClient.sendEmailNotificationChangesPlaceStatus(dto); + } + return dto; + } +} \ No newline at end of file diff --git a/service/src/main/resources/notification.properties b/service/src/main/resources/notification.properties index 5ff3ce4345..3be2ed791f 100644 --- a/service/src/main/resources/notification.properties +++ b/service/src/main/resources/notification.properties @@ -7,27 +7,27 @@ TWO_USERS={user1} and {user2} THREE_OR_MORE_USERS={user1}, {user2} and other users ECONEWS_COMMENT_LIKE_TITLE=You received a like. -ECONEWS_COMMENT_LIKE={user} liked your comment «{message}» to EcoNews «{secondMessage}». +ECONEWS_COMMENT_LIKE={user} liked your comment «{message}» to EcoNews «{secondMessage}». ECONEWS_COMMENT_REPLY_TITLE=You received a comment reply -ECONEWS_COMMENT_REPLY={user} commented your comment «{message}» to EcoNews «{secondMessage}». +ECONEWS_COMMENT_REPLY={user} commented your comment «{message}» to EcoNews «{secondMessage}». EVENT_COMMENT_LIKE_TITLE=You received a like. -EVENT_COMMENT_LIKE={user} liked your comment «{message}» to event «{secondMessage}». +EVENT_COMMENT_LIKE={user} liked your comment «{message}» to event «{secondMessage}». EVENT_COMMENT_REPLY_TITLE=You received a comment reply -EVENT_COMMENT_REPLY={user} commented your comment «{message}» in the event «{secondMessage}». +EVENT_COMMENT_REPLY={user} commented your comment «{message}» in the event «{secondMessage}». EVENT_COMMENT_USER_TAG_TITLE=You have been tagged in the comment. -EVENT_COMMENT_USER_TAG={user} tagged you {times} in the event «{secondMessage}». +EVENT_COMMENT_USER_TAG={user} tagged you {times} in the event «{secondMessage}». ECONEWS_COMMENT_TITLE=You received a comment -ECONEWS_COMMENT=You received {message} from {user} on your news «{secondMessage}». +ECONEWS_COMMENT=You received {message} from {user} on your news «{secondMessage}». ECONEWS_CREATED_TITLE=You have created eco news ECONEWS_CREATED=You successfully created eco news {message}. ECONEWS_LIKE_TITLE=Your news received a like ECONEWS_LIKE=News {message} received a like from {user}. ECONEWS_COMMENT_USER_TAG_TITLE=You have been tagged in the comment. -ECONEWS_COMMENT_USER_TAG={user} tagged you {times} in the eco news «{secondMessage}». +ECONEWS_COMMENT_USER_TAG={user} tagged you {times} in the eco news «{secondMessage}». EVENT_COMMENT_TITLE=You received a comment -EVENT_COMMENT=You received {message} from {user} on your event «{secondMessage}». +EVENT_COMMENT=You received {message} from {user} on your event «{secondMessage}». EVENT_CANCELED_TITLE=Event was canceled EVENT_CANCELED=Unfortunately, event {message} was canceled. EVENT_CREATED_TITLE=You have created event @@ -39,7 +39,7 @@ EVENT_NAME_UPDATED=Event {message} was updated. New name is {secondMessage}. EVENT_UPDATED_TITLE=Event was updated EVENT_UPDATED=Event {message} was updated. EVENT_LIKE_TITLE=Your event received a like. -EVENT_LIKE=Event «{secondMessage}» received a like from {user}. +EVENT_LIKE=Event «{secondMessage}» received a like from {user}. FRIEND_REQUEST_ACCEPTED_TITLE=Your friend request was accepted FRIEND_REQUEST_ACCEPTED={user} accepted your friend request. @@ -49,15 +49,15 @@ FRIEND_REQUEST_RECEIVED={user} sent you a friend request. HABIT_LIKE=Habit {message} received a like from {user}. HABIT_LIKE_TITLE=Your habit received a like HABIT_INVITE_TITLE=Habit invitation. -HABIT_INVITE={user} invite you to add new habit «{secondMessage}». +HABIT_INVITE={user} invite you to add new habit «{secondMessage}». HABIT_COMMENT_TITLE=You received a comment HABIT_COMMENT={user} commented on your habit {message}. HABIT_COMMENT_LIKE_TITLE=You received a like. -HABIT_COMMENT_LIKE={user} liked your comment «{message}» to habit «{secondMessage}». +HABIT_COMMENT_LIKE={user} liked your comment «{message}» to habit «{secondMessage}». HABIT_COMMENT_REPLY_TITLE=You received a comment reply -HABIT_COMMENT_REPLY={user} commented your comment «{message}» to Habit «{secondMessage}». +HABIT_COMMENT_REPLY={user} commented your comment «{message}» to Habit «{secondMessage}». HABIT_COMMENT_USER_TAG_TITLE=You have been tagged in the comment. -HABIT_COMMENT_USER_TAG={user} tagged you {times} in the habit «{secondMessage}». +HABIT_COMMENT_USER_TAG={user} tagged you {times} in the habit «{secondMessage}». HABIT_LAST_DAY_OF_PRIMARY_DURATION_TITLE=Today is last day of your habit primary duration HABIT_LAST_DAY_OF_PRIMARY_DURATION=Today the duration of your habit {message} is run out. To get more information go to Habit's page diff --git a/service/src/main/resources/notification_ua.properties b/service/src/main/resources/notification_ua.properties index cb159a53e1..d514bd322e 100644 --- a/service/src/main/resources/notification_ua.properties +++ b/service/src/main/resources/notification_ua.properties @@ -7,17 +7,17 @@ TWO_USERS=\u007b\u0075\u0073\u0065\u0072\u0031\u007d\u0020\u0442\u0430\u0020\u00 THREE_OR_MORE_USERS=\u007b\u0075\u0073\u0065\u0072\u0031\u007d\u002c\u0020\u007b\u0075\u0073\u0065\u0072\u0032\u007d\u0020\u0442\u0430\u0020\u0456\u043d\u0448\u0456\u0020\u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456 ECONEWS_COMMENT_LIKE_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u043b\u0430\u0439\u043a -ECONEWS_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u0435\u043a\u043e \u043d\u043e\u0432\u0438\u043d\u0438 «{secondMessage}». +ECONEWS_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u0435\u043a\u043e \u043d\u043e\u0432\u0438\u043d\u0438 «{secondMessage}». ECONEWS_COMMENT_REPLY_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c \u043d\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 -ECONEWS_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u0435\u043a\u043e \u043d\u043e\u0432\u0438\u043d\u0438 «{secondMessage}». +ECONEWS_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u0435\u043a\u043e \u043d\u043e\u0432\u0438\u043d\u0438 «{secondMessage}». ECONEWS_COMMENT_USER_TAG_TITLE=\u0412\u0430\u0441\u0020\u0442\u0435\u0433\u043d\u0443\u043b\u0438\u0020\u0432\u0020\u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440\u0456 -ECONEWS_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0432\u0020\u0435\u043a\u043e\u043b\u043e\u0433\u0456\u0447\u043d\u0438\u0445\u0020\u043d\u043e\u0432\u0438\u043d\u0430\u0445 «{secondMessage}». +ECONEWS_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0432\u0020\u0435\u043a\u043e\u043b\u043e\u0433\u0456\u0447\u043d\u0438\u0445\u0020\u043d\u043e\u0432\u0438\u043d\u0430\u0445 «{secondMessage}». EVENT_COMMENT_LIKE_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u043b\u0430\u0439\u043a -EVENT_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u043f\u043e\u0434\u0456\u0457 «{secondMessage}». +EVENT_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u043f\u043e\u0434\u0456\u0457 «{secondMessage}». EVENT_COMMENT_REPLY_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c \u043d\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 -EVENT_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u043f\u043e\u0434\u0456\u0457 «{secondMessage}». +EVENT_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e \u043f\u043e\u0434\u0456\u0457 «{secondMessage}». EVENT_COMMENT_USER_TAG_TITLE=\u0412\u0430\u0441\u0020\u0442\u0435\u0433\u043d\u0443\u043b\u0438\u0020\u0432\u0020\u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440\u0456 -EVENT_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0443\u0020\u043f\u043e\u0434\u0456\u0457 «{secondMessage}». +EVENT_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0443\u0020\u043f\u043e\u0434\u0456\u0457 «{secondMessage}». ECONEWS_COMMENT_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 ECONEWS_COMMENT=\u0414\u043E \u0432\u0430\u0448\u043e\u0457 \u043D\u043E\u0432\u0438\u043D\u0438 \u00AB{secondMessage}\u00BB {user} \u0437\u0430\u043B\u0438\u0448\u0438\u0432(\u043B\u0438) {message}. @@ -49,15 +49,15 @@ FRIEND_REQUEST_RECEIVED={user} \u043D\u0430\u0434\u0456\u0441\u043B\u0430\u0432 HABIT_LIKE=\u0417\u0432\u0438\u0447\u043a\u0430 {message} \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0430 \u043b\u0430\u0439\u043a \u0432\u0456\u0434 {user}. HABIT_LIKE_TITLE=\u0412\u0430\u0448\u0430\u0020\u0437\u0432\u0438\u0447\u043a\u0430\u0020\u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0430\u0020\u043b\u0430\u0439\u043a HABIT_INVITE_TITLE=\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044f\u0020\u0434\u043e\u0020\u0437\u0432\u0438\u0447\u043a\u0438 -HABIT_INVITE={user} \u0437\u0430\u043F\u0440\u043E\u0448\u0443\u0454 \u0432\u0430\u0441 \u0434\u043E\u0434\u0430\u0442\u0438 \u043D\u043E\u0432\u0443 \u0437\u0432\u0438\u0447\u043A\u0443 «{message}». +HABIT_INVITE={user} \u0437\u0430\u043F\u0440\u043E\u0448\u0443\u0454 \u0432\u0430\u0441 \u0434\u043E\u0434\u0430\u0442\u0438 \u043D\u043E\u0432\u0443 \u0437\u0432\u0438\u0447\u043A\u0443 «{message}». HABIT_COMMENT_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 HABIT_COMMENT=\u0414\u043e\u0020\u0432\u0430\u0448\u043e\u0457\u0020\u0437\u0432\u0438\u0447\u043a\u0438 \u00AB{secondMessage}\u00BB {user} \u0437\u0430\u043B\u0438\u0448\u0438\u0432(\u043B\u0438) {message}. HABIT_COMMENT_LIKE_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u043b\u0430\u0439\u043a -HABIT_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e\u0020\u0437\u0432\u0438\u0447\u043a\u0438 «{secondMessage}». +HABIT_COMMENT_LIKE=\u0412\u0456\u0434 {user} \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e \u043b\u0430\u0439\u043a \u043d\u0430 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e\u0020\u0437\u0432\u0438\u0447\u043a\u0438 «{secondMessage}». HABIT_COMMENT_REPLY_TITLE=\u0412\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c \u043d\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 -HABIT_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e\u0020\u0437\u0432\u0438\u0447\u043a\u0438 «{secondMessage}». +HABIT_COMMENT_REPLY={user} \u043f\u0440\u043e\u043a\u043e\u043c\u0435\u043d\u0442\u0443\u0432\u0430\u0432 \u0432\u0430\u0448 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440 «{message}» \u0434\u043e\u0020\u0437\u0432\u0438\u0447\u043a\u0438 «{secondMessage}». HABIT_COMMENT_USER_TAG_TITLE=\u0412\u0430\u0441\u0020\u0442\u0435\u0433\u043d\u0443\u043b\u0438\u0020\u0432\u0020\u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440\u0456 -HABIT_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0443\u0020\u0437\u0432\u0438\u0447\u0446\u0456 «{secondMessage}». +HABIT_COMMENT_USER_TAG={user} \u043f\u043e\u0437\u043d\u0430\u0447\u0438\u0432\u0020\u0432\u0430\u0441 {times} \u0443\u0020\u0437\u0432\u0438\u0447\u0446\u0456 «{secondMessage}». HABIT_LAST_DAY_OF_PRIMARY_DURATION_TITLE=\u0421\u044c\u043e\u0433\u043e\u0434\u043d\u0456\u0020\u043e\u0441\u0442\u0430\u043d\u043d\u0456\u0439\u0020\u0434\u0435\u043d\u044c\u0020\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0457\u0020\u0442\u0440\u0438\u0432\u0430\u043b\u043e\u0441\u0442\u0456\u0020\u0432\u0430\u0448\u043e\u0457\u0020\u0437\u0432\u0438\u0447\u043a\u0438 HABIT_LAST_DAY_OF_PRIMARY_DURATION=\u0421\u044c\u043e\u0433\u043e\u0434\u043d\u0456\u0020\u0442\u0435\u0440\u043c\u0456\u043d\u0020\u0432\u0430\u0448\u043e\u0457\u0020\u0437\u0432\u0438\u0447\u043a\u0438\u0020{message}\u0020\u0437\u0430\u043a\u0456\u043d\u0447\u0438\u0432\u0441\u044f\u002e\u0020\u0414\u043b\u044f\u0020\u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f\u0020\u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e\u0457\u0020\u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457\u0020\u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c\u0020\u043d\u0430\u0020\u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443\u0020\u0048\u0061\u0062\u0069\u0074 diff --git a/service/src/test/java/greencity/service/PlaceServiceImplTest.java b/service/src/test/java/greencity/service/PlaceServiceImplTest.java index 0d40cb129b..9b9a82263b 100644 --- a/service/src/test/java/greencity/service/PlaceServiceImplTest.java +++ b/service/src/test/java/greencity/service/PlaceServiceImplTest.java @@ -1,6 +1,7 @@ package greencity.service; import greencity.ModelUtils; +import greencity.client.RestClient; import greencity.dto.PageableDto; import greencity.dto.category.CategoryDto; import greencity.dto.category.CategoryDtoResponse; @@ -9,18 +10,19 @@ import greencity.dto.language.LanguageVO; import greencity.dto.location.LocationAddressAndGeoForUpdateDto; import greencity.dto.location.LocationVO; +import greencity.dto.place.PlaceByBoundsDto; +import greencity.dto.place.UpdatePlaceStatusWithUserEmailDto; import greencity.dto.place.AddPlaceDto; -import greencity.dto.place.AdminPlaceDto; -import greencity.dto.place.BulkUpdatePlaceStatusDto; -import greencity.dto.place.FilterAdminPlaceDto; +import greencity.dto.place.PlaceResponse; import greencity.dto.place.FilterPlaceCategory; -import greencity.dto.place.PlaceAddDto; -import greencity.dto.place.PlaceByBoundsDto; +import greencity.dto.place.FilterAdminPlaceDto; import greencity.dto.place.PlaceInfoDto; -import greencity.dto.place.PlaceResponse; +import greencity.dto.place.BulkUpdatePlaceStatusDto; +import greencity.dto.place.UpdatePlaceStatusDto; +import greencity.dto.place.AdminPlaceDto; +import greencity.dto.place.PlaceAddDto; import greencity.dto.place.PlaceUpdateDto; import greencity.dto.place.PlaceVO; -import greencity.dto.place.UpdatePlaceStatusDto; import greencity.dto.search.SearchPlacesDto; import greencity.dto.user.UserVO; import greencity.entity.Category; @@ -194,13 +196,15 @@ class PlaceServiceImplTest { private FileService fileService; @Mock private UserNotificationService userNotificationService; + @Mock + private RestClient restClient; @BeforeEach void init() { placeService = new PlaceServiceImpl(placeRepo, modelMapper, categoryService, locationService, specificationService, openingHoursService, userService, discountService, zoneId, proposePlaceMapper, categoryRepo, googleApiService, userRepo, favoritePlaceRepo, fileService, - userNotificationService); + userNotificationService, restClient); } @Test @@ -867,4 +871,92 @@ void searchTest() { assertEquals(List.of(searchPlacesDto, searchPlacesDto), result.getPage()); } + + @Test + void updatePlaceStatusWithUserEmailTest() { + UpdatePlaceStatusWithUserEmailDto dto = new UpdatePlaceStatusWithUserEmailDto(); + dto.setPlaceName("test1"); + dto.setNewStatus(PlaceStatus.APPROVED); + dto.setEmail("user@example.com"); + Place place = new Place(); + place.setId(1L); + place.setName("test1"); + place.setStatus(PlaceStatus.PROPOSED); + when(placeRepo.findByNameIgnoreCase(dto.getPlaceName())).thenReturn(Optional.of(place)); + when(userRepo.findByEmail(dto.getEmail())).thenReturn(Optional.of(user)); + when(placeRepo.save(any(Place.class))).thenReturn(place); + UpdatePlaceStatusWithUserEmailDto result = placeService.updatePlaceStatus(dto); + assertEquals("test1", result.getPlaceName()); + assertEquals(PlaceStatus.APPROVED, place.getStatus()); + verify(placeRepo).findByNameIgnoreCase(dto.getPlaceName()); + verify(userRepo).findByEmail(dto.getEmail()); + verify(placeRepo).save(place); + } + + @Test + void updatePlaceStatusWithPlaceNotFoundTest() { + UpdatePlaceStatusWithUserEmailDto dto = new UpdatePlaceStatusWithUserEmailDto(); + dto.setPlaceName("nonexistentPlace"); + dto.setNewStatus(PlaceStatus.APPROVED); + when(placeRepo.findByNameIgnoreCase(dto.getPlaceName())).thenReturn(Optional.empty()); + NotFoundException exception = assertThrows(NotFoundException.class, () -> placeService.updatePlaceStatus(dto)); + assertEquals("The place does not exist by this name: nonexistentPlace", exception.getMessage()); + verify(placeRepo).findByNameIgnoreCase(dto.getPlaceName()); + verify(placeRepo, times(0)).save(any(Place.class)); + } + + @Test + void updatePlaceStatusWithUserNotFoundTest() { + UpdatePlaceStatusWithUserEmailDto dto = new UpdatePlaceStatusWithUserEmailDto(); + dto.setPlaceName("test1"); + dto.setNewStatus(PlaceStatus.APPROVED); + dto.setEmail("nonexistent@example.com"); + Place place = new Place(); + place.setId(1L); + place.setName("test1"); + place.setStatus(PlaceStatus.PROPOSED); + when(placeRepo.findByNameIgnoreCase(dto.getPlaceName())).thenReturn(Optional.of(place)); + when(userRepo.findByEmail(dto.getEmail())).thenReturn(Optional.empty()); + NotFoundException exception = assertThrows(NotFoundException.class, () -> placeService.updatePlaceStatus(dto)); + assertEquals("The user does not exist by this email: nonexistent@example.com", exception.getMessage()); + verify(placeRepo).findByNameIgnoreCase(dto.getPlaceName()); + verify(userRepo).findByEmail(dto.getEmail()); + verify(placeRepo, times(0)).save(any(Place.class)); + } + + @Test + void updatePlaceStatusWithPlaceAndUserNotFoundTest() { + UpdatePlaceStatusWithUserEmailDto dto = new UpdatePlaceStatusWithUserEmailDto(); + dto.setPlaceName("nonexistentPlace"); + dto.setNewStatus(PlaceStatus.APPROVED); + dto.setEmail("nonexistent@example.com"); + when(placeRepo.findByNameIgnoreCase(dto.getPlaceName())).thenReturn(Optional.empty()); + NotFoundException exception = assertThrows(NotFoundException.class, () -> placeService.updatePlaceStatus(dto)); + assertEquals("The place does not exist by this name: nonexistentPlace", exception.getMessage()); + verify(placeRepo).findByNameIgnoreCase(dto.getPlaceName()); + verify(userRepo, times(0)).findByEmail(dto.getEmail()); + verify(placeRepo, times(0)).save(any(Place.class)); + } + + @Test + void updatePlaceStatusWithoutSendingEmailNotificationTest() { + UpdatePlaceStatusWithUserEmailDto dto = new UpdatePlaceStatusWithUserEmailDto(); + dto.setPlaceName("test1"); + dto.setNewStatus(PlaceStatus.PROPOSED); + dto.setEmail("user@example.com"); + Place place = new Place(); + place.setId(1L); + place.setName("test1"); + place.setStatus(PlaceStatus.PROPOSED); + when(placeRepo.findByNameIgnoreCase(dto.getPlaceName())).thenReturn(Optional.of(place)); + when(userRepo.findByEmail(dto.getEmail())).thenReturn(Optional.of(user)); + when(placeRepo.save(any(Place.class))).thenReturn(place); + UpdatePlaceStatusWithUserEmailDto result = placeService.updatePlaceStatus(dto); + assertEquals("test1", result.getPlaceName()); + assertEquals(PlaceStatus.PROPOSED, place.getStatus()); + verify(placeRepo).findByNameIgnoreCase(dto.getPlaceName()); + verify(userRepo).findByEmail(dto.getEmail()); + verify(placeRepo).save(place); + verify(restClient, times(0)).sendEmailNotificationChangesPlaceStatus(dto); + } }