diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/config/FreeMarkerConfiguration.java b/packtrain-service/src/main/java/edu/mines/packtrain/config/FreeMarkerConfiguration.java index 721b88a6..94c2693a 100644 --- a/packtrain-service/src/main/java/edu/mines/packtrain/config/FreeMarkerConfiguration.java +++ b/packtrain-service/src/main/java/edu/mines/packtrain/config/FreeMarkerConfiguration.java @@ -17,6 +17,8 @@ public class FreeMarkerConfiguration { private final TemplateLoader templateLoader; private final Template extensionCreatedStudentEmailTemplate; + private final Template extensionApprovedStudentEmailTemplate; + private final Template extensionDeniedStudentEmailTemplate; private final Template extensionCreatedInstructorEmailTemplate; public FreeMarkerConfiguration( @@ -24,8 +26,13 @@ public FreeMarkerConfiguration( String templatePath, @Value("${grading-admin.email.templates.extension-created-student}") String extensionCreatedStudentTemplateName, + @Value("${grading-admin.email.templates.extension-approved-student}") + String extensionApprovedStudentTemplateName, + @Value("${grading-admin.email.templates.extension-denied-student}") + String extensionDeniedStudentTemplateName, @Value("${grading-admin.email.templates.extension-created-instructor}") - String extensionCreatedInstructorTemplateName) { + String extensionCreatedInstructorTemplateName + ) { templateLoader = new ClassTemplateLoader(this.getClass(), templatePath); freemarker.template.Configuration configuration = new freemarker.template.Configuration( @@ -34,6 +41,8 @@ public FreeMarkerConfiguration( configuration.setTemplateLoader(templateLoader); extensionCreatedStudentEmailTemplate = readTemplates(configuration, extensionCreatedStudentTemplateName); + extensionApprovedStudentEmailTemplate = readTemplates(configuration, extensionApprovedStudentTemplateName); + extensionDeniedStudentEmailTemplate = readTemplates(configuration, extensionDeniedStudentTemplateName); extensionCreatedInstructorEmailTemplate = readTemplates(configuration, extensionCreatedInstructorTemplateName); } @@ -64,6 +73,16 @@ public Template getExtensionCreatedStudentEmailTemplate() { return extensionCreatedStudentEmailTemplate; } + @Bean(name = "extensionApprovedStudentEmailTemplate") + public Template getExtensionApprovedStudentEmailTemplate() { + return extensionApprovedStudentEmailTemplate; + } + + @Bean(name = "extensionDeniedStudentEmailTemplate") + public Template extensionDeniedStudentEmailTemplate() { + return extensionDeniedStudentEmailTemplate; + } + @Bean(name = "extensionCreatedInstructorEmailTemplate") public Template getExtensionCreatedInstructorEmailTemplate() { return extensionCreatedInstructorEmailTemplate; diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/controllers/InstructorApiImpl.java b/packtrain-service/src/main/java/edu/mines/packtrain/controllers/InstructorApiImpl.java index 153b74eb..d3088cec 100644 --- a/packtrain-service/src/main/java/edu/mines/packtrain/controllers/InstructorApiImpl.java +++ b/packtrain-service/src/main/java/edu/mines/packtrain/controllers/InstructorApiImpl.java @@ -321,12 +321,14 @@ public ResponseEntity> getInstructorEnrollments(UUID cours } @Override + @Transactional public ResponseEntity approveExtension(UUID courseId, UUID extensionId, String reason) { LateRequest lateRequest = extensionService.approveExtension(extensionId, reason); return ResponseEntity.accepted().body(DTOFactory.toDto(lateRequest)); } @Override + @Transactional public ResponseEntity denyExtension(UUID courseId, UUID extensionId, String reason) { LateRequest lateRequest = extensionService.denyExtension(extensionId, reason); return ResponseEntity.accepted().body(DTOFactory.toDto(lateRequest)); diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionApprovedStudentEmailDTO.java b/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionApprovedStudentEmailDTO.java new file mode 100644 index 00000000..e0c60467 --- /dev/null +++ b/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionApprovedStudentEmailDTO.java @@ -0,0 +1,13 @@ +package edu.mines.packtrain.data.templates; + +import lombok.Data; + +@Data +public class ExtensionApprovedStudentEmailDTO { + private String reviewer; + private String student; + private String assignmentName; + private String courseName; + private int extensionDays; + private String reviewerResponse; +} diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionDeniedStudentEmailDTO.java b/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionDeniedStudentEmailDTO.java new file mode 100644 index 00000000..c86daeb9 --- /dev/null +++ b/packtrain-service/src/main/java/edu/mines/packtrain/data/templates/ExtensionDeniedStudentEmailDTO.java @@ -0,0 +1,13 @@ +package edu.mines.packtrain.data.templates; + +import lombok.Data; + +@Data +public class ExtensionDeniedStudentEmailDTO { + private String reviewer; + private String student; + private String assignmentName; + private String courseName; + private int extensionDays; + private String reviewerResponse; +} diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/services/EmailService.java b/packtrain-service/src/main/java/edu/mines/packtrain/services/EmailService.java index f1af87fe..3f617939 100644 --- a/packtrain-service/src/main/java/edu/mines/packtrain/services/EmailService.java +++ b/packtrain-service/src/main/java/edu/mines/packtrain/services/EmailService.java @@ -46,14 +46,14 @@ public void sendEmail(String to, List cc, String subject, String html) { if (!enabled) { return; } - + EmailPopulatingBuilder builder = EmailBuilder.startingBlank() .from(fromName, fromEmail) - .to(overrideTo != null ? overrideTo : to) + .to(!overrideTo.isEmpty() ? overrideTo : to) .withSubject(subject) .withHTMLText(html); - // (overrideCC != null ? List.of(overrideCC) : cc).forEach(s -> builder.cc(s)); + (!overrideCC.isEmpty() ? List.of(overrideCC) : cc).forEach(s -> builder.cc(s)); log.debug("Sending email '{}' to '{}'", subject, to); diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionEmailService.java b/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionEmailService.java index 9ba902de..5e4c35e2 100644 --- a/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionEmailService.java +++ b/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionEmailService.java @@ -9,14 +9,18 @@ import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.mines.packtrain.data.templates.ExtensionApprovedStudentEmailDTO; import edu.mines.packtrain.data.templates.ExtensionCreatedInstructorEmailDTO; import edu.mines.packtrain.data.templates.ExtensionCreatedStudentEmailDTO; +import edu.mines.packtrain.data.templates.ExtensionDeniedStudentEmailDTO; import edu.mines.packtrain.models.Course; import edu.mines.packtrain.models.LateRequest; import edu.mines.packtrain.models.User; import edu.mines.packtrain.models.enums.LateRequestType; import freemarker.template.Template; import freemarker.template.TemplateException; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; @Service @@ -24,18 +28,24 @@ public class ExtensionEmailService { private final EmailService emailService; private final Template extensionCreatedStudentEmailTemplate; + private final Template extensionApprovedStudentEmailTemplate; + private final Template extensionDeniedStudentEmailTemplate; private final Template extensionCreatedInStructorEmailTemplate; private final ObjectMapper mapper; private final String frontendUrl; public ExtensionEmailService(EmailService emailService, @Qualifier("extensionCreatedStudentEmailTemplate") Template extensionCreatedStudentEmailTemplate, + @Qualifier("extensionApprovedStudentEmailTemplate") Template extensionApprovedStudentEmailTemplate, + @Qualifier("extensionDeniedStudentEmailTemplate") Template extensionDeniedStudentEmailTemplate, @Qualifier("extensionCreatedInstructorEmailTemplate") Template extensionCreatedInStructorEmailTemplate, @Value("${grading-admin.frontend-url}") String frontendUrl, ObjectMapper mapper) { this.emailService = emailService; this.extensionCreatedStudentEmailTemplate = extensionCreatedStudentEmailTemplate; + this.extensionApprovedStudentEmailTemplate = extensionApprovedStudentEmailTemplate; + this.extensionDeniedStudentEmailTemplate = extensionDeniedStudentEmailTemplate; this.extensionCreatedInStructorEmailTemplate = extensionCreatedInStructorEmailTemplate; this.frontendUrl = frontendUrl; this.mapper = mapper; @@ -58,11 +68,10 @@ public void handleExtensionCreated(LateRequest lateRequest, Course course, User return; } - { ExtensionCreatedInstructorEmailDTO model = new ExtensionCreatedInstructorEmailDTO(); - model.setCourseName(lateRequest.getAssignment().getName()); - model.setAssignmentName(course.getName()); + model.setCourseName(course.getName()); + model.setAssignmentName(lateRequest.getAssignment().getName()); model.setStudent(requester.getName()); model.setInstructor(instructor.getName()); model.setExtensionDays(lateRequest.getDaysRequested()); @@ -73,6 +82,32 @@ public void handleExtensionCreated(LateRequest lateRequest, Course course, User } } + @Transactional + public void handleExtensionApproved(LateRequest lateRequest, Course course, User student, User approver) { + ExtensionApprovedStudentEmailDTO model = new ExtensionApprovedStudentEmailDTO(); + + model.setAssignmentName(lateRequest.getAssignment().getName()); + model.setStudent(student.getName()); + model.setReviewer(approver.getName()); + model.setReviewerResponse(lateRequest.getExtension().getReviewerResponse()); + model.setExtensionDays(lateRequest.getDaysRequested()); + + createExtensionApprovedStudentEmail(student.getEmail(), approver.getEmail(), model); + } + + @Transactional + public void handleExtensionDenied(LateRequest lateRequest, Course course, User student, User approver) { + ExtensionDeniedStudentEmailDTO model = new ExtensionDeniedStudentEmailDTO(); + + model.setAssignmentName(lateRequest.getAssignment().getName()); + model.setStudent(student.getName()); + model.setReviewer(approver.getName()); + model.setReviewerResponse(lateRequest.getExtension().getReviewerResponse()); + model.setExtensionDays(lateRequest.getDaysRequested()); + + createExtensionDeniedStudentEmail(student.getEmail(), approver.getEmail(), model); + } + private void createExtensionCreatedStudentEmail(String emailAddress, ExtensionCreatedStudentEmailDTO model) { StringWriter writer = new StringWriter(); @@ -124,4 +159,56 @@ private void createExtensionCreatedInstructorEmail(String emailAddress, Extensio renderedTemplate); } + private void createExtensionApprovedStudentEmail(String studentEmail, String instructorEmail, + ExtensionApprovedStudentEmailDTO model) { + StringWriter writer = new StringWriter(); + + try { + extensionApprovedStudentEmailTemplate + .process(mapper.convertValue(model, new TypeReference>() { + }), writer); + } catch (TemplateException | IOException e) { + log.error("Failed to render email template!", e); + return; + } + + String renderedTemplate = writer.toString(); + + if (renderedTemplate.isEmpty()) { + log.error("Failed to render email template! Template is empty!"); + return; + } + + emailService.sendEmail(studentEmail, List.of(instructorEmail), + String.format("[Packtrain] [%s] [%s] Extension Request Approved", model.getCourseName(), + model.getAssignmentName()), + renderedTemplate); + } + + private void createExtensionDeniedStudentEmail(String studentEmail, String instructorEmail, + ExtensionDeniedStudentEmailDTO model) { + StringWriter writer = new StringWriter(); + + try { + extensionDeniedStudentEmailTemplate + .process(mapper.convertValue(model, new TypeReference>() { + }), writer); + } catch (TemplateException | IOException e) { + log.error("Failed to render email template!", e); + return; + } + + String renderedTemplate = writer.toString(); + + if (renderedTemplate.isEmpty()) { + log.error("Failed to render email template! Template is empty!"); + return; + } + + emailService.sendEmail(studentEmail, List.of(instructorEmail), + String.format("[Packtrain] [%s] [%s] Extension Request Denied", model.getCourseName(), + model.getAssignmentName()), + renderedTemplate); + } + } diff --git a/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionService.java b/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionService.java index d3f1aecf..d5929fe4 100644 --- a/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionService.java +++ b/packtrain-service/src/main/java/edu/mines/packtrain/services/ExtensionService.java @@ -24,6 +24,7 @@ import edu.mines.packtrain.models.enums.LateRequestType; import edu.mines.packtrain.repositories.ExtensionRepo; import edu.mines.packtrain.repositories.LateRequestRepo; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -62,6 +63,7 @@ public List getAllLateRequests(UUID courseId, String lateRequestSta return List.of(); } + @Transactional public LateRequest approveExtension(UUID extensionId, String reason) { Optional lateRequest = getLateRequest(extensionId); String userId = securityManager.getUser().getCwid(); @@ -78,9 +80,12 @@ public LateRequest approveExtension(UUID extensionId, String reason) { extensionRepo.save(lateRequest.get().getExtension()); + extensionEmailService.handleExtensionApproved(lateRequest.get(), lateRequest.get().getAssignment().getCourse(), lateRequest.get().getRequestingUser(), securityManager.getUser()); + return lateRequestRepo.save(lateRequest.get()); } + @Transactional public LateRequest denyExtension(UUID extensionId, String reason) { Optional lateRequest = getLateRequest(extensionId); String userId = securityManager.getUser().getCwid(); @@ -96,6 +101,9 @@ public LateRequest denyExtension(UUID extensionId, String reason) { lateRequest.get().setStatus(LateRequestStatus.REJECTED); extensionRepo.save(lateRequest.get().getExtension()); + + extensionEmailService.handleExtensionDenied(lateRequest.get(), lateRequest.get().getAssignment().getCourse(), lateRequest.get().getRequestingUser(), securityManager.getUser()); + return lateRequestRepo.save(lateRequest.get()); } diff --git a/packtrain-service/src/main/resources/application.yaml b/packtrain-service/src/main/resources/application.yaml index 80d43b08..be954437 100644 --- a/packtrain-service/src/main/resources/application.yaml +++ b/packtrain-service/src/main/resources/application.yaml @@ -38,6 +38,8 @@ grading-admin: templates: template-directory: /templates/emails extension-created-student: extension-created-student.ftl + extension-approved-student: extension-approved-student.ftl + extension-denied-student: extension-denied-student.ftl extension-created-instructor: extension-created-instructor.ftl enabled: true smtp-server: ${SMTP_SERVER:localhost.dev} diff --git a/packtrain-service/src/main/resources/templates/emails/extension-approved-student.ftl b/packtrain-service/src/main/resources/templates/emails/extension-approved-student.ftl new file mode 100644 index 00000000..0cd7bdfd --- /dev/null +++ b/packtrain-service/src/main/resources/templates/emails/extension-approved-student.ftl @@ -0,0 +1,17 @@ + + + + + + +

Hi ${student},

+

+ +

Your extension request for ${assignmentName} for ${extensionDays} days has been approved!

+ +

Your request was reviewed by Professor ${reviewer}. Below is their response:

+ +

${reviewerResponse}

+

+ + diff --git a/packtrain-service/src/main/resources/templates/emails/extension-denied-student.ftl b/packtrain-service/src/main/resources/templates/emails/extension-denied-student.ftl new file mode 100644 index 00000000..6e2ed543 --- /dev/null +++ b/packtrain-service/src/main/resources/templates/emails/extension-denied-student.ftl @@ -0,0 +1,19 @@ + + + + + + +

Hi ${student},

+

+ +

Your extension request for ${assignmentName} for ${extensionDays} days has been denied.

+ +

Your request was reviewed by Professor ${reviewer}. Below is their response:

+ +

${reviewerResponse}

+

+ +

Please contact your instructor directly with any questions or concerns.

+ +