feat: implement core use cases and DTOs (OAuth2, tests, and docs pending)

This commit is contained in:
Pablo de la Torre Jamardo 2025-09-11 18:56:33 +02:00
parent 4053c6fa46
commit 06539a05fc
16 changed files with 233 additions and 2 deletions

View File

@ -0,0 +1,8 @@
package com.pablotj.restemailbridge.application.dto;
public record EmailDTO(
String from,
String subject,
String body
) {
}

View File

@ -0,0 +1,5 @@
package com.pablotj.restemailbridge.application.port;
public interface EmailConfigurationPort {
String getDefaultRecipient();
}

View File

@ -0,0 +1,32 @@
package com.pablotj.restemailbridge.application.usecase;
import com.pablotj.restemailbridge.application.dto.EmailDTO;
import com.pablotj.restemailbridge.application.port.EmailConfigurationPort;
import com.pablotj.restemailbridge.domain.model.Email;
import com.pablotj.restemailbridge.domain.repository.EmailRepository;
import com.pablotj.restemailbridge.domain.service.EmailService;
public class SendEmailUseCase {
private final EmailConfigurationPort emailConfigurationPort;
private final EmailService emailService;
private final EmailRepository emailRepository;
public SendEmailUseCase(EmailConfigurationPort emailConfigurationPort, EmailService emailService, EmailRepository emailRepository) {
this.emailConfigurationPort = emailConfigurationPort;
this.emailService = emailService;
this.emailRepository = emailRepository;
}
public void handle(EmailDTO emailDTO) {
String to = emailConfigurationPort.getDefaultRecipient();
Email email = emailService.sendEmail(
Email.builder()
.from(emailDTO.from())
.to(to)
.subject(emailDTO.subject())
.body(emailDTO.body())
.build()
);
emailRepository.save(email);
}
}

View File

@ -6,8 +6,8 @@ import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication(scanBasePackages = "com.pablotj.restemailbridge") @SpringBootApplication(scanBasePackages = "com.pablotj.restemailbridge")
@EnableJpaRepositories(basePackages = {"com.pablotj.restemailbridge.infrastructure"}) @EnableJpaRepositories(basePackages = {"com.pablotj.restemailbridge"})
@EntityScan(basePackages = {"com.pablotj.restemailbridge.infrastructure"}) @EntityScan(basePackages = {"com.pablotj.restemailbridge"})
public class RestEmailBridgeApplication { public class RestEmailBridgeApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -0,0 +1,15 @@
package com.pablotj.restemailbridge.domain.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class Email {
private String from;
private String to;
private String subject;
private String body;
}

View File

@ -0,0 +1,8 @@
package com.pablotj.restemailbridge.domain.repository;
import com.pablotj.restemailbridge.domain.model.Email;
public interface EmailRepository {
Email save(Email email);
}

View File

@ -0,0 +1,8 @@
package com.pablotj.restemailbridge.domain.service;
import com.pablotj.restemailbridge.domain.model.Email;
public interface EmailService {
Email sendEmail(Email email);
}

View File

@ -33,6 +33,12 @@
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<!-- MapStruct --> <!-- MapStruct -->
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>

View File

@ -0,0 +1,12 @@
package com.pablotj.restemailbridge.infrastructure.config;
import com.pablotj.restemailbridge.application.port.EmailConfigurationPort;
import org.springframework.stereotype.Component;
@Component
public class EmailConfigurationAdapter implements EmailConfigurationPort {
@Override
public String getDefaultRecipient() {
return "1234@1234.com";
}
}

View File

@ -0,0 +1,17 @@
package com.pablotj.restemailbridge.infrastructure.config.spring;
import com.pablotj.restemailbridge.application.port.EmailConfigurationPort;
import com.pablotj.restemailbridge.application.usecase.SendEmailUseCase;
import com.pablotj.restemailbridge.domain.repository.EmailRepository;
import com.pablotj.restemailbridge.domain.service.EmailService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UseCaseConfig {
@Bean
public SendEmailUseCase sendEmailUseCase(EmailConfigurationPort emailConfigurationPort, EmailService emailService, EmailRepository emailRepository) {
return new SendEmailUseCase(emailConfigurationPort, emailService, emailRepository);
}
}

View File

@ -0,0 +1,15 @@
package com.pablotj.restemailbridge.infrastructure.mail;
import com.pablotj.restemailbridge.domain.model.Email;
import com.pablotj.restemailbridge.domain.service.EmailService;
import org.springframework.stereotype.Component;
@Component
public class GmailOAuth2MailService implements EmailService {
@Override
public Email sendEmail(Email email) {
System.out.println("Sending email " + email.getSubject() + " to " + email.getTo());
return email;
}
}

View File

@ -0,0 +1,33 @@
package com.pablotj.restemailbridge.infrastructure.persistence;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "MAIL")
@Getter
@Setter
public class MailJpa {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String sender;
@Column(nullable = false)
private String recipient;
@Column(nullable = false)
private String subjet;
@Column(nullable = false)
private String body;
}

View File

@ -0,0 +1,28 @@
package com.pablotj.restemailbridge.infrastructure.persistence;
import com.pablotj.restemailbridge.domain.model.Email;
import com.pablotj.restemailbridge.domain.repository.EmailRepository;
import org.springframework.stereotype.Repository;
@Repository
public class MailRepositoryAdapter implements EmailRepository {
private final SpringDataMailRepository springDataMailRepository;
public MailRepositoryAdapter(SpringDataMailRepository springDataMailRepository) {
this.springDataMailRepository = springDataMailRepository;
}
@Override
public Email save(Email email) {
MailJpa mailJpa = new MailJpa();
mailJpa.setSender(email.getFrom());
mailJpa.setRecipient(email.getTo());
mailJpa.setSubjet(email.getSubject());
mailJpa.setBody(email.getBody());
springDataMailRepository.save(mailJpa);
return email;
}
}

View File

@ -0,0 +1,7 @@
package com.pablotj.restemailbridge.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataMailRepository extends JpaRepository<MailJpa, Long> {
}

View File

@ -0,0 +1,29 @@
package com.pablotj.restemailbridge.infrastructure.rest;
import com.pablotj.restemailbridge.application.dto.EmailDTO;
import com.pablotj.restemailbridge.application.usecase.SendEmailUseCase;
import com.pablotj.restemailbridge.infrastructure.rest.dto.SendMailRequest;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/mail")
public class MailController {
private final SendEmailUseCase sendEmailUseCase;
public MailController(SendEmailUseCase sendEmailUseCase) {
this.sendEmailUseCase = sendEmailUseCase;
}
@PostMapping
public ResponseEntity<Void> send(@Valid @RequestBody SendMailRequest request) {
sendEmailUseCase.handle(new EmailDTO(request.from(), request.subject(), request.body()));
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,8 @@
package com.pablotj.restemailbridge.infrastructure.rest.dto;
public record SendMailRequest(
String from,
String subject,
String body
) {
}