Implemented Persistence with Spring Data JPA. Closes #1

pull/21/head
Niclas Thobaben 2022-02-14 17:51:59 +01:00
parent 76ee50b63f
commit cbe55fbd5a
23 changed files with 290 additions and 52 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@ build/
### VS Code ###
.vscode/
*.db

10
pom.xml
View File

@ -32,12 +32,22 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -12,6 +12,7 @@ import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import javax.annotation.PostConstruct;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.List;
@ -34,17 +35,19 @@ public class ForwardMessageFormTest {
@TestConfiguration
static class TestConfig {
@Autowired
private Relay relay;
@Bean
public Clock clock() {
return FakeClock.fixed(FIXED_TIME);
}
@Bean
public Relay relay() {
@PostConstruct
public void init() {
RelayAccount account = new RelayAccount(UUID.randomUUID(), "company-account", TOKEN, List.of("vip@company.com"));
Relay relay = new Relay();
relay.addAccount(account);
return relay;
}
}

View File

@ -1 +1,3 @@
spring.main.allow-bean-definition-overriding=true
spring.main.allow-bean-definition-overriding=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:h2:mem:testdb

View File

@ -1,14 +1,10 @@
package de.nclazz.service.mailrelay;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.time.Clock;
import java.util.List;
import java.util.UUID;
@SpringBootApplication
public class MailRelayApplication {
@ -22,14 +18,4 @@ public class MailRelayApplication {
return Clock.systemDefaultZone();
}
// Temporary setup for testing. Will be removed when persistence is enabled
@Bean
public Relay relay() {
Relay relay = new Relay();
RelayAccount account = new RelayAccount(UUID.randomUUID(), "My Company", "my-token", List.of("vip@mycompany.com"));
relay.addAccount(account);
return relay;
}
}

View File

@ -0,0 +1,9 @@
package de.nclazz.service.mailrelay.adapter.jpa;
import de.nclazz.service.mailrelay.domain.Message;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface JpaMessageRepository extends JpaRepository<Message, UUID> {
}

View File

@ -0,0 +1,19 @@
package de.nclazz.service.mailrelay.adapter.jpa;
import de.nclazz.service.mailrelay.domain.Message;
import de.nclazz.service.mailrelay.domain.MessageRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class JpaMessageRepositoryAdapter implements MessageRepository {
private final JpaMessageRepository jpaMessageRepository;
@Override
public Message save(@NonNull Message message) {
return this.jpaMessageRepository.save(message);
}
}

View File

@ -0,0 +1,13 @@
package de.nclazz.service.mailrelay.adapter.jpa;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface JpaRelayAccountRepository extends JpaRepository<RelayAccount, UUID> {
Optional<RelayAccount> findByToken(String token);
}

View File

@ -0,0 +1,38 @@
package de.nclazz.service.mailrelay.adapter.jpa;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import de.nclazz.service.mailrelay.domain.RelayAccountRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository
@RequiredArgsConstructor
public class JpaRelayAccountRepositoryAdapter implements RelayAccountRepository {
private final JpaRelayAccountRepository jpaRepository;
@Override
public List<RelayAccount> findAll() {
return this.jpaRepository.findAll();
}
@Override
public Optional<RelayAccount> findByGuid(@NonNull UUID guid) {
return this.jpaRepository.findById(guid);
}
@Override
public Optional<RelayAccount> findByToken(@NonNull String token) {
return this.jpaRepository.findByToken(token);
}
@Override
public RelayAccount save(@NonNull RelayAccount account) {
return this.jpaRepository.save(account);
}
}

View File

@ -32,7 +32,7 @@ public class MessageForm {
}
public Message toMessage(@NonNull LocalDateTime timestamp) {
return new Message(
return Message.of(
this.subject,
this.content,
this.from,

View File

@ -1,19 +1,57 @@
package de.nclazz.service.mailrelay.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "messages")
@Data
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class Message {
@Id
@Column(name = "guid", columnDefinition = "char(36)")
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy ="org.hibernate.id.UUIDGenerator")
@Type(type = "uuid-char")
@JsonIgnore
private UUID guid;
private String subject;
@Lob
@Column(length=2048 )
private String content;
@Column(name = "from_address")
private String from;
@Column(name = "created_at")
private LocalDateTime timestamp;
public static Message of(String subject, String content, String from, LocalDateTime timestamp) {
Message message = new Message();
message.setSubject(subject);
message.setContent(content);
message.setFrom(from);
message.setTimestamp(timestamp);
return message;
}
}

View File

@ -0,0 +1,9 @@
package de.nclazz.service.mailrelay.domain;
import lombok.NonNull;
public interface MessageRepository {
Message save(@NonNull Message message);
}

View File

@ -3,37 +3,31 @@ package de.nclazz.service.mailrelay.domain;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Data
@Slf4j
//@Service
@Service
public class Relay {
private final Set<RelayAccount> accounts = new HashSet<>();
public void addAccount(@NonNull RelayAccount account) {
this.accounts.add(account);
}
private final RelayAccountRepository accountRepository;
private final MessageRepository messageRepository;
public Message forwardMessage(@NonNull String token, @NonNull Message message) {
log.info("Forward message '{}' to <{}>", message.getSubject(), token);
Optional<RelayAccount> accountOptional = findAccountByToken(token);
Optional<RelayAccount> accountOptional = this.accountRepository.findByToken(token);
if(accountOptional.isPresent()) {
return accountOptional.get().forwardMessage(message);
message = this.messageRepository.save(message);
accountOptional.get().forwardMessage(message);
}
return message;
}
private Optional<RelayAccount> findAccountByToken(String token) {
return this.accounts.stream()
.filter(acc -> acc.matchesToken(token))
.findAny();
public void addAccount(@NonNull RelayAccount account) {
this.accountRepository.save(account);
}
}

View File

@ -2,30 +2,52 @@ package de.nclazz.service.mailrelay.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "accounts", indexes = @Index(columnList = "token"))
@Data
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class RelayAccount {
@Id
@Column(name = "guid", columnDefinition = "char(36)")
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy ="org.hibernate.id.UUIDGenerator")
@Type(type = "uuid-char")
private UUID guid;
private String name;
private String token;
private List<String> forwardTo;
@ElementCollection
@CollectionTable(name = "accounts_receivers")
private List<String> receivers;
@OneToMany
private final List<Message> sentMessages = new ArrayList<>();
public Message forwardMessage(@NonNull Message message) {
public void forwardMessage(@NonNull Message message) {
log.info("Forward message '{}' on <{}> ({} <{}>)", message.getSubject(), this.token, this.name, this.guid);
this.sentMessages.add(message);
return message;
}

View File

@ -0,0 +1,20 @@
package de.nclazz.service.mailrelay.domain;
import lombok.NonNull;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface RelayAccountRepository {
List<RelayAccount> findAll();
Optional<RelayAccount> findByGuid(@NonNull UUID guid);
Optional<RelayAccount> findByToken(@NonNull String token);
RelayAccount save(@NonNull RelayAccount account);
}

View File

@ -1 +1,3 @@
# DDL auto schema generation (update, create, create-drop, none, validate)
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:h2:file:./test-db

View File

@ -1,7 +1,8 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.FakeClock;
import de.nclazz.service.mailrelay.domain.Message;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.FakeRelayAccountRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.junit.jupiter.api.Test;
@ -25,7 +26,7 @@ class MessageFormControllerTest {
List.of("vip@account.com")
);
Relay relay = new Relay();
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);
@ -39,7 +40,9 @@ class MessageFormControllerTest {
assertThat(account.getSentMessages())
.hasSize(1)
.containsExactly(new Message("Subject", "Message", "sender@company.com", now));
.element(0)
.extracting("subject", "content", "from", "timestamp")
.containsExactly("Subject", "Message", "sender@company.com", now);
}
}

View File

@ -1,7 +1,8 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.FakeClock;
import de.nclazz.service.mailrelay.domain.Message;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.FakeRelayAccountRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.junit.jupiter.api.Test;
@ -25,7 +26,7 @@ class MessageRestControllerTest {
List.of("vip@account.com")
);
Relay relay = new Relay();
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);
@ -36,6 +37,7 @@ class MessageRestControllerTest {
MessageRestController controller = new MessageRestController(relay, fakeClock);
assertThat(controller.forwardMessage(form))
.isEqualTo(new Message("Subject", "Message", "sender@company.com", now));
.extracting("subject", "content", "from", "timestamp")
.containsExactly("Subject", "Message", "sender@company.com", now);
}
}

