Skip to content

Commit

Permalink
Bug/6593/fix send email for user in patch update status (#426)
Browse files Browse the repository at this point in the history
* Fix email sending for user in PATCH updateStatus

* Add test for sendPlaceStatusChange controller method

* Add documentation for controller method

* Format, checkstyle

* Add test for sendPlaceStatusChangeNotification

* Fix test

* Add Ukrainian language to messages file

* delete import mock*

* Fix for comment from Vitaliy

* checkstyle+formatter

* Fix tests

* The code has been changed according to Vitalii's feedback(delete not useg import, add swagger doc, add test for not found email, use static ErrorMessage)

* add static test to constant
  • Loading branch information
VasylyshynDmytro authored Dec 13, 2024
1 parent c2a3807 commit 573a7b3
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 6 deletions.
26 changes: 26 additions & 0 deletions core/src/main/java/greencity/controller/EmailController.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,30 @@ public ResponseEntity<Void> sendScheduledNotification(@RequestBody ScheduledEmai
emailService.sendScheduledNotificationEmail(message);
return ResponseEntity.ok().build();
}

/**
* Method for sending an email notification about the status change of a place
* to the user.
*
* @param dto Object containing the necessary information for sending the status
* change notification email. The object includes: - userName: The
* name of the user. - userEmail: The email of the user who will
* receive the notification. - placeName: The name of the place whose
* status has been changed. - newStatus: The new status of the place.
*
* @return ResponseEntity with HTTP status 200 OK if the email was successfully
* sent. If any error occurs, an appropriate error response will be
* returned.
*/
@Operation(summary = "Send email notification to user if place status changed")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/sendPlaceStatusChange")
public ResponseEntity<Object> sendPlaceStatusChange(@RequestBody PlaceStatusChangeDto dto) {
emailService.sendPlaceStatusChangeNotification(dto);
return ResponseEntity.ok().build();
}
}
3 changes: 2 additions & 1 deletion core/src/main/resources/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ advice.for.block=You received this email for security reasons. To protect your a
profile.text=If you no longer wish to receive these emails, you can unsubscribe from them in your
profile=profile
advice.general.ubs=You are receiving this email because you are a registered member of Pick Up City.
read.more=READ MORE
read.more=READ MORE
your.place.status.changed=The status of your place has been updated. You are receiving this email because the status of your place was changed. Thank you for using our service!
3 changes: 2 additions & 1 deletion core/src/main/resources/messages_uk.properties
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ advice.for.block=\u0412\u0438\u0020\u043E\u0442\u0440\u0438\u043C\u0430\u043B\u0
profile.text=\u042f\u043a\u0449\u043e\u0020\u0432\u0438\u0020\u0431\u0456\u043b\u044c\u0448\u0435\u0020\u043d\u0435\u0020\u0445\u043e\u0447\u0435\u0442\u0435\u0020\u043e\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u0442\u0438\u0020\u0440\u043e\u0437\u0441\u0438\u043b\u043a\u0443\u002c\u0020\u0432\u0438\u0020\u043c\u043e\u0436\u0435\u0442\u0435\u0020\u0432\u0456\u0434\u043f\u0438\u0441\u0430\u0442\u0438\u0441\u044f\u0020\u0432\u0456\u0434\u0020\u043d\u0435\u0457\u0020\u0443\u0020\u0441\u0432\u043e\u0454\u043c\u0443
profile=\u043f\u0440\u043e\u0444\u0456\u043b\u0456
advice.general.ubs=\u0412\u0438\u0020\u043e\u0442\u0440\u0438\u043c\u0430\u043b\u0438\u0020\u0446\u0435\u0439\u0020\u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0438\u0439\u0020\u043b\u0438\u0441\u0442\u002c\u0020\u0442\u043e\u043c\u0443\u0020\u0449\u043e\u0020\u0454\u0020\u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u043c\u0020\u043a\u043b\u0456\u0454\u043d\u0442\u043e\u043c\u0020\u0050\u0069\u0063\u006b\u0020\u0055\u0070\u0020\u0043\u0069\u0074\u0079\u002e
read.more=\u0427\u0418\u0422\u0410\u0422\u0418 \u0414\u0410\u041b\u0406
read.more=\u0427\u0418\u0422\u0410\u0422\u0418 \u0414\u0410\u041b\u0406
your.place.status.changed=\u0421\u0442\u0430\u0442\u0443\u0441\u0020\u0432\u0430\u0448\u043E\u0433\u043E\u0020\u043C\u0456\u0441\u0446\u044F\u0020\u0431\u0443\u0432\u0020\u043E\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439\u002E\u0020\u0412\u0438\u0020\u043E\u0442\u0440\u0438\u043C\u0430\u043B\u0438\u0020\u0446\u0435\u0439\u0020\u043B\u0438\u0441\u0442\u002C\u0020\u0442\u043E\u043C\u0443\u0020\u0449\u043E\u0020\u0441\u0442\u0430\u0442\u0443\u0441\u0020\u0432\u0430\u0448\u043E\u0433\u043E\u0020\u043C\u0456\u0441\u0446\u044F\u0020\u0431\u0443\u043B\u043E\u0020\u0437\u043C\u0456\u043D\u0435\u043D\u043E\u002E\u0020\u0414\u044F\u043A\u0443\u0454\u043C\u043E\u002C\u0020\u0449\u043E\u0020\u043A\u043E\u0440\u0438\u0441\u0442\u0443\u0454\u0442\u0435\u0441\u044C\u0020\u043D\u0430\u0448\u0438\u043C\u0020\u0441\u0435\u0440\u0432\u0456\u0441\u043E\u043C\u0021
105 changes: 105 additions & 0 deletions core/src/main/resources/templates/email/place-status-change.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html th:lang="${language}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style>
body {
margin: 0;
padding: 0;
background-color: #F5F6F6;
}

