From 19b19f80a9f6214b9994c9228e91009819329f14 Mon Sep 17 00:00:00 2001 From: ChiragJS <118426425+ChiragJS@users.noreply.github.com> Date: Fri, 14 Jun 2024 22:20:31 +0530 Subject: [PATCH] Sending mails (#61) * basic email system --------- Co-authored-by: Sundarakrishnan N <72200790+SundarakrishnanN@users.noreply.github.com> Co-authored-by: SundarakrishnanN Co-authored-by: Prajwal P --- .gitignore | 3 +- build.gradle | 1 + .../gatekeeper/GateKeeperApplication.java | 10 +- .../controller/RequestFormController.java | 81 +++++--- .../gatekeeper/controller/UserController.java | 12 +- .../gatekeeper/dto/Email/EmailDTO.java | 66 ++++++ .../gatekeeper/service/EmailService.java | 59 ++++++ .../gatekeeper/service/UserService.java | 18 ++ src/main/resources/application-dev.properties | 4 +- src/main/resources/application.properties | 10 +- .../resources/templates/emailTemplate.html | 190 ++++++++++++++++++ 11 files changed, 413 insertions(+), 41 deletions(-) create mode 100644 src/main/java/org/ieeervce/gatekeeper/dto/Email/EmailDTO.java create mode 100644 src/main/java/org/ieeervce/gatekeeper/service/EmailService.java create mode 100644 src/main/resources/templates/emailTemplate.html diff --git a/.gitignore b/.gitignore index 5033490..fefbaf7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,4 @@ out/ .vscode/ .env.prod -/src/main/resources/application-prod.properties - +/src/main/resources/application-prod.properties \ No newline at end of file diff --git a/build.gradle b/build.gradle index 64720a6..2ee4ff5 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.modelmapper:modelmapper:3.2.0' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' diff --git a/src/main/java/org/ieeervce/gatekeeper/GateKeeperApplication.java b/src/main/java/org/ieeervce/gatekeeper/GateKeeperApplication.java index 09f519e..f7f0d7f 100644 --- a/src/main/java/org/ieeervce/gatekeeper/GateKeeperApplication.java +++ b/src/main/java/org/ieeervce/gatekeeper/GateKeeperApplication.java @@ -1,8 +1,12 @@ package org.ieeervce.gatekeeper; +import org.ieeervce.gatekeeper.dto.Email.EmailDTO; +import org.ieeervce.gatekeeper.service.EmailService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -10,11 +14,11 @@ @SpringBootApplication @RestController +@EnableAsync public class GateKeeperApplication { - @GetMapping("/") - String index(){ - return "GateKeeper"; + String index() throws Exception{ + return "gatekeeper"; } public static void main(String[] args) { diff --git a/src/main/java/org/ieeervce/gatekeeper/controller/RequestFormController.java b/src/main/java/org/ieeervce/gatekeeper/controller/RequestFormController.java index 627926e..b0842dc 100644 --- a/src/main/java/org/ieeervce/gatekeeper/controller/RequestFormController.java +++ b/src/main/java/org/ieeervce/gatekeeper/controller/RequestFormController.java @@ -2,11 +2,14 @@ import static org.ieeervce.gatekeeper.config.SecurityConfiguration.getRequesterDetails; +import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Optional; +import jakarta.mail.MessagingException; +import org.ieeervce.gatekeeper.dto.Email.EmailDTO; import org.ieeervce.gatekeeper.dto.RequestForm.RequestDTO; import org.ieeervce.gatekeeper.dto.RequestForm.RequestFormPdfDTO; import org.ieeervce.gatekeeper.dto.RequestForm.ResponseRequestFormDTO; @@ -14,15 +17,13 @@ import org.ieeervce.gatekeeper.exception.InvalidDataException; import org.ieeervce.gatekeeper.exception.ItemNotFoundException; import org.ieeervce.gatekeeper.exception.PDFNotConversionException; -import org.ieeervce.gatekeeper.service.RequestFormService; -import org.ieeervce.gatekeeper.service.ReviewLogService; -import org.ieeervce.gatekeeper.service.RoleService; -import org.ieeervce.gatekeeper.service.UserService; +import org.ieeervce.gatekeeper.service.*; import org.modelmapper.ModelMapper; import org.modelmapper.PropertyMap; import org.modelmapper.TypeToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -50,6 +51,9 @@ public class RequestFormController { private final RoleService roleService; private final UserService userService; private final ReviewLogService reviewLogService; + @Autowired + EmailService emailService ; + PropertyMap skipReferencedFieldsMap = new PropertyMap() { @Override protected void configure() { @@ -135,34 +139,28 @@ public void deleteRequestForm(@PathVariable Long requestFormId) throws ItemNotFo @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseRequestFormDTO postRequestForm(@RequestParam("eventTitle") String eventTitle, @RequestParam("isFinance") boolean isFinance, @RequestParam("formPDF") MultipartFile formPDF) - throws InvalidDataException, PDFNotConversionException { + { LOGGER.info("in: post request form"); + RequestForm savedRequestForm; RequestForm requestForm = new RequestForm(); requestForm.setEventTitle(eventTitle); requestForm.setFinance(isFinance); requestForm.setStatus(FinalStatus.PENDING); LOGGER.debug("Requester Details: {}", getRequesterDetails()); + User optionalUser = userService.getUserByEmail(getRequesterDetails()).get(); + requestForm.setRequester(optionalUser); + requestForm.setRequestHierarchy(roleService.generateHierarchy(optionalUser, isFinance)); try { - User optionalUser = userService.getUserByEmail(getRequesterDetails()).get(); - requestForm.setRequester(optionalUser); - - requestForm.setRequestHierarchy(roleService.generateHierarchy(optionalUser, isFinance)); - - userService.setPendingRequests(requestForm, requestForm.getRequestHierarchy(), - requestForm.getRequestIndex(), optionalUser); - } catch (Exception e) { - LOGGER.warn("Exception getting user and hierarchy", e); - } - - try { - requestForm.setFormPDF(formPDF.getBytes()); - } catch (java.io.IOException e) { - throw new PDFNotConversionException("Could not store pdf"); + requestForm.setFormPDF(formPDF.getBytes()); } - - RequestForm savedRequestForm = requestFormService.save(requestForm); - return modelMapper.map(savedRequestForm, ResponseRequestFormDTO.class);// truncated + catch (IOException e) { + LOGGER.warn("Exception on converting pdf to bytes", e); + } + savedRequestForm = requestFormService.save(requestForm); + userService.setPendingRequests(savedRequestForm, savedRequestForm.getRequestHierarchy(), + savedRequestForm.getRequestIndex(), optionalUser); + return modelMapper.map(savedRequestForm, ResponseRequestFormDTO.class); } @PutMapping("/{requestFormId}") @@ -205,6 +203,19 @@ public ResponseRequestFormDTO approveRequest(@PathVariable Long requestFormId, S reviewLogService.addReview(reviewLog); requestForm.getReviewLogs().add(reviewLog); + + //Logic for Sending Mails to Requester + try{ + String messageBody = "Your request for Event Titled" + requestForm.getEventTitle() +" has successfully been approved by "+ optionalUser.getName() +"."; + User requester = requestForm.getRequester(); + EmailDTO emailDTO = new EmailDTO(requester,messageBody,requestForm); + emailDTO.setSubject("Approved By "+optionalUser.getName()); + emailService.sendSimpleMail(emailDTO); + } + catch (MessagingException e){ + LOGGER.error("Approval Confirmation Mail To Requester Failed"); + } + requestForm.setRequestIndex(index + 1); index++; if (index < requestForm.getRequestHierarchy().size()) @@ -212,9 +223,18 @@ public ResponseRequestFormDTO approveRequest(@PathVariable Long requestFormId, S requestForm.getRequester()); else { requestForm.setStatus(FinalStatus.ACCEPTED); + try{ + String messageBody = "Your request for Event Titled , " + requestForm.getEventTitle() +" has been approved."; + User requester = requestForm.getRequester(); + EmailDTO emailDTO = new EmailDTO(requester,messageBody,requestForm); + emailDTO.setSubject("Event Request Approved"); + emailService.sendSimpleMail(emailDTO); + } + catch (MessagingException e) + { + LOGGER.error("ACCEPTED Request Mail Failed"); + } } - // TODO send mails to requester at every step and send mail to the next set of - // users assigned(update setPendingRequests() method to add this) return modelMapper.map(requestFormService.save(requestForm), ResponseRequestFormDTO.class);// truncated } @@ -250,8 +270,17 @@ public ResponseRequestFormDTO rejectRequest(@PathVariable Long requestFormId, St reviewLogService.addReview(reviewLog); requestForm.setStatus(FinalStatus.REJECTED); requestForm.getReviewLogs().add(reviewLog); + try{ + String messageBody = "Your request for Event Titled , " + requestForm.getEventTitle() +" has been rejected."; + User requester = requestForm.getRequester(); + EmailDTO emailDTO = new EmailDTO(requester,messageBody,requestForm); + emailDTO.setSubject("Event Request Rejected"); + emailService.sendSimpleMail(emailDTO); + } + catch (MessagingException e){ + LOGGER.error("Failed To Send Rejection Mail"); + } return modelMapper.map(requestFormService.save(requestForm), ResponseRequestFormDTO.class);// truncated - // TODO update requester with email } @GetMapping("/pdf/{requestFormId}") diff --git a/src/main/java/org/ieeervce/gatekeeper/controller/UserController.java b/src/main/java/org/ieeervce/gatekeeper/controller/UserController.java index ab09a02..207627b 100644 --- a/src/main/java/org/ieeervce/gatekeeper/controller/UserController.java +++ b/src/main/java/org/ieeervce/gatekeeper/controller/UserController.java @@ -8,6 +8,7 @@ import org.ieeervce.gatekeeper.entity.Role; import org.ieeervce.gatekeeper.entity.Society; import org.ieeervce.gatekeeper.entity.User; +import org.ieeervce.gatekeeper.service.EmailService; import org.ieeervce.gatekeeper.service.RoleService; import org.ieeervce.gatekeeper.service.SocietyService; import org.modelmapper.ModelMapper; @@ -47,10 +48,12 @@ public UserController(UserService userService, ModelMapper modelMapper,SocietySe this.modelMapper.addMappings(skipReferencedFieldsMap); this.modelMapper.getConfiguration().setAmbiguityIgnored(true); } + @Autowired + EmailService emailService; @PostMapping User addUser(@RequestBody UserDTO userDTO) throws InvalidDataException { - User user = modelMapper.map(userDTO,User.class); + User user = modelMapper.map(userDTO,User.class); user.setPassword(passwordEncoder.encode(userDTO.getPassword())); try { if(userDTO.getSocietyId()!=null) { @@ -63,10 +66,9 @@ User addUser(@RequestBody UserDTO userDTO) throws InvalidDataException { catch (ItemNotFoundException e){ throw new InvalidDataException("Invalid Data"); } - - - - return userService.saveUser(user); + User savedUser = userService.saveUser(user); + emailService.sendUserCredentials(userDTO.getEmail(),userDTO.getPassword()); + return savedUser; } @GetMapping public String getUser() diff --git a/src/main/java/org/ieeervce/gatekeeper/dto/Email/EmailDTO.java b/src/main/java/org/ieeervce/gatekeeper/dto/Email/EmailDTO.java new file mode 100644 index 0000000..8bb94f5 --- /dev/null +++ b/src/main/java/org/ieeervce/gatekeeper/dto/Email/EmailDTO.java @@ -0,0 +1,66 @@ +package org.ieeervce.gatekeeper.dto.Email; + +import org.ieeervce.gatekeeper.entity.RequestForm; +import org.ieeervce.gatekeeper.entity.User; + +public class EmailDTO { + private String recipient; + private String messageBody; + private String subject; + private byte[] attachment; + private String name; + private String formLink; + public String getFormLink() { + return formLink; + } + public EmailDTO(User user, String messageBody,RequestForm requestForm){ + this.recipient =user.getEmail(); + this.messageBody=messageBody; + this.name = user.getName().split(" ")[0]; + this.attachment = requestForm.getFormPDF(); + this.formLink = "gate.ieee-rvce.org/event/"+requestForm.getRequestFormId(); + } + public void setFormLink(String formLink) { + this.formLink = formLink; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRecipient() { + return recipient; + } + + public void setRecipient(String recipient) { + this.recipient = recipient; + } + + public String getMessageBody() { + return messageBody; + } + + public void setMessageBody(String messageBody) { + this.messageBody = messageBody; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public byte[] getAttachment() { + return attachment; + } + + public void setAttachment(byte[] attachment) { + this.attachment = attachment; + } +} diff --git a/src/main/java/org/ieeervce/gatekeeper/service/EmailService.java b/src/main/java/org/ieeervce/gatekeeper/service/EmailService.java new file mode 100644 index 0000000..fccc5c5 --- /dev/null +++ b/src/main/java/org/ieeervce/gatekeeper/service/EmailService.java @@ -0,0 +1,59 @@ +package org.ieeervce.gatekeeper.service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.ieeervce.gatekeeper.dto.Email.EmailDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.mail.MailException; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + + +@Service +public class EmailService { + //in this implementation the unchecked exception MailException has not been handled, if anything breaks do check this out too + @Autowired + private JavaMailSender javaMailSender; + @Autowired + private TemplateEngine templateEngine; + @Value("${spring.mail.username}") private String sender; + @Async + public void sendUserCredentials(String userEmail,String userPassword) { + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setFrom(sender); + simpleMailMessage.setTo(userEmail); + simpleMailMessage.setSubject("GateKeeper Account Successfully Created!"); + simpleMailMessage.setText("Dear User,\n Your GateKeeper account has successfully been created!\nPlease login to https://gate.ieee-rvce.org/ and change your password as soon as you can.\n\nEmail: " + userEmail + "\nPassword: "+userPassword+"\n\nThank you,\nIEEE RVCE Web Team"); + javaMailSender.send(simpleMailMessage); + } + @Async + public void sendSimpleMail(EmailDTO emailDetails) throws MessagingException{ + + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper ; + + mimeMessageHelper = new MimeMessageHelper(mimeMessage,true); + mimeMessageHelper.setFrom(sender); + mimeMessageHelper.setTo(emailDetails.getRecipient()); + mimeMessageHelper.setSubject(emailDetails.getSubject()); + //Setting context of variables for the html file + Context context = new Context(); + context.setVariable("header",emailDetails.getSubject()); + context.setVariable("name",emailDetails.getName().split(" ")[0]); + context.setVariable("messageBody",emailDetails.getMessageBody()); + context.setVariable("linkUrl",emailDetails.getFormLink()); + + String htmlContent = templateEngine.process("emailTemplate",context); + mimeMessageHelper.setText(htmlContent,true); + mimeMessageHelper.addAttachment("Event PDF.pdf",new ByteArrayResource(emailDetails.getAttachment())); + javaMailSender.send(mimeMessage); + + } +} diff --git a/src/main/java/org/ieeervce/gatekeeper/service/UserService.java b/src/main/java/org/ieeervce/gatekeeper/service/UserService.java index c0f0cbb..40a16d0 100644 --- a/src/main/java/org/ieeervce/gatekeeper/service/UserService.java +++ b/src/main/java/org/ieeervce/gatekeeper/service/UserService.java @@ -1,7 +1,12 @@ package org.ieeervce.gatekeeper.service; +import jakarta.mail.MessagingException; +import org.ieeervce.gatekeeper.controller.RequestFormController; +import org.ieeervce.gatekeeper.dto.Email.EmailDTO; import org.ieeervce.gatekeeper.exception.ItemNotFoundException; import org.ieeervce.gatekeeper.entity.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.*; import org.ieeervce.gatekeeper.repository.UserRepository; @@ -16,7 +21,11 @@ public class UserService { private RoleValue roleValue; private final String ITEM_NOT_FOUND = "User Id not found"; + private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class); + private final UserRepository repository; + @Autowired + EmailService emailService ; public UserService(UserRepository repository) { this.repository = repository; @@ -62,6 +71,15 @@ public void setPendingRequests(RequestForm requestForm, List requestHierar } for (User u : users) { u.getPendingRequests().add(requestForm); + String messageBody ="Request for approval for the event titled " + requestForm.getEventTitle() + ", submitted by " + u.getName() + "."; + EmailDTO emailDTO = new EmailDTO(u,messageBody,requestForm); + emailDTO.setSubject("Mail For Approval - IEEE Event"); + try{ + emailService.sendSimpleMail(emailDTO); + } + catch (MessagingException e){ + LOGGER.error("Failed To Send Mail For Approval"); + } } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 321311c..00130ec 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -3,4 +3,6 @@ debug=true spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=myuser spring.datasource.password=secret -spring.docker.compose.file=compose.dev.yaml + +spring.mail.username= +spring.mail.password= \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7c634a0..476ba74 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,12 +6,14 @@ spring.servlet.multipart.max-file-size=10MB spring.security.user.name=chirag spring.security.user.password=chirag spring.servlet.multipart.location=uploads -spring.mail.host=localhost -spring.mail.port=1025 -spring.mail.username=admin@mailhog.com -spring.mail.password=secret + +#smtp server stuff + +spring.mail.host=smtp.gmail.com +spring.mail.port=465 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.ssl.enable= true management.health.mail.enabled=false management.endpoints.web.exposure.include=health,info diff --git a/src/main/resources/templates/emailTemplate.html b/src/main/resources/templates/emailTemplate.html new file mode 100644 index 0000000..a7189b6 --- /dev/null +++ b/src/main/resources/templates/emailTemplate.html @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file