View File

@ -0,0 +1,21 @@
package de.nclazz.service.mailrelay.domain;
import lombok.NonNull;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class FakeMessageRepository implements MessageRepository {
private final Set<Message> messages = new HashSet<>();
@Override
public Message save(@NonNull Message message) {
UUID guid = (message.getGuid() != null ? message.getGuid() : UUID.randomUUID());
message.setGuid(guid);
this.messages.add(message);
return message;
}
}

View File

@ -0,0 +1,44 @@
package de.nclazz.service.mailrelay.domain;
import lombok.NonNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
public class FakeRelayAccountRepository implements RelayAccountRepository {
private final Set<RelayAccount> accounts = new HashSet<>();
@Override
public List<RelayAccount> findAll() {
return new ArrayList<>(this.accounts);
}
@Override
public Optional<RelayAccount> findByGuid(@NonNull UUID guid) {
return this.accounts.stream()
.filter(account -> account.getGuid().equals(guid))
.findAny();
}
@Override
public Optional<RelayAccount> findByToken(@NonNull String token) {
return this.accounts.stream()
.filter(account -> account.getToken().equals(token))
.findAny();
}
@Override
public RelayAccount save(@NonNull RelayAccount account) {
UUID guid = (account.getGuid() != null ? account.getGuid() : UUID.randomUUID());
account.setGuid(guid);
this.accounts.add(account);
return account;
}
}

View File

@ -20,11 +20,11 @@ class ForwardMessageTest {
List.of("info@company.com")
);
Relay relay = new Relay();
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
LocalDateTime now = LocalDateTime.now();
Message message = new Message(
Message message = Message.of(
"Urgent message",
"Please reply",
"vip@company.com",
@ -47,11 +47,11 @@ class ForwardMessageTest {
List.of("info@company.com")
);
Relay relay = new Relay();
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
LocalDateTime now = LocalDateTime.now();
Message message = new Message(
Message message = Message.of(
"Urgent message",
"Please reply",
"vip@company.com",

View File

@ -20,7 +20,7 @@ class RelayAccountTest {
);
LocalDateTime now = LocalDateTime.now();
Message message = new Message(
Message message = Message.of(
"Urgent message",
"Please reply",
"vip@company.com",

View File

@ -18,10 +18,11 @@ class RelayTest {
List.of("vip@account.com")
);
Relay relay = new Relay();
FakeRelayAccountRepository repository = new FakeRelayAccountRepository();
Relay relay = new Relay(repository, new FakeMessageRepository());
relay.addAccount(account);
assertThat(relay.getAccounts())
assertThat(repository.findAll())
.hasSize(1)
.containsExactly(account);
}