diff --git a/ticketing-eventing/README.md b/ticketing-eventing/README.md index 149c45bb..9ce35b48 100644 --- a/ticketing-eventing/README.md +++ b/ticketing-eventing/README.md @@ -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 @@ -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" } ``` diff --git a/ticketing-eventing/eventing-service/src/main/resources/application-local.yml b/ticketing-eventing/eventing-service/src/main/resources/application-local.yml index 046f0de8..8be36479 100644 --- a/ticketing-eventing/eventing-service/src/main/resources/application-local.yml +++ b/ticketing-eventing/eventing-service/src/main/resources/application-local.yml @@ -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" diff --git a/ticketing-eventing/mail-handler-service/pom.xml b/ticketing-eventing/mail-handler-service/pom.xml index b16871a0..59e4ac58 100644 --- a/ticketing-eventing/mail-handler-service/pom.xml +++ b/ticketing-eventing/mail-handler-service/pom.xml @@ -43,7 +43,7 @@ 2025.0.0 - 0.1.0 + 0.2.0 8.1 diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailAdapter.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailAdapter.java index 2062213a..ff7d4c5e 100644 --- a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailAdapter.java +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailAdapter.java @@ -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 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; + // } + // } + } diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailMessage.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailMessage.java new file mode 100644 index 00000000..9703ddb9 --- /dev/null +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/adapter/out/mail/MailMessage.java @@ -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 attachments; +} diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/port/out/SendMailOutPort.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/port/out/SendMailOutPort.java index 9ef8a4d5..65b60f1d 100644 --- a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/port/out/SendMailOutPort.java +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/port/out/SendMailOutPort.java @@ -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); } diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/usecase/EventHandlingUseCase.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/usecase/EventHandlingUseCase.java index 8ce294f3..495dbcc3 100644 --- a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/usecase/EventHandlingUseCase.java +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/application/usecase/EventHandlingUseCase.java @@ -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; @@ -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; @@ -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 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 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 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
Body: %s".formatted(i.getSubject(), i.getBody())) - // build body - .collect(Collectors.joining("
")); + 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 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 attachments = buildAttachments(article); + sendMailOutport.sendMail(new MailMessage(recipient, subject, body, attachments)); + } + private String buildSubject(final TicketInternal ticket, final Map 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 = TO_POSTBOX_HIGH.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 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 getParsedForm(final TicketInternal ticket) { @@ -109,4 +162,12 @@ private Map 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); + } } diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/config/MailHandlerProperties.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/config/MailHandlerProperties.java index db90aed9..f581bf32 100644 --- a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/config/MailHandlerProperties.java +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/config/MailHandlerProperties.java @@ -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 relevantTicketTypes; } diff --git a/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/exceptions/NoValidArticleException.java b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/exceptions/NoValidArticleException.java new file mode 100644 index 00000000..405dea25 --- /dev/null +++ b/ticketing-eventing/mail-handler-service/src/main/java/de/muenchen/oss/dbs/ticketing/eventing/mailhandler/exceptions/NoValidArticleException.java @@ -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); + } +} diff --git a/ticketing-eventing/mail-handler-service/src/main/resources/application-local.yml b/ticketing-eventing/mail-handler-service/src/main/resources/application-local.yml index 22cc2f0a..57a5df93 100644 --- a/ticketing-eventing/mail-handler-service/src/main/resources/application-local.yml +++ b/ticketing-eventing/mail-handler-service/src/main/resources/application-local.yml @@ -23,13 +23,13 @@ spring: client: provider: zammad: - issuer-uri: TODO + issuer-uri: https://ssodev.muenchen.de/auth/realms/KM81 registration: zammad: provider: zammad authorization-grant-type: client_credentials - client-id: TODO - client-secret: TODO + client-id: mpdz-ticketing-fk + client-secret: V8zmjkcqgXE9xIWhljjVT45Bxli2c02B dbs: eai: diff --git a/ticketing-eventing/mail-handler-service/src/main/resources/application.yml b/ticketing-eventing/mail-handler-service/src/main/resources/application.yml index 85fd19d4..957eac92 100644 --- a/ticketing-eventing/mail-handler-service/src/main/resources/application.yml +++ b/ticketing-eventing/mail-handler-service/src/main/resources/application.yml @@ -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"