diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..d3dc7a63
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,32 @@
+services:
+ postgres:
+ image: postgres:16
+ environment:
+ POSTGRES_DB: postgres
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: zenbook14
+ ports:
+ - "5555:5556"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ redis:
+ image: redis:7
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ postgres_data:
+ redis_data:
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 03bc446d..4a4785e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,34 +1,109 @@
-
4.0.0
+
+ com.emobile
+ SpringToDo
+ 0.0.1-SNAPSHOT
+ SpringToDo
+ Spring Boot ToDo Application
+
org.springframework.boot
spring-boot-starter-parent
3.3.5
-
+
- com.emobile
- SpringToDo
- 0.0.1-SNAPSHOT
- SpringToDo
- SpringToDo
21
+ 1.6.2
+
org.springframework.boot
- spring-boot-starter
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.flywaydb
+ flyway-core
+ 10.17.1
-
+
+ org.flywaydb
+ flyway-database-postgresql
+ 10.17.1
+
+
+ org.skyscreamer
+ jsonassert
+ 1.5.3
+ test
+
+
+
+ org.postgresql
+ postgresql
+ 42.7.4
+
+
org.springframework.boot
spring-boot-starter-test
test
+
+
+ org.testcontainers
+ postgresql
+ 1.20.2
+ test
+
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.20.2
+ test
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+ provided
+
@@ -37,7 +112,22 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ 21
+ 21
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java
index 41980514..d1f04a64 100644
--- a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java
+++ b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java
@@ -2,7 +2,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+@EnableCaching
@SpringBootApplication
public class SpringToDoApplication {
diff --git a/src/main/java/com/emobile/springtodo/config/RedisConfig.java b/src/main/java/com/emobile/springtodo/config/RedisConfig.java
new file mode 100644
index 00000000..a04a50ce
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/config/RedisConfig.java
@@ -0,0 +1,20 @@
+package com.emobile.springtodo.config;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisCacheConfiguration cacheConfiguration() {
+ return RedisCacheConfiguration.defaultCacheConfig()
+ .serializeValuesWith(RedisSerializationContext.SerializationPair
+ .fromSerializer(new GenericJackson2JsonRedisSerializer()));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/emobile/springtodo/controller/ToDoAPI.java b/src/main/java/com/emobile/springtodo/controller/ToDoAPI.java
new file mode 100644
index 00000000..b2e98fdb
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/controller/ToDoAPI.java
@@ -0,0 +1,48 @@
+package com.emobile.springtodo.controller;
+
+import com.emobile.springtodo.dto.ToDoDto;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+import java.util.List;
+
+@Tag(name = "ToDoAPI", description = "API for managing TODO items")
+public interface ToDoAPI {
+
+ @Operation(summary = "Create a new TODO item")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "TODO created"),
+ @ApiResponse(responseCode = "404", description = "invalid input")
+ })
+ ToDoDto create(ToDoDto dto);
+
+ @Operation(summary = "Get TODO item by ID")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "TODO found"),
+ @ApiResponse(responseCode = "404", description = "TODO not found")
+ })
+ ToDoDto getById(Long id);
+
+ @Operation(summary = "Get all TODO items with pagination")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "List of TODOs")
+ })
+ List getAll(int limit, int offset);
+
+ @Operation(summary = "Update TODO item")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "TODO updated"),
+ @ApiResponse(responseCode = "404", description = "TODO not found"),
+ @ApiResponse(responseCode = "400", description = "Invalid input")
+ })
+ ToDoDto update(Long id, ToDoDto dto);
+
+ @Operation(summary = "Delete TODO item")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "TODO deleted"),
+ @ApiResponse(responseCode = "404", description = "TODO not found")
+ })
+ void delete(Long id);
+}
diff --git a/src/main/java/com/emobile/springtodo/controller/ToDoController.java b/src/main/java/com/emobile/springtodo/controller/ToDoController.java
new file mode 100644
index 00000000..45ed6ef4
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/controller/ToDoController.java
@@ -0,0 +1,45 @@
+package com.emobile.springtodo.controller;
+
+import com.emobile.springtodo.dto.ToDoDto;
+import com.emobile.springtodo.service.ToDoService;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("api/todos")
+public class ToDoController implements ToDoAPI {
+
+ private final ToDoService service;
+
+ public ToDoController(ToDoService service) {
+ this.service = service;
+ }
+
+ @PostMapping
+ public ToDoDto create(@Valid @RequestBody ToDoDto dto){
+ return service.create(dto);
+ }
+
+ @GetMapping("/{id}")
+ public ToDoDto getById(@PathVariable Long id){
+ return service.findById(id);
+ }
+
+ @GetMapping
+ public List getAll(@RequestParam(defaultValue = "10") int limit,
+ @RequestParam(defaultValue = "0") int offset){
+ return service.findAll(limit, offset);
+ }
+
+ @PutMapping("/{id}")
+ public ToDoDto update(@PathVariable Long id, @Valid @RequestBody ToDoDto dto){
+ return service.update(id, dto);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable Long id){
+ service.delete(id);
+ }
+}
diff --git a/src/main/java/com/emobile/springtodo/dto/ToDoDto.java b/src/main/java/com/emobile/springtodo/dto/ToDoDto.java
new file mode 100644
index 00000000..cb801ad9
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/dto/ToDoDto.java
@@ -0,0 +1,52 @@
+package com.emobile.springtodo.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+
+
+public class ToDoDto {
+
+ private Long id;
+
+ @NotBlank(message = "Title is mandatory")
+ @Size(max = 100, message = "Title must not exceed 100 characters")
+ private String title;
+
+ @Size(max = 500, message = "Description must not exceed 500 characters")
+ private String description;
+
+ private Boolean completed;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Boolean getCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(Boolean completed) {
+ this.completed = completed;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
diff --git a/src/main/java/com/emobile/springtodo/entity/ToDoEntity.java b/src/main/java/com/emobile/springtodo/entity/ToDoEntity.java
new file mode 100644
index 00000000..9ed44d28
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/entity/ToDoEntity.java
@@ -0,0 +1,64 @@
+package com.emobile.springtodo.entity;
+
+
+
+import java.time.LocalDateTime;
+
+
+public class ToDoEntity {
+
+ private Long id;
+ private String title;
+ private String description;
+ private boolean completed;
+ private LocalDateTime createdAt;
+ private LocalDateTime updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
diff --git a/src/main/java/com/emobile/springtodo/exception/GlobalExceptionHandler.java b/src/main/java/com/emobile/springtodo/exception/GlobalExceptionHandler.java
new file mode 100644
index 00000000..fa4015c1
--- /dev/null
+++ b/src/main/java/com/emobile/springtodo/exception/GlobalExceptionHandler.java
@@ -0,0 +1,39 @@
+package com.emobile.springtodo.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(ToDoNotFoundException.class)
+ public ResponseEntity