diff --git a/.gitignore b/.gitignore
index 332b36a..7890366 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,6 @@ build/
### Environment Variables ###
src/main/resources/.env.properties
+
+### DB Data ###
+db/redis/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 0976592..16784ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,12 @@
spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
com.h2database
@@ -129,6 +135,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/seyed/ali/timeentryservice/config/cache/RedisConfiguration.java b/src/main/java/com/seyed/ali/timeentryservice/config/cache/RedisConfiguration.java
new file mode 100644
index 0000000..505e37b
--- /dev/null
+++ b/src/main/java/com/seyed/ali/timeentryservice/config/cache/RedisConfiguration.java
@@ -0,0 +1,52 @@
+package com.seyed.ali.timeentryservice.config.cache;
+
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericToStringSerializer;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+
+@Configuration
+@EnableCaching
+public class RedisConfiguration {
+
+ /**
+ * Remember to configure the properties in application.yml or properties file.
+ */
+ @Bean
+ @Primary
+ public RedisProperties properties() {
+ return new RedisProperties();
+ }
+
+ /**
+ * Lettuce is non-blocking, and Jedis is blocking.
+ */
+ @Bean
+ public LettuceConnectionFactory lettuceConnectionFactory() {
+ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
+ redisStandaloneConfiguration.setHostName(this.properties().getHost());
+ redisStandaloneConfiguration.setPort(this.properties().getPort());
+
+ return new LettuceConnectionFactory(redisStandaloneConfiguration);
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate() {
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+
+ redisTemplate.setConnectionFactory(this.lettuceConnectionFactory());
+ redisTemplate.setEnableTransactionSupport(true);
+
+ redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
+ redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Object.class));
+
+ return redisTemplate;
+ }
+
+}
diff --git a/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryController.java b/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryController.java
index 1af36eb..963d5e8 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryController.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryController.java
@@ -42,7 +42,7 @@ public ResponseEntity getTimeEntries() {
));
}
- @GetMapping("/{userId}")
+ @GetMapping("/user/{userId}")
public ResponseEntity getUsersTimeEntry(@PathVariable String userId) {
return ResponseEntity.ok(new Result(
true,
@@ -52,6 +52,16 @@ public ResponseEntity getUsersTimeEntry(@PathVariable String userId) {
));
}
+ @GetMapping("/{timeEntryId}")
+ public ResponseEntity getSpecificTimeEntry(@PathVariable String timeEntryId) {
+ return ResponseEntity.ok(new Result(
+ true,
+ OK,
+ "Time entry: '" + timeEntryId + "'.",
+ this.timeEntryService.getTimeEntryById(timeEntryId)
+ ));
+ }
+
@PostMapping
public ResponseEntity addTimeEntryManually(@Valid @RequestBody TimeEntryDTO timeEntryDTO) {
return ResponseEntity.status(CREATED).body(new Result(
diff --git a/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingController.java b/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingController.java
index a3049d1..c46ed10 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingController.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingController.java
@@ -24,18 +24,11 @@ public class TimeEntryTrackingController {
@PostMapping("/start")
public ResponseEntity startTrackingTimeEntry(@Valid @RequestBody TimeBillingDTO timeBillingDTO) {
- boolean billable = false;
- BigDecimal hourlyRate = BigDecimal.ZERO;
-
- if (timeBillingDTO != null) {
- billable = timeBillingDTO.billable();
- hourlyRate = timeBillingDTO.hourlyRate();
- }
return ResponseEntity.status(CREATED).body(new Result(
true,
CREATED,
"Time tracking started...",
- this.timeEntryService.startTrackingTimeEntry(billable, hourlyRate)
+ this.timeEntryService.startTrackingTimeEntry(timeBillingDTO)
));
}
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 2a71ca7..57908cb 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryServiceImpl.java
@@ -11,6 +11,8 @@
import com.seyed.ali.timeentryservice.util.TimeParser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -44,6 +46,20 @@ public TimeEntryResponse getUsersTimeEntry(String userId) {
return this.timeEntryUtility.convertToTimeEntryResponse(timeEntry);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+// @Cacheable(
+// cacheNames = "time-entry-cache",
+// key = "#timeEntryId"
+// )
+ public TimeEntryResponse getTimeEntryById(String timeEntryId) {
+ return this.timeEntryRepository.findById(timeEntryId)
+ .map(this.timeEntryUtility::convertToTimeEntryResponse)
+ .orElseThrow(()-> new ResourceNotFoundException("Time entry with ID: '" + timeEntryId +"' was not found."));
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImpl.java b/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImpl.java
index aff0c4e..c5cafb1 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImpl.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImpl.java
@@ -3,6 +3,7 @@
import com.seyed.ali.timeentryservice.client.AuthenticationServiceClient;
import com.seyed.ali.timeentryservice.exceptions.OperationNotSupportedException;
import com.seyed.ali.timeentryservice.model.domain.TimeEntry;
+import com.seyed.ali.timeentryservice.model.dto.TimeBillingDTO;
import com.seyed.ali.timeentryservice.model.dto.TimeEntryDTO;
import com.seyed.ali.timeentryservice.repository.TimeEntryRepository;
import com.seyed.ali.timeentryservice.service.interfaces.TimeEntryTrackingService;
@@ -10,6 +11,8 @@
import com.seyed.ali.timeentryservice.util.TimeParser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -33,7 +36,19 @@ public class TimeEntryTrackingServiceImpl implements TimeEntryTrackingService {
*/
@Override
@Transactional
- public String startTrackingTimeEntry(boolean billable, BigDecimal hourlyRate) {
+ @CachePut(
+ cacheNames = "time-entry-cache",
+ key = "#result"
+ )
+ public String startTrackingTimeEntry(TimeBillingDTO timeBillingDTO) {
+ boolean billable = false;
+ BigDecimal hourlyRate = BigDecimal.ZERO;
+
+ if (timeBillingDTO != null) {
+ billable = timeBillingDTO.billable();
+ hourlyRate = timeBillingDTO.hourlyRate();
+ }
+
TimeEntry timeEntry = this.timeEntryUtility.createNewTimeEntry(billable, hourlyRate, this.authenticationServiceClient);
this.timeEntryRepository.save(timeEntry);
return timeEntry.getTimeEntryId();
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 13cfd9e..ff3de86 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
@@ -27,6 +27,15 @@ public interface TimeEntryService {
*/
TimeEntryResponse getUsersTimeEntry(String userId);
+ /**
+ * Retrieves a specific time entry.
+ *
+ * @param timeEntryId The ID of the time entry.
+ * @return A TimeEntryResponse object representing the found time entry.
+ * @throws ResourceNotFoundException if the time entry is not found.
+ */
+ TimeEntryResponse getTimeEntryById(String timeEntryId);
+
/**
* Adds a new time entry manually.
*
diff --git a/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryTrackingService.java b/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryTrackingService.java
index 221b455..b2fcac6 100644
--- a/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryTrackingService.java
+++ b/src/main/java/com/seyed/ali/timeentryservice/service/interfaces/TimeEntryTrackingService.java
@@ -1,9 +1,8 @@
package com.seyed.ali.timeentryservice.service.interfaces;
+import com.seyed.ali.timeentryservice.model.dto.TimeBillingDTO;
import com.seyed.ali.timeentryservice.model.dto.TimeEntryDTO;
-import java.math.BigDecimal;
-
/**
* Interface for Time Entry tracking service operations.
*/
@@ -12,11 +11,10 @@ public interface TimeEntryTrackingService {
/**
* Starts tracking a new time entry.
*
- * @param billable A boolean indicating whether the time entry is billable or not.
- * @param hourlyRate The hourly rate for the time entry (if billable).
+ * @param timeBillingDTO A dto class with: A boolean indicating whether the time entry is billable or not & The hourly rate for the time entry (if billable).
* @return The ID of the created time entry.
*/
- String startTrackingTimeEntry(boolean billable, BigDecimal hourlyRate);
+ String startTrackingTimeEntry(TimeBillingDTO timeBillingDTO);
/**
* Stops tracking an existing time entry.
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 532c102..fc151f9 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -44,6 +44,20 @@ spring:
jwt:
issuer-uri: http://${KEYCLOAK_SERVER_HOST:localhost}:8080/realms/DevVault-v2.0
+--- # Redis
+spring:
+ data:
+ redis:
+ host: ${REDIS_HOST:localhost}
+ port: ${REDIS_PORT:6379}
+ cache:
+ type: redis
+ cache-names:
+ - time-entry-cache
+ redis:
+ cache-null-values: true
+ time-to-live: ${REDIS_CACHE_TIME_TO_LIVE:600000} # 1 hour
+
--- # Swagger
springdoc:
swagger-ui:
diff --git a/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryControllerTest.java b/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryControllerTest.java
index d8afed8..f881465 100644
--- a/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryControllerTest.java
+++ b/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryControllerTest.java
@@ -135,7 +135,7 @@ public void getUsersTimeEntryTest() throws Exception {
// When
ResultActions resultActions = this.mockMvc.perform(
- MockMvcRequestBuilders.get(this.baseUrl + "/" + userId)
+ MockMvcRequestBuilders.get(this.baseUrl + "/user/" + userId)
.accept(APPLICATION_JSON)
.with(jwt().authorities(new SimpleGrantedAuthority(some_authority)))
);
diff --git a/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingControllerTest.java b/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingControllerTest.java
index 392f65f..96cae16 100644
--- a/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingControllerTest.java
+++ b/src/test/java/com/seyed/ali/timeentryservice/controller/TimeEntryTrackingControllerTest.java
@@ -60,7 +60,7 @@ void setUp() {
public void startTrackingTimeEntryTest() throws Exception {
// given
String timeEntryId = "some_time_entry_id";
- when(this.timeEntryTrackingService.startTrackingTimeEntry(isA(Boolean.class), isA(BigDecimal.class)))
+ when(this.timeEntryTrackingService.startTrackingTimeEntry(isA(TimeBillingDTO.class)))
.thenReturn(timeEntryId);
String json = this.objectMapper.writeValueAsString(new TimeBillingDTO(true, BigDecimal.ONE));
diff --git a/src/test/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImplTest.java b/src/test/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImplTest.java
index 2cc1a93..835d84a 100644
--- a/src/test/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImplTest.java
+++ b/src/test/java/com/seyed/ali/timeentryservice/service/TimeEntryTrackingServiceImplTest.java
@@ -3,6 +3,7 @@
import com.seyed.ali.timeentryservice.client.AuthenticationServiceClient;
import com.seyed.ali.timeentryservice.model.domain.TimeEntry;
import com.seyed.ali.timeentryservice.model.domain.TimeSegment;
+import com.seyed.ali.timeentryservice.model.dto.TimeBillingDTO;
import com.seyed.ali.timeentryservice.model.dto.TimeEntryDTO;
import com.seyed.ali.timeentryservice.repository.TimeEntryRepository;
import com.seyed.ali.timeentryservice.util.TimeEntryUtility;
@@ -84,7 +85,8 @@ public void startTrackingTimeEntryTest() {
when(this.timeEntryRepository.save(isA(TimeEntry.class))).thenReturn(timeEntry);
// Act
- String timeEntryId = this.timeEntryTrackingService.startTrackingTimeEntry(false, BigDecimal.ONE);
+ TimeBillingDTO timeBillingDTO = new TimeBillingDTO(false, BigDecimal.ZERO);
+ String timeEntryId = this.timeEntryTrackingService.startTrackingTimeEntry(timeBillingDTO);
System.out.println(timeEntryId);
// Assert