From e3eae98f8aa258a0c99ab76713c246ad04ffe65e Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Wed, 4 Sep 2024 20:33:36 +0200 Subject: [PATCH 01/11] chore: makes the app cacheable with Redis --- backend/pom.xml | 4 ++++ .../com/greenfoxacademy/backend/BackendApplication.java | 2 ++ backend/src/main/resources/application.properties | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/pom.xml b/backend/pom.xml index 455b02c5..ba7641b8 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -108,6 +108,10 @@ spring-boot-starter-mail 3.3.2 + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/backend/src/main/java/com/greenfoxacademy/backend/BackendApplication.java b/backend/src/main/java/com/greenfoxacademy/backend/BackendApplication.java index d4092c00..4eea1d00 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/BackendApplication.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/BackendApplication.java @@ -2,11 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; /** * This is the main class of the application. */ @SpringBootApplication +@EnableCaching public class BackendApplication { public static void main(String[] args) { diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4bdc3553..cba1486c 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -25,4 +25,9 @@ spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true # Where the verification link will redirect -email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} \ No newline at end of file +email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} + +# Redis configuration +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.cache.type=redis From eeeded76d70eefc33f5ee5023918c0e276b48ccd Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Wed, 4 Sep 2024 20:51:00 +0200 Subject: [PATCH 02/11] feat: makes login and load user cacheable --- .../backend/services/user/UserServiceImpl.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index 9a1c5602..eb2634d5 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -13,8 +13,11 @@ import com.greenfoxacademy.backend.services.auth.AuthService; import com.greenfoxacademy.backend.services.mail.EmailService; import jakarta.transaction.Transactional; + import java.util.UUID; + import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; @@ -47,9 +50,9 @@ public RegisterResponseDto register(RegisterRequestDto registerRequestDto) try { User saved = userRepository.save(user); emailService.sendRegistrationEmail( - saved.getEmail(), - saved.getFirstName(), - saved.getVerificationId() + saved.getEmail(), + saved.getFirstName(), + saved.getVerificationId() ); return new RegisterResponseDto(saved.getId()); } catch (Exception e) { @@ -57,7 +60,7 @@ public RegisterResponseDto register(RegisterRequestDto registerRequestDto) } } - + @Cacheable @Override public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception { User user = userRepository.findByEmail(loginRequestDto.email()) @@ -79,7 +82,7 @@ public ProfileUpdateResponseDto profileUpdate( .orElseThrow(() -> new UsernameNotFoundException("User not found")); if ( userRepository.existsByEmail(profileUpdateRequestDto.email()) - && !email.equals(profileUpdateRequestDto.email()) + && !email.equals(profileUpdateRequestDto.email()) ) { throw new CannotUpdateUserException("Email is already taken!"); } @@ -92,6 +95,7 @@ public ProfileUpdateResponseDto profileUpdate( return new ProfileUpdateResponseDto(authService.generateToken(updatedUser)); } + @Cacheable @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByEmail(username) From b280481d552577bfb7b3f18a0218f55a0db8f4ab Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Wed, 4 Sep 2024 21:47:02 +0200 Subject: [PATCH 03/11] feat: updating and deleting the profile clears the cache --- .../greenfoxacademy/backend/services/user/UserServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index eb2634d5..3350af4a 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -17,6 +17,7 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -73,6 +74,7 @@ public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception return new LoginResponseDto(authService.generateToken(user)); } + @CacheEvict @Override public ProfileUpdateResponseDto profileUpdate( String email, @@ -105,6 +107,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx /** * Delete the user by username. */ + @CacheEvict @Transactional @Override public void deleteProfile(String username) { From b69c840048cd359ca1e3bce1c33faeec926e4fb0 Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 20:29:30 +0200 Subject: [PATCH 04/11] chore: configure TTL with Redis --- .../backend/config/RedisConfig.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java diff --git a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java new file mode 100644 index 00000000..6ad316ac --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java @@ -0,0 +1,33 @@ +package com.greenfoxacademy.backend.config; + +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +/** + * RedisConfig is a configuration class that defines the cache management strategy + * for Redis in the Spring Boot application. It configures the Redis Cache Manager + * and sets key serialization and expiration policies. + */ + +@Configuration +public class RedisConfig { + + @Bean + public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(5)) // Set TTL to 5 minutes + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); + + return RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(cacheConfig) + .build(); + } +} \ No newline at end of file From 74df238d4fed64d204ba088bdaaab8f8c0f7600b Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 21:47:11 +0200 Subject: [PATCH 05/11] chore: configure Redis container --- .../com/greenfoxacademy/backend/config/RedisConfig.java | 3 ++- backend/src/main/resources/application.properties | 4 ++-- docker-compose.yaml | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java index 6ad316ac..a7d5fd6a 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java @@ -23,7 +23,8 @@ public class RedisConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofMinutes(5)) // Set TTL to 5 minutes + .entryTtl(Duration.ofMinutes(5)) // Set TTL to 5 minutes + .disableCachingNullValues() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); return RedisCacheManager.builder(redisConnectionFactory) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index cba1486c..8482850c 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -28,6 +28,6 @@ spring.mail.properties.mail.smtp.starttls.enable=true email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} # Redis configuration -spring.data.redis.host=localhost -spring.data.redis.port=6379 +spring.data.redis.host=${CACHE_URL:localhost} +spring.data.redis.port=${CACHE_PORT:6379} spring.cache.type=redis diff --git a/docker-compose.yaml b/docker-compose.yaml index c29112e5..ae76a93b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,6 +10,11 @@ services: POSTGRES_PASSWORD: petclinic_db_password POSTGRES_DB: petclinic_db + redis-cache: + image: redis + ports: + - 6379:6379 + frontend: build: context: ./frontend @@ -28,5 +33,8 @@ services: - DATABASE_USER=petclinic_db_user - DATABASE_PASSWORD=petclinic_db_password - CORS_URLS=http://localhost:5173 + - CACHE_PORT=6379 + - CACHE_URL=redis://redis-cache depends_on: - postgres-db + - redis-cache From a81140995316ed7765116490bef353d5c9dfab98 Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 22:11:13 +0200 Subject: [PATCH 06/11] chore: adds key - value pair to the login cache --- .../java/com/greenfoxacademy/backend/config/RedisConfig.java | 2 +- .../com/greenfoxacademy/backend/dtos/LoginResponseDto.java | 5 ++++- .../backend/services/user/UserServiceImpl.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java index a7d5fd6a..899e6f40 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java @@ -23,7 +23,7 @@ public class RedisConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofMinutes(5)) // Set TTL to 5 minutes + .entryTtl(Duration.ofMinutes(3)) // Set TTL to 5 minutes .disableCachingNullValues() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java index fef2c72e..7b2207bf 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java @@ -1,12 +1,15 @@ package com.greenfoxacademy.backend.dtos; +import java.io.Serializable; + /** * This class is responsible for the response of the login endpoint. * * @param token the token that is returned to identify the user */ + public record LoginResponseDto( String token -) { +) implements Serializable { } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index 3350af4a..066472b8 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -61,7 +61,7 @@ public RegisterResponseDto register(RegisterRequestDto registerRequestDto) } } - @Cacheable + @Cacheable (value = "login-cache", key = "#loginRequestDto.email()") @Override public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception { User user = userRepository.findByEmail(loginRequestDto.email()) From 44164818a59204d917cff6af13a39e6b59e82540 Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 22:14:27 +0200 Subject: [PATCH 07/11] fix: eliminates cache for login due to security reasons --- .../com/greenfoxacademy/backend/dtos/LoginResponseDto.java | 4 +--- .../backend/services/user/UserServiceImpl.java | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java index 7b2207bf..2186f2c9 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/LoginResponseDto.java @@ -1,8 +1,6 @@ package com.greenfoxacademy.backend.dtos; -import java.io.Serializable; - /** * This class is responsible for the response of the login endpoint. * @@ -11,5 +9,5 @@ public record LoginResponseDto( String token -) implements Serializable { +) { } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index 066472b8..710961ee 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -60,8 +60,7 @@ public RegisterResponseDto register(RegisterRequestDto registerRequestDto) throw new UserAlreadyExistsError("Email is already taken!"); } } - - @Cacheable (value = "login-cache", key = "#loginRequestDto.email()") + @Override public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception { User user = userRepository.findByEmail(loginRequestDto.email()) From b2242e37fe43da49569205a854898ac06b8ec16a Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 22:35:06 +0200 Subject: [PATCH 08/11] chore: added key - value pairs to the cacheable and cacheevict annotations --- .../backend/services/user/UserServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index 710961ee..a21a0a15 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -60,7 +60,7 @@ public RegisterResponseDto register(RegisterRequestDto registerRequestDto) throw new UserAlreadyExistsError("Email is already taken!"); } } - + @Override public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception { User user = userRepository.findByEmail(loginRequestDto.email()) @@ -73,7 +73,7 @@ public LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception return new LoginResponseDto(authService.generateToken(user)); } - @CacheEvict + @CacheEvict(value = "update-profile-cache", key = "#email") @Override public ProfileUpdateResponseDto profileUpdate( String email, @@ -96,7 +96,7 @@ public ProfileUpdateResponseDto profileUpdate( return new ProfileUpdateResponseDto(authService.generateToken(updatedUser)); } - @Cacheable + @Cacheable(value = "profile-cache", key = "#username") @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByEmail(username) @@ -106,7 +106,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx /** * Delete the user by username. */ - @CacheEvict + @CacheEvict(value = "delete-cache", key = "#username") @Transactional @Override public void deleteProfile(String username) { From b0fc8b1ef99f1fd32a53824b8451c1d549035302 Mon Sep 17 00:00:00 2001 From: Anna Ugrai Date: Thu, 5 Sep 2024 22:56:22 +0200 Subject: [PATCH 09/11] chore: add cache dependencies to test app properties --- backend/src/test/resources/application.properties | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index ba4ed94d..195fa5c5 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -23,4 +23,9 @@ spring.mail.properties.mail.smtp.starttls.enable=true cors.urls=${CORS_URLS:http://localhost:5173} # Where the verification link will redirect -email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} \ No newline at end of file +email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} + +# Redis configuration +spring.data.redis.host=${CACHE_URL:localhost} +spring.data.redis.port=${CACHE_PORT:6379} +spring.cache.type=none \ No newline at end of file From 7d302d040831b3a82388d8018898fd79f22cd3b9 Mon Sep 17 00:00:00 2001 From: markkovari Date: Fri, 6 Sep 2024 10:34:44 +0200 Subject: [PATCH 10/11] chore: make redis connection conditional --- .../backend/config/RedisConfig.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java index 899e6f40..2a1cc2db 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/config/RedisConfig.java @@ -1,5 +1,7 @@ package com.greenfoxacademy.backend.config; +import java.time.Duration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,23 +11,28 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; -import java.time.Duration; - /** * RedisConfig is a configuration class that defines the cache management strategy * for Redis in the Spring Boot application. It configures the Redis Cache Manager * and sets key serialization and expiration policies. */ - @Configuration +@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis") public class RedisConfig { + /** + * The cacheManager method creates a RedisCacheManager bean that is used to manage. + */ @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(3)) // Set TTL to 5 minutes .disableCachingNullValues() - .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); + .serializeKeysWith( + RedisSerializationContext + .SerializationPair + .fromSerializer(new StringRedisSerializer()) + ); return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(cacheConfig) From 0851821ef900b52dc63f08a9183002a30c6f1caf Mon Sep 17 00:00:00 2001 From: markkovari Date: Sat, 7 Sep 2024 09:24:30 +0200 Subject: [PATCH 11/11] chore: add password for redis cache --- backend/src/main/resources/application.properties | 1 + backend/src/test/resources/application.properties | 2 -- docker-compose.yaml | 5 ++++- frontend/Dockerfile | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 8482850c..51376b05 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -30,4 +30,5 @@ email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} # Redis configuration spring.data.redis.host=${CACHE_URL:localhost} spring.data.redis.port=${CACHE_PORT:6379} +spring.data.redis.password=${CACHE_PASSWORD} spring.cache.type=redis diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index 195fa5c5..9e400d71 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -26,6 +26,4 @@ cors.urls=${CORS_URLS:http://localhost:5173} email.baseUrl=${EMAIL_BASEURL:http://localhost:8080} # Redis configuration -spring.data.redis.host=${CACHE_URL:localhost} -spring.data.redis.port=${CACHE_PORT:6379} spring.cache.type=none \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index ae76a93b..f4c306c5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,7 +11,9 @@ services: POSTGRES_DB: petclinic_db redis-cache: - image: redis + image: bitnami/redis + environment: + - REDIS_PASSWORD=some_redis_password ports: - 6379:6379 @@ -35,6 +37,7 @@ services: - CORS_URLS=http://localhost:5173 - CACHE_PORT=6379 - CACHE_URL=redis://redis-cache + - CACHE_PASSWORD=some_redis_password depends_on: - postgres-db - redis-cache diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 48d50621..de382f6f 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ #create frontend container -FROM node:lts as builder +FROM node:lts AS builder WORKDIR /app COPY package*.json ./ RUN npm install