diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b1e98a9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +- **Start development server**: `./mvnw` (runs Spring Boot with Vaadin dev mode) +- **Build for production**: `./mvnw -Pproduction package` +- **Clean build**: `./mvnw clean compile` + +## Architecture Overview + +This is a **Vaadin Spring Boot** application for job/task management with real-time mobile app communication via MQTT. The system manages logistics jobs with tasks that mobile app users complete. + +### Core Architecture Layers + +**Frontend**: Vaadin Flow views (server-side rendered) +- `src/main/java/de/assecutor/votianlt/pages/view/` - Main UI views +- `src/main/java/de/assecutor/votianlt/pages/base/ui/` - Shared UI components + +**Backend Services**: +- `src/main/java/de/assecutor/votianlt/service/` - Business logic +- `src/main/java/de/assecutor/votianlt/controller/` - MQTT message handling +- `src/main/java/de/assecutor/votianlt/repository/` - MongoDB data access + +**Models**: +- `src/main/java/de/assecutor/votianlt/model/` - Domain entities +- Task hierarchy: `BaseTask` with subtypes (`PhotoTask`, `BarcodeTask`, `SignatureTask`, etc.) + +### Key Architectural Patterns + +**Job-Task Relationship**: Jobs contain multiple ordered tasks. Tasks have completion states and can store completion data (photos, barcodes, signatures). + +**User Hierarchy**: +- `User` - Web interface users (job managers) +- `AppUser` - Mobile app users (task executors) +- `AppUser.owner` field links to `User` for notifications + +**MQTT Communication**: +- `MqttV5ClientManager` handles bidirectional communication with mobile apps +- `MessageController` routes inbound MQTT messages and processes task completions +- Topics: `/server/{clientId}/task_completed`, `/server/login`, etc. + +**History Tracking**: `JobHistoryService` logs all job/task changes with detailed audit trail displayed in `JobHistoryView`. + +**Email Notifications**: `EmailService` sends notifications for job creation, task completion, and job completion using Spring Mail with SMTP. + +## Data Storage + +**MongoDB Collections**: +- `jobs` - Main job entities with status tracking +- `tasks` - Polymorphic task storage (discriminated by `taskType`) +- `job_history` - Audit trail for all job changes +- `photos`, `barcodes`, `signatures` - Task completion data +- `users` - Web interface users +- `app_user` - Mobile app users +- `cargo_item` - Job cargo/delivery items + +## Configuration + +**Database**: MongoDB at `192.168.180.25:27017/votianlt` +**MQTT**: HiveMQ client connects to `mqtt-2.assecutor.de` with credentials `app`/`apppwd` +**Email**: SMTP via `mailhub.assecutor.org:587` using Spring Boot mail auto-configuration + +## Development Environment + +**Java 21** with **Spring Boot 3.4.3** and **Vaadin 24.7.0** +**Security**: Spring Security with role-based access (`USER` role required) +**Profiles**: `production` profile available for optimized builds + +## Key Integration Points + +When adding new task types: +1. Extend `BaseTask` and add to `@JsonSubTypes` +2. Add completion logic in `MessageController.handleTaskCompleted()` +3. Update `JobHistoryView` for task-specific previews if needed + +When modifying job status flow: +1. Update `JobStatus` enum +2. Modify `EmailService.updateJobStatusToCompleted()` logic +3. Consider email notification templates + +MQTT message routing follows pattern: extract `taskType` from payload, route to appropriate processor method in `MessageController`. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 61256ac..b2c9c1a 100644 --- a/pom.xml +++ b/pom.xml @@ -103,10 +103,10 @@ openpdf 1.3.30 + - com.sun.mail - jakarta.mail - 2.0.1 + org.springframework.boot + spring-boot-starter-mail diff --git a/src/main/bundles/prod.bundle b/src/main/bundles/prod.bundle index 01d0be8..26f8738 100644 Binary files a/src/main/bundles/prod.bundle and b/src/main/bundles/prod.bundle differ diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageController.java b/src/main/java/de/assecutor/votianlt/controller/MessageController.java index d766b10..eb4aec2 100644 --- a/src/main/java/de/assecutor/votianlt/controller/MessageController.java +++ b/src/main/java/de/assecutor/votianlt/controller/MessageController.java @@ -19,6 +19,7 @@ import de.assecutor.votianlt.model.Photo; import de.assecutor.votianlt.model.Barcode; import de.assecutor.votianlt.model.Signature; import de.assecutor.votianlt.service.JobHistoryService; +import de.assecutor.votianlt.service.EmailService; import lombok.extern.slf4j.Slf4j; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -57,8 +58,9 @@ public class MessageController { private final BarcodeRepository barcodeRepository; private final SignatureRepository signatureRepository; private final JobHistoryService jobHistoryService; + private final EmailService emailService; - public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository, JobHistoryService jobHistoryService) { + public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository, JobHistoryService jobHistoryService, EmailService emailService) { this.mqttPublisher = mqttPublisher; this.appUserRepository = appUserRepository; this.appUserService = appUserService; @@ -69,6 +71,7 @@ public class MessageController { this.barcodeRepository = barcodeRepository; this.signatureRepository = signatureRepository; this.jobHistoryService = jobHistoryService; + this.emailService = emailService; } /** @@ -427,6 +430,20 @@ public class MessageController { log.warn("Failed to log task completion history for task {}: {}", taskIdStr, e.getMessage()); } + // Send email notification for task completion + try { + ObjectId jobId = new ObjectId(task.getJobIdAsString()); + String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown"; + String completedBy = task.getCompletedBy(); + + emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy); + + // Check if this was the last task and send job completion notification + emailService.checkAndSendJobCompletionNotification(jobId, completedBy); + } catch (Exception e) { + log.warn("Failed to send task completion email notification for task {}: {}", taskIdStr, e.getMessage()); + } + log.info("Task marked completed. taskId={}, completedBy={}, extraData={}", taskIdStr, task.getCompletedBy(), extraDataSummary); } catch (IllegalArgumentException ex) { diff --git a/src/main/java/de/assecutor/votianlt/pages/service/AddJobService.java b/src/main/java/de/assecutor/votianlt/pages/service/AddJobService.java index c81f9a0..73d60f7 100644 --- a/src/main/java/de/assecutor/votianlt/pages/service/AddJobService.java +++ b/src/main/java/de/assecutor/votianlt/pages/service/AddJobService.java @@ -9,6 +9,7 @@ import de.assecutor.votianlt.repository.TaskRepository; import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.repository.CargoItemRepository; import de.assecutor.votianlt.service.JobHistoryService; +import de.assecutor.votianlt.service.EmailService; import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,6 +31,7 @@ public class AddJobService { private final TaskRepository taskRepository; private final SecurityService securityService; private final JobHistoryService jobHistoryService; + private final EmailService emailService; /** * Speichert einen neuen Auftrag samt CargoItems und Tasks * @param job der Auftrag @@ -107,6 +109,13 @@ public class AddJobService { log.warn("Failed to log job creation history for job {}: {}", savedJob.getIdAsString(), e.getMessage()); } + // E-Mail-Benachrichtigung für Job-Erstellung senden + try { + emailService.sendJobCreationNotification(savedJob.getId(), savedJob.getCreatedBy()); + } catch (Exception e) { + log.warn("Failed to send job creation email notification for job {}: {}", savedJob.getIdAsString(), e.getMessage()); + } + log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber()); return savedJob; diff --git a/src/main/java/de/assecutor/votianlt/repository/AppUserRepository.java b/src/main/java/de/assecutor/votianlt/repository/AppUserRepository.java index b884d76..99ea269 100644 --- a/src/main/java/de/assecutor/votianlt/repository/AppUserRepository.java +++ b/src/main/java/de/assecutor/votianlt/repository/AppUserRepository.java @@ -17,7 +17,10 @@ public interface AppUserRepository extends MongoRepository { AppUser findByEmail(String email); AppUser findByPasswordCode(String passwordCode); - + + // Find AppUser by appCode for task completion notifications + java.util.Optional findByAppCode(String appCode); + // Custom query methods can be added here if needed // List findByBezeichnung(String bezeichnung); } diff --git a/src/main/java/de/assecutor/votianlt/repository/UserRepository.java b/src/main/java/de/assecutor/votianlt/repository/UserRepository.java index 47488e4..b8a0324 100644 --- a/src/main/java/de/assecutor/votianlt/repository/UserRepository.java +++ b/src/main/java/de/assecutor/votianlt/repository/UserRepository.java @@ -1,13 +1,14 @@ package de.assecutor.votianlt.repository; import de.assecutor.votianlt.model.User; +import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface UserRepository extends MongoRepository { +public interface UserRepository extends MongoRepository { Optional findByEmail(String email); diff --git a/src/main/java/de/assecutor/votianlt/service/EmailService.java b/src/main/java/de/assecutor/votianlt/service/EmailService.java new file mode 100644 index 0000000..309c97c --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/service/EmailService.java @@ -0,0 +1,408 @@ +package de.assecutor.votianlt.service; + +import de.assecutor.votianlt.model.AppUser; +import de.assecutor.votianlt.model.Job; +import de.assecutor.votianlt.model.JobStatus; +import de.assecutor.votianlt.model.User; +import de.assecutor.votianlt.repository.AppUserRepository; +import de.assecutor.votianlt.repository.JobRepository; +import de.assecutor.votianlt.repository.TaskRepository; +import de.assecutor.votianlt.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailService { + + private final AppUserRepository appUserRepository; + private final UserRepository userRepository; + private final JobRepository jobRepository; + private final TaskRepository taskRepository; + private final JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String smtpUsername; + + public void sendTaskCompletionNotification(ObjectId jobId, String taskType, String taskId, String completedBy) { + try { + // Load job to get context information + Optional jobOpt = jobRepository.findById(jobId); + if (jobOpt.isEmpty()) { + log.warn("Job not found for task completion notification: {}", jobId); + return; + } + Job job = jobOpt.get(); + + // Find the app user who completed the task + Optional appUserOpt = appUserRepository.findByAppCode(completedBy); + if (appUserOpt.isEmpty()) { + log.warn("AppUser not found for task completion notification: {}", completedBy); + return; + } + AppUser appUser = appUserOpt.get(); + + // Find the owner (User) of the AppUser + if (appUser.getOwner() == null) { + log.warn("AppUser has no owner for task completion notification: {}", completedBy); + return; + } + + Optional userOpt = userRepository.findById(appUser.getOwner()); + if (userOpt.isEmpty()) { + log.warn("Owner user not found for task completion notification: {}", appUser.getOwner()); + return; + } + User user = userOpt.get(); + + // Check if user has email + if (user.getEmail() == null || user.getEmail().isBlank()) { + log.warn("User has no email address for task completion notification: {}", user.getId()); + return; + } + + // Send email + sendEmail(user, job, taskType, taskId, appUser); + log.info("Task completion notification sent to {} for job {} task {}", user.getEmail(), job.getJobNumber(), taskId); + + } catch (Exception e) { + log.error("Failed to send task completion notification for job {} task {}: {}", jobId, taskId, e.getMessage(), e); + } + } + + private void sendEmail(User user, Job job, String taskType, String taskId, AppUser appUser) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(smtpUsername); + message.setTo(user.getEmail()); + message.setSubject("Aufgabe abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId())); + + String fullName = buildFullName(user); + String appUserName = buildAppUserName(appUser); + String taskTypeName = getTaskTypeDisplayName(taskType); + + StringBuilder body = new StringBuilder(); + body.append("Hallo ").append(fullName).append(",\n\n"); + body.append("eine Aufgabe wurde von ").append(appUserName).append(" abgeschlossen:\n\n"); + body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n"); + if (job.getDeliveryCompany() != null) { + body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n"); + } + body.append("Aufgabe: ").append(taskTypeName).append("\n"); + body.append("Abgeschlossen von: ").append(appUserName).append("\n\n"); + + if (job.getPickupCity() != null || job.getDeliveryCity() != null) { + body.append("Route: "); + if (job.getPickupCity() != null) { + body.append(job.getPickupCity()); + } + if (job.getPickupCity() != null && job.getDeliveryCity() != null) { + body.append(" → "); + } + if (job.getDeliveryCity() != null) { + body.append(job.getDeliveryCity()); + } + body.append("\n\n"); + } + + body.append("Mit freundlichen Grüßen,\n"); + body.append("Ihr Votianlt-System"); + + message.setText(body.toString()); + mailSender.send(message); + } + + private String buildFullName(User user) { + StringBuilder name = new StringBuilder(); + if (user.getTitle() != null && !user.getTitle().isBlank()) { + name.append(user.getTitle()).append(" "); + } + if (user.getFirstname() != null && !user.getFirstname().isBlank()) { + name.append(user.getFirstname()).append(" "); + } + if (user.getName() != null && !user.getName().isBlank()) { + name.append(user.getName()); + } + String fullName = name.toString().trim(); + return fullName.isEmpty() ? "Benutzer" : fullName; + } + + private String buildAppUserName(AppUser appUser) { + StringBuilder name = new StringBuilder(); + if (appUser.getVorname() != null && !appUser.getVorname().isBlank()) { + name.append(appUser.getVorname()).append(" "); + } + if (appUser.getNachname() != null && !appUser.getNachname().isBlank()) { + name.append(appUser.getNachname()); + } + String fullName = name.toString().trim(); + if (fullName.isEmpty() && appUser.getBezeichnung() != null && !appUser.getBezeichnung().isBlank()) { + return appUser.getBezeichnung(); + } + return fullName.isEmpty() ? "App-Benutzer" : fullName; + } + + private String getTaskTypeDisplayName(String taskType) { + if (taskType == null) return "Unbekannte Aufgabe"; + + return switch (taskType.toUpperCase()) { + case "PHOTO" -> "Foto-Aufgabe"; + case "SIGNATURE" -> "Unterschrift"; + case "BARCODE" -> "Barcode scannen"; + case "CONFIRMATION" -> "Bestätigung"; + case "TODO_LIST" -> "Checkliste"; + default -> taskType; + }; + } + + public void checkAndSendJobCompletionNotification(ObjectId jobId, String completedBy) { + try { + // Check if all tasks for this job are completed + var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId); + if (allTasks.isEmpty()) { + log.debug("No tasks found for job {}", jobId); + return; + } + + boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted()); + + if (allCompleted) { + log.info("All tasks completed for job {}, updating job status and sending completion notification", jobId); + + // Update job status to COMPLETED + updateJobStatusToCompleted(jobId); + + // Send completion notification + sendJobCompletionNotification(jobId, completedBy); + } else { + long completedCount = allTasks.stream().mapToLong(task -> task.isCompleted() ? 1L : 0L).sum(); + log.debug("Job {} not yet complete: {}/{} tasks completed", jobId, completedCount, allTasks.size()); + } + } catch (Exception e) { + log.error("Failed to check job completion for job {}: {}", jobId, e.getMessage(), e); + } + } + + private void sendJobCompletionNotification(ObjectId jobId, String completedBy) { + try { + // Load job + Optional jobOpt = jobRepository.findById(jobId); + if (jobOpt.isEmpty()) { + log.warn("Job not found for completion notification: {}", jobId); + return; + } + Job job = jobOpt.get(); + + // Find the app user who completed the last task + Optional appUserOpt = appUserRepository.findByAppCode(completedBy); + if (appUserOpt.isEmpty()) { + log.warn("AppUser not found for job completion notification: {}", completedBy); + return; + } + AppUser appUser = appUserOpt.get(); + + // Find the owner (User) of the AppUser + if (appUser.getOwner() == null) { + log.warn("AppUser has no owner for job completion notification: {}", completedBy); + return; + } + + Optional userOpt = userRepository.findById(appUser.getOwner()); + if (userOpt.isEmpty()) { + log.warn("Owner user not found for job completion notification: {}", appUser.getOwner()); + return; + } + User user = userOpt.get(); + + // Check if user has email + if (user.getEmail() == null || user.getEmail().isBlank()) { + log.warn("User has no email address for job completion notification: {}", user.getId()); + return; + } + + // Send job completion email + sendJobCompletionEmail(user, job, appUser); + log.info("Job completion notification sent to {} for job {}", user.getEmail(), job.getJobNumber()); + + } catch (Exception e) { + log.error("Failed to send job completion notification for job {}: {}", jobId, e.getMessage(), e); + } + } + + private void sendJobCompletionEmail(User user, Job job, AppUser appUser) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(smtpUsername); + message.setTo(user.getEmail()); + message.setSubject("Job abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId())); + + String fullName = buildFullName(user); + String appUserName = buildAppUserName(appUser); + + // Count completed tasks + var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId()); + int taskCount = allTasks.size(); + + StringBuilder body = new StringBuilder(); + body.append("Hallo ").append(fullName).append(",\n\n"); + body.append("alle Aufgaben für den folgenden Job wurden erfolgreich abgeschlossen:\n\n"); + body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n"); + if (job.getDeliveryCompany() != null) { + body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n"); + } + + if (job.getPickupCity() != null || job.getDeliveryCity() != null) { + body.append("Route: "); + if (job.getPickupCity() != null) { + body.append(job.getPickupCity()); + } + if (job.getPickupCity() != null && job.getDeliveryCity() != null) { + body.append(" → "); + } + if (job.getDeliveryCity() != null) { + body.append(job.getDeliveryCity()); + } + body.append("\n"); + } + + body.append("Anzahl erledigter Aufgaben: ").append(taskCount).append("\n"); + body.append("Abgeschlossen von: ").append(appUserName).append("\n\n"); + + body.append("Der Job ist nun vollständig erledigt und kann weiterverarbeitet werden.\n\n"); + body.append("Mit freundlichen Grüßen,\n"); + body.append("Ihr Votianlt-System"); + + message.setText(body.toString()); + mailSender.send(message); + } + + private void updateJobStatusToCompleted(ObjectId jobId) { + try { + Optional jobOpt = jobRepository.findById(jobId); + if (jobOpt.isEmpty()) { + log.warn("Job not found for status update: {}", jobId); + return; + } + + Job job = jobOpt.get(); + JobStatus oldStatus = job.getStatus(); + + // Only update if not already completed + if (job.getStatus() != JobStatus.COMPLETED) { + job.setStatus(JobStatus.COMPLETED); + job.setUpdatedAt(java.time.LocalDateTime.now()); + jobRepository.save(job); + + log.info("Job status updated from {} to COMPLETED for job {}", + oldStatus != null ? oldStatus : "null", job.getJobNumber()); + } else { + log.debug("Job {} already has COMPLETED status", job.getJobNumber()); + } + } catch (Exception e) { + log.error("Failed to update job status to COMPLETED for job {}: {}", jobId, e.getMessage(), e); + } + } + + public void sendJobCreationNotification(ObjectId jobId, String createdBy) { + try { + // Load job + Optional jobOpt = jobRepository.findById(jobId); + if (jobOpt.isEmpty()) { + log.warn("Job not found for creation notification: {}", jobId); + return; + } + Job job = jobOpt.get(); + + // Find the user who created the job (using createdBy as User ID string) + ObjectId createdByUserId; + try { + createdByUserId = new ObjectId(createdBy); + } catch (IllegalArgumentException e) { + log.warn("Invalid createdBy format for job creation notification: {}", createdBy); + return; + } + + Optional userOpt = userRepository.findById(createdByUserId); + if (userOpt.isEmpty()) { + log.warn("User not found for job creation notification: {}", createdBy); + return; + } + User user = userOpt.get(); + + // Check if user has email + if (user.getEmail() == null || user.getEmail().isBlank()) { + log.warn("User has no email address for job creation notification: {}", user.getId()); + return; + } + + // Send job creation email + sendJobCreationEmail(user, job); + log.info("Job creation notification sent to {} for job {}", user.getEmail(), job.getJobNumber()); + + } catch (Exception e) { + log.error("Failed to send job creation notification for job {}: {}", jobId, e.getMessage(), e); + } + } + + private void sendJobCreationEmail(User user, Job job) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(smtpUsername); + message.setTo(user.getEmail()); + message.setSubject("Neuer Job erstellt - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId())); + + String fullName = buildFullName(user); + + // Count tasks for this job + var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId()); + int taskCount = allTasks.size(); + + StringBuilder body = new StringBuilder(); + body.append("Hallo ").append(fullName).append(",\n\n"); + body.append("ein neuer Job wurde erfolgreich erstellt:\n\n"); + body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n"); + + if (job.getDeliveryCompany() != null) { + body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n"); + } + + if (job.getPickupCity() != null || job.getDeliveryCity() != null) { + body.append("Route: "); + if (job.getPickupCity() != null) { + body.append(job.getPickupCity()); + } + if (job.getPickupCity() != null && job.getDeliveryCity() != null) { + body.append(" → "); + } + if (job.getDeliveryCity() != null) { + body.append(job.getDeliveryCity()); + } + body.append("\n"); + } + + if (taskCount > 0) { + body.append("Anzahl Aufgaben: ").append(taskCount).append("\n"); + } + + body.append("Status: ").append(job.getStatus() != null ? job.getStatus().getDisplayName() : "Unbekannt").append("\n"); + + if (job.getRemark() != null && !job.getRemark().isBlank()) { + body.append("Bemerkung: ").append(job.getRemark()).append("\n"); + } + + body.append("Erstellt am: ").append(job.getCreatedAt() != null ? + job.getCreatedAt().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")) : "Unbekannt").append("\n\n"); + + body.append("Der Job ist nun im System verfügbar und kann bearbeitet werden.\n\n"); + body.append("Mit freundlichen Grüßen,\n"); + body.append("Ihr Votianlt-System"); + + message.setText(body.toString()); + mailSender.send(message); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f9858f0..b022eb6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,11 +22,13 @@ spring.data.mongodb.socket-timeout=30000 spring.data.mongodb.connect-timeout=10000 spring.data.mongodb.server-selection-timeout=5000 -# Mail Configuration -mail.smtp.username=no-reply@appcreation.de -mail.smtp.password=SV1705CA!noreply -mail.smtp.host=smtp.ionos.de -mail.smtp.port=587 +# Mail Configuration (Spring Boot Standard) +spring.mail.host=mailhub.assecutor.org +spring.mail.port=587 +spring.mail.username=noreply@assecutor.org +spring.mail.password=OStRIL,_,31 +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true # HTTP request size limits for large payloads server.max-http-request-header-size=8MB