feat(mail-service): initialize Mail-Service and integrate online nomination workflow
- Created `MailServiceApplication` with Spring Boot setup. - Added `MailPollingService` for IMAP polling, `TurnierNr` extraction, and auto-reply functionality. - Implemented structured email sending for online nominations via `OnlineNennungFormular`. - Updated frontend with `Erfolgsscreen` for nomination confirmation and fallback handling. - Added build configurations for Mail-Service and frontend nomination module. - Documented phase-based roadmap for Online-Nennung and Mail-Service rollout.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.spring.boot)
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinSpring)
|
||||
}
|
||||
|
||||
springBoot {
|
||||
mainClass.set("at.mocode.mail.service.MailServiceApplicationKt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Interne Module
|
||||
implementation(projects.platform.platformDependencies)
|
||||
implementation(projects.core.coreUtils)
|
||||
implementation(projects.core.coreDomain)
|
||||
|
||||
// Spring Boot Starters
|
||||
implementation(libs.spring.boot.starter.web)
|
||||
implementation(libs.spring.boot.starter.validation)
|
||||
implementation(libs.spring.boot.starter.actuator)
|
||||
implementation(libs.spring.boot.starter.mail)
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
implementation(libs.spring.cloud.starter.consul.discovery)
|
||||
implementation(libs.micrometer.tracing.bridge.brave)
|
||||
implementation(libs.zipkin.reporter.brave)
|
||||
implementation(libs.zipkin.sender.okhttp3)
|
||||
|
||||
// Testing
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.spring.boot.starter.test)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package at.mocode.mail.service
|
||||
|
||||
import jakarta.mail.Flags
|
||||
import jakarta.mail.Folder
|
||||
import jakarta.mail.Session
|
||||
import jakarta.mail.internet.InternetAddress
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.mail.SimpleMailMessage
|
||||
import org.springframework.mail.javamail.JavaMailSender
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.*
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
class MailPollingService(
|
||||
private val mailSender: JavaMailSender,
|
||||
@Value("\${spring.mail.host}") private val imapHost: String,
|
||||
@Value("\${spring.mail.port}") private val imapPort: Int,
|
||||
@Value("\${spring.mail.username}") private val username: String,
|
||||
@Value("\${spring.mail.password}") private val password: String
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(MailPollingService::class.java)
|
||||
|
||||
@Scheduled(fixedDelay = 60000) // Alle 60 Sekunden pollen
|
||||
fun pollMails() {
|
||||
if (password.isBlank()) {
|
||||
logger.warn("Mail-Passwort nicht gesetzt. Polling übersprungen.")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val props = Properties()
|
||||
props["mail.store.protocol"] = "imaps"
|
||||
props["mail.imaps.host"] = imapHost
|
||||
props["mail.imaps.port"] = imapPort.toString()
|
||||
props["mail.imaps.ssl.enable"] = "true"
|
||||
|
||||
val session = Session.getInstance(props)
|
||||
val store = session.getStore("imaps")
|
||||
store.connect(imapHost, username, password)
|
||||
|
||||
val inbox = store.getFolder("INBOX")
|
||||
inbox.open(Folder.READ_WRITE)
|
||||
|
||||
// Nur ungelesene Nachrichten
|
||||
val messages = inbox.getMessages()
|
||||
logger.info("Gefundene Nachrichten in INBOX: ${messages.size}")
|
||||
|
||||
for (message in messages) {
|
||||
if (!message.isSet(Flags.Flag.SEEN)) {
|
||||
val recipients = message.getRecipients(jakarta.mail.Message.RecipientType.TO)
|
||||
val toAddress = (recipients?.firstOrNull() as? InternetAddress)?.address ?: ""
|
||||
|
||||
logger.info("Neue Mail empfangen von: ${message.from?.firstOrNull()} an: $toAddress")
|
||||
|
||||
// Turnier-Nr extrahieren: meldestelle-26128@mo-code.at
|
||||
val turnierNr = extractTurnierNr(toAddress)
|
||||
|
||||
if (turnierNr != null) {
|
||||
logger.info("Nennung für Turnier $turnierNr erkannt.")
|
||||
// TODO: Payload parsen und in DB speichern (Tenant-Routing)
|
||||
|
||||
// Auto-Reply senden
|
||||
sendAutoReply(message.from?.firstOrNull()?.toString() ?: "", turnierNr)
|
||||
|
||||
// Mail als gelesen markieren
|
||||
message.setFlag(Flags.Flag.SEEN, true)
|
||||
} else {
|
||||
logger.warn("Keine Turnier-Nr in Adresse $toAddress gefunden. Mail wird ignoriert.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inbox.close(false)
|
||||
store.close()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Fehler beim Mail-Polling: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractTurnierNr(address: String): String? {
|
||||
val regex = Regex("meldestelle-(\\d+)@.*")
|
||||
val match = regex.find(address)
|
||||
return match?.groupValues?.get(1)
|
||||
}
|
||||
|
||||
private fun sendAutoReply(to: String, turnierNr: String) {
|
||||
try {
|
||||
val message = SimpleMailMessage()
|
||||
message.from = username
|
||||
message.setTo(to)
|
||||
message.subject = "Eingangsbestätigung: Ihre Nennung für Turnier $turnierNr"
|
||||
message.text = """
|
||||
Sehr geehrte Damen und Herren,
|
||||
|
||||
vielen Dank für Ihre Online-Nennung für das Turnier $turnierNr.
|
||||
|
||||
Ihre Nennung ist erfolgreich in unserem System eingegangen und wird nun von der Meldestelle geprüft.
|
||||
Sobald die Nennung final verarbeitet wurde, erhalten Sie eine weitere Bestätigung.
|
||||
|
||||
Mit freundlichen Grüßen,
|
||||
Ihre Turniermeldestelle
|
||||
""".trimIndent()
|
||||
|
||||
mailSender.send(message)
|
||||
logger.info("Auto-Reply an $to für Turnier $turnierNr gesendet.")
|
||||
} catch (e: Exception) {
|
||||
logger.error("Fehler beim Senden des Auto-Replies: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package at.mocode.mail.service
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class MailServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<MailServiceApplication>(*args)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
spring:
|
||||
application:
|
||||
name: mail-service
|
||||
mail:
|
||||
host: ${MAIL_HOST:imap.world4you.com}
|
||||
port: ${MAIL_PORT:993}
|
||||
username: ${MAIL_USERNAME:online-nennen@mo-code.at}
|
||||
password: ${MAIL_PASSWORD:}
|
||||
properties:
|
||||
mail:
|
||||
store:
|
||||
protocol: imaps
|
||||
imaps:
|
||||
host: ${MAIL_HOST:imap.world4you.com}
|
||||
port: ${MAIL_PORT:993}
|
||||
ssl:
|
||||
enable: true
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
host-smtp: ${SMTP_HOST:smtp.world4you.com}
|
||||
port-smtp: ${SMTP_PORT:587}
|
||||
|
||||
server:
|
||||
port: 8085
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "health,info,prometheus"
|
||||
Reference in New Issue
Block a user