diff --git a/pom.xml b/pom.xml
index 16784ec..ea91cf3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,12 @@
spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
org.springframework.boot
@@ -106,6 +112,11 @@
org.springframework.security
spring-security-test
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
diff --git a/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventListener.java b/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventListener.java
new file mode 100644
index 0000000..fe6f6c9
--- /dev/null
+++ b/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventListener.java
@@ -0,0 +1,33 @@
+package com.seyed.ali.timeentryservice.event;
+
+import com.seyed.ali.timeentryservice.model.enums.OperationType;
+import com.seyed.ali.timeentryservice.model.payload.ProjectDTO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ProjectEventListener {
+
+ private final ProjectEventService projectEventService;
+
+ @KafkaListener(
+ topics = "${spring.kafka.topic.name}",
+ groupId = "${spring.kafka.consumer.group-id}" // if we change the group-id name, the events from the previous data will also be logged, otherwise, only the new events will be logged!
+ )
+ public void handleProjectEvent(@Payload ConsumerRecord record, @Header("OperationType") String operationType) {
+ ProjectDTO projectDTO = record.value();
+ switch (OperationType.valueOf(operationType)) {
+ case DELETE -> this.projectEventService.handleDeleteOperation(projectDTO);
+ case DETACH -> this.projectEventService.handleDetachOperation(projectDTO);
+ default -> log.warn("\"{}\" operation type not supported.", operationType);
+ }
+ }
+
+}
diff --git a/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventService.java b/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventService.java
new file mode 100644
index 0000000..2a4dfed
--- /dev/null
+++ b/src/main/java/com/seyed/ali/timeentryservice/event/ProjectEventService.java
@@ -0,0 +1,36 @@
+package com.seyed.ali.timeentryservice.event;
+
+import com.seyed.ali.timeentryservice.model.payload.ProjectDTO;
+import com.seyed.ali.timeentryservice.repository.TimeEntryRepository;
+import com.seyed.ali.timeentryservice.service.interfaces.TimeEntryService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ProjectEventService {
+
+ private final TimeEntryService timeEntryService;
+ private final TimeEntryRepository timeEntryRepository;
+
+ public void handleDeleteOperation(ProjectDTO projectDTO) {
+ log.info("\"Delete\" project, associated tasks & time entries: {}", projectDTO);
+ this.timeEntryRepository.findByProjectId(projectDTO.getProjectId())
+ .forEach(this.timeEntryService::deleteTimeEntry);
+ }
+
+ public void handleDetachOperation(ProjectDTO projectDTO) {
+ log.info("\"Detach\" project, associated tasks & time entries: {}", projectDTO);
+ this.timeEntryRepository.findByProjectId(projectDTO.getProjectId())
+ .forEach(timeEntry -> {
+ timeEntry.setProjectId(null);
+ timeEntry.setTaskId(null);
+ this.timeEntryRepository.save(timeEntry);
+ });
+ }
+
+}
diff --git a/src/main/java/com/seyed/ali/timeentryservice/model/enums/OperationType.java b/src/main/java/com/seyed/ali/timeentryservice/model/enums/OperationType.java
new file mode 100644
index 0000000..d402fa4
--- /dev/null
+++ b/src/main/java/com/seyed/ali/timeentryservice/model/enums/OperationType.java
@@ -0,0 +1,7 @@
+package com.seyed.ali.timeentryservice.model.enums;
+
+public enum OperationType {
+
+ DELETE, DETACH
+
+}
diff --git a/src/main/java/com/seyed/ali/timeentryservice/model/payload/ProjectDTO.java b/src/main/java/com/seyed/ali/timeentryservice/model/payload/ProjectDTO.java
new file mode 100644
index 0000000..0bb96bf
--- /dev/null
+++ b/src/main/java/com/seyed/ali/timeentryservice/model/payload/ProjectDTO.java
@@ -0,0 +1,39 @@
+package com.seyed.ali.timeentryservice.model.payload;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * DTO for {@link com.seyed.ali.projectservice.model.domain.Project}
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ProjectDTO implements Serializable {
+
+ @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the project", example = "12345")
+ private String projectId;
+
+ @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The project name", example = "Microservices-Springboot")
+ private String projectName;
+
+ @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The project name", example = "Learning microservices is really exciting and HARD ;)")
+ private String projectDescription;
+
+ @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The task associated with the project", implementation = TaskDTO.class)
+ private List taskDTO = new ArrayList<>();
+
+ public ProjectDTO(String projectId, String projectName, String projectDescription) {
+ this.projectId = projectId;
+ this.projectName = projectName;
+ this.projectDescription = projectDescription;
+ this.taskDTO = new ArrayList<>();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/seyed/ali/timeentryservice/repository/TimeEntryRepository.java b/src/main/java/com/seyed/ali/timeentryservice/repository/TimeEntryRepository.java
index 5dac1b1..56e9ed5 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/repository/TimeEntryRepository.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/repository/TimeEntryRepository.java
@@ -3,6 +3,7 @@
import com.seyed.ali.timeentryservice.model.domain.TimeEntry;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
import java.util.Optional;
public interface TimeEntryRepository extends JpaRepository {
@@ -11,4 +12,8 @@ public interface TimeEntryRepository extends JpaRepository {
TimeEntry findByUserIdAndTimeEntryId(String userId, String timeEntryId);
+ List findByProjectId(String projectId);
+
+ List findByTaskId(String taskId);
+
}
\ No newline at end of file
diff --git a/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java b/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java
index e952dad..f24f63d 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java
@@ -113,4 +113,19 @@ public void deleteTimeEntry(String timeEntryId) {
this.timeEntryRepository.deleteById(timeEntryId);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @Transactional
+ @CacheEvict(
+ cacheNames = TimeEntryCacheManager.TIME_ENTRY_CACHE,
+ key = "#timeEntry.timeEntryId"
+ )
+ public void deleteTimeEntry(TimeEntry timeEntry) {
+ TimeEntry foundTimeEntry = this.timeEntryRepository.findById(timeEntry.getTimeEntryId())
+ .orElseThrow(() -> new ResourceNotFoundException("The provided timeEntryId does not exist"));
+ this.timeEntryRepository.delete(foundTimeEntry);
+ }
+
}
diff --git a/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryService.java b/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryService.java
index e9d2fc7..a654317 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryService.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryService.java
@@ -55,10 +55,17 @@ public interface TimeEntryService {
TimeEntry updateTimeEntryManually(String id, TimeEntryDTO timeEntryDTO);
/**
- * Deletes a time entry.
+ * Deletes a time entry by ID.
*
* @param timeEntryId The ID of the time entry to be deleted.
*/
void deleteTimeEntry(String timeEntryId);
+ /**
+ * Deletes a time entry.
+ *
+ * @param timeEntry The time entry to be deleted.
+ */
+ void deleteTimeEntry(TimeEntry timeEntry);
+
}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index fc151f9..f91d153 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -70,4 +70,23 @@ authentication:
service:
user-persistence-controller:
base-url: http://${AUTHENTICATION_SERVICE_HANDLE_USER_BASE_URL:localhost:8081}/keycloak-user
- handle-user-url: /handle-user
\ No newline at end of file
+ handle-user-url: /handle-user
+
+--- # Kafka
+spring:
+ kafka:
+ consumer:
+ bootstrap-servers: localhost:9092
+ group-id: project_group_timeentry
+ auto-offset-reset: earliest
+
+ #configure deserialize classes for key & value pair
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ properties:
+ spring.json.trusted.packages: "*"
+ spring.json.type.mapping: ProjectEvent:com.seyed.ali.timeentryservice.model.payload.ProjectDTO
+
+ #custom
+ topic:
+ name: project_name
\ No newline at end of file
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
index 9f6ba4a..87d60e7 100644
--- a/src/test/resources/application-test.yml
+++ b/src/test/resources/application-test.yml
@@ -38,4 +38,23 @@ authentication:
service:
user-persistence-controller:
base-url: http://${AUTHENTICATION_SERVICE_HANDLE_USER_BASE_URL:localhost:8081}/keycloak-user
- handle-user-url: /handle-user
\ No newline at end of file
+ handle-user-url: /handle-user
+
+--- # Kafka
+spring:
+ kafka:
+ consumer:
+ bootstrap-servers: localhost:9092
+ group-id: project_group_timeentry
+ auto-offset-reset: earliest
+
+ #configure deserialize classes for key & value pair
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ properties:
+ spring.json.trusted.packages: "*"
+ spring.json.type.mapping: ProjectEvent:com.seyed.ali.timeentryservice.model.payload.ProjectDTO
+
+ #custom
+ topic:
+ name: project_name
\ No newline at end of file