Implement event domain model and application service
Add Event entity, CreateEventCommand, ports (CreateEventUseCase, EventRepository), and EventService with Clock injection for deterministic testing. Expiry date must be in the future. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package de.fete.application.service;
|
||||
|
||||
import de.fete.domain.model.CreateEventCommand;
|
||||
import de.fete.domain.model.Event;
|
||||
import de.fete.domain.port.in.CreateEventUseCase;
|
||||
import de.fete.domain.port.out.EventRepository;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** Application service implementing event creation. */
|
||||
@Service
|
||||
public class EventService implements CreateEventUseCase {
|
||||
|
||||
private final EventRepository eventRepository;
|
||||
private final Clock clock;
|
||||
|
||||
/** Creates a new EventService with the given repository and clock. */
|
||||
public EventService(EventRepository eventRepository, Clock clock) {
|
||||
this.eventRepository = eventRepository;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event createEvent(CreateEventCommand command) {
|
||||
if (!command.expiryDate().isAfter(LocalDate.now(clock))) {
|
||||
throw new ExpiryDateInPastException(command.expiryDate());
|
||||
}
|
||||
|
||||
var event = new Event();
|
||||
event.setEventToken(UUID.randomUUID());
|
||||
event.setOrganizerToken(UUID.randomUUID());
|
||||
event.setTitle(command.title());
|
||||
event.setDescription(command.description());
|
||||
event.setDateTime(command.dateTime());
|
||||
event.setLocation(command.location());
|
||||
event.setExpiryDate(command.expiryDate());
|
||||
event.setCreatedAt(OffsetDateTime.now(clock));
|
||||
|
||||
return eventRepository.save(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.fete.application.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** Thrown when an event's expiry date is not in the future. */
|
||||
public class ExpiryDateInPastException extends RuntimeException {
|
||||
|
||||
private final LocalDate expiryDate;
|
||||
|
||||
/** Creates a new exception for the given invalid expiry date. */
|
||||
public ExpiryDateInPastException(LocalDate expiryDate) {
|
||||
super("Expiry date must be in the future: " + expiryDate);
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
/** Returns the invalid expiry date. */
|
||||
public LocalDate getExpiryDate() {
|
||||
return expiryDate;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package de.fete.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Clock;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -14,6 +16,11 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
Clock clock() {
|
||||
return Clock.systemDefaultZone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class));
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.fete.domain.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/** Command carrying the data needed to create an event. */
|
||||
public record CreateEventCommand(
|
||||
String title,
|
||||
String description,
|
||||
OffsetDateTime dateTime,
|
||||
String location,
|
||||
LocalDate expiryDate
|
||||
) {}
|
||||
109
backend/src/main/java/de/fete/domain/model/Event.java
Normal file
109
backend/src/main/java/de/fete/domain/model/Event.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package de.fete.domain.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Domain entity representing an event. */
|
||||
public class Event {
|
||||
|
||||
private Long id;
|
||||
private UUID eventToken;
|
||||
private UUID organizerToken;
|
||||
private String title;
|
||||
private String description;
|
||||
private OffsetDateTime dateTime;
|
||||
private String location;
|
||||
private LocalDate expiryDate;
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
/** Returns the internal database ID. */
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Sets the internal database ID. */
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/** Returns the public event token (UUID). */
|
||||
public UUID getEventToken() {
|
||||
return eventToken;
|
||||
}
|
||||
|
||||
/** Sets the public event token. */
|
||||
public void setEventToken(UUID eventToken) {
|
||||
this.eventToken = eventToken;
|
||||
}
|
||||
|
||||
/** Returns the secret organizer token (UUID). */
|
||||
public UUID getOrganizerToken() {
|
||||
return organizerToken;
|
||||
}
|
||||
|
||||
/** Sets the secret organizer token. */
|
||||
public void setOrganizerToken(UUID organizerToken) {
|
||||
this.organizerToken = organizerToken;
|
||||
}
|
||||
|
||||
/** Returns the event title. */
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/** Sets the event title. */
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/** Returns the event description. */
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/** Sets the event description. */
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/** Returns the event date and time with UTC offset. */
|
||||
public OffsetDateTime getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/** Sets the event date and time. */
|
||||
public void setDateTime(OffsetDateTime dateTime) {
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
/** Returns the event location. */
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/** Sets the event location. */
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/** Returns the expiry date after which event data is deleted. */
|
||||
public LocalDate getExpiryDate() {
|
||||
return expiryDate;
|
||||
}
|
||||
|
||||
/** Sets the expiry date. */
|
||||
public void setExpiryDate(LocalDate expiryDate) {
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
/** Returns the creation timestamp. */
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/** Sets the creation timestamp. */
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.fete.domain.port.in;
|
||||
|
||||
import de.fete.domain.model.CreateEventCommand;
|
||||
import de.fete.domain.model.Event;
|
||||
|
||||
/** Inbound port for creating a new event. */
|
||||
public interface CreateEventUseCase {
|
||||
|
||||
/** Creates an event from the given command and returns the persisted event. */
|
||||
Event createEvent(CreateEventCommand command);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.fete.domain.port.out;
|
||||
|
||||
import de.fete.domain.model.Event;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Outbound port for persisting and retrieving events. */
|
||||
public interface EventRepository {
|
||||
|
||||
/** Persists the given event and returns it with generated fields populated. */
|
||||
Event save(Event event);
|
||||
|
||||
/** Finds an event by its public event token. */
|
||||
Optional<Event> findByEventToken(UUID eventToken);
|
||||
}
|
||||
Reference in New Issue
Block a user