Add staff application email and Discord notification integration
This commit is contained in:
+32
-1
@@ -10,7 +10,9 @@ import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
|
||||
import com.alttd.altitudeweb.mappers.StaffApplicationDataMapper;
|
||||
import com.alttd.altitudeweb.model.FormResponseDto;
|
||||
import com.alttd.altitudeweb.model.StaffApplicationDto;
|
||||
import com.alttd.altitudeweb.services.discord.StaffApplicationDiscord;
|
||||
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||
import com.alttd.altitudeweb.services.mail.StaffApplicationMail;
|
||||
import com.alttd.altitudeweb.setup.Connection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -29,6 +31,8 @@ import java.util.concurrent.TimeUnit;
|
||||
public class ApplicationController implements ApplicationsApi {
|
||||
|
||||
private final StaffApplicationDataMapper staffApplicationDataMapper;
|
||||
private final StaffApplicationMail staffApplicationMail;
|
||||
private final StaffApplicationDiscord staffApplicationDiscord;
|
||||
|
||||
@Override
|
||||
public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
|
||||
@@ -40,8 +44,35 @@ public class ApplicationController implements ApplicationsApi {
|
||||
Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, application.email());
|
||||
boolean verified = optionalEmail.map(EmailVerification::verified).orElse(false);
|
||||
|
||||
boolean success = true;
|
||||
if (verified) {
|
||||
markAsSent(application.id());
|
||||
// Send mail first; only if sent, send to Discord, then mark as sent
|
||||
boolean mailSent = false;
|
||||
try {
|
||||
mailSent = staffApplicationMail.sendApplicationEmail(application);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while sending staff application email for {}", application.id(), e);
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (mailSent) {
|
||||
try {
|
||||
staffApplicationDiscord.sendApplicationToDiscord(application);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send staff application {} to Discord", application.id(), e);
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
markAsSent(application.id());
|
||||
}
|
||||
}
|
||||
|
||||
if (verified && !success) {
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
|
||||
FormResponseDto response = buildResponse(application, verified);
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package com.alttd.altitudeweb.services.discord;
|
||||
|
||||
import com.alttd.altitudeweb.database.Databases;
|
||||
import com.alttd.altitudeweb.database.discord.OutputChannel;
|
||||
import com.alttd.altitudeweb.database.discord.OutputChannelMapper;
|
||||
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
|
||||
import com.alttd.altitudeweb.setup.Connection;
|
||||
import com.alttd.webinterface.send_message.DiscordSender;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StaffApplicationDiscord {
|
||||
|
||||
private static final String OUTPUT_TYPE = "STAFF_APPLICATION";
|
||||
|
||||
public void sendApplicationToDiscord(StaffApplication application) {
|
||||
// Fetch channels for staff applications
|
||||
CompletableFuture<List<OutputChannel>> channelsFuture = new CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DISCORD).runQuery(sql -> {
|
||||
try {
|
||||
List<OutputChannel> channels = sql.getMapper(OutputChannelMapper.class)
|
||||
.getChannelsWithOutputType(OUTPUT_TYPE);
|
||||
channelsFuture.complete(channels);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load output channels for {}", OUTPUT_TYPE, e);
|
||||
channelsFuture.complete(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
|
||||
List<OutputChannel> channels = channelsFuture.join();
|
||||
if (channels.isEmpty()) {
|
||||
log.warn("No Discord output channels found for type {}. Skipping Discord send.", OUTPUT_TYPE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build embed content
|
||||
List<DiscordSender.EmbedField> fields = new ArrayList<>();
|
||||
fields.add(new DiscordSender.EmbedField(
|
||||
"Applicant",
|
||||
"UUID: " + safe(String.valueOf(application.uuid())) + "\n" +
|
||||
"Discord: " + safe(application.discordUsername()) + "\n" +
|
||||
"Email: " + safe(application.email()) + "\n" +
|
||||
"Age: " + safe(String.valueOf(application.age())) + "\n" +
|
||||
"Meets reqs: " + (application.meetsRequirements() != null && application.meetsRequirements()),
|
||||
false
|
||||
));
|
||||
fields.add(new DiscordSender.EmbedField(
|
||||
"Availability",
|
||||
"Days: " + safe(application.availableDays()) + "\n" +
|
||||
"Times: " + safe(application.availableTimes()),
|
||||
false
|
||||
));
|
||||
fields.add(new DiscordSender.EmbedField(
|
||||
"Experience",
|
||||
"Previous: " + safe(application.previousExperience()) + "\n" +
|
||||
"Plugins: " + safe(application.pluginExperience()) + "\n" +
|
||||
"Expectations: " + safe(application.moderatorExpectations()),
|
||||
false
|
||||
));
|
||||
if (application.additionalInfo() != null && !application.additionalInfo().isBlank()) {
|
||||
fields.add(new DiscordSender.EmbedField(
|
||||
"Additional Info",
|
||||
application.additionalInfo(),
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
List<Long> channelIds = channels.stream()
|
||||
.map(OutputChannel::channel)
|
||||
.toList();
|
||||
|
||||
Instant timestamp = application.createdAt() != null ? application.createdAt() : Instant.now();
|
||||
DiscordSender.getInstance().sendEmbedToChannels(
|
||||
channelIds,
|
||||
"New Staff Application Submitted",
|
||||
"Join date: " + (application.joinDate() != null ? application.joinDate().toString() : "unknown") +
|
||||
"\nSubmitted: " + formatInstant(timestamp),
|
||||
fields,
|
||||
null,
|
||||
timestamp,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private String safe(String s) {
|
||||
return s == null ? "unknown" : s;
|
||||
}
|
||||
|
||||
private String formatInstant(Instant instant) {
|
||||
if (instant == null) return "unknown";
|
||||
return instant.atZone(ZoneId.of("UTC"))
|
||||
.format(DateTimeFormatter.ofPattern("yyyy MMMM dd hh:mm a '(UTC)'"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.alttd.altitudeweb.services.mail;
|
||||
|
||||
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
|
||||
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;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StaffApplicationMail {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
private final SpringTemplateEngine templateEngine;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String fromEmail;
|
||||
|
||||
private static final String STAFF_APPLICATION_EMAIL = "[email protected]";
|
||||
|
||||
/**
|
||||
* Sends an email with the staff application details to the staff applications team mailbox.
|
||||
* Returns true if the email was sent successfully.
|
||||
*/
|
||||
public boolean sendApplicationEmail(StaffApplication application) {
|
||||
try {
|
||||
doSend(application);
|
||||
log.info("Staff application email sent successfully for application ID: {}", application.id());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send staff application email for application ID: {}", application.id(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doSend(StaffApplication application) throws MessagingException {
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
|
||||
helper.setFrom(fromEmail);
|
||||
helper.setTo(STAFF_APPLICATION_EMAIL);
|
||||
helper.setReplyTo(application.email());
|
||||
helper.setSubject("New Staff Application - " + safe(application.discordUsername()));
|
||||
|
||||
// Prepare template context
|
||||
String createdAt = application.createdAt()
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.format(DateTimeFormatter.ofPattern("yyyy MMMM dd hh:mm a '(UTC)'"));
|
||||
Context context = new Context();
|
||||
context.setVariable("application", application);
|
||||
context.setVariable("createdAt", createdAt);
|
||||
|
||||
String content = templateEngine.process("staff-application-email", context);
|
||||
helper.setText(content, true);
|
||||
mailSender.send(message);
|
||||
}
|
||||
|
||||
private String safe(String s) {
|
||||
return s == null ? "unknown" : s;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user