diff --git a/ems-common/pom.xml b/ems-common/pom.xml
index ba9e536..3afe918 100644
--- a/ems-common/pom.xml
+++ b/ems-common/pom.xml
@@ -10,7 +10,7 @@
com.example
ems-common
- 1.1.7
+ 1.1.8
ems-common
Common module for Event Management System
diff --git a/ems-common/src/main/java/com/example/ems_common/exceptions/EventAlreadyEndedException.java b/ems-common/src/main/java/com/example/ems_common/exceptions/EventAlreadyEndedException.java
new file mode 100644
index 0000000..e213221
--- /dev/null
+++ b/ems-common/src/main/java/com/example/ems_common/exceptions/EventAlreadyEndedException.java
@@ -0,0 +1,7 @@
+package com.example.ems_common.exceptions;
+
+public class EventAlreadyEndedException extends RuntimeException {
+ public EventAlreadyEndedException(String message) {
+ super(message);
+ }
+}
diff --git a/ems-common/src/main/java/com/example/ems_common/exceptions/GlobalExceptionHandler.java b/ems-common/src/main/java/com/example/ems_common/exceptions/GlobalExceptionHandler.java
index 388c4bc..c78f452 100644
--- a/ems-common/src/main/java/com/example/ems_common/exceptions/GlobalExceptionHandler.java
+++ b/ems-common/src/main/java/com/example/ems_common/exceptions/GlobalExceptionHandler.java
@@ -89,7 +89,7 @@ public ResponseEntity handleForbiddenException(RuntimeExceptio
@ExceptionHandler({CannotJoinOwnEventException.class})
public ResponseEntity handleCannotJoinOwnEventException(CannotJoinOwnEventException ex,
- HttpServletRequest request) {
+ HttpServletRequest request) {
ErrorResponseDto error = new ErrorResponseDto(
"CANNOT_JOIN_OWN_EVENT",
ex.getMessage(),
@@ -102,6 +102,21 @@ public ResponseEntity handleCannotJoinOwnEventException(Cannot
.body(error);
}
+ @ExceptionHandler({EventAlreadyEndedException.class})
+ public ResponseEntity handleEventAlreadyEndedException(EventAlreadyEndedException ex,
+ HttpServletRequest request) {
+ ErrorResponseDto error = new ErrorResponseDto(
+ "EVENT_ALREADY_ENDED",
+ ex.getMessage(),
+ HttpStatus.BAD_REQUEST.value(),
+ LocalDateTime.now(),
+ request.getRequestURI()
+ );
+ return ResponseEntity
+ .status(HttpStatus.BAD_REQUEST)
+ .body(error);
+ }
+
@ExceptionHandler({TooManyRequestsException.class})
public ResponseEntity handleTooManyRequestsException(TooManyRequestsException ex,
HttpServletRequest request) {
diff --git a/event-service/event-service-app/src/main/java/com/example/event_service_app/configs/QuartzConfig.java b/event-service/event-service-app/src/main/java/com/example/event_service_app/configs/QuartzConfig.java
index 5b95270..7847426 100644
--- a/event-service/event-service-app/src/main/java/com/example/event_service_app/configs/QuartzConfig.java
+++ b/event-service/event-service-app/src/main/java/com/example/event_service_app/configs/QuartzConfig.java
@@ -1,6 +1,7 @@
package com.example.event_service_app.configs;
import com.example.event_service_app.scheduler.EventReminderJob;
+import com.example.event_service_app.scheduler.EventStatusUpdateJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -8,14 +9,18 @@
@Configuration
public class QuartzConfig {
- private static final String JOB_NAME = "eventReminderJob";
- private static final String JOB_GROUP = "reminderGroup";
- private static final String TRIGGER_NAME = "eventReminderTrigger";
+ private static final String REMINDER_JOB_NAME = "eventReminderJob";
+ private static final String REMINDER_JOB_GROUP = "reminderGroup";
+ private static final String REMINDER_TRIGGER_NAME = "eventReminderTrigger";
+
+ private static final String STATUS_JOB_NAME = "eventStatusUpdateJob";
+ private static final String STATUS_JOB_GROUP = "statusGroup";
+ private static final String STATUS_TRIGGER_NAME = "eventStatusUpdateTrigger";
@Bean
public JobDetail eventReminderJobDetail() {
return JobBuilder.newJob(EventReminderJob.class)
- .withIdentity(JOB_NAME, JOB_GROUP)
+ .withIdentity(REMINDER_JOB_NAME, REMINDER_JOB_GROUP)
.withDescription("24 saat içinde başlayacak etkinlikler için Kafka'ya hatırlatıcı event gönderir")
.storeDurably() // trigger olmasa da silinmesin
.requestRecovery() // node crash sonrası yeniden çalıştır
@@ -27,7 +32,7 @@ public Trigger eventReminderTrigger(JobDetail eventReminderJobDetail) {
// Her saat başı çalışır
return TriggerBuilder.newTrigger()
.forJob(eventReminderJobDetail)
- .withIdentity(TRIGGER_NAME, JOB_GROUP)
+ .withIdentity(REMINDER_TRIGGER_NAME, REMINDER_JOB_GROUP)
.withDescription("Saatlik reminder trigger")
.withSchedule(
CronScheduleBuilder.cronSchedule("0 0 * * * ?") // her saat başı
@@ -35,5 +40,29 @@ public Trigger eventReminderTrigger(JobDetail eventReminderJobDetail) {
)
.build();
}
+
+ @Bean
+ public JobDetail eventStatusUpdateJobDetail() {
+ return JobBuilder.newJob(EventStatusUpdateJob.class)
+ .withIdentity(STATUS_JOB_NAME, STATUS_JOB_GROUP)
+ .withDescription("Event status'lerini otomatik olarak günceller (UPCOMING -> ONGOING -> COMPLETED)")
+ .storeDurably()
+ .requestRecovery()
+ .build();
+ }
+
+ @Bean
+ public Trigger eventStatusUpdateTrigger(JobDetail eventStatusUpdateJobDetail) {
+ // Her 5 dakikada bir çalışır
+ return TriggerBuilder.newTrigger()
+ .forJob(eventStatusUpdateJobDetail)
+ .withIdentity(STATUS_TRIGGER_NAME, STATUS_JOB_GROUP)
+ .withDescription("5 dakikalık event status update trigger")
+ .withSchedule(
+ CronScheduleBuilder.cronSchedule("0 0/5 * * * ?") // her 5 dakika
+ .withMisfireHandlingInstructionFireAndProceed()
+ )
+ .build();
+ }
}
diff --git a/event-service/event-service-app/src/main/java/com/example/event_service_app/repository/EventRepository.java b/event-service/event-service-app/src/main/java/com/example/event_service_app/repository/EventRepository.java
index e12ad2c..6c1f855 100644
--- a/event-service/event-service-app/src/main/java/com/example/event_service_app/repository/EventRepository.java
+++ b/event-service/event-service-app/src/main/java/com/example/event_service_app/repository/EventRepository.java
@@ -5,8 +5,11 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import java.time.LocalDateTime;
+import java.util.List;
public interface EventRepository extends JpaRepository {
Page findByCategoryId(Long categoryId, Pageable pageable);
@@ -14,5 +17,11 @@ public interface EventRepository extends JpaRepository {
Page findByStartDateBetween(LocalDateTime start, LocalDateTime end, Pageable pageable);
Page findByStatus(EventStatus status, Pageable pageable);
Page findByOwnerId(Long ownerId, Pageable pageable);
+
+ @Query("SELECT e FROM Event e WHERE e.status = :status AND e.endDate < :now")
+ List findByStatusAndEndDateBefore(@Param("status") EventStatus status, @Param("now") LocalDateTime now);
+
+ @Query("SELECT e FROM Event e WHERE e.status = :status AND e.startDate <= :now AND e.endDate >= :now")
+ List findByStatusAndStartDateBeforeAndEndDateAfter(@Param("status") EventStatus status, @Param("now") LocalDateTime now);
}
diff --git a/event-service/event-service-app/src/main/java/com/example/event_service_app/scheduler/EventStatusUpdateJob.java b/event-service/event-service-app/src/main/java/com/example/event_service_app/scheduler/EventStatusUpdateJob.java
new file mode 100644
index 0000000..144d658
--- /dev/null
+++ b/event-service/event-service-app/src/main/java/com/example/event_service_app/scheduler/EventStatusUpdateJob.java
@@ -0,0 +1,63 @@
+package com.example.event_service_app.scheduler;
+
+import com.example.event_service_app.entity.Event;
+import com.example.event_service_app.repository.EventRepository;
+import com.example.event_service_client.enums.EventStatus;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@DisallowConcurrentExecution
+public class EventStatusUpdateJob extends QuartzJobBean {
+
+ private final EventRepository eventRepository;
+
+ @Override
+ @Transactional
+ protected void executeInternal(JobExecutionContext context) {
+ LocalDateTime now = LocalDateTime.now();
+
+ // UPCOMING -> ONGOING (startDate <= now < endDate)
+ List upcomingToOngoing = eventRepository.findByStatusAndStartDateBeforeAndEndDateAfter(
+ EventStatus.UPCOMING, now);
+
+ for (Event event : upcomingToOngoing) {
+ event.setStatus(EventStatus.ONGOING);
+ eventRepository.save(event);
+ log.info("[EventStatusUpdateJob] Event {} status updated from UPCOMING to ONGOING", event.getId());
+ }
+
+ // ONGOING -> COMPLETED (endDate < now)
+ List ongoingToCompleted = eventRepository.findByStatusAndEndDateBefore(
+ EventStatus.ONGOING, now);
+
+ for (Event event : ongoingToCompleted) {
+ event.setStatus(EventStatus.COMPLETED);
+ eventRepository.save(event);
+ log.info("[EventStatusUpdateJob] Event {} status updated from ONGOING to COMPLETED", event.getId());
+ }
+
+ // UPCOMING -> COMPLETED (endDate < now, missed ONGOING window)
+ List upcomingToCompleted = eventRepository.findByStatusAndEndDateBefore(
+ EventStatus.UPCOMING, now);
+
+ for (Event event : upcomingToCompleted) {
+ event.setStatus(EventStatus.COMPLETED);
+ eventRepository.save(event);
+ log.info("[EventStatusUpdateJob] Event {} status updated from UPCOMING to COMPLETED", event.getId());
+ }
+
+ int total = upcomingToOngoing.size() + ongoingToCompleted.size() + upcomingToCompleted.size();
+ log.info("[EventStatusUpdateJob] Total {} events updated", total);
+ }
+}
diff --git a/event-service/event-service-app/src/main/java/com/example/event_service_app/serviceImpl/ParticipationServiceImpl.java b/event-service/event-service-app/src/main/java/com/example/event_service_app/serviceImpl/ParticipationServiceImpl.java
index 12d1b83..b566e39 100644
--- a/event-service/event-service-app/src/main/java/com/example/event_service_app/serviceImpl/ParticipationServiceImpl.java
+++ b/event-service/event-service-app/src/main/java/com/example/event_service_app/serviceImpl/ParticipationServiceImpl.java
@@ -4,7 +4,9 @@
import com.example.ems_common.dto.NotificationEventType;
import com.example.ems_common.exceptions.AlreadyExistsException;
import com.example.ems_common.exceptions.CannotJoinOwnEventException;
+import com.example.ems_common.exceptions.EventAlreadyEndedException;
import com.example.ems_common.exceptions.NotFoundException;
+import com.example.event_service_client.enums.EventStatus;
import com.example.ems_common.security.SecurityUtils;
import com.example.event_service_app.entity.Event;
import com.example.event_service_app.entity.OutboxEvent;
@@ -48,6 +50,12 @@ public ParticipationResponseDto register(ParticipationCreateDto dto) {
throw new CannotJoinOwnEventException("You cannot join an event that you have created");
}
+ if (event.getEndDate().isBefore(LocalDateTime.now()) ||
+ event.getStatus() == EventStatus.COMPLETED ||
+ event.getStatus() == EventStatus.CANCELLED) {
+ throw new EventAlreadyEndedException("Event has already ended or been cancelled");
+ }
+
if (participationRepository.existsByEventIdAndParticipantId(dto.getEventId(), currentUserId)) {
throw new AlreadyExistsException("Already registered for this event");
}