.email-container {
font-family: Lato, sans-serif;
font-size: 16px;
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #E3E6E8;
}

.header {
text-align: center;
padding: 30px;
background-color: #13AA57;
}

.header-image {
height: 30px;
vertical-align: bottom;
}

.content {
padding: 26px;
text-align: left;
color: #333333 !important;
}

.top-text {
margin-top: 0;
color: #333333 !important;
}

.main-text {
line-height: 1.4;
color: #333333 !important;
}

.bottom-text {
margin-bottom: 0;
color: #333333 !important;
}

.footer {
text-align: center;
padding: 10px;
font-size: 12px;
color: #666666;
background-color: #CACFD3;
}

@media only screen and (max-width: 600px) {
body {
font-size: 13px;
}

.header {
padding: 20px;
}

.header-image {
height: 25px;
}

.footer {
font-size: 10px;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="header">
<a th:href="${clientLink}" target="_blank">
<img class="header-image" src="https://csb10032000a548f571.blob.core.windows.net/allfiles/5eaa9e18-f91f-4d3a-9926-da1239317a98GreenCity-Logo-White.png" alt="GreenCityLogo">
</a>
</div>

<div class="content">
<p class="top-text">
<b th:text="#{hi.user(${name})}"></b>
</p>

<p class="main-text">
The status of the place "<b th:text="${placeName}"></b>" has been updated to: <b th:text="${placeStatus}"></b>.
</p>
</div>

<div class="footer">
<p class="bottom-text" th:text="#{your.place.status.changed}"></p>
</div>
</div>
</body>
</html>
24 changes: 24 additions & 0 deletions core/src/test/java/greencity/controller/EmailControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import greencity.dto.econews.InterestingEcoNewsDto;
import greencity.dto.violation.UserViolationMailDto;
import greencity.enums.PlaceStatus;
import greencity.message.PlaceStatusChangeDto;
import greencity.message.ScheduledEmailMessage;
import greencity.message.SendHabitNotification;
import greencity.message.SendReportEmailMessage;
Expand Down Expand Up @@ -176,4 +178,26 @@ void sendUserReceivedScheduledNotification() {
.content(content))
.andExpect(status().isOk());
}

@Test
@SneakyThrows
void sendPlaceStatusChangeTest() {
PlaceStatusChangeDto dto = new PlaceStatusChangeDto();
dto.setUserName("John Doe");
dto.setEmail("test@example.com");
dto.setPlaceName("Green Park");
dto.setNewStatus(PlaceStatus.APPROVED);

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
String content = objectMapper.writeValueAsString(dto);

mockMvc.perform(MockMvcRequestBuilders.post(LINK + "/sendPlaceStatusChange")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer your_token_here")
.content(content))
.andExpect(status().isOk());

verify(emailService).sendPlaceStatusChangeNotification(dto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class EmailConstants {
public static final String BODY = "body";
public static final String UNLOCK_USER_LINK = "unlockUserLink";
public static final String NOTIFICATIONS_LINK = "notificationsLink";
public static final String PLACE_NAME = "placeName";
public static final String PLACE_STATUS = "placeStatus";
// templates
public static final String VERIFY_EMAIL_PAGE = "verify-email-page";
public static final String RESTORE_EMAIL_PAGE = "restore-email-page";
Expand All @@ -53,4 +55,5 @@ public class EmailConstants {
public static final String SCHEDULED_NOTIFICATION_PAGE = "scheduled-notification-email-page";
public static final String RECEIVE_INTERESTING_NEWS_EMAIL_PAGE = "receive-interesting-news-email-page";
public static final String BLOCKED_USER_PAGE = "blocked-user-page";
public static final String PLACE_STATUS_CHANGE_PAGE = "place-status-change";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package greencity.message;

import greencity.enums.PlaceStatus;
import jakarta.validation.constraints.NotNull;
import lombok.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PlaceStatusChangeDto {
@NotNull
private String placeName;

@NotNull
private PlaceStatus newStatus;

@NotNull
private String userName;

@NotNull
private String email;
}
12 changes: 11 additions & 1 deletion service-api/src/main/java/greencity/service/EmailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import greencity.dto.user.UserActivationDto;
import greencity.dto.user.UserDeactivationReasonDto;
import greencity.dto.violation.UserViolationMailDto;
import greencity.message.PlaceStatusChangeDto;
import greencity.message.ScheduledEmailMessage;
import greencity.message.SendReportEmailMessage;

Expand Down Expand Up @@ -85,7 +86,7 @@ void sendRestoreEmail(Long userId, String userFistName, String userEmail, String

/**
* Method for send violation to user.
*
*
* @param dto {@link UserViolationMailDto}-includes all information about
* Violation.
*/
Expand Down Expand Up @@ -138,4 +139,13 @@ void sendCreateNewPasswordForEmployee(Long employeeId, String employeeFistName,
void sendBlockAccountNotificationWithUnblockLinkEmail(
Long userId, String userFistName, String userEmail, String token, String language,
boolean isUbs);

/**
* Sends an email notification to a user regarding the status change of a place.
*
* @param dto the data transfer object containing information about the user,
* place, and the new status of the place (e.g., PROPOSED, DECLINED,
* APPROVED, DELETED).
*/
void sendPlaceStatusChangeNotification(PlaceStatusChangeDto dto);
}
28 changes: 27 additions & 1 deletion service/src/main/java/greencity/service/EmailServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package greencity.service;

import greencity.constant.EmailConstants;
import greencity.constant.ErrorMessage;
import greencity.constant.LogMessage;
import greencity.dto.econews.InterestingEcoNewsDto;
import greencity.dto.user.SubscriberDto;
import greencity.dto.user.UserActivationDto;
import greencity.dto.user.UserDeactivationReasonDto;
import greencity.dto.violation.UserViolationMailDto;
import greencity.entity.User;
import greencity.message.PlaceStatusChangeDto;
import greencity.message.ScheduledEmailMessage;
import greencity.message.SendReportEmailMessage;
import greencity.repository.LanguageRepo;
import greencity.repository.UserRepo;
import greencity.validator.EmailAddressValidator;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
Expand Down Expand Up @@ -42,6 +47,7 @@ public class EmailServiceImpl implements EmailService {
private final String senderEmailAddress;
private final MessageSource messageSource;
private static final String PARAM_USER_ID = "&user_id=";
private final UserRepo userRepo;

/**
* Constructor.
Expand All @@ -51,13 +57,15 @@ public EmailServiceImpl(JavaMailSender javaMailSender,
ITemplateEngine templateEngine,
@Qualifier("sendEmailExecutor") Executor executor,
@Value("${client.address}") String clientLink,
@Value("${sender.email.address}") String senderEmailAddress, MessageSource messageSource) {
@Value("${sender.email.address}") String senderEmailAddress, MessageSource messageSource, UserRepo userRepo,
LanguageRepo languageRepo) {
this.javaMailSender = javaMailSender;
this.templateEngine = templateEngine;
this.executor = executor;
this.clientLink = clientLink;
this.senderEmailAddress = senderEmailAddress;
this.messageSource = messageSource;
this.userRepo = userRepo;
}

/**
Expand Down Expand Up @@ -317,6 +325,24 @@ private Map<String, Object> buildModelMapForPasswordRestore(Long userId, String
return model;
}

@Override
public void sendPlaceStatusChangeNotification(PlaceStatusChangeDto dto) {
Map<String, Object> model = new HashMap<>();
String userEmail = dto.getEmail();
User user = userRepo.findByEmail(userEmail)
.orElseThrow(() -> new RuntimeException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + userEmail));
String userLanguageCode = user.getLanguage().getCode();
model.put(EmailConstants.CLIENT_LINK, clientLink);
model.put(EmailConstants.USER_NAME, dto.getUserName());
model.put(EmailConstants.PLACE_NAME, dto.getPlaceName());
model.put(EmailConstants.PLACE_STATUS, dto.getNewStatus().name());
model.put(EmailConstants.LANGUAGE, userLanguageCode);

String template = createEmailTemplate(model, EmailConstants.PLACE_STATUS_CHANGE_PAGE);
sendEmail(userEmail, messageSource.getMessage(EmailConstants.PLACE_STATUS, null,
getLocale(userLanguageCode)), template);
}

private String getClientLinkByIsUbs(boolean isUbs) {
return clientLink + "/#" + (isUbs ? "/ubs" : "/greenCity");
}
Expand Down
1 change: 1 addition & 0 deletions service/src/test/java/greencity/ModelUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -711,4 +711,5 @@ public static TestersSignInRequest getTestersSignInRequestWithInvalidSecretKey()
.secretKey("invalid-secret-key")
.build();
}

}
2 changes: 2 additions & 0 deletions service/src/test/java/greencity/TestConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public final class TestConst {
public static final Long SIMPLE_LONG_NUMBER = 1L;
public static final Long SIMPLE_LONG_TWO_NUMBER = 2L;
public static final Long SIMPLE_LONG_NUMBER_BAD_VALUE = 100L;
public static final String ENGLISH_CODE = "en";
public static final String PLACE_NAME = "Central Park";
}
Loading

0 comments on commit 573a7b3

Please sign in to comment.