Add email notification service for appeals using Spring Mail and Thymeleaf templates.

This commit is contained in:
akastijn 2025-08-16 20:23:35 +02:00
parent f026f24263
commit db642103ed
6 changed files with 100 additions and 1 deletions

View File

@ -36,6 +36,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.security:spring-security-oauth2-resource-server") implementation("org.springframework.security:spring-security-oauth2-resource-server")
implementation("org.springframework.security:spring-security-oauth2-jose") implementation("org.springframework.security:spring-security-oauth2-jose")
implementation("org.springframework.boot:spring-boot-starter-mail:3.1.5")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
//AOP //AOP
implementation("org.aspectj:aspectjrt:1.9.19") implementation("org.aspectj:aspectjrt:1.9.19")

View File

@ -5,7 +5,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication @SpringBootApplication(scanBasePackages = {"com.alttd.altitudeweb"})
@EnableAspectJAutoProxy @EnableAspectJAutoProxy
public class AltitudeWebApplication { public class AltitudeWebApplication {

View File

@ -10,6 +10,7 @@ import com.alttd.altitudeweb.model.DiscordAppealDto;
import com.alttd.altitudeweb.model.MinecraftAppealDto; import com.alttd.altitudeweb.model.MinecraftAppealDto;
import com.alttd.altitudeweb.model.UpdateMailDto; import com.alttd.altitudeweb.model.UpdateMailDto;
import com.alttd.altitudeweb.services.limits.RateLimit; import com.alttd.altitudeweb.services.limits.RateLimit;
import com.alttd.altitudeweb.services.mail.AppealMail;
import com.alttd.altitudeweb.setup.Connection; import com.alttd.altitudeweb.setup.Connection;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -29,6 +30,7 @@ import java.util.concurrent.TimeUnit;
public class AppealController implements AppealsApi { public class AppealController implements AppealsApi {
private final AppealDataMapper mapper; private final AppealDataMapper mapper;
private final AppealMail appealMail;
@RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "discordAppeal") @RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "discordAppeal")
@Override @Override
@ -55,6 +57,8 @@ public class AppealController implements AppealsApi {
}); });
Appeal appeal = appealCompletableFuture.join(); Appeal appeal = appealCompletableFuture.join();
appealMail.sendAppealNotification(appeal);
AppealResponseDto appealResponseDto = new AppealResponseDto( AppealResponseDto appealResponseDto = new AppealResponseDto(
appeal.id().toString(), appeal.id().toString(),
"Your appeal has been submitted. You will be notified when it has been reviewed.", "Your appeal has been submitted. You will be notified when it has been reviewed.",

View File

@ -0,0 +1,61 @@
package com.alttd.altitudeweb.services.mail;
import com.alttd.altitudeweb.database.web_db.forms.Appeal;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
@Slf4j
@Service
@RequiredArgsConstructor
public class AppealMail {
private final JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String fromEmail;
private static final String APPEAL_EMAIL = "appeal@alttd.com";
/**
* Sends an email notification about the appeal to both the user and the appeals team.
*
* @param appeal The appeal object containing all necessary information
*/
public void sendAppealNotification(Appeal appeal) {
try {
sendEmailToAppealsTeam(appeal);
log.info("Appeal notification emails sent successfully for appeal ID: {}", appeal.id());
} catch (Exception e) {
log.error("Failed to send appeal notification emails for appeal ID: {}", appeal.id(), e);
}
}
private SpringTemplateEngine templateEngine;
private void sendEmailToAppealsTeam(Appeal appeal) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromEmail);
helper.setTo(APPEAL_EMAIL);
helper.setReplyTo(appeal.email());
helper.setSubject("New Appeal Submitted - " + appeal.username());
Context context = new Context();
context.setVariable("appeal", appeal);
String content = templateEngine.process("appeal-email", context);
helper.setText(content, true);
mailSender.send(message);
}
}

View File

@ -10,3 +10,10 @@ particles.file_path=${user.home}/.altitudeweb/particles
notification.server.url=${SERVER_IP:10.0.0.107}:${SERVER_PORT:8080} notification.server.url=${SERVER_IP:10.0.0.107}:${SERVER_PORT:8080}
my-server.address=${SERVER_ADDRESS:https://alttd.com} my-server.address=${SERVER_ADDRESS:https://alttd.com}
logging.level.com.alttd.altitudeweb=INFO logging.level.com.alttd.altitudeweb=INFO
discord.token=${DISCORD_TOKEN}
spring.mail.host=${MAIL_HOST:smtp.zoho.com}
spring.mail.port=${MAIL_PORT:465}
spring.mail.username=${MAIL_USER}
spring.mail.password=${MAIL_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>Appeal Notification</title>
<style>
ul {
list-style-type: none;
padding-left: 20px;
}
</style>
</head>
<body>
<h2 th:text="'Appeal by ' + ${appeal.username}">Appeal by Username</h2>
<p>Punishment information</p>
<ul>
<li><strong>Username:</strong> <span th:text="${appeal.username}">username</span></li>
<li><strong>UUID:</strong> <span th:text="${appeal.uuid}">uuid</span></li>
<li><strong>Email:</strong> <span th:text="${appeal.email}">email</span></li>
<li><strong>Submitted:</strong> <span th:text="${appeal.createdAt}">date</span></li>
</ul>
<h3>Appeal:</h3>
<p th:text="${appeal.reason}">Reason text</p>
</body>
</html>