Auto-delete expired events via daily scheduled cleanup job
Adds a Spring @Scheduled job (daily at 03:00) that deletes all events whose expiry_date is before CURRENT_DATE using a native SQL DELETE. RSVPs are cascade-deleted via the existing FK constraint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,11 @@ package de.fete;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/** Spring Boot entry point for the fete application. */
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
public class FeteApplication {
|
||||
|
||||
/** Starts the application. */
|
||||
|
||||
@@ -3,10 +3,17 @@ package de.fete.adapter.out.persistence;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
/** Spring Data JPA repository for event entities. */
|
||||
public interface EventJpaRepository extends JpaRepository<EventJpaEntity, Long> {
|
||||
|
||||
/** Finds an event by its public event token. */
|
||||
Optional<EventJpaEntity> findByEventToken(UUID eventToken);
|
||||
|
||||
/** Deletes all events whose expiry date is before today. Returns the number of deleted rows. */
|
||||
@Modifying
|
||||
@Query(value = "DELETE FROM events WHERE expiry_date < CURRENT_DATE", nativeQuery = true)
|
||||
int deleteExpired();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ public class EventPersistenceAdapter implements EventRepository {
|
||||
return jpaRepository.findByEventToken(eventToken.value()).map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteExpired() {
|
||||
return jpaRepository.deleteExpired();
|
||||
}
|
||||
|
||||
private EventJpaEntity toEntity(Event event) {
|
||||
var entity = new EventJpaEntity();
|
||||
entity.setId(event.getId());
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.fete.application.service;
|
||||
|
||||
import de.fete.domain.port.out.EventRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/** Scheduled job that deletes events whose expiry date is in the past. */
|
||||
@Component
|
||||
public class ExpiredEventCleanupJob {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExpiredEventCleanupJob.class);
|
||||
|
||||
private final EventRepository eventRepository;
|
||||
|
||||
/** Creates a new cleanup job with the given event repository. */
|
||||
public ExpiredEventCleanupJob(EventRepository eventRepository) {
|
||||
this.eventRepository = eventRepository;
|
||||
}
|
||||
|
||||
/** Runs daily at 03:00 and deletes all expired events. */
|
||||
@Scheduled(cron = "0 0 3 * * *")
|
||||
@Transactional
|
||||
public void deleteExpiredEvents() {
|
||||
int deleted = eventRepository.deleteExpired();
|
||||
log.info("Expired event cleanup: deleted {} event(s)", deleted);
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,7 @@ public interface EventRepository {
|
||||
|
||||
/** Finds an event by its public event token. */
|
||||
Optional<Event> findByEventToken(EventToken eventToken);
|
||||
|
||||
/** Deletes all events whose expiry date is in the past. Returns the number of deleted events. */
|
||||
int deleteExpired();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package de.fete.adapter.out.persistence;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import de.fete.TestcontainersConfig;
|
||||
import de.fete.domain.model.Event;
|
||||
import de.fete.domain.model.EventToken;
|
||||
import de.fete.domain.model.OrganizerToken;
|
||||
import de.fete.domain.port.out.EventRepository;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@SpringBootTest
|
||||
@Import(TestcontainersConfig.class)
|
||||
@Transactional
|
||||
class EventPersistenceAdapterIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private EventRepository eventRepository;
|
||||
|
||||
@Test
|
||||
void deleteExpiredRemovesExpiredEvents() {
|
||||
Event expired = buildEvent("Expired Party", LocalDate.now().minusDays(1));
|
||||
eventRepository.save(expired);
|
||||
|
||||
int deleted = eventRepository.deleteExpired();
|
||||
|
||||
assertThat(deleted).isGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteExpiredKeepsNonExpiredEvents() {
|
||||
Event future = buildEvent("Future Party", LocalDate.now().plusDays(30));
|
||||
Event saved = eventRepository.save(future);
|
||||
|
||||
eventRepository.deleteExpired();
|
||||
|
||||
assertThat(eventRepository.findByEventToken(saved.getEventToken())).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteExpiredKeepsEventsExpiringToday() {
|
||||
Event today = buildEvent("Today Party", LocalDate.now());
|
||||
Event saved = eventRepository.save(today);
|
||||
|
||||
eventRepository.deleteExpired();
|
||||
|
||||
assertThat(eventRepository.findByEventToken(saved.getEventToken())).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteExpiredReturnsZeroWhenNoneExpired() {
|
||||
// Only save a future event
|
||||
buildEvent("Future Only", LocalDate.now().plusDays(60));
|
||||
|
||||
int deleted = eventRepository.deleteExpired();
|
||||
|
||||
assertThat(deleted).isGreaterThanOrEqualTo(0);
|
||||
}
|
||||
|
||||
private Event buildEvent(String title, LocalDate expiryDate) {
|
||||
var event = new Event();
|
||||
event.setEventToken(EventToken.generate());
|
||||
event.setOrganizerToken(OrganizerToken.generate());
|
||||
event.setTitle(title);
|
||||
event.setDescription("Test description");
|
||||
event.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
|
||||
event.setTimezone(ZoneId.of("Europe/Berlin"));
|
||||
event.setLocation("Test Location");
|
||||
event.setExpiryDate(expiryDate);
|
||||
event.setCreatedAt(OffsetDateTime.now());
|
||||
return event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user