diff --git a/pom.xml b/pom.xml index 2566156..c993578 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter-webflux + + org.springframework.boot + spring-boot-starter-mail + diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/controllers/EmailController.java b/src/main/java/ar/utn/ba/ddsi/mailing/controllers/EmailController.java index fec8ee0..5eb555a 100644 --- a/src/main/java/ar/utn/ba/ddsi/mailing/controllers/EmailController.java +++ b/src/main/java/ar/utn/ba/ddsi/mailing/controllers/EmailController.java @@ -1,5 +1,7 @@ package ar.utn.ba.ddsi.mailing.controllers; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailInputDTO; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailOutputDTO; import ar.utn.ba.ddsi.mailing.models.entities.Email; import ar.utn.ba.ddsi.mailing.services.IEmailService; import org.springframework.web.bind.annotation.*; @@ -15,12 +17,12 @@ public EmailController(IEmailService emailService) { } @PostMapping - public Email crearEmail(@RequestBody Email email) { + public EmailOutputDTO crearEmail(@RequestBody EmailInputDTO email) { return emailService.crearEmail(email); } @GetMapping - public List obtenerEmails(@RequestParam(required = false) Boolean pendiente) { + public List obtenerEmails(@RequestParam(required = false) Boolean pendiente) { return emailService.obtenerEmails(pendiente); } } \ No newline at end of file diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailInputDTO.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailInputDTO.java new file mode 100644 index 0000000..df37efb --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailInputDTO.java @@ -0,0 +1,12 @@ +package ar.utn.ba.ddsi.mailing.models.dto.email; + +import ar.utn.ba.ddsi.mailing.models.entities.adapters.IEmailSender; +import lombok.Data; + +@Data +public class EmailInputDTO { + private String destinatario; + private String remitente; + private String asunto; + private String contenido; +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailOutputDTO.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailOutputDTO.java new file mode 100644 index 0000000..dcb82cc --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/dto/email/EmailOutputDTO.java @@ -0,0 +1,12 @@ +package ar.utn.ba.ddsi.mailing.models.dto.email; + +import lombok.Data; + +@Data +public class EmailOutputDTO { + private Long id; + private String destinatario; + private String remitente; + private String asunto; + private String contenido; +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Alerta.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Alerta.java new file mode 100644 index 0000000..49d2700 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Alerta.java @@ -0,0 +1,24 @@ +package ar.utn.ba.ddsi.mailing.models.entities; + +import ar.utn.ba.ddsi.mailing.models.entities.strategies.CondicionAlerta; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +public class Alerta { + @Getter + @Setter + private Long id; + private String nombre; + private List condiciones; + + public Alerta(String nombre, List condiciones) { + this.nombre = nombre; + this.condiciones = condiciones; + } + + public boolean cumpleCondicionesAlerta(Clima clima){ + return condiciones.stream().allMatch(condicion -> condicion.seCumple(clima)); + } +} \ No newline at end of file diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Email.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Email.java index eb940be..a46bba6 100644 --- a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Email.java +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/Email.java @@ -1,5 +1,6 @@ package ar.utn.ba.ddsi.mailing.models.entities; +import ar.utn.ba.ddsi.mailing.models.entities.adapters.IEmailSender; import lombok.Getter; import lombok.Setter; @@ -12,6 +13,7 @@ public class Email { private String asunto; private String contenido; private boolean enviado; + private IEmailSender emailSender; public Email(String destinatario, String remitente, String asunto, String contenido) { this.destinatario = destinatario; @@ -23,5 +25,7 @@ public Email(String destinatario, String remitente, String asunto, String conten public void enviar() { //TODO: Implementación pendiente. Podríamos usar adapters + // HECHO: IMPLEMENTACIÓN DE ADAPTER + this.emailSender.send(this); } } \ No newline at end of file diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/IEmailSender.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/IEmailSender.java new file mode 100644 index 0000000..5d6f648 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/IEmailSender.java @@ -0,0 +1,7 @@ +package ar.utn.ba.ddsi.mailing.models.entities.adapters; + +import ar.utn.ba.ddsi.mailing.models.entities.Email; + +public interface IEmailSender { + void send(Email email); +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/impl/JavaMailAdapter.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/impl/JavaMailAdapter.java new file mode 100644 index 0000000..35a178a --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/adapters/impl/JavaMailAdapter.java @@ -0,0 +1,34 @@ +package ar.utn.ba.ddsi.mailing.models.entities.adapters.impl; + +import ar.utn.ba.ddsi.mailing.models.entities.Email; +import ar.utn.ba.ddsi.mailing.models.entities.adapters.IEmailSender; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; + +public class JavaMailAdapter implements IEmailSender { + private final JavaMailSender javaMailSender; + + public JavaMailAdapter(JavaMailSender javaMailSender) { + this.javaMailSender = javaMailSender; + } + + @Override + public void send(Email email) { + // 1) Convertimos nuestro dominio Email a MimeMessage + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, "UTF-8"); + helper.setTo(email.getDestinatario()); + helper.setFrom(email.getRemitente()); + helper.setSubject(email.getAsunto()); + helper.setText(email.getContenido(), false); // false = texto plano + + // 2) Enviamos usando JavaMailSender + javaMailSender.send(mimeMessage); + } catch (MessagingException ex) { + throw new RuntimeException("Error al enviar email: " + ex.getMessage(), ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/CondicionAlerta.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/CondicionAlerta.java new file mode 100644 index 0000000..1a5a768 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/CondicionAlerta.java @@ -0,0 +1,7 @@ +package ar.utn.ba.ddsi.mailing.models.entities.strategies; + +import ar.utn.ba.ddsi.mailing.models.entities.Clima; + +public interface CondicionAlerta { + boolean seCumple(Clima clima); +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Humedad.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Humedad.java new file mode 100644 index 0000000..07bfaac --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Humedad.java @@ -0,0 +1,17 @@ +package ar.utn.ba.ddsi.mailing.models.entities.strategies.impl; + +import ar.utn.ba.ddsi.mailing.models.entities.Clima; +import ar.utn.ba.ddsi.mailing.models.entities.strategies.CondicionAlerta; + +public class Humedad implements CondicionAlerta { + private Double umbral; + + public Humedad(Double umbral){ + this.umbral = umbral; // 60% + } + + @Override + public boolean seCumple(Clima clima) { + return clima.getHumedad() > this.umbral; + } +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Temperatura.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Temperatura.java new file mode 100644 index 0000000..111d0b0 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/entities/strategies/impl/Temperatura.java @@ -0,0 +1,18 @@ +package ar.utn.ba.ddsi.mailing.models.entities.strategies.impl; + +import ar.utn.ba.ddsi.mailing.models.entities.Clima; +import ar.utn.ba.ddsi.mailing.models.entities.strategies.CondicionAlerta; + +public class Temperatura implements CondicionAlerta { + private final Double umbralCelsius; + + public Temperatura(Double umbralCelsius) { + this.umbralCelsius = umbralCelsius; // 35 + } + + @Override + public boolean seCumple(Clima clima) { + return clima.getTemperaturaCelsius() > this.umbralCelsius + || clima.getTemperaturaFahrenheit() > this.umbralCelsius + 60.0; + } +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/IAlertaRepository.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/IAlertaRepository.java new file mode 100644 index 0000000..93523a3 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/IAlertaRepository.java @@ -0,0 +1,11 @@ +package ar.utn.ba.ddsi.mailing.models.repositories; + +import ar.utn.ba.ddsi.mailing.models.entities.Alerta; +import ar.utn.ba.ddsi.mailing.models.entities.Email; + +import java.util.List; + +public interface IAlertaRepository { + Alerta save(Alerta alerta); + List findAll(); +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/impl/AlertaRepository.java b/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/impl/AlertaRepository.java new file mode 100644 index 0000000..fb03de9 --- /dev/null +++ b/src/main/java/ar/utn/ba/ddsi/mailing/models/repositories/impl/AlertaRepository.java @@ -0,0 +1,34 @@ +package ar.utn.ba.ddsi.mailing.models.repositories.impl; + +import ar.utn.ba.ddsi.mailing.models.entities.Alerta; +import ar.utn.ba.ddsi.mailing.models.repositories.IAlertaRepository; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Repository +public class AlertaRepository implements IAlertaRepository { + private final Map alertas = new HashMap<>(); + private final AtomicLong idGenerator = new AtomicLong(1); + + @Override + public Alerta save(Alerta alerta) { + if (alerta.getId() == null) { + Long id = idGenerator.getAndIncrement(); + alerta.setId(id); + alertas.put(id, alerta); + } else { + alertas.put(alerta.getId(), alerta); + } + return alerta; + } + + @Override + public List findAll() { + return new ArrayList<>(alertas.values()); + } +} diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/services/IEmailService.java b/src/main/java/ar/utn/ba/ddsi/mailing/services/IEmailService.java index 5be1682..f69da6e 100644 --- a/src/main/java/ar/utn/ba/ddsi/mailing/services/IEmailService.java +++ b/src/main/java/ar/utn/ba/ddsi/mailing/services/IEmailService.java @@ -1,11 +1,13 @@ package ar.utn.ba.ddsi.mailing.services; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailInputDTO; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailOutputDTO; import ar.utn.ba.ddsi.mailing.models.entities.Email; import java.util.List; public interface IEmailService { - Email crearEmail(Email email); - List obtenerEmails(Boolean pendiente); + EmailOutputDTO crearEmail(EmailInputDTO inputDTO); + List obtenerEmails(Boolean pendiente); void procesarPendientes(); void loguearEmailsPendientes(); } \ No newline at end of file diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/AlertasService.java b/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/AlertasService.java index f5d52b6..a8c54a5 100644 --- a/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/AlertasService.java +++ b/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/AlertasService.java @@ -1,7 +1,9 @@ package ar.utn.ba.ddsi.mailing.services.impl; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailInputDTO; import ar.utn.ba.ddsi.mailing.models.entities.Clima; import ar.utn.ba.ddsi.mailing.models.entities.Email; +import ar.utn.ba.ddsi.mailing.models.repositories.IAlertaRepository; import ar.utn.ba.ddsi.mailing.models.repositories.IClimaRepository; import ar.utn.ba.ddsi.mailing.services.IAlertasService; import org.slf4j.Logger; @@ -15,19 +17,19 @@ @Service public class AlertasService implements IAlertasService { private static final Logger logger = LoggerFactory.getLogger(AlertasService.class); - private static final double TEMPERATURA_ALERTA = 35.0; - private static final int HUMEDAD_ALERTA = 60; + private final IAlertaRepository alertaRepository; private final IClimaRepository climaRepository; private final EmailService emailService; private final String remitente; private final List destinatarios; public AlertasService( - IClimaRepository climaRepository, + IAlertaRepository alertaRepository, IClimaRepository climaRepository, EmailService emailService, @Value("${email.alertas.remitente}") String remitente, @Value("${email.alertas.destinatarios}") String destinatarios) { + this.alertaRepository = alertaRepository; this.climaRepository = climaRepository; this.emailService = emailService; this.remitente = remitente; @@ -63,8 +65,8 @@ public Mono generarAlertasYAvisar() { private boolean cumpleCondicionesAlerta(Clima clima) { //TODO: podríamos refactorizar el diseño para que no sea un simple método, pues puede ser más complejo - return clima.getTemperaturaCelsius() > TEMPERATURA_ALERTA && - clima.getHumedad() > HUMEDAD_ALERTA; + // HECHO + return this.alertaRepository.findAll().stream().anyMatch(alerta -> alerta.cumpleCondicionesAlerta(clima)); } private void generarYEnviarEmail(Clima clima) { @@ -84,8 +86,13 @@ private void generarYEnviarEmail(Clima clima) { ); for (String destinatario : destinatarios) { - Email email = new Email(destinatario, remitente, asunto, mensaje); - emailService.crearEmail(email); + // Email email = new Email(destinatario, remitente, asunto, mensaje); + EmailInputDTO dto = new EmailInputDTO(); + dto.setDestinatario(destinatario); + dto.setRemitente(remitente); + dto.setAsunto(asunto); + dto.setContenido(mensaje); + emailService.crearEmail(dto); } logger.info("Email de alerta generado para {} - Enviado a {} destinatarios", diff --git a/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/EmailService.java b/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/EmailService.java index e1ffb85..1e52e3a 100644 --- a/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/EmailService.java +++ b/src/main/java/ar/utn/ba/ddsi/mailing/services/impl/EmailService.java @@ -1,5 +1,7 @@ package ar.utn.ba.ddsi.mailing.services.impl; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailInputDTO; +import ar.utn.ba.ddsi.mailing.models.dto.email.EmailOutputDTO; import ar.utn.ba.ddsi.mailing.models.entities.Email; import ar.utn.ba.ddsi.mailing.models.repositories.IEmailRepository; import ar.utn.ba.ddsi.mailing.services.IEmailService; @@ -18,16 +20,17 @@ public EmailService(IEmailRepository emailRepository) { } @Override - public Email crearEmail(Email email) { - return emailRepository.save(email); + public EmailOutputDTO crearEmail(EmailInputDTO inputDTO) { + Email email = this.emailRepository.save(this.dtoTOEmail(inputDTO)); + return this.emailToDTO(email); } @Override - public List obtenerEmails(Boolean pendiente) { + public List obtenerEmails(Boolean pendiente) { if (pendiente != null) { - return emailRepository.findByEnviado(!pendiente); + return emailRepository.findByEnviado(!pendiente).stream().map(this::emailToDTO).toList(); } - return emailRepository.findAll(); + return emailRepository.findAll().stream().map(this::emailToDTO).toList(); } @Override @@ -40,9 +43,28 @@ public void procesarPendientes() { } } + public Email dtoTOEmail (EmailInputDTO dto) { + return new Email( + dto.getDestinatario(), + dto.getRemitente(), + dto.getAsunto(), + dto.getContenido() + ); + } + + public EmailOutputDTO emailToDTO(Email email) { + EmailOutputDTO outputDTO = new EmailOutputDTO(); + outputDTO.setId(email.getId()); + outputDTO.setDestinatario(email.getDestinatario()); + outputDTO.setRemitente(email.getRemitente()); + outputDTO.setAsunto(email.getAsunto()); + outputDTO.setContenido(email.getContenido()); + return outputDTO; + } + @Override public void loguearEmailsPendientes() { - List pendientes = obtenerEmails(true); + List pendientes = obtenerEmails(true); logger.info("Emails pendientes de envío: {}", pendientes.size()); pendientes.forEach(email -> logger.info("Email pendiente - ID: {}, Destinatario: {}, Asunto: {}",