diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..dc404805
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,11 @@
+FROM maven:3.9.9-eclipse-temurin-24 AS builder
+WORKDIR /build
+COPY pom.xml .
+COPY src ./src
+RUN mvn clean package
+
+FROM openjdk:24-jdk
+WORKDIR /app
+ARG JAR_FILE=target/springboot25-0.0.1-SNAPSHOT.jar
+COPY --from=builder /build/${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app/app.jar"]
diff --git a/compose.yaml b/compose.yaml
index 25863817..4f7d4c49 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,4 +1,8 @@
services:
+ redis:
+ image: 'redis:7-alpine'
+ ports:
+ - "6379:6379"
postgres:
image: 'postgres:latest'
container_name: 'postgres-db'
@@ -9,13 +13,19 @@ services:
ports:
- '5432:5432'
app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: 'catinder:latest'
depends_on:
+ - redis
- postgres
- image: 'catinder:latest'
env_file:
- .env
environment:
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
+ - SPRING_REDIS_HOST=redis
+ - SPRING_REDIS_PORT=6379
ports:
- "8080:8080"
diff --git a/pom.xml b/pom.xml
index 3b87c695..f4674e05 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,12 +51,12 @@
flyway-database-postgresql
-
- org.springframework.boot
- spring-boot-devtools
- runtime
- true
-
+
+
+
+
+
+
org.springframework.boot
spring-boot-docker-compose
@@ -148,6 +148,19 @@
com.fasterxml.jackson.core
jackson-databind
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+ 3.1.2
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/src/main/java/org/example/springboot25/Springboot25Application.java b/src/main/java/org/example/springboot25/Springboot25Application.java
index c4775056..f71cf8fd 100644
--- a/src/main/java/org/example/springboot25/Springboot25Application.java
+++ b/src/main/java/org/example/springboot25/Springboot25Application.java
@@ -2,10 +2,12 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@SpringBootApplication
@EnableWebSecurity
+@EnableCaching
public class Springboot25Application {
public static void main(String[] args) {
diff --git a/src/main/java/org/example/springboot25/config/RedisCacheConfig.java b/src/main/java/org/example/springboot25/config/RedisCacheConfig.java
new file mode 100644
index 00000000..20af84c1
--- /dev/null
+++ b/src/main/java/org/example/springboot25/config/RedisCacheConfig.java
@@ -0,0 +1,24 @@
+package org.example.springboot25.config;
+
+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.lettuce.LettuceConnectionFactory;
+
+import java.time.Duration;
+
+@Configuration
+public class RedisCacheConfig {
+
+ @Bean
+ public RedisCacheManager cacheManager(LettuceConnectionFactory cf) {
+ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
+ .entryTtl(Duration.ofMinutes(10))
+ .disableCachingNullValues();
+
+ return RedisCacheManager.builder(cf)
+ .cacheDefaults(config)
+ .build();
+ }
+}
diff --git a/src/main/java/org/example/springboot25/controller/CatPhotoViewController.java b/src/main/java/org/example/springboot25/controller/CatPhotoViewController.java
index 58eb9e5e..30469dd3 100644
--- a/src/main/java/org/example/springboot25/controller/CatPhotoViewController.java
+++ b/src/main/java/org/example/springboot25/controller/CatPhotoViewController.java
@@ -6,10 +6,11 @@
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
+import java.io.Serializable;
import java.util.List;
@Controller // 🔹 Detta gör att Spring vet att den ska hantera HTML-sidor
-public class CatPhotoViewController {
+public class CatPhotoViewController implements Serializable {
private final CatPhotoService catPhotoService;
@@ -25,4 +26,3 @@ public String showCatPhotos(Model model) {
return "catphoto/catphotos"; // 🔹 Returnerar en HTML-sida som heter "catphotos.html"
}
}
-
diff --git a/src/main/java/org/example/springboot25/controller/CatViewController.java b/src/main/java/org/example/springboot25/controller/CatViewController.java
index e4d7afbf..9b1a137a 100644
--- a/src/main/java/org/example/springboot25/controller/CatViewController.java
+++ b/src/main/java/org/example/springboot25/controller/CatViewController.java
@@ -39,14 +39,12 @@ public CatViewController(CatService catService, UserService userService) {
this.catService = catService;
this.userService = userService;
}
-
@GetMapping
public String showAllCats(Model model) {
List cats = catService.getAllCats();
model.addAttribute("cats", cats);
return "cat/cats-list";
}
-
@GetMapping("/{catId}")
public String showCatDetail(@PathVariable Long catId, Model model, Principal principal) {
CatOutputDTO cat = catService.getCatDtoById(catId);
diff --git a/src/main/java/org/example/springboot25/dto/CatOutputDTO.java b/src/main/java/org/example/springboot25/dto/CatOutputDTO.java
index 502a1e6d..cb2ef0c3 100644
--- a/src/main/java/org/example/springboot25/dto/CatOutputDTO.java
+++ b/src/main/java/org/example/springboot25/dto/CatOutputDTO.java
@@ -1,6 +1,14 @@
package org.example.springboot25.dto;
-public class CatOutputDTO {
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.OneToMany;
+import org.example.springboot25.entities.CatPhoto;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CatOutputDTO implements Serializable {
private Long catId;
private String catName;
private String catProfilePicture;
@@ -10,6 +18,17 @@ public class CatOutputDTO {
private String catPersonality;
private Long userId;
+ @OneToMany(mappedBy = "catPhotoCat", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List catPhotos = new ArrayList<>();
+
+ public List getCatPhotos() {
+ return catPhotos;
+ }
+
+ public void setCatPhotos(List catPhotos) {
+ this.catPhotos = catPhotos;
+ }
+
public Long getCatId() {
return catId;
}
diff --git a/src/main/java/org/example/springboot25/entities/Cat.java b/src/main/java/org/example/springboot25/entities/Cat.java
index de68d02b..6fe5cf71 100644
--- a/src/main/java/org/example/springboot25/entities/Cat.java
+++ b/src/main/java/org/example/springboot25/entities/Cat.java
@@ -5,12 +5,14 @@
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
+import org.springframework.data.redis.core.RedisHash;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Entity
-public class Cat {
+public class Cat implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
diff --git a/src/main/java/org/example/springboot25/entities/User.java b/src/main/java/org/example/springboot25/entities/User.java
index 0d79c079..2ff3d920 100644
--- a/src/main/java/org/example/springboot25/entities/User.java
+++ b/src/main/java/org/example/springboot25/entities/User.java
@@ -8,12 +8,13 @@
import jakarta.validation.constraints.NotNull;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "app_user")
-public class User {
+public class User implements Serializable {
private static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();
diff --git a/src/main/java/org/example/springboot25/service/CatPhotoService.java b/src/main/java/org/example/springboot25/service/CatPhotoService.java
index 6131d319..b174e49e 100644
--- a/src/main/java/org/example/springboot25/service/CatPhotoService.java
+++ b/src/main/java/org/example/springboot25/service/CatPhotoService.java
@@ -2,6 +2,7 @@
import org.example.springboot25.entities.CatPhoto;
import org.example.springboot25.repository.CatPhotoRepository;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -50,4 +51,4 @@ public boolean deleteCatPhoto(Long id) {
}
return false;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/example/springboot25/service/CatService.java b/src/main/java/org/example/springboot25/service/CatService.java
index f1770054..5e23d43a 100644
--- a/src/main/java/org/example/springboot25/service/CatService.java
+++ b/src/main/java/org/example/springboot25/service/CatService.java
@@ -15,6 +15,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.WebDataBinder;
@@ -93,6 +96,7 @@ public Cat updateCat(Long catId, Cat catDetails) {
}).orElseThrow(() -> new NotFoundException("Cat not found with id " + catId));
}
+ @CacheEvict(cacheNames = "deleteCatById", key = "#catId")
public void deleteCatById(Long catId) {
if (!catRepository.existsById(catId)) {
throw new NotFoundException("Cat not found with id " + catId);
@@ -101,6 +105,7 @@ public void deleteCatById(Long catId) {
catRepository.deleteById(catId);
}
+ @CacheEvict(cacheNames = "deleteCat", key = "#catId")
public void deleteCat(Long catId) {
deleteCatById(catId);
}
@@ -109,18 +114,22 @@ public void deleteCat(Long catId) {
// Externa metoder (DTO)
// ========================
+ @Cacheable(cacheNames = "allCats")
public List getAllCats() {
return catRepository.findAll().stream().map(catMapper::toDto).toList();
}
+ @Cacheable(cacheNames = "allCatsByUser", key = "#user")
public List getAllCatsByUser(User user) {
return catRepository.findAllByUserCatOwner(user).stream().map(catMapper::toDto).toList();
}
+ @Cacheable(cacheNames = "allCatsByName", key = "#name")
public List getCatsByName(String name) {
return catRepository.findByCatNameContainingIgnoreCase(name).stream().map(catMapper::toDto).toList();
}
+ @Cacheable(cacheNames = "catById", key = "#id")
public CatOutputDTO getCatDtoById(Long id) {
return catMapper.toDto(findCatById(id));
}
@@ -136,6 +145,7 @@ public Cat addCat(Cat cat) {
return saveCat(cat);
}
+ @CachePut(cacheNames = "putCat", key = "#catId")
public CatOutputDTO updateCat(Long catId, CatUpdateDTO dto) {
Cat cat = findCatById(catId);
log.info("Updating cat: {}", cat.getCatName());
@@ -143,6 +153,7 @@ public CatOutputDTO updateCat(Long catId, CatUpdateDTO dto) {
return catMapper.toDto(saveCat(cat));
}
+ @CachePut(cacheNames = "patchCat", key = "#catId")
public CatOutputDTO partialUpdateCat(Long catId, Map updates) {
Cat cat = findCatById(catId);
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index 0db516bc..93ca4a4d 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -9,3 +9,17 @@ spring.config.import=optional:file:.env[.properties]
server.servlet.session.persistent=false
catapi.key=${CATAPI_KEY}
+# Enable caching
+spring.cache.type=redis
+
+# Toggle Redis
+spring.redis.enabled=true
+spring.data.redis.repositories.enabled=false
+
+# Redis connection details
+spring.data.redis.host=${SPRING_REDIS_HOST:localhost}
+spring.data.redis.port=${SPRING_REDIS_PORT:6379}
+
+spring.datasource.url=jdbc:postgresql://postgres:5432/mydatabase
+spring.datasource.username=myuser
+spring.datasource.password=secret