Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ticketing-eventing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Open **Event-Service** via Swagger (http://localhost:8080/swagger-ui/index.html)
- User: zammad
- Password: password
- Method: POST `/api/event`
- X-Zammad-Trigger: `Trigger Name`
- X-Zammad-Trigger: `T2805_Event_Nachricht_In_Postkorb`
- X-Zammad-Delivery: `myID`
- Request Body:
```json
Expand All @@ -33,7 +33,7 @@ Open **Event-Service** via Swagger (http://localhost:8080/swagger-ui/index.html)
"status": "closed",
"status_id": "1",
"anliegenart": "technischer Bürgersupport",
"lhmExtId": "33caabe6-317c-4c2d-8bf7-6c36230599db"
"lhmextid": "33caabe6-317c-4c2d-8bf7-6c36230599db"
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ logging:
dbs:
eventing:
trigger-mapping:
- trigger-name: "Trigger Name"
- trigger-name: "T2800_Event_Statusaenderung"
action: "state_changed"
- trigger-name: "T2805_Event_Nachricht_In_Postkorb"
action: "send_to_postbox"
2 changes: 1 addition & 1 deletion ticketing-eventing/mail-handler-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

<!-- Core Frameworks -->
<spring-cloud-dependencies.version>2025.0.0</spring-cloud-dependencies.version> <!-- Must match the chosen Spring Boot version -->
<dbs-handler-core.version>0.1.0</dbs-handler-core.version>
<dbs-handler-core.version>0.2.0</dbs-handler-core.version>

<!-- Logging -->
<logstash-logback-encoder.version>8.1</logstash-logback-encoder.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
package de.muenchen.oss.dbs.ticketing.eventing.mailhandler.adapter.out.mail;

import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.application.port.out.SendMailOutPort;
import jakarta.activation.DataSource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.util.ByteArrayDataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class MailAdapter implements SendMailOutPort {
private final JavaMailSender mailSender;
private final MailProperties mailProperties;

@Override
public void sendMail(final String recipient, final String subject, final String body) {
public void sendMail(final MailMessage mailMessage) {
final MimeMessage mimeMessage = mailSender.createMimeMessage();
final MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, "utf-8");
try {
final MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
helper.setFrom(mailProperties.getFromAddress());
helper.setTo(recipient);
helper.setSubject(subject);
helper.setText(body, true);
helper.setTo(mailMessage.getRecipient());
helper.setSubject(mailMessage.getSubject());
helper.setText(mailMessage.getBody(), true);
for (final Map.Entry<String, InputStream> entry : mailMessage.getAttachments().entrySet()) {
//TODO is there a way to do this with streaming? The following line loads the attachment into RAM...
final DataSource dataSource = new ByteArrayDataSource(entry.getValue(), "application/octet-stream");
helper.addAttachment(entry.getKey(), dataSource);
//maybe like this?
//helper.addAttachment(e.getKey(), new InputStreamSourceImpl(e.getValue()));
}
mailSender.send(mimeMessage);
} catch (MessagingException e) {
} catch (final MessagingException | IOException e) {
throw new RuntimeException(e);
}
}

// private class InputStreamSourceImpl implements InputStreamSource {
// private final InputStream inputStream;
//
// public InputStreamSourceImpl(InputStream inputStream) {
// this.inputStream = inputStream;
// }
//
// @Override
// public InputStream getInputStream() {
// return inputStream;
// }
// }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.muenchen.oss.dbs.ticketing.eventing.mailhandler.adapter.out.mail;

import java.io.InputStream;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class MailMessage {
private String recipient;
private String subject;
private String body;
private Map<String, InputStream> attachments;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.muenchen.oss.dbs.ticketing.eventing.mailhandler.application.port.out;

import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.adapter.out.mail.MailMessage;

public interface SendMailOutPort {
void sendMail(String recipient, String subject, String body);
void sendMail(MailMessage mailMessage);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package de.muenchen.oss.dbs.ticketing.eventing.mailhandler.application.usecase;

import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import de.muenchen.oss.dbs.ticketing.eai.client.model.ArticleAttachment;
import de.muenchen.oss.dbs.ticketing.eai.client.model.ArticleInternal;
import de.muenchen.oss.dbs.ticketing.eai.client.model.TicketInternal;
import de.muenchen.oss.dbs.ticketing.eai.client.model.UpdateTicketDTO;
import de.muenchen.oss.dbs.ticketing.eventing.handlercore.application.port.in.EventHandlerInPort;
import de.muenchen.oss.dbs.ticketing.eventing.handlercore.application.port.out.TicketingOutPort;
import de.muenchen.oss.dbs.ticketing.eventing.handlercore.domain.model.Event;
import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.adapter.out.mail.MailMessage;
import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.application.port.out.SendMailOutPort;
import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.config.MailHandlerProperties;
import de.muenchen.oss.dbs.ticketing.eventing.mailhandler.exceptions.NoValidArticleException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,6 +27,12 @@
public class EventHandlingUseCase implements EventHandlerInPort {
private static final String INTERNAL_ATTACHMENTS_ARTICLE_TITLE = "Interner Artikel für interne Anhänge.";
private static final String FORM_ATTACHMENT_NAME = "XML-Daten.xml";
public static final String TICKETING_VERTRAUENSNIVEAU = "ticketingVertrauensniveau";
public static final String LEGACY_POSTKORB_HANDLE = "legacyPostkorbHandle";
public static final String ACCOUNT_SOURCE = "accountSource";

public static final String TO_POSTBOX_DEFAULT = "send";
public static final String TO_POSTBOX_HIGH = "send_high_authLevel";

private final XmlMapper xmlMapper = new XmlMapper();
private final MailHandlerProperties mailHandlerProperties;
Expand All @@ -36,52 +47,94 @@ public void handleEvent(final Event event) {
return;
}
log.info("Handling event");
// find event ticket
final TicketInternal ticket = ticketingOutPort.getTicket(event.ticket());
// get parsed form
final Map<String, Object> form = getParsedForm(ticket);
// send mail
final String subject = buildSubject(ticket, form);
final String body = buildBody(ticket);
sendMailOutport.sendMail(
mailHandlerProperties.getRecipient(), subject, body);
log.info("Handled event successfully");
try {
// find event ticket
final TicketInternal ticket = ticketingOutPort.getTicket(event.ticket());
//check if ticket should be sent
if (!isRelevantTicket(ticket)) {
log.debug("Ticket not relevant");
return;
}
// get parsed form
final Map<String, Object> form = getParsedForm(ticket);
//find relevant article
final ArticleInternal article = findRelevantArticle(ticket);
sendMail(ticket, form, article);

//reset flag sende_nachricht_nach_extern
resetTicket(ticket);

log.info("Event handled successfully");
} catch (NoValidArticleException e) {
log.error(e.getMessage());
log.error("Event NOT handled successfully");
}
}

private boolean isRelevantEvent(final Event event) {
log.debug("checking event: " + event);
return
// state was changed
mailHandlerProperties.getStateChangeAction().equals(event.action()) &&
// new state is closed
mailHandlerProperties.getClosedState().equals(event.status()) &&
// is relevant anliegen
// state was changed by trigger send-to-postbox
mailHandlerProperties.getTicketChangeAction().equals(event.action()) &&
// is relevant anliegen
mailHandlerProperties.getRelevantTicketTypes().contains(event.anliegenart()) &&
// user does have an lhmExtId (i.e. BayernID or BundID user)
event.lhmExtId() != null && !event.lhmExtId().isEmpty();
}

private String buildSubject(final TicketInternal ticket, final Map<String, Object> form) {
return "[%s;%s;%s;%s] Ihr Anliegen '%s' wurde abschließend bearbeitet"
.formatted(
form.get("legacyPostkorbHandle"),
form.get("accountSource"),
form.get("ticketingVertrauensniveau"),
"Dummy",
ticket.getTitle());
private boolean isRelevantTicket(final TicketInternal ticket) {
log.debug("Checking value of sende_nachricht_nach_extern: " + ticket.getSendeNachrichtNachExtern());
return TO_POSTBOX_DEFAULT.equals(ticket.getSendeNachrichtNachExtern()) || TO_POSTBOX_HIGH.equals(ticket.getSendeNachrichtNachExtern());
}

private String buildBody(final TicketInternal ticket) {
private ArticleInternal findRelevantArticle(final TicketInternal ticket) throws NoValidArticleException {
assert ticket.getArticles() != null;
return ticket.getArticles().stream()
// only public articles of type "web" or "note"
// find last public articles of type "note"
.filter(i -> Boolean.FALSE.equals(i.getInternal()) &&
(ArticleInternal.TypeEnum.WEB.equals(i.getType()) || ArticleInternal.TypeEnum.NOTE.equals(i.getType())))
// format single article
.map(i -> "Titel: %s<br>Body: %s".formatted(i.getSubject(), i.getBody()))
// build body
.collect(Collectors.joining("<hr>"));
ArticleInternal.TypeEnum.NOTE.equals(i.getType()))
.reduce((first, second) -> second)
.orElseThrow(() -> new NoValidArticleException("no valid article found in ticket " + ticket.getId()));
}

private void sendMail(final TicketInternal ticket, final Map<String, Object> form, final ArticleInternal article) {
final String recipient = mailHandlerProperties.getRecipient();
final String subject = buildSubject(ticket, form);
log.debug("Created subject: " + subject);
final String body = buildBody(article);
log.debug("Created body: " + body);
final Map<String, InputStream> attachments = buildAttachments(article);
sendMailOutport.sendMail(new MailMessage(recipient, subject, body, attachments));
}

private String buildSubject(final TicketInternal ticket, final Map<String, Object> form) {
final String authlevel;
if (form.get(TICKETING_VERTRAUENSNIVEAU) == null) {
log.error("no ticketingVertrauensniveau found in ticket " + ticket.getId() + " - setting level1");
authlevel = "level1";
} else {
authlevel = "3".equals(ticket.getSendeNachrichtNachExtern()) ? "level3" : String.valueOf(form.get(TICKETING_VERTRAUENSNIVEAU));
}

return "[%s;%s;%s;%s] Neue Nachricht zu Ihrem Anliegen '%s'"
.formatted(
form.get(LEGACY_POSTKORB_HANDLE),
form.get(ACCOUNT_SOURCE),
authlevel,
"Zammad-Eventing",
ticket.getTitle());
}

private String buildBody(final ArticleInternal article) {
return article.getBody();
}

private Map<String, InputStream> buildAttachments(final ArticleInternal article) {
if (article.getAttachments() == null) {
return new HashMap<>();
}
return article.getAttachments().stream().collect(Collectors.toMap(ArticleAttachment::getFilename,
a -> ticketingOutPort.getAttachmentContent(article.getTicketId(), article.getId(), a.getId())));
}

private Map<String, Object> getParsedForm(final TicketInternal ticket) {
Expand Down Expand Up @@ -109,4 +162,12 @@ private Map<String, Object> getParsedForm(final TicketInternal ticket) {
throw new RuntimeException(e);
}
}

private void resetTicket(final TicketInternal ticket) {
final UpdateTicketDTO updateTicketDTO = new UpdateTicketDTO();
updateTicketDTO.setId(ticket.getId());
updateTicketDTO.setDirektkennwort(ticket.getDirektkennwort());
updateTicketDTO.setSendeNachrichtNachExtern(null);
ticketingOutPort.updateTicket(updateTicketDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ public class MailHandlerProperties {
@NotBlank
private String recipient;
@NotBlank
private String stateChangeAction;
@NotBlank
private String closedState;
private String ticketChangeAction;
@NotNull
private List<String> relevantTicketTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.muenchen.oss.dbs.ticketing.eventing.mailhandler.exceptions;

import java.io.Serial;

public class NoValidArticleException extends Exception {
// Default access modifier is private
@Serial
private static final long serialVersionUID = 1L;

public NoValidArticleException(final String s) {
super(s);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ spring:
dbs:
eai:
client:
eai-base-url: http://localhost:8082/
eai-base-url: https://dbs-ticketing-eai-integration-dbs-ticketing-eai.apps.capk.muenchen.de/
# eai-base-url: http://localhost:39146
eventing:
mail:
recipient: [email protected]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ spring:
json:
trusted:
packages: '*'
codec:
max-in-memory-size: 5MB

dbs:
eventing:
mail:
smtp:
from-address: ${spring.mail.username}
state-change-action: "state_changed"
closed-state: "closed"
ticket-change-action: "send_to_postbox"

server:
shutdown: "graceful"
Expand Down
Loading