Skip to content

Commit

Permalink
create mail service #248
Browse files Browse the repository at this point in the history
  • Loading branch information
iam-flo committed Mar 3, 2025
1 parent 97a1006 commit 3241239
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 0 deletions.
10 changes: 10 additions & 0 deletions server/application-server/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ services:
networks:
- app-network

postfix:
image: ghcr.io/ls1admin/postfix:latest
container_name: hephaestus-postfix
restart: unless-stopped
volumes:
- ./postfix-config:/config # See https://github.com/ls1admin/postfix-container-tum-mailrelay/tree/main for details
hostname: hephaestus.aet.cit.tum.de
networks:
- app-network

networks:
app-network:
driver: bridge
Expand Down
8 changes: 8 additions & 0 deletions server/application-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ public class User extends BaseGitServiceEntity {
@ToString.Exclude
private Set<PullRequestReviewComment> reviewComments = new HashSet<>();

@NonNull
private boolean notificationsEnabled = false;

// Current ranking points for the leaderboard leagues
private int leaguePoints;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package de.tum.in.www1.hephaestus.notification;

import de.tum.in.www1.hephaestus.gitprovider.user.User;
import de.tum.in.www1.hephaestus.gitprovider.user.UserInfoDTO;
import jakarta.activation.DataHandler;
import jakarta.activation.FileDataSource;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.util.ByteArrayDataSource;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.JavaMailSender;
import org.thymeleaf.context.Context;

import java.util.*;

public class MailBuilder {
private static final Logger log = LoggerFactory.getLogger(MailBuilder.class);
private final MailConfig config;

private final List<User> primarySenders;
private final List<User> primaryRecipients;

@Getter
private final String subject;

@Getter
private final String template;

@Getter
private final Map<String, Object> variables;

@Getter
private final Set<String> notificationNames;

public MailBuilder(MailConfig config, String subject, String template) {
this.config = config;

this.primarySenders = new ArrayList<>();
this.primaryRecipients = new ArrayList<>();

this.subject = subject;
this.template = template;

this.variables = new HashMap<>();
this.variables.put("config", config.getConfigDto());

this.notificationNames = new HashSet<>();
}

public MailBuilder addNotificationName(String name) {
notificationNames.add(name);

return this;
}

public MailBuilder addPrimarySender(User user) {
this.primarySenders.add(user);

return this;
}

public MailBuilder addPrimaryRecipient(User user) {
if (primaryRecipients.contains(user)) {
return this;
}

primaryRecipients.add(user);

return this;
}


public void send(JavaMailSender mailSender) {
List<User> toRecipients = new ArrayList<>();

for (User recipient : primaryRecipients) {
if (!recipient.isNotificationsEnabled()) {
continue;
}
toRecipients.add(recipient);
}

for (User recipient : toRecipients) {
try {
MimeMessage message = mailSender.createMimeMessage();

message.setFrom("ThesisManagement <" + config.getSender().getAddress() + ">");
message.setSender(config.getSender());

message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient.getEmail()));

Context templateContext = new Context();
templateContext.setVariables(this.variables);
templateContext.setVariable("recipient", UserInfoDTO.fromUser(recipient));

message.setSubject(subject);

Multipart messageContent = new MimeMultipart();

BodyPart messageBody = new MimeBodyPart();
messageBody.setContent(config.getTemplateEngine().process(template, templateContext), "text/html; charset=utf-8");
messageContent.addBodyPart(messageBody);

message.setContent(messageContent);

if (config.isEnabled()) {
mailSender.send(message);
} else {
log.info("Sending Mail (postfix disabled)\n{}", messageBody.getContent());
}
} catch (Exception exception) {
log.warn("Failed to send email", exception);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package de.tum.in.www1.hephaestus.notification;

import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;

import java.util.*;

@Component
public class MailConfig {

private final Boolean enabled;

@Getter
private final String clientHost;

@Getter
private final InternetAddress sender;

@Getter
private final String signature;

@Getter
private final TemplateEngine templateEngine;

@Autowired
public MailConfig(
@Value("${hephaestus.mail.sender}") InternetAddress sender,
@Value("${hephaestus.mail.enabled}") boolean enabled,
@Value("${hephaestus.mail.signature}") String mailSignature,
@Value("${hephaestus.client.host}") String clientHost,
TemplateEngine templateEngine
) {
this.enabled = enabled;
this.sender = sender;
this.signature = mailSignature;
this.clientHost = clientHost;
this.templateEngine = templateEngine;
}

public boolean isEnabled() {
return enabled;
}

public record MailConfigDto(
String signature,
String clientHost
) {}

public MailConfigDto getConfigDto() {
return new MailConfigDto(
Objects.requireNonNullElse(signature, ""),
Objects.requireNonNullElse(getClientHost(), "")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package de.tum.in.www1.hephaestus.notification;

import de.tum.in.www1.hephaestus.gitprovider.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MailService {

private final JavaMailSender javaMailSender;

private final MailConfig mailConfig;

@Autowired
public MailService(JavaMailSender javaMailSender, MailConfig mailConfig) {
this.javaMailSender = javaMailSender;
this.mailConfig = mailConfig;
}

public void sendBadPracticesDetectedEmail(User user, List<String> badPractices) {
MailBuilder mailBuilder = new MailBuilder(mailConfig, "Bad Practices Detected", "bad-practices-detected");
mailBuilder
.addPrimaryRecipient(user)
.send(javaMailSender);
}
}
21 changes: 21 additions & 0 deletions server/application-server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ spring:
jwt:
issuer-uri: http://localhost:8081/realms/hephaestus

thymeleaf:
prefix: ${MAIL_TEMPLATE_FOLDER:classpath:/mail-templates/}
suffix: .html

mail:
host: ${POSTFIX_HOST:localhost}
port: ${POSTFIX_PORT:25}
username: ${POSTFIX_USERNAME:}
password: ${POSTFIX_PASSWORD:}
properties:
mail:
transport:
protocol: smtp
smtp:
starttls:
enable: true

springdoc:
default-produces-media-type: application/json

Expand All @@ -55,6 +72,10 @@ hephaestus:
contributors:
# Eviction rate in milliseconds (1 hour)
evict-rate: 3600000
mail:
enabled: ${MAIL_ENABLED:true}
sender: ${MAIL_SENDER:[email protected]}
signature: ${MAIL_SIGNATURE:}

keycloak:
url: http://localhost:8081
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p th:inline="text">
Bad practice detected.
</p>

0 comments on commit 3241239

Please sign in to comment.